目录
1.构造函数调用规则
2.深拷贝和浅拷贝
3.初始化列表
4.类对象作为类成员
1.构造函数调用规则
默认情况下,C++编译器至少给类添加三个函数:
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值的拷贝
构造函数调用规则如下:
如果用户自己实现了有参构造函数,C++不会再提供无参构造函数,但是会提供默认拷贝构造如果用户自己实现了拷贝构造函数,C++不会提供任何构造函数
比如:
1.如果用户自己实构造函数,C++不会再提供无参构造函数,但是会提供默认拷贝构造
#include<iostream>
using namespace std;
class Person
{
public://有参构造函数Person(int age){_age = age;cout << "有参构造函数调用" << endl;}//析构函数~Person(){cout << "析构函数调用" << endl;}//拷贝构造函数Person(const Person& p){_age = p._age;cout << "拷贝构造函数调用" << endl;}int _age;};
int main()
{Person p;p._age = 18;Person p2(p);cout << "p2的年龄为:" << p2._age << endl;return 0;
}
此时会显示报错:
类 "Person" 不存在默认构造函数
因为我们写下了Person p,没有参数,要调用系统或者用户提供的无参构造函数,但因为我们实现了有参构造函数,系统就不会提供无参构造函数,同时我们自己也没有实现无参构造函数,就会报错。
还是会提供默认构造,如果我们把Person p和拷贝构造函数代码删掉:
#include<iostream>
using namespace std;
class Person
{
public://有参构造函数Person(int age){_age = age;cout << "有参构造函数调用" << endl;}//析构函数~Person(){cout << "析构函数调用" << endl;}int _age;};
int main()
{Person p(18);Person p2(p);cout << "p2的年龄为:" << p2._age << endl;return 0;
}
运行结果:
有参构造函数调用
p2的年龄为:18
析构函数调用
析构函数调用
可以看到,系统会提供默认拷贝构造
2.如果用户自己实现了拷贝构造函数,C++不会提供任何构造函数
#include<iostream>
using namespace std;
class Person
{
public://拷贝构造函数Person(const Person& p){_age = p._age;cout << "拷贝构造函数调用" << endl;}//析构函数~Person(){cout << "析构函数调用" << endl;}int _age;};
int main()
{Person p;return 0;
}
报错:
“Person”: 没有合适的默认构造函数可用
可以看出,当我们自己实现了拷贝构造函数,如果我们自己不实现无参构造函数,系统就不会提供,有参构造函数系统本来就提供不了。
2.深拷贝和浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
#include<iostream>
using namespace std;
class Person
{
public://无参构造函数Person(){cout << "无参构造函数" << endl;}//有参构造函数Person(int age,int height){_age = age;_height=new int(height);cout << "有参构造函数调用" << endl;}//析构函数~Person(){//将堆区开辟的数据做释放操作if (_height != NULL){delete _height;_height = NULL;}cout << "析构函数调用" << endl;}int _age;int* _height;
};
int main()
{Person p1(18,180);cout << "p1的年龄为:" << p1._age <<"身高为:"<<*p1._height<<endl;Person p2(p1);cout << "p2的年龄为:" << p2._age <<"身高为:"<<*p2._height<<endl;return 0;
}
我们想把身高这个数据开辟在堆区,就可以用new语法来创建,new创建之后返回一个指向该空间的指针,用_height来接收,同时堆区开辟的空间需要我们程序员手动来释放,上面给出了代码,运行看看怎么样,不好,出错了,这时候会出现报错:
已在 Project15.exe 中执行断点指令(__debugbreak()语句或类似调用)。
这是为什么呢?其实是因为我们利用编译器提供的拷贝构造函数,做的是浅拷贝的操作
p1和p2的指针都指向同一块空间,同一块空间释放两次肯定是不允许的,所以报错意料之中,浅拷贝带来的问题就是堆区的内存重复释放,要用深拷贝来解决
#include<iostream>
using namespace std;
class Person
{
public://无参构造函数Person(){cout << "无参构造函数" << endl;}//有参构造函数Person(int age,int height){_age = age;_height=new int(height);cout << "有参构造函数调用" << endl;}//自己实现拷贝构造函数,解决浅拷贝问题Person(const Person& p){_age = p._age;//_height=p._height;编译器提供的拷贝构造函数实现这段代码,就是直接赋值_height = new int(*p._height);cout<<"拷贝构造函数调用"<<endl;}//析构函数~Person(){//将堆区开辟的数据做释放操作if (_height != NULL){delete _height;_height = NULL;}cout << "析构函数调用" << endl;}int _age;int* _height;
};
int main()
{Person p1(18,180);cout << "p1的年龄为:" << p1._age <<"身高为:"<<*p1._height<<endl;Person p2(p1);cout << "p2的年龄为:" << p2._age <<"身高为:"<<*p2._height<<endl;return 0;
}
自己再开辟一块堆区空间,将指针指向这块空间,释放的时候就不会再重复释放空间了。
3.初始化列表
初始化列表语法用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)...{}
示例:
传统的初始化操作:
class Person
{
public:Person(int a, int b, int c){_a = a;_b = b;_c = c;}int _a;int _b;int _c;
};
初始化列表操作:
#include<iostream>
using namespace std;
class Person
{
public:Person(int a, int b, int c):_a(a),_b(b),_c(c){}int _a;int _b;int _c;
};
int main()
{Person p(10, 20, 30);cout << "_a=" << p._a << endl;cout << "_b=" << p._b << endl;cout << "_c=" << p._c << endl;return 0;
}
输出:
_a=10
_b=20
_c=30
使用初始化列表,函数体就可以写其他的内容,很灵活。
4.类对象作为类成员
C++中类的成员可以是另一个类的对象,这种成员叫对象成员
比如:
class A{};
class B
{A a;
};
那我们就有问题了,当我们创建B的对象时,A和B的构造函数和析构函数先执行谁的呢?让我们一起来看看
#include<iostream>
using namespace std;
class Phone
{
public:Phone(string Pname){_Pname = Pname;cout << "Phone的构造函数调用" << endl;}~Phone(){cout << "Phone的析构函数调用" << endl;}string _Pname;
};
class Person
{
public:Person(string name, string Pname):_name(name),_Phone(Pname){cout << "Person的构造函数调用" << endl;}~Person(){cout << "Person的析构函数调用" << endl;}string _name;//姓名Phone _Phone;//手机
};
int main()
{Person p("张三", "华为Mate60pro");cout << p._name << "拿着" << p._Phone._Pname << endl;return 0;
}
输出:
Phone的构造函数调用
Person的构造函数调用
张三拿着华为Mate60pro
Person的析构函数调用
Phone的析构函数调用
可以看到,当其他对象作为本类对象时,构造时先构造其他对象,再构造本类对象;析构的顺序是先调用本类对象的析构函数,再调用其他对象的析构函数。