> 文章列表 > 并非从0开始的c++ day15

并非从0开始的c++ day15

并非从0开始的c++ day15

并非从0开始的c++ day15

  • 点和圆的关系 案例
    • 要求
    • 点的类
    • 圆的类
    • 全局
  • 构造函数与析构函数
    • 构造函数与析构函数的作用
    • 构造函数和析构函数的运行
    • 有参构造和无参构造函数
    • 构造函数和析构函数的注意点
    • 默认的构造函数和析构函数
    • 拷贝构造函数
      • 什么是拷贝构造函数
      • 编译器提供了默认的拷贝构造函数
      • 拷贝构造函数中形参要用引用
    • 构造函数的分类及调用
    • 匿名对象
    • 拷贝构造函数调用的时机
    • 构造函数的调用规则
    • 多个对象的拷贝和析构
  • 对象的深浅拷贝

点和圆的关系 案例

要求

创建一个点的类,要求能输入和输出点
创建一个圆的类,要求能输入和输出圆
要求创建一个方法,能比较点和圆的关系

点的类

.h文件中

#pragma once
class Point
{
public:void setX(int X);void setY(int Y);int getX();int getY();private:int x;int y;
};

.cpp文件中

#include "Point.h"void Point::setX(int X)
{x = X;
}
void Point::setY(int Y)
{y = Y;
}
int Point::getX()
{return x;
}
int Point::getY()
{return y;
}

在.cpp文件中要想调用.h文件中的成员变量,除了需要添加头文件,还需要在函数前加入作用域

圆的类

.h文件中

#pragma once
#include"Point.h"class Circle
{
public:void setMHear(Point MHear);void setMHear(int x, int y);void setMR(int MR);Point getMHear();int getMR();void isPointAndCircle(Point &p);
private:Point mHear;int mR;
};

在.h文件中,因为圆需要有圆心,圆心也是一个点,所以这里直接引入点的类,在圆的类中之间以私有权限创建一个点的对象,以充当圆心

.cpp文件中

#include "Circle.h"
#include <iostream>
using namespace std;void Circle::setMHear(Point p)
{mHear.setX(p.getX());mHear.setY(p.getY());
}void Circle::setMHear(int x, int y)
{mHear.setX(x);mHear.setY(y);
}void Circle::setMR(int MR)
{mR = MR;
}Point Circle::getMHear()
{return mHear;
}int Circle::getMR()
{return mR;
}void Circle::isPointAndCircle(Point &p)
{double distance = pow(p.getX() - mHear.getX(), 2) + pow(p.getY() - mHear.getY(), 2);double tempR = pow(mR, 2);if (distance == tempR){cout << "点在圆上" << endl;}else if (distance < tempR){cout << "点在圆内" << endl;}else {cout << "点在圆外" << endl;}
}

由于深拷贝和浅拷贝的区别,在传入点的形参建立圆心的对象时,我们不直接拷贝点,而是利用点里面的获取x和y来赋予圆心

在写比较函数时,要秉持高内聚低耦合的设计理念,将半径和两点距离分开写,别都堆在if语句中

全局

void G_isPointAndCircle(Circle& c, Point& p)
{//获取点和圆之间的距离double distance = pow(p.getX() - c.getMHear().getX(), 2) + pow(p.getY() - c.getMHear().getY(), 2);//半径的平方double tempR = pow(c.getMR(), 2);if (distance == tempR){cout << "全局 点在圆上" << endl;}else if (distance < tempR){cout << "全局 点在圆内" << endl;}else {cout << "全局 点在圆外" << endl;}
}void test()
{Point p;p.setX(30);p.setY(40);Circle c;c.setMR(50);c.setMHear(0, 0);c.isPointAndCircle(p);c.setMHear(p);G_isPointAndCircle(c, p);
}

全局函数在写的时候由于不能直接调用类里的成员变量并直接获取它的值,所以需要两次get函数来获取。
传入参数时,记得要加&取地址符

构造函数与析构函数

构造函数与析构函数的作用

构造函数的作用是初始化成员变量
析构函数运行在对象销毁前
构造函数和析构函数都是编译器去调用的

当没有构造函数时,直接将成员变量赋值给全局变量,会报错

构造函数和析构函数的运行

在实例化对象时,内部做了两件事,分配空间(栈区),调用构造函数进行初始化

class Maker {
public://构造函数的作用是初始化成员变量,是编译器去调用的//无参构造Maker(){a = 10;cout << "构造函数" << endl;}//析构函数,在对象销毁前,编译器调用析构函数~Maker(){cout << "析构函数" << endl;}public:int a;
};void test01()
{//实例化对象,内部做了两件事,分配空间(栈区),调用构造函数进行初始化Maker m;int b = m.a;cout << b << endl;
}

在mian函数中调用test01函数时,实例化对象m后,会运行一次构造函数,打印一次构造函数,然后逐步执行下来,打印一次b的值,函数运行结束前,运行一次析构函数。
在main函数中,实例化一次对象,将其放在test01后面,在上述打印完成后,才会打印一次构造函数,然后运行至return0,主函数结束,然后出现输入任意键退出,退出后,打印一次析构函数,并关闭程序

有参构造和无参构造函数

//析构函数的作用
class Maker2
{
public://有参构造Maker2(const char * name,int age){cout << "有参构造" << endl;//从堆区空间申请pName = (char*)malloc(strlen(name) + 1);strcpy(pName, name);mAge = age;}void printMaker2(){cout << "name:" << pName << " age:" << mAge << endl;}~Maker2(){cout << "析构函数" << endl;//释放堆区空间if (pName != NULL){free(pName);pName = NULL;}}private:char* pName;int mAge;
};

构造函数分为有参构造和无参构造,
有参构造可以用malloc从堆区空间申请一块空间,我们都知道malloc完需要free掉,所以在这个类的析构函数中,我们可以写free函数

构造函数和析构函数的注意点

  1. 构造函数可以重构
class Maker3
{
public://注意1:构造函数可以重构Maker3()//无参构造函数{}Maker3(int a)//有参构造函数{}
};
  1. 构造函数,析构函数必须是公有权限
    当构造函数私有时,实例化不了对象
    当析构函数私有时,会直接在写代码的阶段报错
  2. 有对象产生必然会调用构造函数,有对象销毁必然会调用析构函数
    有多少个对象产生就会调用多少次构造函数,有多少个对象销毁就会调用多少次析构函数
  3. 构造函数没有返回值,不能用void,构造函数可以有参数,析构函数没有返回值,不能用void,析构函数不能有参数

默认的构造函数和析构函数

class Maker
{
public :Maker()//默认的构造函数,函数体是空的{}~Maker()//默认的析构函数,函数体也是空的{}//编译器默认提供默认的构造函数和析构函数void printfMaker(){a = 100;cout << "a = " << a << endl;}
private:int a;
};

拷贝构造函数

什么是拷贝构造函数

class Maker
{
public:Maker(){cout << "无参构造函数" << endl;a = 20;}//拷贝构造函数Maker(const Maker &m){cout << "拷贝构造函数" << endl;a = m.a;}//打印函数void printMaker(){cout << "a = " << a << endl;}
private:int a;
};void test01()
{Maker m1;m1.printMaker();//用一个已有的对象去初始化另一个对象Maker m2(m1);m2.printMaker();
}

传入const Maker &m是为了不让类修改函数里面的传入的变量,所以只能引用传递不能值传递

编译器提供了默认的拷贝构造函数

默认拷贝构造函数进行了成员变量的简单拷贝

class Maker2
{
public:Maker2(){cout << "无参构造函数" << endl;a = 20;}//编译器提供了默认的拷贝构造函数//Maker2(const Maker2& m)//{//	//默认拷贝构造函数进行了成员变量的简单拷贝//	a = m.a;//}//打印函数void printMaker(){cout << "a = " << a << endl;}private:int a;
};void test02()
{Maker2 m1;m1.printMaker();//用一个已有的对象去初始化另一个对象Maker2 m2(m1);m2.printMaker();
}

拷贝构造函数中形参要用引用

因为用的是引用,所以不是本身,是别名

如果拷贝构造函数中的形参不是引用
/*
Maker3(const Maker3 m)//const Maker3 m = m1;
{
cout << “拷贝构造函数” << endl;
}

1.Maker3 m2(m1);
2.const Maker3 m = m1;
3.const Maker3 m(m1);
4.const Maker3 m = m1;
5.进入死循环
*/
class Maker3
{
public :Maker3(int Ma){cout << "有参构造函数" << endl;ma = Ma;}Maker3(const Maker3 &m){cout << "拷贝构造函数" << endl;}
private:int ma;
};void test03()
{Maker3 m1(10);//调用有参构造Maker3 m2(m1);
}

构造函数的分类及调用

  1. 构造函数的分类:无参构造函数,有参构造函数,拷贝构造函数
  2. 类默认提供了哪些函数:默认的构造函数,默认的析构函数,默认的拷贝构造函数,默认的赋值函数
  3. 构造函数的调用
void test()
{
Maker m;//调用无参构造函数
Maker m1(10);//调用有参构造
Maker m2(m1);//调用拷贝构造//不常用
Maker m4 = Maker(10);//调用的是有参构造函数
Maker m3 = m2 ;//调用拷贝构造
cout << "= = = = ="<<endl;
Maker m5 = 10;//Maker m5 = Maker(10);Maker m6;
m6 = m5;//赋值操作
}

匿名对象

匿名对象的生命周期在当前行
如Maker();

注意:如果匿名对象由名字来接,那么就不是匿名对象

如Maker m1 = Maker();

拷贝构造函数调用的时机

  1. 对象以值方式给函数参数
void func(Maker m)//Maker m = m1;
{}void test01()
{Maker m1;func(m1);
}
  1. 用一个已有的对象去初始化另一个对象
void test02()
{Maker m1;Maker m2(m1);
}
  1. 函数的局部对象以值的方式从函数返回
    vs Debug 模式下,会调用拷贝构造,vs Release模式下不会调用拷贝构造
Maker func2()
{//局部对象Maker m;cout << "局部对象的地址" << &m << endl;return m;
}void test03()
{Maker m1 = func2();cout << "m1对象的地址:" << &m1 << endl;
}

构造函数的调用规则

  1. 如果程序员提供了有参构造,那么编译器不会提供默认构造函数,但是会提供默认拷贝构造函数

void test01()
{//Maker m;//err//Maker m(10);//调用有参构造//Maker m2(m);//调用默认的拷贝构造函数
}
  1. 如果程序员提供了拷贝构造,那么编译器不会提供构造函数

void test02()
{//Maker m;
}

多个对象的拷贝和析构

  1. 如果类有成员对象,那么先调用成员对象的构造函数,再调用本身的构造函数,析构函数的调用顺序反之
  2. 成员对象的构造函数调用和定义顺序一样
  3. 注意,如果有成员对象,那么实例化对象时,必须保证成员对象的构造和析构能被调用
class BMW
{
public:BMW(){cout << "BMW有参构造" << endl;}~BMW(){cout << "BMW析构" << endl;}
};class Buick
{
public:Buick(){cout << "Buick构造" << endl;}~Buick(){cout << "Buick析构" << endl;}
};class Maker
{
public:Maker(){cout << "Maker构造" << endl;}~Maker(){cout << "Maker析构" << endl;}
private:BMW bmw;//成员对象Buick bui;//成员对象
};

初始化列表

初始化列表是调用成员对象的指定构造函数
注意1:初始化列表只能写在构造函数

class BMW2
{
public:BMW2(int a){cout << "BMW有参构造2" << a << endl;}~BMW2(){cout << "BMW析构2" << endl;}
};class Buick2
{
public:Buick2(int x, int y){cout << "Buick构造2" << endl;}~Buick2(){cout << "Buick析构2" << endl;}
};class Maker2
{
public://Maker2() :bmw2(10)//{//	cout << "Maker构造2" << endl;//}Maker2(int a) :bmw2(a),bui2(10,20){cout << "Maker构造2" << endl;}Maker2(const Maker& m2):bmw2(40),bui2(10,20){cout << "Maker拷贝构造2" << endl;}~Maker2(){cout << "Maker析构2" << endl;}
private:BMW2 bmw2;//成员对象Buick2 bui2;//成员对象
};

注意2:如果使用了初始化列表,那么所有的构造函数都要写初始化列表

注意3:如果有多个对象需要指定调用某个构造函数,用逗号隔开

注意4:可以使用对象的构造函数传递数值给成员对象的变量

对象的深浅拷贝

  1. 默认的拷贝构造函数进行了简单的赋值操作(浅拷贝)
  2. 浅拷贝的问题
    同一块空间被释放多次
class Student
{
public:Student(const char* name, int Age){pName = (char*)malloc(strlen(name) + 1);strcpy(pName, name);age = Age;}~Student(){cout << "析构函数" << endl;if (pName != NULL){free(pName);pName = NULL;}}
public:char* pName;int age;
};void test02()
{Student s1("小花", 18);Student s2(s1);//调用默认的拷贝构造函数cout << "s1 Name = " << s1.pName << " s1 age = " << s1.age << endl;cout << "s2 Name = " << s2.pName << " s2 age = " << s2.age << endl;
}
  1. 用深拷贝解决问题
    自己写拷贝构造函数
//深拷贝
Student(const Student &stu)
{cout << "重写的拷贝构造函数" << endl;//1.申请空间pName = (char*)malloc(strlen(stu.pName) + 1);//2.拷贝数据strcpy(pName, stu.pName);age = stu.age;
}

周公解梦