1.类的实例化
#include <iostream>
class MyClass {
public:
int myInt;
MyClass() {
myInt = 0;
std::cout << "Constructor called" << std::endl;
}
void printInt() {
std::cout << "myInt = " << myInt << std::endl;
}
};
int main() {
// 实例化一个MyClass对象
MyClass obj;
// 调用对象的成员函数
obj.printInt();
return 0;
}
在上面的代码中,我们定义了一个名为MyClass的类。该类有一个整型数据成员myInt和一个构造函数MyClass()。构造函数用于初始化myInt的值为0,并在创建对象时打印一条消息。在main函数中,我们使用类的名称和括号来实例化一个MyClass对象obj。然后,我们调用该对象的成员函数printInt(),它会打印对象的myInt值。
2.计算类的大小
我们使用sizeof来计算类的大小。
我们在上述代码中看到计算类的大小,和计算对象的大小是相同的,但是大家是否想过为何结果是这样呢 ?其实计算类大小,只会计算成员变量大小。用class创建类时,类名是可以代表类型的。
我们也可以看到d1的大小也是相同的,这里就说明了对象里面只有成员变量。那么哪里才是成员变量的定义呢?我们知道变量的定义就是在开辟空间的地方,我们在类的里面写的成员变量其实是声明,当我们创建对象的时候,也就是像的实例化过程中开辟空间,这时才是定义。
那么这里是否存在结构体对齐现象吗?
我们来测试一下:
将这里的month改为char型后,计算的结果还是12,这就足以说明,在类中还是存在内存对齐的。
有些同学可能忘记对其规则了,不过我将其整理在下方了。
结构体内存对齐规则 :
1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的对齐数为8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是 所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
结构体对齐的主要目的是优化内存访问的效率和减少内存的浪费。它可以提高内存访问速度,减少内存碎片化,并且使结构体在内存中的布局更加连续。
在vs中默认对齐数是8,我们也可以修改其对齐数。
这里我们将默认对齐数改为1后,计算出来的大小就变为9了。
3.类的存储方式
类的存储方式取决于编译器和操作系统的实现。一般来说,类的存储方式可以分为以下几种:
-
静态存储:类的静态成员变量和静态成员函数存储在静态存储区,它们在程序生命周期内都存在,不依赖于类的实例。静态成员变量在内存中只有一个副本,被所有类的实例共享。
-
实例存储:类的实例存储在堆或栈上,具体取决于类的实例是通过new运算符创建(存储在堆上)还是作为局部变量创建(存储在栈上)。实例存储包括类的成员变量和成员函数指针。
-
虚函数表存储:如果类中有虚函数,编译器会生成一个虚函数表(vtable)来存储类的虚函数。虚函数表是一个指针数组,每个指针指向相应的虚函数。每个类的实例中会有一个指向虚函数表的指针(vptr),通过vptr可以动态地调用类的虚函数。
-
内联函数存储:如果类的成员函数被声明为内联函数,在使用时会直接将函数的代码插入到调用处,而不需要通过函数调用的方式和存储在内存中。内联函数可以带来性能上的提升,但会增加代码的大小。
需要注意的是,存储方式的具体细节可能因编译器和操作系统的不同而有所差异。
成员函数只会保存一份,存放在公共区域用的时候调用,对象中只有成员变量。
4.this指针
我们先来看一段代码:
执行这段代码时,编译器是如何知道要对那个对象执行操作的呢?当我们对两个对象初始化好之后,调用打印时又是怎么知道打印那个对象的呢?
其实有一个隐藏的this指针,作为成员函数的形参,当我们调用成员函数时,成员函数就会将调用对象的地址传给成员函数,在用这个形参去执行,这一步骤是编译器去执行,我们不能在调用函数时写this指针,但是可以在类中使用。
这样写出来大家就应该好理解了。
