首页 > 编程语言 >C++-类和对象(2)

C++-类和对象(2)

时间:2023-09-09 16:32:18浏览次数:39  
标签:函数 对象 C++ 对齐 构造函数 我们 指针

今天,我们继续学习类和对象的相关知识,本次学习的内容,主要是this指针和默认构造函数。

继上篇文章结尾,我们讲到了,一个类实例化出对象后,它的成员变量和成员函数是如何存储的。类实例化出的对象,会给成员变量开辟空间,而成员函数则放在公共代码段区(这个类共有的空间),不会单独开辟空间。

对于类的成员变量和成员函数的存储方式,我们了解了。那它的大小是如何计算的呢?

计算类对象(类实例化出来的对象)的大小

类对象的大小计算方式和结构体的计算方式相同。我们在学习C语言的结构体时,已经学习过了结构体的大小计算方式。现在,我们来回顾一下,对于结构体的大小的计算,要掌握以下四点:

第一点:第一个变量在与结构体偏移量为0的地址处

第二点:其他成员变量要对齐到某个数字的整数倍的地址

注意:对齐数=编译器默认的一个对齐数与该成员大小取较小值

VS中默认对齐数为8

第三点:结构体总大小为最大对齐数(所有类型中的最大者与编译器的默认对齐数取最小)的整数倍

第四点:如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是最大对齐数的整数倍。这里的最大对齐数是包含嵌套结构体中的对齐数,的所有对齐数的最大值。

通过简单的回顾,我们又重新掌握了结构体大小的计算方式,现在,我们来以类对象来实践一下。

C++-类和对象(2)_构造函数


我们上图的类A为例:

C++-类和对象(2)_析构函数_02

首先,第一个变量a,放在与类对象偏移量为0的地址处,占4个字节。往后就是变量c,char的类型大小为1,而vs环境下的默认对齐数为8,两者取较小值。对齐数为1,偏移量4是1的整数倍,所以,变量c直接跟在变量a后面存放就行了,接下来,是变量d的存放,d的类型大小为8,与默认对齐数8相等,因此,对齐数取8,而8的最小的整数倍偏移量为8,所以,我们在偏移量为8的地址处继续往下存。到这里,我们三个变量就存放好了,类对象的大小为16。接下来,我们来验证一下结果。

C++-类和对象(2)_析构函数_03

通过上图,我们验证了我们的结果是正确的。接下来,我们再来看看嵌套类对象的类对象的计算方式。

C++-类和对象(2)_构造函数_04

我们以类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

C++-类和对象(2)_析构函数_05

通过前面的学习,我们只到类对象中只存储成员变量。那如果一个类中没有成员变量,那它实例化出的对象,大小是多少呢?对于这样的对象。大小为1个字节,目的是为了占用空间,表示该对象存在。那这样的空类有什么用呢?它的意义在仿函数。对于仿函数的具体细节,我们后面再讲。

接下来,我们来看看this指针。

this指针

首先,我们先来定义一个日期类。

C++-类和对象(2)_析构函数_06

我们对类进行实例化,设置对象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寄存器自动传递,不需要用户手动传递。

C++-类和对象(2)_析构函数_07

那我们可不可以显示传递this指针呢?

C++-类和对象(2)_析构函数_08

从上图中,我们能清楚的看到,我们显示传递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++-类和对象(2)_构造函数_09

这块涉及的内容比较多,我们就先讲讲两个,构造函数和析构函数。

构造函数

构造函数的功能,并非如其名所视,构造。它的功能是初始化对象。在没有学习C++之前,我们通过C语言来实现顺序表,链表和栈等数据结构。在写这些数据结构的一开始,我们都会写一个具有初始化功能函数用来初始化对象。在使用一个数据结构前,我们首先要做的就是调用初始化函数对对象进行初始化,然后,才能正式开始使用这个数据结构。

C语言的这些方式都没什么问题,可为什么到了C++这里就要更名叫构造函数呢?

C++为什么取名叫构造函数?咱也不懂。不过构造函数的引用,是有原因的。我们在使用C语言写的数据结构,都需要手动的调用初始化函数,有时我们还容易忘记初始化。到C++这里,作了一些优化,引入构造函数承担初始化的功能,在我们定义类对象后,编译器会自动调用构造函数进行初始化。

接下来,我们来看一下构造函数的定义方法,我们还是以日期类为例。

C++-类和对象(2)_构造函数_10

首先,我们需要以类名作为函数名定义一个函数,然后,根据需求写函数的实现,这样一个用来初始化的构造函数,就完成了。

我们来简单的总结一下,对于构造函数,有四个特征:

1.函数名与类名相同

2.无返回值

3.对象实例化时编译器会自动调用相应的构造函数初始化对象

4.构造函数可以重载

我们来看一个重载错误的例子

C++-类和对象(2)_this指针_11

当我们定义类无参数初始化的对象d1时,编译器无法确定该调用哪一个构造函数。如果我们需要进行类似的定义构造函数,可以删除第二个构造函数缺省值,来避免调用歧义。

C++-类和对象(2)_this指针_12

构造函数如果我们不写,系统会自动生成一个无参的构造函数。不过很不靠谱,很容易出问题。因为不同的平台,底层实现是不同的。

对于构造函数我们先了解到这,我们来看看析构函数。

析构函数

析构函数,它的功能是销毁对象。

为了便于大家理解,我们这次以数据结构栈为例子

C++-类和对象(2)_this指针_13

上图是我们在学C语言时,用C语言写的销毁函数,当我们不需要使用栈的时候,我们需要手动调用它,把我们申请的空间释放掉,避免内存的泄露。可是,并不是每个人的记性都很好,总有些时候,我们会忘记把空间的释放。祖师爷,也是内存泄露受害者,深知程序员的痛苦,于是引入的析构函数,在对象生命周期结束时,让C++编译器自动调用析构函数,执行销毁对象的功能,把我们申请的空间释放掉。让程序员不用再因为忘记销毁对象,而导致内存泄漏的问题的烦恼。

析构函数的定义方式与构造函数类似

C++-类和对象(2)_析构函数_14

析构函数的名称也需要以类名作为函数名,与构造函数不同的地方是,析构函数前面还要加上波浪号。然后,在大括号里填写相应的函数实现方式,一个栈的析构函数就完成了。

对于析构函数也有四个特性:

1.析构函数的函数名是在类名前加上~

2.无参数无返回值

3.一个类只能有一个析构函数,如果析构函数没有写,编译器会自动生成析构函数。注意:析构函数不能被重载

4.对象生命周期结束时,C++编译器会自动调用析构函数销毁对象

好了,到这里,我们本次的分享就到此结束了,不知道我有没有说明白,给予你一点点收获。如果你有所收获,别忘了给我点个赞,这是对我最好的回馈,当然你也可以在评论发表一下你的收获和心得,亦或者指出我的不足之处。如果喜欢我的分享,别忘了给我点关注噢。

标签:函数,对象,C++,对齐,构造函数,我们,指针
From: https://blog.51cto.com/u_15933803/7420058

相关文章

  • Windows平台 CLion 远程调试 Linux 的 C++ 程序
    Windows平台CLion远程调试Linux的C++程序1.CLion的安装Pass2.Linux环境的配置2.1.安装gdbserver这里举例Ubuntu环境下的安装:sudoapt-getinstallgdbserver2.2配置CLion2.2.1.配置Toolchains首先在CLion的File->Settings->Tools->SSHConfigu......
  • c++创建简单窗口
    #include<Windows.h>//自定义的窗口过程LRESULTCALLBACKMyWindowProc(HWNDhWnd,UINTMsg,WPARAMwParam,LPARAMlParam){switch(Msg){caseWM_DESTROY://WM_DESTORY代表“窗口关闭”消息PostQuitMessage(0);return0;default......
  • C++基础总结
    1C++初识1.1第一个C++程序编写一个C++程序总共分为4个步骤创建项目创建文件编写代码运行程序1.1.1创建项目 VisualStudio是我们用来编写C++程序的主要工具,我们先将它打开1.1.2创建文件右键源文件,选择添加->新建项给C++文件起个名称,然后点击添加即可。1.1.3编写代码#include<......
  • C++常用操作
    C++常用操作数组输入二维数组#include<iostream>#include<vector>usingnamespacestd;intmain(){intr=0,c=0;cout<<"输出行数r:";//规定二维数组行数cin>>r;cout<<"输入列数c:";//规定二维数组列数cin>......
  • JavaScript-Number对象
    概述Number对象是数值对应的包装对象,可以作为构造函数使用,也可以作为工具函数使用。作为构造函数时,它用于生成值为数值的对象。varn=newNumber(1);typeofn//"object"上面代码中,Number对象作为构造函数使用,返回一个值为1的对象。作为工具函数时,它可以将任何类型的值转为数值......
  • 自己动手写一个C++日志库
    自己动手写一个C++日志库logger.h////CreatedbyFkkton2023/9/8.//#pragmaonce#include<string>#include<iostream>#include<fstream>#include<chrono>#include<sstream>namespacefkkt{classlogger{public:......
  • C# 封装 C++的dll
    C#的程序引用C++的dll时,首先要保证两者基于的平台一致,比如都是x64,或者都是x86的程序,否者两者之间不能直接调用,然后,要保证两者的数据类型可以相互识别,相互通用。在此重点介绍几个常用的数据转换。C++的char*和char[]数组,对应到C#的string类型C++的Handle类型,一般是一个很......
  • C++系列三:QT-Quick
    目录前言:理论:案例:前言:其实和我接触过的Flutter,有异曲同工之处。记住F1,其实就ok了。参考链接:官方、教程1、教程2、教程3、教程3理论:案例://main.cpp:QQmlApplicationEngineengine;engine.load(QUrl(QStringLiteral("qrc:/qt/qml/qtquickapplication1/main.qml")));if(e......
  • 空指针产生的条件 null对象调用属性
    null对象调用属性 e.gpublicclassStudent{Integerage;Stringname;Stringaddress;Useruser;}Studentstudent=newStudent();12:student.getUser().getName()Exceptioninthread"main"java.lang.NullPointerException atpatter......
  • C++的纯虚函数和抽象类
    在C++中,可以将虚函数声明为纯虚函数,语法格式为:virtual返回值类型函数名(函数参数)=0;纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数。最后的=0并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”。包含纯虚函数的类称为抽......