类
- class为定义类的关键字,Stack为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。
我们通过创建一个栈来了解类的使用:
class Stack{private://成员变量int*array;intcapacity;inttop;public:// 成员函数voidInit(intn=4){array=(int*)malloc(sizeof(int)*n);if(nullptr==array){perror("malloc申请空间失败");return;}capacity=n;top=0;}voidPush(intx){// ...扩容array[top++]=x;}intTop(){assert(top>0);returnarray[top-1];}voidDestroy(){free(array);array=nullptr;capacity=top=0;}};intmain(){Stack st;st.Init();st.Push(1);st.Push(2);st.Push(3);cout<<st.Top()<<endl;//3st.Destroy();return0;}与我们在用c语言的struct创建栈的区别是,在实现栈的功能时,都需要调用struct,而类是把struct和函数的定义进行了整合。
- 为了区分成员变量,一般习惯上成员变量会加一个特殊标识,如成员变量前面或者后面加_ 或者 m开头,注意C++中这个并不是强制的,只是一些惯例,具体看公司的要求。
classDate{public:voidDate01(intyear,intmonth,intday){// 为了区分成员变量,⼀般习惯上成员变量// 会加⼀个特殊标识,如_ 或者 m开头int_year=year;// year_ m_year这些都行int_month=month;int_day=day;cout<<_year<<" "<<_month<<" "<<_day<<endl;}};intmain(){Date date;date.Date01(2026,10,11);//2026 10 11return0;}- C++中struct也可以定义类,C++兼容C中struct的用法,同时struct升级成了类,明显的变化是struct中可以定义函数,一般情况下我们还是推荐用class定义类。class定义成员没有被访问限定符修饰时默认为private,struct默认为public。
//c++中兼容c中struct的用法typedefstructListNodeC{structListNodeC*next;intval;}LTNode;//C++中不再需要typedef,ListNodeCPP就可以代表类型structListNodeCpp{//C++ 的 struct 里面可以写函数voidInit(intx){next=nullptr;val=x;}ListNodeCpp*next;intval;};- 定义在类面的成员函数默认为inline。
如果我们不想让类里的成员函数为inline,我们可以这样做:
F.h:
classStack{public://声明成员函数,定义写在.cppvoidInit(intn=4);voidDestory();voidPush(intx);voidTop();private://成员变量int*array;size_t capacity;size_t top;};F.cpp:
#include"F.h"//需要使用类域,不然array等变量未定义voidStack::Init(intn)//缺省参数(int n = 4)不能写{array=(int*)malloc(sizeof(int)*n);if(nullptr==array){perror("malloc申请空间失败");return;}capacity=n;top=0;}voidStack::Push(intx){// ...扩容array[top++]=x;}如果想让函数成为类联则直接在类里定义就行。
- 一般成员变量都会被限制为private/protected,需要给别人使用的成员函数会放为public。
类在没有实例化时成员变量无地址即只是声明。
classStack{public://声明成员函数,定义写在.cppvoidInit(intn=4);voidDestory();voidPush(intx);voidTop();//private://成员变量int*array;//只是声明,没有空间size_t capacity;size_t top;};intmain(){//cout << Stack::array << endl;//error,对非静态成员“Stack::array”的非法引用Stack s;s.Init();s.array++;//right,需要创建了实体,即类实例化return0;}我们刚刚在上面使用了类域,接下来我们来学习类域。
类域
类定义了一个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。
实例化
- 用类类型在物理内存中创建对象的过程,称为类实例化出对象。
- 类是对象进行一种抽象描述,是一个模型一样的东西,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,用类实例化出对象时,才会分配空间。
类的大小
对象有空间了,我们就能分析它们的大小了,举一个例子:
classStack{public://成员函数voidInit(intn=4);voidDestory();voidPush(intx);voidTop();voidtest();//private://成员变量int*array;size_t capacity;size_t top;};intmain(){Stack s1;cout<<sizeof(s1)<<endl;//12return0;}这里注意下;这里以32位系统为例
- 32 位系统
sizeof(size_t) = 4字节(32bit) - 64 位系统
sizeof(size_t) = 8字节(64bit)
我们可以发现,我们计算类的空间大小时,我们没有加入成员函数的空间,为什么呢?
首先函数被编译后是一段指令,对象中没办法存储,这些指令存储在一个单独的区域(代码段),那么对象中非要存储的话,只能是成员函数的指针。再分析一下,对象中是否有存储指针的必要呢,假设Stack实例化s1和s2,s1和s2都有各自独立的成员变量存储各自的数据,但是s1和s2的成员函数指针却是一样的,存储在对象中就浪费了。如果用Stack实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。
接下来我们再来观察一个例子:
classtest{public:voidprint(){cout<<"hehe"<<endl;}};intmain(){test t1;t1.print();cout<<sizeof(t1)<<endl;//1return0;}上面的程序运行后,我们看到没有成员变量的t1类对象的大小是1,为什么没有成员变量还要给1个字节呢?因为如果一个字节都不给,怎么表示对象存在过呢!所以这里给1字节,纯粹是为了占位标识对象存在。
this指针
来看这个代码:
classDate{public:voidInit(inty,intm,intd){_y=y;_m=m;_d=d;}voidprint(){cout<<_y<<"/"<<_m<<"/"<<_d<<endl;}private:int_y,_m,_d;};intmain(){Date d1,d2;d1.Init(2026,10,15);d1.print();d2.Init(2026,10,22);d2.print();return0;}- Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?那么这里就要看到C++给了一个隐含的this指针解决这里的问题。
- 编译器编译后,类的成员函数默认都会在形参第一个位置,增加一个当前类类型的指针,叫做this指针。比如Date类的print的真实原型:
voidprint()voidprint(Date*constthis)- 类的成员函数中访问成员变量,本质都是通过this指针访问的.
voidInit(inty,intm,intd){this->_y=y;this->_m=m;this->_d=d;}voidprint(){cout<<this->_y<<"/"<<this->_m<<"/"<<_d<<endl;}- C++规定不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显示使用this指针。不显示写也没事,编译器会自动写入。
- 我们注意到,this的类型是Date*const,这说明我们可以改变this指向的内容,但不能改变this。
// void Init(Date* const this, int year, int month, int day)voidInit(intyear,intmonth,intday){// 编译报错:error C2106: “=”: 左操作数必须为左值// this = nullptr;//this本身不能修改// this->_year = year;//可以修改this指向的内容_year=year;this->_month=month;this->_day=day;}我们来看一些经典题目:
1.下面程序编译运行结果是(c)
A、编译报错 B、运行崩溃 C、正常运行
classA{public:voidPrint(){cout<<"A::Print()"<<endl;}private:int_a=0;};intmain(){A*p=nullptr;p->Print();//(*p).Print();二者意义相同,只是写法不同,后者更具迷惑性//把 p 里面存的值(nullptr),隐式作为 this 指针传给 Print 成员函数 //代码编译成指令,核心为 call Print地址,而Print是有独立的地址,//且Print里没有调用类变量,运行时不会访问 p 指向的那块对象内存return0;}2.下面程序编译运行结果是(b)
A、编译报错 B、运行崩溃 C、正常运行
classA{public:voidPrint(){cout<<"A::Print()"<<endl;cout<<_a<<endl;// cout << this->_a << endl;//对空指针进行了解引用,运行崩溃}private:int_a=0;};intmain(){A*p=nullptr;p->Print();return0;}3… this指针存在内存哪个区域的 (A)
A. 栈或者寄存器 B.堆 C.静态区 D.常量区 E.对象里面