> 文档中心 > 三部曲深剖C++类与对象——中篇

三部曲深剖C++类与对象——中篇

目录

    • 传统艺能😎
    • this指针🤔
    • this指针存放在哪🤔
    • nullptr与类🤔
  • 类的默认成员函数😎
    • 构造函数🤔
    • 意义🤔
    • 析构函数🤔
    • 拷贝构造🤔

传统艺能😎

小编是双非本科大一菜鸟不赘述,欢迎大佬指点江山(QQ:1319365055)
此前博客点我!点我!请搜索博主 【知晓天空之蓝】

🎉🎉非科班转码社区诚邀您入驻🎉🎉
小伙伴们,打码路上一路向北,背后烟火,彼岸之前皆是疾苦
一个人的单打独斗不如一群人的砥砺前行
这是我和梦想合伙人组建的社区,诚邀各位有志之士的加入!!
社区用户好文均加精(“标兵”文章字数2000+加精,“达人”文章字数1500+加精)
直达: 社区链接点我

🎉🎉🎉倾力打造转码社区微信公众号🎉🎉🎉
在这里插入图片描述


在这里插入图片描述

this指针🤔

现在给出一段代码,实现一个普通的日期 date 的打印:

class date{public:void print(){cout << year<<' ' << month <<' '<< day << endl;}void init(int y, int m, int d){year = y;month = m;day = d;}private:int year;int month;int day;};int main(){date d1;date d2;date d3;d1.init(2022, 5, 15);d2.init(2022, 5, 14);d3.init(2022, 5, 13);d1.print();d2.print();d3.print();return 0;}

结果如你所想:
在这里插入图片描述
那么问题来了,d1,d2,d3在调用类里面的 print 函数时,并没有指明对象或者给出形参,最后结果却能打印出不同的三个结果,这是为什么呢?、

这就是C++语法中的隐藏的 this 指针这里的 this 指针其实就是隐含的形参,这个形参会对函数进行处理,比如刚刚的 print 函数以及他的调用,他们的真面目其实是这样:

void print(date* this)
{
cout<year << ’ ’ <month << ’ ’ <day << endl;
}

d1->init(&d1,2022,5,15);

也就是说所有成员变量前面都会有一个 this 指针修饰(实际上 this 指针是个 const 类型,指针不能修改但指向内容可以修改),使得形参和实参之间架起一座无形的桥梁,进行连结。值得注意的是,这个过程是编译器在捣鼓实现的,不需要我们自己去搞,要是写的时候强行带上 this 指针反而还会报错
在这里插入图片描述

那数据是如何对应上的呢?换个问题就是 print 每次是怎么样对应上 d1,d2,d3的,其实访问成员变量年月日,并不是在访问 private 里的年月日,private 里只是声明并不存在空间的开辟,访问但是同一个类里面的 init 函数从而访问到成员变量。

this指针存放在哪🤔

this 指针存在寄存器里面

其实编译器在生成程序时加入了获取对象首地址的相关代码。并把获取的首地址存放在了寄存器ECX中(VC++编译器是放在ECX中,其它编译器有可能不同)。也就是成员函数的其它参数正常都是存放在栈中,而this指针参数则是存放在寄存器中。

类的静态成员函数因为没有this指针这个参数,所以类的静态成员函数也就无法调用类的非静态成员变量。

在这里插入图片描述

nullptr与类🤔

如果我定义了一个空值指针,让空值指针分别去访问我类里面的函数与成员变量会怎么样:

class Data{public:void print(){cout << "hello" << endl;}void printa(){cout << a << endl;}private:int a;};int main(){Data* n = nullptr;n->print();//访问函数n->printa();//访问成员变量return 0;}

结果如图:
在这里插入图片描述
没错,程序崩溃辣!但是很明显,hello 打印出来了就说明函数的访问是没有问题的,但是是没有办法访问成员变量的。

我们说类里面用空指针访问函数,成员变量结果会不同,原因就是函数在公共代码区,不需要解引用,直接找到函数地址变成 call 地址即可,而成员变量的访问需要解引用自然空指针就会寄。

空指针 nullptr 其实并不是真的“空”,实际上是真实存在的,他指向虚拟进程空间里面地址为 0 的地方,这个 0 地址处是用来程序初始化的,是预留出来的,并不是用来存储数据的。因此空指针一旦指向数据,这个数据就是不被认可的,没有意义的。
在这里插入图片描述

类的默认成员函数😎

类里面什么都没有,就称它为空类,实际上空类中真的什么都没有吗?答案是 NO!任何一个类在默认情况下都会生成 6 个成员函数。

在这里插入图片描述

构造函数🤔

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名叫构造,但他的主要任务并不是开辟空间或者创建对象,而是将对象初始化。我们不写,编译器也会生成一个默认无参数的构造函数,但这个默认的构造函数不一定有用,而 C++11打的补丁,针对编辑器自己生成的默认成员函数不初始化的问题,给了缺省值来供默认构造函数使用。

需要注意的是:

  1. 类名与函数名保持一致
  2. 可以不用传参,没有返回值
  3. 对象实例化时编辑器自动调用对应的构造函数
  4. 构造函数支持函数重载
  5. 如果类中没有显式定义构造函数,C++编译器会自动生成一个无参的默认构造函数,如果我们自己显式定义了就不会给出了
  6. 无参的构造函数和全缺省的构造函数都被称为默认构造函数,并且默认构造函数只能有一个

意义🤔

C++将变量分为两种:内置类型(int,char,指针类型等等)自定义类型(struct/class 去定义的类型对象),默认生成的构造函数对于内置类型成员变量不做处理,对自定义类型才进行处理。而这正好就是 C++ 语法设计的一个败笔,他会导致

如果有内置类型的成员就得自己写构造函数,比如:

class Stack{public:     void push(int x){     }     void pop(){     }private:     Stack stackpush;     Stack stackpop;}

该类里面只有自定义类型成员变量,就不需要去写构造函数,默认的构造函数就可以完成了。总结一下就是如果类里面只有自义定类型,就可以用默认构造函数,如果存在内置类型或者需要显示传参初始化就需要自己写构造函数。

析构函数🤔

如果构造函数高速了我们对象是怎么来的,那么析构函数就是在告诉我们对象是怎么走的。析构函数与构造函数功能相反,他并不是完成了对象的销毁,因为局部对象销毁是由编译器来完成,而析构函数是完成对象的一些资源清理工作,他是在对象销毁时自动调用
在这里插入图片描述

再三强调不是销毁对象本身!不是销毁对象本身!所谓的资源清理针对的对象是 malloc,new 或者 fopen 这类的操作进行清理收尾,其实本质上就相当于我们之前的 destroy 函数。

其特征如下:

  1. 析构函数名是在类名前面加上字符 -
  2. 无参数也无返回值
  3. 一个类有且仅有一个析构函数,如果没有显式定义,系统会自动生成析构函数
  4. 对象声明周期结束时,C++编译系统自动调用析构函数

但是注意一个顺序问题

int main(){Stack st1;Stack st2;return 0;}

st1 相比 st2 先构造这没什么问题,但 st2 却比 st1 先析构,析构和构造的顺序是相反的。但他和构造函数一样,对内置类型不处理但是对于自定义类型会去调用。

拷贝构造🤔

有没有可能你会想搞一个和自己一样的对象出来呢?如果想那就该我拷贝构造登场辣!

int main(){     Date d1(2022,5,16);     Date d2(d1);     return 0;}

这里的 d2 就是拿 d1 来初始化,所以拷贝构造只有单个形参,该形参是对类类型对象的引用,一般由 const 修饰,再用已存在的类类型对象创建新对象时由编译器自动调用。他实际上是构造函数的一种函数重载,他的参数只要一个,而且必须使用引用传参,因为使用传值会引发无穷递归调用。

C++规定了自义定类型对象,拷贝初始化要调用拷贝构造完成。这就引出来一个问题,比如我们拷贝栈,定义了两个对象 st1 和 st2,假如我写成下面的样子就会出乱子:

Stack st1(1);Stack st2(st1);

因为是拷贝构造, st2 指向地址和 st1 是一样的,这并不是我们想要的结果,我们传统拷贝是指向同一块空间,而且这里拷贝构造会崩,原因很简单,st1 构建完成变成 st2 参数去初始化 st2,st2 一旦拷贝完成就要进行析构,而析构会先执行 st2 ,st2 一旦被清理这块空间就会被销毁,而此时 st1 还在使用这块空间,就会导致内存错误。这就需要深拷贝实现,我们后面再去学习。

今天就先到这里吧,润了家人们。