C++初阶(4)类和对象(上)
1. 面向过程和面向对象初步认识
C语言:是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++:是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完 成。
对象:人——需要实现与衣服交互的接口、与洗衣粉交互的接口。
对象:衣服、洗衣粉——需要实现与洗衣机交互的接口。
面向对象的思想关注的是对象及对象之间的关系。(交互接口)
2. 类的引入
- C语言:结构体中只能定义变量;
- C++ :结构体内可以定义变量,也可以定义函数。
之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;
现在以C++方式实现,会发现struct中也可以定义函数。
C++兼容C中struct的用法——C++中把struct升级成了类。
1个类,可以实例化出N个对象。(一对多的关系)
C++中类的概念和C语言的结构体的概念相似,C语言的结构体类型可以用来定义变量,C语言偏向于叫变量,C++既可以叫变量,也可以叫对象。
C++不是纯面向对象的语言,C++叫基于面向对象。
(JAVA是纯面向对象的语言,没有面向过程的那部分,JAVA的main函数都包在类里面)
因为C++兼容C,C++既有面向对象,也有面向过程——C语言的语法。
C语言的结构体升级到C++的类的升级部分:
- 升级1:类里面可以定义数据、函数。
(结构体里面只有数据——C语言数据和方法是分离的) - 升级2:struct升级成类后,不需要“struct+名称”,单独使用“名称”就能表示一个类型
typedef int DataType;
//C语言
struct Stack
{DataType* array;size_t capacity;size_t size;
};void StackInit(size_t capacity) //这个Init函数在全局,有stackinit、queueinit……不加前缀分不清
{//……
}//C++
struct Stack
{void Init() //函数可以放到类里面{ //好处1:那么函数取名就可以简化——默认是针对这个Stack类型的//……}void Push() //好处2:这个类型的成员变量不需要写入形参列表,可以直接访问{//……}DataType* array;size_t capacity;size_t size;
};int main()
{struct Stack s; //兼容用法Stack s; //升级用法return 0;
}
- 好处1:简化函数名;
- 好处2:简化形参列表;
- C++中struct后面的名称就可以代表类型,不需要再像C语言一样使用struct + 名称才能表示完整的结构体类型。
//C语言
typedef struct ListNodeC
{struct ListNodeC* next;int val;
}LTNode;//C++
struct ListNodeCPP
{ListNodeCPP* next;int val;
};
完善栈类型的初始化函数、插入函数。
这时的函数不在全局域,在类(结构体)里面,就需要通过“结构体+结构体成员访问符”的方式来进行调用。
但是这里有编译报错,C++的类还引入了一个概念叫作访问限定符。
- class的默认访问权限为private,struct为public(因为struct要兼容C)
C++禁止直接访问private的成员变量,只能通过公开的渠道(成员函数)去获取:
C++类的优势之一:强制用户,规范数据访问行为,必须使用公有的接口函数。
面向对象有很多特性,最典型的3个就是:封装、继承、多态。
总结——升级了哪些方面?
- 结构体只有变量、类型——>而类里面还可以有函数
- 升级之后,struct stack这个结构体的名称stack就可以代表这个类型,可以直接使用stack去定义结构体
- 加入了访问限定符——想给你访问的就设置成公有,不想给你访问的就设置成私有
以前:数据、方法——分开放置
现在:【方法+数据】—— 封装到一起
- 优势1:方便成员函数去访问成员变量。
- 优势2:封装起来之后,杜绝对成员变量的随意访问。
3. 类的定义
C++中struct可以定义类,C++兼容C中struct的用法,同时struct升级成了类,明显的变化是struct中可以定义函数,一般情况下我们还是推荐用class定义类。
在C++中更喜欢用class来代替struct定义类。
class为定义类的关键字,ClassName为类的名字,{}中为类的主体。
class Stack
{//……
};int main()
{class Stack st;Stack st; //一般都是直接使用“类名”代表这个类型return 0;
}
【注意】类定义结束时后面分号不能省略。
类体中内容称为类的成员:
- 类中的变量称为类的属性或成员变量;
- 类中的函数称为类的方法或者成员函数。
- 定义在类里面的成员函数默认为inline。
3.1 类的两种定义方式
(1)类的声明和定义不分离
类的成员函数的声明和定义全部在类体中。
【注意】成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
以栈类为例:
【注意】
- 成员变量的声明和定义(对象的实例化)一般都是分离的。
【注】
- 变量的声明:给出变量的类型、名字。
- 变量的定义:给变量实际在内存中申请空间——“开空间”。
而类这里还没有开空间,那什么地方被定义?
- 在stack st1的时候,即对象的实例化的时候,成员变量作为对象的一部分被定义出来。(开出空间)
(2)类的声明和定义分离
类声明放在.h文件中,成员函数定义放在.cpp文件中。
以栈类为例:
用任何一个变量/函数,编译器都要去找它定义的地方(出处)。
- 默认域:在当前局部域搜索,再到全局域、展开的命名空间域。
- 指定域:在指定的域去搜索。(指定全局域、命名空间域、类域)
命名空间(域)是对全局域的变量/函数/类型进行一个名字的隔离。
类有两层作用:
- 用来形成一个新的类型——既有数据,又有函数——其名字也需要被隔离。
- 形成类域,对成员进行保护——使用的时候就需要指定类域。
花括号{ }括起来的都是一个域。
类除了可以用来定义类的成员变量、成员函数以外,它还形成了一个域——类域。
类域是对这个类的变量/函数/类型进行一个名字的隔离。
【注意】
- 类的成员函数声明和定义分离,在定义的地方需要加“类名::”——指定类域。
- 在函数名的前面指定类域。(返回值之前)
指明出处:
让编译器知道这个Init是stack类里面的成员函数,而非一个全局函数,可以使用类的成员变量。
一般情况下,都建议采用第二种方式——声明和定义分离。
【注意】为了方便教学演示,使用方式1定义类,大家后序项目工作中最好使用方式2。
3.2 成员变量命名规则的建议
栈、日期这两个类会贯穿整个类和对象的学习。
日期类的初始化函数:给年、月、日三个成员变量进行初始化。
方法1.——成员变量变个名字——>伤害可读性。
方法2——C++为了方便区分成员变量,编码者都会自己给类的成员变量加一个特殊的标识
(前杠、后杠、m杠)
4. 类的访问限定符、封装
4.1 访问限定符
C++实现封装的方式
- 用类将对象的属性与方法结合在一块,让对象更加完善;
- 通过访问权限选择性的将其接口提供给外部的用户使用。
【访问限定符说明】
- public修饰的成员在类外可以直接被访问。
- protected和private修饰的成员在类外不能直接被访问。
(此处protected和private是类似的,暂且把它们当作是一种)- 访问权限作用域从该访问限定符出现的位置开始,直到下一个访问限定符出现时为止
- 如果后面没有访问限定符,作用域就到 } 即类结束。
- class的默认访问权限为private,struct为public(因为struct要兼容C)
通常情况下,设计类的时候,会把成员变量设计成私有(防止随便修改),成员函数设计成公有。
【注意】
- 访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。
【面试题】 C++中struct和class的区别是什么?
【解答】C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的。
区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。
注意:在继承和模板参数列表位置,struct和class也有区别,后续给大家介绍。
4.2 封装
面向对象的三大特性:封装、继承、多态。在类和对象阶段,主要是研究类的封装特性。
那什么是封装呢?
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装本质上是一种管理,让用户更方便、安全地使用类。
(C语言更容易出现不规范,更考验写代码人的素养)
数据规范管理的心得
- 不让用户直接操作原始数据(私有),只提供规范数据访问的接口(公有),让用户只能通过规范访问的接口去访问数据。
C语言就没有封装,能随意接触、操作原始数据。
类比:对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。
对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。
因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可。
在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。
5. 类的作用域
类域:类定义了一个新的作用域,类的所有成员都在类的作用域中。
在类体外定义成员时(声明和定义分离的时候),需要使用 :: 作用域操作符指明成员属于哪个类域。
class Person
{
public:void PrintPersonInfo();
private:char _name[20];char _gender[3];int _age;
};// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{cout << _name << " "<< _gender << " " << _age << endl;
}
//指定类域后,函数体内访问的成员就允许到类域里面去找
类域影响的是编译的查找规则,栈类的Init如果不指定类域Stack,那么编译器就把Init当成全局函数,那么编译时,找不到array等成员的声明/定义在哪里,就会报错。
指定类域Stack,就是知道Init是成员函数,当前域找不到的array等成员,就会到类域中去查找。
6. 类的实例化
• 用类类型在物理内存中创建对象的过程,称为 类的实例化 。
• 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员变量,这些成员变量只 是声明,没有分配空间,用类实例化出对象时,才会分配空间。
- 定义出一个类并没有分配实际的内存空间来存储它;
比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。
类就像谜语一样,对谜底来进行描述,谜底就是谜语的一个实例。
谜语:"年纪不大,胡子一把,主人来了,就喊妈妈" 谜底:山羊。
• 一个类可以实例化出多个对象,实例化出的对象,会占用实际的物理空间,存储类的成员变量。
Person类是没有空间的,只有Person类实例化出的对象才有具体的年龄。★★★
做个比方:类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在。
同样的,类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。
7. 类对象模型
7.1 如何计算类对象的大小
class A
{
public:void PrintA(){cout<<_a<<endl;}private:char _a;
};
问题:类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算 一个类的大小?
分析一下类对象中哪些成员呢?
类实例化出的每个对象,都有独立的数据空间,所以对象中肯定包含成员变量,那么成员函数是否包含呢?
首先函数被编译后是一段指令,对象中没办法存储,这些指令存储在一个单独的区域(代码段),那么对象中非要存储的话,只能是成员函数的指针。
再分析一下,对象中是否有存储指针的必要呢,Date实例化d1和d2两个对象,d1和d2都有各自独立的成员变量_year/_month/_day存储各自的数据,但是d1和d2的成员函数Init/Print指针却是一样的,存储在对象中就浪费了。如果用Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。
这里需要再额外哆嗦一下,其实函数指针是不需要存储的,函数指针是一个地址,调用函数被编译成汇编指令[call 地址], 其实编译器在编译链接时,就要找到函数的地址,不是在运行时找,只有动态多态是在运行时找,就需要存储函数地址,这个我们以后会讲解。
7.2 类对象的存储方式猜测
1. 对象中包含类的各个成员(成员变量、成员函数)
成员函数:函数名是一个函数指针。
缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。
那么如何解决呢?
2. 代码只保存一份,在对象中保存存放代码的地址
3. 只保存成员变量,成员函数存放在公共的代码段
问题:对于上述三种存储方式,那计算机到底是按照那种方式来存储的?
我们再通过对下面的不同对象分别获取大小来分析看下
sizeof(A1) : ______ sizeof(A2) : ______ sizeof(A3) : ______
结果:4,1,1
空类的大小 == 1:这一个字节,不存储有效数据,只是作为标识,标识对象被定义出来。
class A3
{};A3 aa1;
A3 aa2; //如果空类的大小是0,怎么表示对象被定义出来了呢?这两个对象定义出来了吗?//对于变量(对象),定义的核心就是开出空间,有了自己的地址
每个实例化出的对象都是既能调用自己的成员变量,也能调用自己的成员函数。
但是成员函数不算在对象的大小当中, 因为不同对象的同名成员变量st1.top/st2.top占的是不同的空间,但是不同对象的同名成员函数st1.Init/st2.Init就占的是同一块空间。
(这两个Init是同一个函数(指针))
- 每个对象都有各自不同的成员变量,但是每个对象的成员函数都是一样的,所以不需要存储多份相同的成员函数。(相同的成员函数)
- 同理,同一个成员函数只申请一份空间时,也没有必要每个对象都存储一份这个成员函数的地址。(相同的成员函数地址)
访问限定符public限制的是成员函数在语法上公有——外界可以访问。
而成员函数存放在对象之外的公共区域(代码段/公共代码区)则是另外一个概念。
【结论】
- 类的大小:实际就是该类中”成员变量”之和。(当然要注意内存对齐)
- 空类的大小:空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
- 对象的大小:对象的大小只考虑成员变量的大小。(注意内存对齐)
7.3 结构体内存对齐规则
1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
【注意】对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
(VS中默认的对齐数为8)
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
【面试题】
1. 结构体怎么对齐? 为什么要进行内存对齐?
答:CPU访问内存的数据的时候,有32根数据总线,一次可读取32个电信号位(32位)——4字节的数据。同理可得,VS最小对齐数是8是因为64位,由于硬件的一些设计,都是从4字节的整数倍位置去读取数据。
先存_i,再存_ch,_ch后面也要补齐到8个字节——因为内存访问从4字节的整数倍位置开始。
所以就算用了_ch后面3字节空间来存储,访问这些数据也是需要从_ch处开始访问内存。
其次若是存储4字节的数据,就会需要两次访问内存来获取。
(注:_ch后面3字节能存储_ch、_short,都只需要访问一次内存来获取)
所以不如把后面3字节空着,补齐8字节。
2. 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?
3. 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景?
8. this指针
8.1 this指针的引出
我们先来定义一个日期类 Date
class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout <<_year<< "-" <<_month << "-"<< _day <<endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};int main()
{Date d1, d2;d1.Init(2022,1,11);d2.Init(2022,1,12);d1.Print();d2.Print();return 0;
}
对于上述类,有这样的一个问题:
Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分。
那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?
C++中通过引入 this指针 解决该问题。
C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。
只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
//用户的代码
void Print()
{cout <<_year<< "-" <<_month << "-"<< _day <<endl;
}//编译器作的处理——有一个隐藏参数,会把对象的地址传递给被调函数
//void Print(Date* this)
void Print(Date*const this)
{cout << this->_year<< "-" << this->_month << "-"<< this->_day <<endl;
}//用户的代码
d1.Print();
d2.Print();//编译器作的处理——有一个隐藏参数,会把对象的地址传递给被调函数
d1.Print(&d1);
d2.Print(&d2);
this(对象指针)是用来访问成员变量——因为对象里面只有成员变量。
java抄了c++的作业,也有this指针,Python里面叫self。
• 编译器编译后,类的成员函数默认都会在形参第一个位置,增加一个当前类类型的指针,叫做this指针。比如Date类的Init的真实原型为 void Init(Date* const this, int year, int month, int day)
• 类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值, this- >_year = year;
• C++规定不能在实参和形参的位置显⽰的写this指针(编译时编译器会处理),但是可以在函数体内显式使用this指针。
8.2 this指针的特性
- this指针的类型:类类型* const
- 成员函数中,不能给this指针赋值。
//编译报错:error C2106: “=”: 左操作数必须为左值
this = nullptr
- 只能在“成员函数”的内部使用。
不能在实参位置、形参位置显式地写,但是可以在成员函数体内部显式地用。
- this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。
- 所以对象中不存储this指针。
- this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
【题】
题1:正常运行;
题2:运行崩溃;
【题的分析】
不要看到p->就觉得空指针解引用了。
函数调用首先去找它的出处(地址),而成员函数的地址不在对象内,而是在一个公共区域内,在公共区域内去找函数的出处(地址),也就不会对p进行解引用。
int main()
{A aa;A* p = &aa;aa.Print(); //并不会到aa里面去找Print()的地址p->Print(); //并不会到p里面去找Print()的地址aa.Print(&aa); //成员函数调用,编译器只会干一件事情——传对象的地址,给到thisp->Print(p); //成员函数调用,编译器只会干一件事情——传对象的地址,给到this
}
转到汇编观察:
A* p = nullptr
这里给到this的是空指针,传递一个空指针并不会报错。
在成员函数体的定义内部,不通过this指针去访问成员变量,也就不会出错。
所以题1不出错,题2会出错。
1. this指针存在哪里?——作为形参,放到栈上
this指针是形参,形参和局部变量都是存储在栈上的。
有时候也会存储在ecx这个寄存器。
寄存器的访问速度最快,而this需要频繁访问——>成员函数每次对于成员变量的访问,都需要借助于this指针。
一般情况this指针是放到栈上——和前3个参数一样,一起压到栈上。
有些地方会直接放到寄存器上(不通过栈传递,不压栈帧)——例:VS编译器。
这相当于是一种优化。
2. this指针可以为空吗?——可以传空指针、打印空指针,不能对空指针解引用
成员函数里面不解引用this指针就行。
8.3 C语言 && C++实现Stack的对比
- 从函数定义上,C语言每次都要传结构体过来,才能访问数据,C++直接就能访问。
- 从函数声明上,C++函数名变短了,函数名简化了。(类域隔离,不怕冲突)
- 从函数调用上,也能少写一个结构体参数。
成员函数、成员变量在类域里面,类域限制了什么???——域的概念是编译时的规则。
在编译时会检查语法合不合乎规则——编译的时候用一个变量要找它的出处。
语法规则:任何一个变量/函数都要先定义再使用,要过编译器的语法关,不符合语法规则(不加Stack)就会报错,加了Stack就表明是类的成员函数,编译的时候就会检查语法规则,去找它的出处,先定义,后使用,过语法关。
类的成员函数,调用的未声明变量可能是全局变量 or 类的成员变量,所以会去全局&类里面都去找一下,找不到就会报语法错误。
而对象(指针)去调用这个函数的时候要不要去找成员变量(课件例题)是运行时的规则——找这个东西在内存里面的哪一块位置,this—>a用this指针指向的地址(nullptr)去找a的值,找不到就产生运行错误——运行错误返回错误码,没有报错。
p—>Init(),但是这个Init本身不在p指向的空间上,而在公共代码区域 ,链接的时候用函数名去找这个地址, 而不是在运行时这个对象里面去找它的地址。
8.3.1 C语言实现
typedef int DataType;
typedef struct Stack
{DataType* array;int capacity;int size;
}Stack;void StackInit(Stack* ps)
{assert(ps);ps->array = (DataType*)malloc(sizeof(DataType) * 3);if (NULL == ps->array){assert(0);return;}ps->capacity = 3;ps->size = 0;
}void StackDestroy(Stack* ps)
{assert(ps);if (ps->array){free(ps->array);ps->array = NULL;ps->capacity = 0;ps->size = 0;}
}void CheckCapacity(Stack* ps)
{if (ps->size == ps->capacity){int newcapacity = ps->capacity * 2;DataType* temp = (DataType*)realloc(ps->array, newcapacity*sizeof(DataType));if (temp == NULL){perror("realloc申请空间失败!!!");return;}ps->array = temp;ps->capacity = newcapacity;}
}void StackPush(Stack* ps, DataType data)
{assert(ps);CheckCapacity(ps);ps->array[ps->size] = data;ps->size++;
}int StackEmpty(Stack* ps)
{assert(ps);return 0 == ps->size;
}void StackPop(Stack* ps)
{if (StackEmpty(ps))return;ps->size--;
}DataType StackTop(Stack* ps)
{assert(!StackEmpty(ps));return ps->array[ps->size - 1];
}int StackSize(Stack* ps)
{assert(ps);return ps->size;
}int main()
{Stack s;StackInit(&s);StackPush(&s, 1);StackPush(&s, 2);StackPush(&s, 3);StackPush(&s, 4);printf("%d\n", StackTop(&s));printf("%d\n", StackSize(&s));StackPop(&s);StackPop(&s);printf("%d\n", StackTop(&s));printf("%d\n", StackSize(&s));StackDestroy(&s);return 0;
}
可以看到,在用C语言实现时,Stack相关操作函数有以下共性:
- 每个函数的第一个参数都是Stack*
- 函数中必须要对第一个参数检测,因为该参数可能会为NULL
- 函数中都是通过Stack*参数操作栈的
- 调用时必须传递Stack结构体变量的地址
结构体中只能定义存放数据的结构,操作数据的方法不能放在结构体中。
数据和操作数据的方式是分离开的(更自由),在函数实现上复杂一点,涉及到大量指针操作,稍不注意可能就会出错。
8.3.2 C++实现
typedef int DataType;
class Stack
{
public:void Init(){_array = (DataType*)malloc(sizeof(DataType) * 3);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = 3;_size = 0;}void Push(DataType data){CheckCapacity(); _array[_size] = data;_size++;}void Pop(){if (Empty())return;_size--;}DataType Top(){ return _array[_size - 1]; }int Empty(){ return 0 == _size; }int Size(){ return _size; }void Destroy(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}private:void CheckCapacity(){if (_size == _capacity){int newcapacity = _capacity * 2;DataType* temp = (DataType*)realloc(_array, newcapacity * sizeof(DataType));if (temp == NULL){perror("realloc申请空间失败!!!");return;}_array = temp;_capacity = newcapacity;}}private:DataType* _array;int _capacity;int _size;
};int main()
{Stack s;s.Init();s.Push(1);s.Push(2);s.Push(3);s.Push(4);printf("%d\n", s.Top());printf("%d\n", s.Size());s.Pop();s.Pop();printf("%d\n", s.Top());printf("%d\n", s.Size());s.Destroy();return 0;
}
C++中通过类可以将数据、操作数据的方法进行完美结合,通过访问权限可以控制那些方法在类外可以被调用,即封装,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。
而且每个方法不需要传递Stack*的参数了,编译器编译之后该参数会自动还原,即C++中 Stack * 参数是编译器维护的,C语言中需用用户自己维护。
C++的成员函数在访问成员变量上的方便,其实是编译器做了更多的事情。(this指针)
• C++中数据和函数都放到了类里面,通过访问限定符进行了限制,不能再随意通过对象直接修改数据,这是C++封装的一种体现,这个是最重要的变化。这里的封装的本质是一种更严格规范的管 理,避免出现乱访问修改的问题。当然封装不仅仅是这样的,我们后面还需要不断的去学习。
• C++中有一些相对方便的语法,比如Init给的缺省参数会方便很多,成员函数每次不需要传对象地址,因为this指针隐含的传递了,方便了很多,使用类型不再需要typedef用类名就很方便。
• 在我们这个C++入门阶段实现的Stack看起来变了很多,但是实质上变化不大。等着我们后面看STL 中的用适配器实现的Stack,大家再感受C++的魅力。