四.类和对象 -- 对象初始化和清理(4.2.3 -- 4.2.7)
前言
本文记录本渣渣依据黑马程序员C++课程视频学习C++笔记
本节记录C++中的类和对象 – 对象初始化和清理(4.2.3 – 4.2.7)
4.2.3 拷贝构造函数调用时机
C++中拷贝构造函数调用时机
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
简单代码示例:
#include <iostream>
using namespace std;
class A_Class {
public:
A_Class() {
value = 0;
}
//默认构造函数
A_Class(const A_Class& temp) {
cout << "拷贝构造函数调用" << endl;
}
//拷贝构造函数
private:
int value;
};
A_Class Func(A_Class temp) {//第二,第三种
return temp;
}
void Fun(A_Class temp) { //第二种
}
int main() {
A_Class c1;
A_Class c2(c1); //使用一个已经创建完毕的对象来初始化一个新对象
Fun(c2); //值传递的方式给函数参数传值
A_Class c3 = Func(c2); //以值方式返回局部对象
//实际上调用了4次
system("pause");
return 0;
}
运行结果:
4.2.4 构造函数调用规则
默认情况下,C++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构寒素(无参,函数体为空)
- 默认拷贝构造函数,对成员属性进行值拷贝
- “ = ”运算符重载函数(之后将会详细展开,功能类似默认拷贝构造函数)
构造函数调用规则如下:
- 如果用户定义了有参构造函数,C++将不再提供默认构造函数,但是会提供默认拷贝构造函数
- 如果用户定义了拷贝构造函数,C++将不再提供其他构造函数
即:
定义有参,提供拷贝
定义拷贝,就无提供
我们可以通过简单代码证明:
第一条规则:
第二条规则:
4.2.5 深拷贝与浅拷贝
浅拷贝:简单但赋值操作(逐字节拷贝,也是默认拷贝方式)
深拷贝:在堆区重新申请空间,进行拷贝操作
浅拷贝简单代码示例:
class Myclass {
public:
int getValue() {
return *value;
}
Myclass() {
value = new int(0);
}
Myclass(const Myclass& temp) {
value = temp.value; //直接赋值,为浅拷贝
}
private:
int* value; //成员属性为int型指针
};
浅拷贝造成的问题
- 浅拷贝,在析构对象时,容易执行重复释放操作,造成错误
图示(不怎么会画图,其中是temp,和,tem2个对象):
当析构函数尝试释放tem.value 中值时,tem.value所指内存在temp销毁时已经释放了,系统回收后可能分配给其他程序使用,此时就会出错,所以在成员属性有指针时,我们应当尽量使用深拷贝
深拷贝简单代码示例:
class Myclass {
public:
int getValue() {
return *value;
}
Myclass() {
value = new int(0);
}
Myclass(const Myclass& temp) {
if (value != NULL) { //判断value是否为空,不为空先释放,再赋值
delete value;
value = new int(*temp.value);
}
value = new int(*temp.value); //在堆区重新申请空间,进行赋值
}
private:
int* value; //成员属性int型指针
};
4.2.6 构造函数初始化列表
C++提供了初始化列表语法,用在构造函数初始化属性,
初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。
- 语法:构造函数():成员属性(值), 成员属性(值){函数主体}
简单代码示例:
class Myclass {
public:
Myclass() :value(new int(0)) {} //构造函数列表初始化
private:
int* value; //成员属性int型指针
};
函数执行
- 构造函数的执行可以分成两个阶段,初始化阶段和计算阶段
- 先进行初始化,再进行计算
初始化阶段
- 所有类类型(class type)的成员都会在初始化阶段初始化,即使该成员没有出现在构造函数的初始化列表中.
- C++初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。
计算阶段
一般用于执行构造函数体内的赋值操作。
简单代码示例:
#include <iostream>
using namespace std;
class myClass {
public:
myClass():value(0) { //列表初始化
cout << "初始化value:" << value << endl;
value = 100; //计算
cout << "计算value:" << value << endl;
}
private:
int value;
};
int main()
{
myClass test;
system("pause");
return 0;
}
运行结果:
为何要使用列表初始化
- 当A类对象作B类对象成员时,若B类构造函数使用了列表初始化,则B类中定义A类对象成员时,将不再调用A类构造函数
- 对于内置类型int,float,double等类型来说,使用初始化列表和函数内初始化区别不大
- 但是对于类来说:由于一个类有可能极其复杂,当成员非常多时,不使用列表初始化,将多调用一次构造函数,导致性能下降。此时使用列表初始化,是非常高效的。
由以下简单代码示例证明:
#include <iostream>
using namespace std;
class myClass {
public:
myClass() :value(0) {
cout << "myClass 默认(无参)构造函数" << endl;
} //列表初始化
myClass(int temp) :value(temp){
cout << "myClass 有参构造函数" << endl;
} //列表初始化
myClass(const myClass& temp) :value(temp.value){
cout << "myClass 拷贝构造函数" << endl;
} //列表初始化
private:
int value;
};
class yourClass {
private:
myClass mine; //myclass类对象作yourClass 类成员
myClass your; //使用列表初始化,将不再调用类myClass默认构造函数
public:
yourClass():mine(0), your(mine){} //列表初始化
};
int main()
{
yourClass test;
system("pause");
return 0;
}
运行结果:
可以看到,只调用了2次函数,是由于类yourClass中初始化列表中
yourClass():mine(0), your(mine){} //列表初始化
调用了类myClass中的有参和拷贝构造函数,而定义时,未调用无参(默认)构造函数
注意事项
- C++初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。所以在编写列表初始化时,应当按成员出现顺序书写
- 在类成员组成十分复杂时,应尽量使用初始化列表
4.2.7 类对象作为类成员
上面以有提到,C++一个类中的成员可以是另一个类的对象,我们称该成员为对象成员
简单代码示例:
class yourClass {
private:
myClass mine; //myclass类对象作yourClass 类成员
myClass your;
public:
};
注意事项
- 当其他类对象作为本类成员是,构造时先构造类对象成员,再构造自身
- 析构的顺序与构造相反,即析构自身,再析构类对象成员
简单代码示例:
#include <iostream>
using namespace std;
class myClass {
public:
myClass() {
cout << "myClass 构造函数" << endl;
}
~myClass() {
cout << "myClass 析构函数" << endl;
}
private:
int value;
};
class yourClass {
public:
yourClass() {
cout << "yourClass 构造函数" << endl;
}
~yourClass() {
cout << "yourClass 析构函数" << endl;
}
private:
myClass mine; //myclass类对象作yourClass 类成员
};
void test() {
yourClass test;
}
int main()
{
test();//函数结束,对象销毁,执行析构函数
system("pause");
return 0;
}
运行结果:
总结
- 了解构造函数细则
- 了解深拷贝与浅拷贝问题
- 了解构造函数列表初始化细则