一、 对象的创建和销毁过程分析
- 对象的创建过程
-
给对象划分内存空间
-
执行初始化列表
- 根据继承表的顺序调用父类的无参构造或者有参构造
- 通过:父类名(val) 调用父类的有参构造
- 根据成员变量的定义顺序调用类类型成员的无参构造或者有参构造
- 通过:类类型成员名(val) 调用类类型成员的有参构造
- 对其它成员初始化
- 根据继承表的顺序调用父类的无参构造或者有参构造
-
执行自己的构造函数、可能会去申请资源
class A { public: A(void) { cout << "A 的无参构造" << endl; } A(int num) { cout << "A 的有参构造" << endl; } ~A(void) { cout << "A 的析构函数" << endl; } }; class B { public: B(void) { cout << "B 的无参构造" << endl; } B(int num) { cout << "B 的有参构造" << endl; } ~B(void) { cout << "B 的析构函数" << endl; } }; class C { public: C(void) { cout << "C 的无参构造" << endl; } C(int num) { cout << "C 的有参构造" << endl; } ~C(void) { cout << "C 的析构函数" << endl; } }; class D { public: D(void) { cout << "D 的无参构造" << endl; } D(int num) { cout << "D 的有参构造" << endl; } ~D(void) { cout << "D 的析构函数" << endl; } }; class E:public A,public B//继承表顺序 { C c;// 输出"A 的有参构造"\n "B 的有参构造"\n"C 的无参构造"\n" 的无参构造"\n"E 的无参构造" D d; public: E(void):A(20),B(10)// 初始化列表,会显示 "A 的有参构造"\n "B 的有参构造"\n"E 的无参构造" 顺序只与继承表顺序有关 { cout << "E 的无参构造" << endl; } ~E(void) { cout << "E 的析构函数" << endl; } }; int main() { E* e = new E; // "A 的无参构造"\n "B 的无参构造"、"E 的无参构造" delete e;//输出上述的逆序的析构函数 }
- 对象的销毁过程(创建的逆序)
-
执行自己的析构函数、可能去释放资源
-
根据类类型成员的顺序的逆序来调用它们是析构函数
-
根据继承表的逆序,调用父类的析构函数
-
释放对象的内存
示例代码
class Student
{
char name[20];
char sex;
int id;
public:
Student(const char* name,char sex,int id):sex(sex),id(id)
{
strcpy(this->name,name);// 不能用于初始化列表中
cout << "构造函数" << endl;
}
void show(void)
{
cout << name << " " << id << endl;
}
}
二、 成员函数是如何区分调用它的对象 -- 使用隐藏的this指针
-
对象的内存只存储了成员变量,没有存储成员函数指针,相当于所有对象调用的是同一份成员函数
-
当对象调用成员函数时,编译器会自动把对象的地址传递给该成员函数,也就是说普通的成员函数中都有一个隐藏的参数,该参数名字叫做this指针,this指针用来接收调用对象的地址
-
this指针拿到了调用对象的地址后,就可以直接访问该对象的成员,完成区分对象的任务
-
虽然this指针是隐藏定义的,但是可以显示地使用它,但不要多此一举地显式定义它
三、 常函数
-
被const修饰了this指针的成员函数,称为常函数
-
当对象调用成员函数时,编译器会隐式地把对象地址传递给成员函数
-
当对象被const修饰过具有常属性,就不能直接调用普通的成员函数,因为此时传递的对象地址也具有了常属性,而普通成员函数的this指针参数不具备常属性,所以编译器会报错,C++编译器不允许用带常属性的指针数据给不带常属性的指针变量赋值
-
因此需要让普通成员函数中的this指针也具备常属性,通过const修饰变成常函数,所以const其实修饰的是成员函数中的this指针,这样就可以让具有常属性的对象能够调用常函数了。
返回值 类名::成员函数(参数列表)const
{
// 常函数
}
-
具有常属性的对象只能调用常函数,并且常函数中也只能调用常函数;不具有常属性的对象都可以调用
-
同名的成员函数,如果其它参数列表完全相同,但是常属性不同,也可以构成重载
-
正常来说在常函数中是不能修改成员变量的,除非该成员在定义时通过mutable修饰
const 在C和C++的相同点和不同点
-
相同点:const在C和C++都是用来"显式"地保护数据不被修改
-
不同点:
-
C++编译器会优化const变量的取值过程,即使该变量内存被强行修改,也不会改变通过变量访问的数值,这种机制会更安全。C语言编译器不会优化
-
在C++中const还可以用于修饰成员函数的this指针,从而定义常函数
-
一个空结构体在C语言、C++中分别占多少字节?为什么?
-
在C语言中空结构体占0字节
-
在C++空结构体占1字节
在C++结构体中可以定义成员函数,并默认有四个隐藏的成员函数(构造,析构,拷贝构造,赋值),当对象去调用成员函数时,需要传递对象的地址给成员函数,这种机制就要求结构对象需要在内存中有一席之地,所以如果结构没有任何的成员变量,编译器会让结构至少拥有1字节的不使用的内存,让上面这套机制自洽
四、拷贝构造
拷贝构造就是一种特殊版本的构造函数,格式为
类名(const 类名& that)
{
// 执行给每个成员变量进行赋值
}
什么时候会调用拷贝构造:
- 当使用旧对象给新对象初始化时,会自动调用拷贝构造
Test t1; // 调用无参构造
Test t2 = t1; // 调用拷贝构造
Test t3(t2); // 调用拷贝构造
示例代码
class Test
{
public:
int num;
Test(void)
{
cout << "无参构造" << endl;
}
Test(const Test& that)
{
cout << "我是拷贝构造" << endl;
}
};
拷贝对象的任务
- 负责把旧对象中的成员变量拷贝给新对象,并且编译器默认已经自动生成具有该功能的拷贝构造函数
什么时候需要显式地写拷贝构造?
- 普通情况下编译器自动生成的拷贝构造完全够用,但当类中成员是指针类型且为该指针成员分配了堆内存,使用默认自动生成的拷贝构造只会对指针的值进行拷贝,此时就会导致两个对象的指针成员指向同一块内存,所以在执行析构函数时会造成重复释放错误,此时应该显式地实现拷贝构造
浅拷贝和深拷贝
-
浅拷贝:当类中的成员有指针且分配堆内存,只拷贝指针变量的值
-
深拷贝:不拷贝指针变量的值,而是拷贝指针变量所指向的内存的内容
五、 赋值操作(拷贝赋值、赋值运算符函数)
任务:用一个旧对象给另一个旧对象赋值(两个对象都已经完成创建)
Test t1,t2; // 无参构造
t1 = t2; // 赋值操作操作
//注意:在C++中会把运算符当做函数处理,使用运算符时会调用运算符函数
类名& operator=(const 类名& that)
{
}
什么时候需要显式地写赋值
- 类似于需要显式地写拷贝构造一样,当需要进行深拷贝时,就需要显式地写拷贝构造和赋值操作
实现赋值操作需要注意的问题:
-
虽然赋值操作与拷贝构造的任务相同,都需要深拷贝,但是环境不同(旧对象,新对象)
-
问题1:被赋值的对象的指针已经分配有内存
-
先释放被赋值者指针指向的原内存
-
根据赋值者的赋值重新申请内存
-
把赋值者内存的内容深拷贝到新内存中
-
-
问题2:可能会出现对象自己给自己赋值的情况
- 通过判断this指针与赋值者的地址是否相同,如果相同,立即返回*this结束,如果不相同才进行赋值操作
实现一个类似于string的类 String的四个成员函数
class String
{
char* str;
public:
String(char* str)
{
this->str = new char[strlen(str)+1];
strcpy(this->str,str);
}
~String(void)
{
delete[] str;
}
String(const String& that)
{
str = new char[strlen(that.str)+1];
strcpy(str,that.str);
}
String& operator=(const String& that)
{
delete[] str;
str = new char[strlen(that.str)+1];
strcpy(str,that.str);
return *this;
}
};
标签:函数,--,成员,C++,day03,对象,拷贝,赋值,指针
From: https://www.cnblogs.com/bigflyny/p/17652198.html