今天,我们继续学习类和对象的相关知识,本次学习的内容,主要是this指针和默认构造函数。
继上篇文章结尾,我们讲到了,一个类实例化出对象后,它的成员变量和成员函数是如何存储的。类实例化出的对象,会给成员变量开辟空间,而成员函数则放在公共代码段区(这个类共有的空间),不会单独开辟空间。
对于类的成员变量和成员函数的存储方式,我们了解了。那它的大小是如何计算的呢?
计算类对象(类实例化出来的对象)的大小
类对象的大小计算方式和结构体的计算方式相同。我们在学习C语言的结构体时,已经学习过了结构体的大小计算方式。现在,我们来回顾一下,对于结构体的大小的计算,要掌握以下四点:
第一点:第一个变量在与结构体偏移量为0的地址处
第二点:其他成员变量要对齐到某个数字的整数倍的地址
注意:对齐数=编译器默认的一个对齐数与该成员大小取较小值
VS中默认对齐数为8
第三点:结构体总大小为最大对齐数(所有类型中的最大者与编译器的默认对齐数取最小)的整数倍
第四点:如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是最大对齐数的整数倍。这里的最大对齐数是包含嵌套结构体中的对齐数,的所有对齐数的最大值。
通过简单的回顾,我们又重新掌握了结构体大小的计算方式,现在,我们来以类对象来实践一下。
我们上图的类A为例:
首先,第一个变量a,放在与类对象偏移量为0的地址处,占4个字节。往后就是变量c,char的类型大小为1,而vs环境下的默认对齐数为8,两者取较小值。对齐数为1,偏移量4是1的整数倍,所以,变量c直接跟在变量a后面存放就行了,接下来,是变量d的存放,d的类型大小为8,与默认对齐数8相等,因此,对齐数取8,而8的最小的整数倍偏移量为8,所以,我们在偏移量为8的地址处继续往下存。到这里,我们三个变量就存放好了,类对象的大小为16。接下来,我们来验证一下结果。
通过上图,我们验证了我们的结果是正确的。接下来,我们再来看看嵌套类对象的类对象的计算方式。
我们以类B为例,首先,我们把第一个变量b放在与对象偏移量为0的地址处。然后,到第二个变量ch,它的类型大小为1与编译器默认的对齐数8相比,较小值为1,也就是对齐数为1,而偏移量5刚好是1的整数倍,因而,变量ch可以跟在后面存放,变量d也是同样滴道理,可以跟在变量ch后面存放。接下来,就只剩下变量a了,前面,我们已经得出了变量a的最大对齐数是8,现在是8的整倍数的最小偏移量刚好是8,因此,我们需要从偏移量8开始存储变量a。前面我们通过计算类A得知,类A的类对象大小为16,因此,往后16个字节用来存放变,加上前面已经使用的8个字节,类B的类对象大小为24,而类B的最大对齐数为8,因而,类B的类对象的总大小就为24
通过前面的学习,我们只到类对象中只存储成员变量。那如果一个类中没有成员变量,那它实例化出的对象,大小是多少呢?对于这样的对象。大小为1个字节,目的是为了占用空间,表示该对象存在。那这样的空类有什么用呢?它的意义在仿函数。对于仿函数的具体细节,我们后面再讲。
接下来,我们来看看this指针。
this指针
首先,我们先来定义一个日期类。
我们对类进行实例化,设置对象d1和d2,使用d1来调用Init函数。那问题来了,编译器是怎么知道是d1调用的,而不是d2调用它的呢?这是因为C++增加了一个隐藏的指针,当函数被调用时,会让该指针指向调用函数的对象,函数中对成员变量的操作,都会通过该指针来访问,不过这些所有的操作对于用户都是隐藏的,编译器会自动完成。我们把这个指针称为this指针。
对于this指针,它具有4个特性。
第一个:this指针的类型,是类类型*const ,也就是在成员函数中,不能被修改
注意:这里const的位置和平常我们使用的不太同,平时我们都是
const A*a;//这个表示的是a指向的内容不能被修改。
a=nullptr;
//如果把const往后放一点
A*const a;//这样表示a本身不能被修改
a=nullptr;//我们还像之前把空指针nullptr赋值给变量a,是错误的做法
第二点:只能在成员函数中使用
第三点:this指针本质上是成员函数的形参,当对象调用成员函数时,会将对象的地址作为实参传递给this形参,所以,对象中不存放this指针
第四点:this指针是成员函数的第一个隐藏的指针形参,一般由编译器通过exc寄存器自动传递,不需要用户手动传递。
那我们可不可以显示传递this指针呢?
从上图中,我们能清楚的看到,我们显示传递this指针会报错,但我们使用this指针来访问成员变量是没有问题的。
接下来,我们来看两道例题,来加深我们对this指针的理解
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
return 0;
}
第一道,第二道题你们的答案分别是什么?
第一道题的正确答案是C,我们来分析一下原因,p调用PrintA函数,并没由对this指针解引用,因为Print的地址并不存在对象里。p作为实参传给this形参,虽然this是空指针,但它并没有解引用。
第二道题的正确答案是B,为什么程序会崩呢?我们跟着程序来走一遍,我们还是把变量p赋值为空指针nullptr,通过p来调用函数PrintA(),将p作为实参传给this,然后,通过this来访问变量_a。this指针是空指针,为什么能解引用访问成员变量?这显然是荒唐的,于是程序就崩了
通过刚刚的学习,我们对于this指针,有了一个比较全面的了解。你觉得它会存放在哪一个部分(堆,栈,静态区,常量区)呢?前面,我么提到this是成员函数的形参,而函数在调用的时候,会放到栈中,因此,和函数相关的this指针自然也就放到了栈中。
说到这里,关于this指针的内容,我们大致就说完了。
接下来,我们进入下一个知识点的学习。
6个默认成员函数
如果一个类中什么成员都没有,简称空类。
但空类里面真的什么都没有吗?任何类不管有没有写东西,如果没有写这六个函数,编译器都会默认的生成
这块涉及的内容比较多,我们就先讲讲两个,构造函数和析构函数。
构造函数
构造函数的功能,并非如其名所视,构造。它的功能是初始化对象。在没有学习C++之前,我们通过C语言来实现顺序表,链表和栈等数据结构。在写这些数据结构的一开始,我们都会写一个具有初始化功能函数用来初始化对象。在使用一个数据结构前,我们首先要做的就是调用初始化函数对对象进行初始化,然后,才能正式开始使用这个数据结构。
C语言的这些方式都没什么问题,可为什么到了C++这里就要更名叫构造函数呢?
C++为什么取名叫构造函数?咱也不懂。不过构造函数的引用,是有原因的。我们在使用C语言写的数据结构,都需要手动的调用初始化函数,有时我们还容易忘记初始化。到C++这里,作了一些优化,引入构造函数承担初始化的功能,在我们定义类对象后,编译器会自动调用构造函数进行初始化。
接下来,我们来看一下构造函数的定义方法,我们还是以日期类为例。
首先,我们需要以类名作为函数名定义一个函数,然后,根据需求写函数的实现,这样一个用来初始化的构造函数,就完成了。
我们来简单的总结一下,对于构造函数,有四个特征:
1.函数名与类名相同
2.无返回值
3.对象实例化时编译器会自动调用相应的构造函数初始化对象
4.构造函数可以重载
我们来看一个重载错误的例子
当我们定义类无参数初始化的对象d1时,编译器无法确定该调用哪一个构造函数。如果我们需要进行类似的定义构造函数,可以删除第二个构造函数缺省值,来避免调用歧义。
构造函数如果我们不写,系统会自动生成一个无参的构造函数。不过很不靠谱,很容易出问题。因为不同的平台,底层实现是不同的。
对于构造函数我们先了解到这,我们来看看析构函数。
析构函数
析构函数,它的功能是销毁对象。
为了便于大家理解,我们这次以数据结构栈为例子
上图是我们在学C语言时,用C语言写的销毁函数,当我们不需要使用栈的时候,我们需要手动调用它,把我们申请的空间释放掉,避免内存的泄露。可是,并不是每个人的记性都很好,总有些时候,我们会忘记把空间的释放。祖师爷,也是内存泄露受害者,深知程序员的痛苦,于是引入的析构函数,在对象生命周期结束时,让C++编译器自动调用析构函数,执行销毁对象的功能,把我们申请的空间释放掉。让程序员不用再因为忘记销毁对象,而导致内存泄漏的问题的烦恼。
析构函数的定义方式与构造函数类似
析构函数的名称也需要以类名作为函数名,与构造函数不同的地方是,析构函数前面还要加上波浪号。然后,在大括号里填写相应的函数实现方式,一个栈的析构函数就完成了。
对于析构函数也有四个特性:
1.析构函数的函数名是在类名前加上~
2.无参数无返回值
3.一个类只能有一个析构函数,如果析构函数没有写,编译器会自动生成析构函数。注意:析构函数不能被重载
4.对象生命周期结束时,C++编译器会自动调用析构函数销毁对象
好了,到这里,我们本次的分享就到此结束了,不知道我有没有说明白,给予你一点点收获。如果你有所收获,别忘了给我点个赞,这是对我最好的回馈,当然你也可以在评论发表一下你的收获和心得,亦或者指出我的不足之处。如果喜欢我的分享,别忘了给我点关注噢。
标签:函数,对象,C++,对齐,构造函数,我们,指针 From: https://blog.51cto.com/u_15933803/7420058