C++ 类与对象(二)—类成员初始化、静态分配和动态分配、this指针
类成员初始化
包括赋值初始化和列表初始化
赋值初始化
在函数体内复制初始化,这是会产生临时对象的。
在所有数据成员被分配内存空间时之后才进行的。
列表初始化
冒号之后使用初始化列表进行初始化,就是纯粹的初始化操作。
列表初始化时给数据成员分配内存空间的时候就进行初始化,也就是分配了内存空间之后进入函数体之前给数据成员赋值,也就是说初始化数据成员的时候函数体还没有执行。
为什么列表初始化快一些?
少了一次调用构造函数的过程,而在函数体中赋值多了一次默认构造函数的调用过程。如果是在构造函数体内进行赋值,相当于是一次默认构造函数加一次赋值,而初始化列表只做一次赋值。
哪些情况必须使用列表初始化?
1、初始化一个引用成员
2、初始化一个常量成员
3、调用一个基类的构造函数,而它拥有一组参数的时候
4、调用一个成员类的构造函数,而它拥有一组参数的时候
初始化列表做了什么?
- 编译器一一操作初始化列表,以适当的顺序在构造函数之内安插初始化操作,并且在任何显示用户代码之前
- 列表中的项目顺序是由类中成员声明顺序决定的,不是由初始化列表顺序决定的。
派生类构造函数执行顺序
- 虚拟基类的构造函数(如果有多个虚拟基类则按照继承的顺序执行构造函数)
- 基类构造函数(如果有多个普通基类也按照继承的顺序执行构造函数)
- 类类型的成员对象的构造函数(按照初始化顺序)
- 派生类自己的构造函数
类如何实现只能静态分配和dongtaifenpe
静态分配
静态建立一个类对象,就是由编译器为对象在栈空间中分配内存。静态分配可以使代码更简单,因为不需要显式释放内存,对象的生命周期由编译器自动管理。但是,静态分配的对象无法在运行的时候改变大小或释放。而且如果对象生命周期比当前作用域范围长,可能导致过早释放或者内存泄漏。
动态分配
A*p =new A()
动态建立一个类对象,就是使用new运算符在堆空间中分配内存。这个过程分为两步:
- 执行operator new()函数,在堆中搜索一块内存分配
- 调用类构造函数
动态分配可以更灵活地管理生命周期,可以在运行时动态地创建、销毁、大小调整对象。但是需要显式释放内存,否则可能导致内存泄漏。动态分配的对象的创建和销毁可能会影响程序性能。
只使用静态分配
限制new,delete为private类型
只使用动态分配
- 将构造、析构函数设置为protected属性,再用子类来动态创建
- 禁用、删除默认构造函数、拷贝函数
- 提供一个只能通过动态分配获取对象的静态成员函数
this指针
C++中,this是一个特殊指针,指向当前正在执行的对象,可以将this指针视作当前对象的应用,可以在类的成员函数中使用。
this指针使用场景
类的成员变量与函数参数同名
class MyClass {
public:
void setX(int x) {
this->x = x;
}
private:
int x;
};
在一个类的成员函数中,需要访问类的其它成员变量或者成员函数
class MyClass {
public:
void print() {
cout << "x = " << this->x << endl;
cout << "y = " << this->y << endl;
}
private:
int x;
int y;
};
需要返回当前对象的引用
class MyClass {
public:
MyClass& operator=(const MyClass& other) {
this->x = other.x;
this->y = other.y;
return *this;
}
private:
int x;
int y;
};
- this指针只能在非静态成员函数中使用,因为静态成员函数不属于任何对象,不存在当前对象的概念。此外,this指针的类型是指向当前对象的指针,类型为指向类类型的指针。
- this指向对象的首地址;只能在成员函数中使用。在全局函数、静态成员函数中都不能使用
- 存储位置会因为编译器不同而有不同存储位置
this指针用处
一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。及时你没有写this指针,编译器在编译的时候也是加上了this指针的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行
this特点
- 传入参数为当前对象的地址,成员函数第一个参数为T*const this。this在成员函数开始之前构造,在成员函数结束之后清除(如果class或者struct里面没有方法,那是没有构造函数的,只能当作C语言的struct来使用)
- 如果采用TYPE XX的方式定义,就是在栈分配内存,this指向这块内存的值
采用new分配内存的话,就是在堆上分配内存,new操作符通过eax累加寄存器返回分配的地址,然后设置给指针变量。之后去调用构造函数(如果有的话),这时将这个内存块的地址传给ecx(计数寄存器存储参数和一些局部变量,this指针)。
- C++编译器有可能会对this指针进行一些优化
this指针一些常见的优化
-
空对象优化(Empty-Base Optimization,EBO):当类没有任何成员变量时,编译器会将空对象的大小设为1,以便确保每个对象都有一个唯一的地址。这种优化可以避免使用this指针,因为空对象不需要任何额外的存储空间。
-
寄存器调用(Register Calling Convention):如果函数参数太多,将它们传递到堆栈上可能会导致性能下降。因此,编译器可能会使用寄存器来传递this指针,从而减少访问堆栈的次数。
-
内联函数(Inline Functions):编译器可能会将类的成员函数内联,以避免使用this指针和函数调用的开销。在内联函数中,this指针通常被编译器优化成一个常量。
-
基于指针的虚函数调用(Pointer-Based Virtual Function Calls):如果类有虚函数,编译器可能会将函数调用优化为指针调用,以避免使用this指针。在这种情况下,编译器会生成一个指向虚函数表的指针,用于查找正确的虚函数实现。
具体优化策略会因为编译器版本、编译器选项等因素而异,所以编写程序时最好不要依赖于这些优化。
this指针存放位置
this指针会因为编译器不同而存放在不同位置。可能是栈、可能是寄存器、甚至是全局变量。在汇编里面,一个值只会以三种形式出现:立即数、寄存器值、内存变量值。不是存放在寄存器就是存放在内存中。
每个类编译之后,是否创建一个类中函数表来保存函数指针?
普通的类函数,都不会创建一个函数表来保存函数指针,只有虚函数才会被放到函数表中。正是由于this指针的存在,用来指向不同的对象,从而确保不同对象之间调用的函数可以互不干扰。
成员函数中调用delete this会出现什么问题
类对象内存空间被释放,之后其它任何函数调用,只要不涉及this指针内容,都可以正常运行,一旦涉及到this指针内容,比如操作数据成员,调用虚函数等都会出现不可预期的问题
为什么是不可预期的问题
因为这个时候内存空间暂时没有被系统收回,这段内存是可以访问的,但是里面的值是不能确定的。造成系统崩溃
在类的析构函数调用delete this会发生什么问题
堆栈溢出。
delete的本质是为释放的内存调用一个或者多个析构函数,然后释放内存。delete this 会显式调用本对象的析构函数,而析构函数中又调用delete this,形成无限递归,造成堆栈溢出,系统崩溃。
this指针调用成员变量的时候,堆栈会发生什么变化?
当在类的非静态成员函数访问类的非静态成员时,编译器会自动将对象的地址传给作为隐含参数传递给函数,这个隐含参数就是this指针。
当建立类的多个对象时,在调用类的成员函数时,不知道具体是哪个对象在调用,此时可以通过查看this 指针来查看具体是哪个对象在调用。this指针首先入栈,然后成员函数的参数从右往左入栈,最后函数返回地址入栈。