1. 构造和析构
当对象产生时,必须初始化成员变量,当对象销毁前,必须清理对象
1.1 构造函数和析构函数的概念
初始化对象用构造函数,清理对象用析构函数,这是编译器调用的
构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用
析构函数主要用于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:
- 构造函数函数名和类名相同,没有返回值,不能有void,但可以有参数。
- ClassName(){}
析构函数语法:
- 析构函数函数名是在类名前面加”~”组成,没有返回值,不能有void,不能有参数,不能重载。
- ~ClassName(){}
class Maker
{
public:
int a;
public:
// 构造函数的作用是初始化成员变量,是编译器去调用
Maker()
{
a = 10;
cout << "构造函数" << endl;
}
// 析构函数,在对象销毁前,编译器会调用析构函数
~Maker()
{
cout << "析构函数" << endl;
}
};
void test01()
{
// 实例化对象,内部做了两件事,1.分配空间 2.调用构造函数初始化变量
Maker m;
int b = m.a;
cout << b << endl;
}
1.2 析构函数的作用
重写析构函数可以在对象释放前,手动释放对象中申请的空间,避免内存泄漏
class Maker2
{
public:
Maker2(const char *name, int age)
{
cout << "有参构造" << endl;
// 从堆区空间申请
// +1 ,后面有一位\0
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;
};
void test02()
{
Maker2 m2("Emily", 18);
m2.printMaker2();
}
1.3 构造函数和析构函数注意事项
- 1. 构造函数可以重载
- 2. 构造函数必须是公有的
- 3. 构造函数没有返回值,不能用void,构造函数可以有参数,析构函数没有返回值,不能用void,也没有参数
- 4.有对象产生必然会调用构造函数,有对象销毁必然会调用析构函数。有多少个对象产生就会调用多少次构造函数,有多少个对象销毁就会调用多少次析构函数。
class Maker3
{
private:
int age;
public: // 注意2:构造函数必须是公有的
// 注意1:构造函数可以重载
Maker3() // 无参构造函数
{
cout << "Maker3的无参构造" << endl;
}
Maker3(int a) // 有参构造函数
{
cout << "Maker3的有参构造" << endl;
}
~Maker3()
{
cout << "Maker3的析构函数" << endl;
}
};
void test03() {
Maker3 m1; // 当构造函数私有时,实例化不了对象
// 有对象产生必然会调用构造函数,有对象销毁必然会调用析构函数
// 有多少个对象产生就会调用多少次构造函数,有多少个对象销毁就会调用多少次析构函数,
Maker3 m2(10);
}
1.4 默认的构造函数和析构函数
编译器默认提供默认的构造函数和析构函数
class Maker
{
private:
int a;
public:
// 编译器提供默认的构造函数和析构函数
// 默认的构造函数和析构函数,函数体都是空的
void printMaker() {
a = 100;
cout << " a = " << a << endl;
}
};
// 对象销毁前会去调用析构函数,对象会在test()函数结束时销毁
void test() {
Maker a;
a.printMaker();
}
2. 拷贝构造函数
2.1 什么是拷贝构造函数
拷贝构造函数(复制构造函数),使用另一个对象初始化本对象
ClassName(const ClassName& m){}
// 1. 什么是拷贝构造函数
class Maker1
{
public:
Maker1()
{
a = 20;
cout << "Maker1 无参构造函数" << endl;
}
Maker1(const Maker1 &m)
{
cout << "Maker1 拷贝构造函数" << endl;
a = m.a;
}
void printMaker1()
{
cout << "Maker1 Print a " << a << endl;
}
~Maker1()
{
cout << "Maker1 析构函数" << endl;
}
private:
int a;
};
void test01()
{
Maker1 m1;
m1.printMaker1();
// 用一个已有的对象去初始化另一个对象
Maker1 m2(m1);
m2.printMaker1();
}
2.2 默认的拷贝构造函数
编译器提供了默认的拷贝构造函数,默认拷贝函数进行了成员变量的简单拷贝
// 2.编译器提供了默认的拷贝构造函数
class Maker2
{
public:
Maker2()
{
a = 20;
cout << "Maker2 无参构造函数" << endl;
}
// 编译器提供了默认的拷贝构造函数
// 默认拷贝函数进行了成员变量的简单拷贝
// Maker2(const Maker2 &m)
// {
// cout << "Maker2 拷贝构造函数" << endl;
// a = m.a;
// }
void printMaker2()
{
cout << "Maker2 Print a " << a << endl;
}
~Maker2()
{
cout << "Maker2 析构函数" << endl;
}
private:
int a;
};
void test02()
{
Maker2 m1;
m1.printMaker2();
// 用一个已有的对象去初始化另一个对象
Maker2 m2(m1);
m2.printMaker2();
}
2.3 拷贝构造函数中形参要用引用
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);
//如果拷贝构造函数中的形参不是引用
/*
Maker3(const Maker3 m)//const Maker3 m=m1; 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.进入死循环
*/
}
3. 构造函数分类及调用
构造函数的分类
- 按参数类型:分为无参构造函数和有参构造函数
- 按类型分类:普通构造函数和拷贝构造函数(复制构造函数)
类默认提供了哪些函数
默认的构造函数 默认的析构函数 默认拷贝构造函数 默认的赋值函数
class Maker1
{
public:
// 按照参数分类:无参 有参
Maker1() {
cout << "无参构造函数" << endl;
}
Maker1(int a)
{
cout << "有参构造函数" << endl;
ma = a;
}
// 按照类型:普通构造 拷贝构造
Maker1(const Maker1 &m)
{
cout << "拷贝构造函数" << endl;
}
// 默认的赋值函数
public:
int ma;
};
void test01()
{
// 常用构造函数调用
Maker1 m; // 调用无参构造
Maker1 m1(10); // 调用有参构造
Maker1 m2(m1); // 调用拷贝构造
// 不常用构造函数调用
Maker1 m3 = m1; // 调用拷贝构造
Maker1 m4 = 10; // 调用有参构造 Maker1 m4 = Maker1(10)
Maker1 m5 = Maker1(10); // 调用有参构造
Maker1 m6 = Maker1(); // 调用无参构造
Maker1 m7(Maker1(10)); // 有参构造函数
Maker1 m8; // 无参构造
m8 = m5; // 赋值操作
//cout << m2.ma << endl;
}
注意:不能调用拷贝构造函数去初始化匿名对象
// 不能调用拷贝构造函数去初始化匿名对象
// 不能调用拷贝构造函数去初始化匿名对象
class Teacher{
public:
Teacher(){
cout << "默认构造函数!" << endl;
}
Teacher(const Teacher& teacher){
cout << "拷贝构造函数!" << endl;
}
public:
int mAge;
};
void test02()
{
Teacher t1; // 默认构造函数
// error C2086:“Teacher t1”: 重定义
// Teacher(t1); //此时等价于 Teacher t1;
Teacher(t2); // 此时等价于 Teacher t2 默认构造函数
Teacher t3 = Teacher(t2); // 拷贝构造函数
}
b为A的实例化对象,A a = A(b) 和 A(b)的区别?
当A(b) 有变量来接的时候,那么编译器认为他是一个匿名对象,当没有变量来接的时候,编译器认为A(b) 等价于 A b.
4. 匿名对象
匿名对象的生命周期在当前行
// 匿名对象的生命周期在当前行
// 如果匿名对象有名字来接,那么就不是匿名对象 Maker m1 = Maker();
class Maker
{
public:
Maker() {
cout << "无参构造函数" << endl;
}
Maker(int a){
cout << "有参构造函数" << endl;
}
Maker(const Maker &m){
cout << "拷贝构造函数" << endl;
}
~Maker() {
cout << "析构函数" << endl;
}
private:
};
void test01()
{
Maker(); // 匿名对象的生命周期在当前行
Maker(10);
// 注意:如果匿名对象有名字来接,那么就不是匿名对象
Maker m1 = Maker();
cout << "test01()函数结束" << endl;
}
5. 拷贝构造函数调用的时机
5.1 对象以值方式给函数参数
// 1. 对象以值方式给函数参数
void func(Maker m){
}
void test01() {
Maker m1; // 无参构造
func(m1); // 拷贝构造 Maker m = m1
}
5.2 用一个已有的对象初始化另一个对象
// 2.用一个已有的对象初始化另一个对象
void test02() {
Maker m;
Maker m1(m);
}
5.3 函数的局部对象以值的方式从函数返回
// 3. 函数的局部对象以值的方式从函数返回
// vs Debug模式下,会调用拷贝构造, vs Release模式下不会调用拷贝构造,qt也不会调用 (节约内存)
Maker func2() {
// 局部对象
Maker m(10);
cout << "局部对象的地址 " << &m << endl;
return m;
}
void test03() {
// Maker m1; 执行到;时,对象才产生
// Maker m1 = 此时对象还未产生
Maker m1 = func2();
cout << "m1对象的地址 " << &m1 << endl;
}
[Test03结果说明:]
编译器存在一种对返回值的优化技术,RVO(Return Value Optimization).在vs debug模式下并没有进行这种优化,所以函数MyBusiness中创建p对象,调用了一次构造函数,当编译器发现你要返回这个局部的对象时,编译器通过调用拷贝构造生成一个临时Person对象返回,然后调用p的析构函数。
我们从常理来分析的话,这个匿名对象和这个局部的p对象是相同的两个对象,那么如果能直接返回p对象,就会省去一个拷贝构造和一个析构函数的开销,在程序中一个对象的拷贝也是非常耗时的,如果减少这种拷贝和析构的次数,那么从另一个角度来说,也是编译器对程序执行效率上进行了优化。
6. 构造函数调用规则
- 如果用户定义了普通构造(非拷贝),c++不在提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,c++不会再提供任何默认构造函数
如果程序员提供了有参构造,那么编译器不会提供默认构造函数,但是会提供默认的拷贝构造
class Maker
{
public:
Maker(int a){
cout << "有参构造函数" << endl;
}
~Maker() {
cout << "析构函数" << endl;
}
private:
};
// 如果程序提供了有参构造函数,那么编译器就不会提供默认的构造函数,但会提供默认的拷贝构造函数
void test01()
{
// Maker m; // 报错
Maker m1(10);
Maker m2(m1);
}
如果程序员提供了拷贝构造函数,那么编译器不会提供默认的构造函数和默认的拷贝构造函数
class Maker2
{
public:
Maker2(const Maker &m)
{
cout << "拷贝构造函数" << endl;
}
~Maker2()
{
cout << "析构函数" << endl;
}
private:
};
// 如果程序提供了拷贝构造函数,那么编译器就不会提供默认的构造函数和默认的拷贝构造函数
void test02()
{
// Maker2 m; // 错误
}
网友评论