C++自制心得——类与对象上
前言:
从这里开始,我们将面对C++的第一个拦路虎,类与对象。这里的内容大概要写四个专栏,开篇一章,六大成员函数两章,收尾一章,每一章都是超级大长篇,请做好心里准备。而且,至少在类与对象这一节,没有几个语言比C++更麻烦,一是C++需要向下兼容C语言,二是C++出的早,坑会多。废话不多讲,我们开始吧。
1.类与结构体
我们这里不讲面向对象的具体定义,别人都讲烂了,自己找一个看就是。我们从结构体开始讲,在C++里,结构体直接被升级成了类,同时也保有了C语言原本的用法。
s1是C语言用法,s2是C++的用法。升级后的结构体支持成员函数,并且定义对象时也不用加struct关键字了。
不过在实际应用中我们更喜欢用类来代替结构体,如图
但是换成类以后程序就跑不了了,如图

这是什么错误,你不是说结构体被升级成了类吗?怎么换了类就报错了。其实类与结构体还是有一点区别的,那就是默认访问限定符的不同。
访问限定符有三种类型,public(公有),private(私有),protected(保护),目前,我们可以认为私有和保护是一样的(它们的区别在继承和多态才有体现,现在只讲封装)
被public修饰的成员可以在类外通过正常方式访问,而被private修饰的成员不能在类外通过正常方式访问。而结构体的默认访问限定符为公有,类的默认访问限定符为私有,这就是报错原因。
那么,类将private作为默认修饰符有什么好处吗?上代码
这里有一个栈,现在我们想访问其栈顶元素,那就写一个top函数访问,一句话的事。不过,某些人喜欢用另一种方案,他们觉得访问栈顶元素可以直接用top变量访问,没必要写一个函数,想法还不错,但是......

为什么结果是随机值,我相信观众们都清楚,不过这种编程思路就反映出C语言的一大痛点,过于自由。作为代码的设计者,我们自然希望用户只通过我们提供的接口使用代码,而不希望用户自由访问。显然用private修饰我们不想让用户访问的成员是很科学的,这就是封装的好处。
2.类的声明与定义
这个问题很简单,我就直接贴代码了
stack.h
stack.cpp
前四个成员函数不用管,后面会讲,我们看后面的。类的声明定义规则实际上是函数声明定义与结构体声明定义的集成,成员变量的声明定义按照结构体的来,成员函数的声明定义按照函数的来,唯一需要注意的是成员函数的定义处在全局而非类域里,所以定义处需要添加::
3.成员变量风格
你应该已经发现了,我给出的示例代码里成员变量在前面都有“_”符号,你可能在学习C语言的时候也被告知要给成员变量加修饰。这当然是有理由的,我以日期类的init来举例子。
如果你不给成员变量加修饰,那这个init就起不到它应有的功能,编译器会将它认定为自己给自己赋值(你要愿意用this当我没说)
这就行了。
4.实例化
可以这么写代码吗?
不行,因为这个类没有实例化,我们访问的只是一个类型

将类比作房子图纸,实例化的对象比作房子,逻辑会很清晰,房子的图纸不能住人,但房子可以。把图纸变成房子的过程就是实例化,这个过程就会给对象开空间,你就能访问了。
再看一个问题,对象d1占用多少空间?根据内存对齐可以得出d1的成员变量所占空间为12字节,但是成员函数占不占空间?不知道,那就试试。

哦,结果是12,看上去成员函数不占对象空间,事实也的确如此。实例化对象只会给成员变量开空间,而成员函数不会进入具体对象本身,而是会进入公共代码区。这是符合逻辑的,成员变量需要有很多份,而函数只需要一份,显然,把成员函数放入对象就是一件特别浪费的事情。
上点难度,这三个对象的空间是多少?







揭晓答案

第一二个很好理解,默认对齐数为8,所以第一个对齐数为4,第二个对齐数为8,所以占用空间为8和24,但是第三个为什么是1,不是0。是这样的,如果第三个是0,我们就无法区分多个A3类的对象,因此我们象征性的给了一个字节以示区分。
5.this指针
你觉得init函数里的成员变量是谁的成员变量?







哪个对象调用了这个函数,函数里的成员变量就是那个对象的。但是从表面上看init函数的形参只有年月日要被赋的值,没有调用对象的年月日,那么编译器是怎么做到这一点的?答案就是this指针。C++给每一个成员函数都添加了一个隐含参数this指针,固定为函数的第一形参,指向调用对象,不可更改指向(类型 + * const this),并且其调用和传参均由编译器自动进行,不可显式写出,但在函数里可显式写出。如果我们把this指针显式写出,那应该是这样的
( ̄▽ ̄)现在我要出三道题,看看各位对this指针的理解是否到位。
第一个代码的运行结果是:A. 编译错误 B. 运行错误 C. 正常运行
第二个代码的运行结果是:A. 编译错误 B. 运行错误 C. 正常运行
第三个代码的运行结果是:A. 编译错误 B. 运行错误 C. 正常运行







揭晓答案,CCB



以我在这一块学习的经验来说一定有很多人选BBB。那么各位要想清楚一个问题,在a->print()这句代码里,print()是不是a直接指向的?No,前面我们说了,成员函数不在对象里,在公共代码区,那实际上a直接指向的只有int x,显然,在第一二种情况里,a压根没有解引用,代码实际上只完成了nullptr的拷贝,而没有解引用,所以代码可以正常运行。只有第三种情况我们解引用了nullptr。为了验证我的结论,以第一种情况为例,我们去翻反汇编。

看见了吗,a->print()只干了两件事,将nullptr赋给ecx寄存器(C++觉得this指针会被经常调用,所以会把this指针存在寄存器里,而非进行压栈操作),调用print函数,没有解引用操作。
下面再出两道面试题:
1. this指针可以为空吗?
2. this指针存在哪里?







第一题我们演示过了,可以为空。第二题的答案是栈区,为什么,因为this是函数形参,函数形参存在栈区(有没有人写this指针存在对象里?有没有?有没有?)
可喜可贺,类与对象上终于讲完了,下一个就是六大成员函数,到时候,等着被虐吧。