目录
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的析构函数调用
可以看到,当其他对象作为本类对象时,构造时先构造其他对象,再构造本类对象;析构的顺序是先调用本类对象的析构函数,再调用其他对象的析构函数。