资讯动态

手把手教你C++——类和对象

发布时间:2026/9/15 8:55:00 来源:尧图企业网站定制
1.类的定义1.1 类的格式定义类用字母class表示而Stack就是类的名字{}中为类的主体注意类定义时后面的分号不能省略。类体中的内容为类的成员类中的变量称为类的属性或者成员变量类中的函数称为类的方法或者成员函数#includeiostreamusingnamespacestd;classStack{//{}里面的内容就是类的主体};//分号不能省略intmain(){return0;}为了区分成员变量一般会在成员变量前面加上_或者m开头当然这也只是惯例C没有强制要求。#includeiostreamusingnamespacestd;classData{public:voidInit(intyear,intmonth,intday){_yearyear;_monthmonth;_dayday;}private:int_year;int_month;int_day;};intmain(){Data d;d.Init(2026,9,8);return0;}C是兼容C的所以在C中也可以使用struct同时struct也升级成了类最大的变化就是struct里面也可以定义函数了当然一般情况下我们还是用class定义类定义在类里面的函数默认为inline#includeiostream#includeassert.husingnamespacestd;classStack{public:// 成员函数voidInit(intn4){array(int*)malloc(sizeof(int)*n);if(nullptrarray){perror(malloc申请空间失败);return;}capacityn;top0;}voidPush(intx){// ...扩容array[top]x;}intTop(){assert(top0);returnarray[top-1];}voidDestroy(){free(array);arraynullptr;topcapacity0;}private:// 成员变量int*array;size_t capacity;size_t top;};// 分号不能省略//{}里面的内容就是类的主体intmain(){Stack st;st.Init();st.Push(1);st.Push(2);coutst.Top()endl;st.Destroy();return0;}#includeiostreamusingnamespacestd;//C升级struct升级成了类//1.类里面可以定义函数//2.struct名称就可以代表类型typedefstructListNodeC{structListNodec*next;intval;}LTNode;//不需要再typedefstruct直接可以代表类型structListNodeCPP{voidInit(intx){nextnullptr;valx;}ListNodeCPP*next;intval;};intmain(){}1.2 访问限定符C一种实现分装的方式用类将对象的属性和方法结合在一块让对象更加完善通过访问权限选择性的将其接口提供给外部用户使用。public修饰的成员在外部可以直接被访问protected和privata修饰的成员不可以直接被访问访问权限作用域从该访问限定作用符开始到下一个访问限定作用符结束后面如果没有访问限定符那么就到类结束的时候就结束了class定义成员没有被访问限定符修饰时默认为privatestruct定义成员时默认为public的因为要兼容C一般成员变量都会被限制为private需要给别人用的成员函数就定义为public#includeiostream#includeassert.husingnamespacestd;classStack{public:// 成员函数voidInit(intn4){array(int*)malloc(sizeof(int)*n);if(nullptrarray){perror(malloc申请空间失败);return;}capacityn;top0;}voidPush(intx){// ...扩容array[top]x;}intTop(){assert(top0);returnarray[top-1];}voidDestroy(){free(array);arraynullptr;topcapacity0;}private:// 成员变量int*array;size_t capacity;size_t top;};// 分号不能省略//{}里面的内容就是类的主体intmain(){Stack st;st.Init();st.Push(1);st.Push(2);coutst.Top()endl;st.Destroy();return0;}1.3 类域类定义了一个新的作用域类的所有成员都在类的作用域中在类体外定义成员时需要使用::作用域操作符指明成员属于哪个类域类域影响的是编译查找规则下面程序中如果不在Init里面指定类域Stack的话编译器就会把Init当成是全局变量那么编译时找不到array等成员的声明/定义在哪里时就会报错指定就在Stack这个类域的话就会去这个特定的类域里面找#includeiostreamusingnamespacestd;classStack{public:// 成员函数voidInit(intn4);private:// 成员变量int*array;size_t capacity;size_t top;};// 声明和定义分离需要指定类域voidStack::Init(intn){array(int*)malloc(sizeof(int)*n);if(nullptrarray){perror(malloc申请空间失败);return;}capacityn;top0;}intmain(){Stack st;st.Init();return0;}2. 实例化2.1 实例化概念用类类型在物理内存里面创造出对象的过程就叫做实例化称为类实例化出对象类是对象的一种抽象描述是一个模型一样的东西限定了类有哪些成员变量这些成员变量只是声明并没有分配实际的空间用类实例化出对象是才会分配空间一个类可以实例化出多个对象 占用实际的物理空间存储成员变量。这个过程就好像用建造图纸建造房子一样建造图纸上设计好了房间的各个部分但是却不占用任何物理空间也就像类一样只有类本身是没有占中内存空间的只有用类实例化出对象之后才会分配空间。#includeiostreamusingnamespacestd;classDate{public:voidInit(intyear,intmonth,intday){_yearyear;_monthmonth;_dayday;}voidPrint(){cout_year/_month/_dayendl;}private:// 这里只是声明没有开辟任何空间int_year;int_month;int_day;};intmain(){Date d1;//d1 d2就是实例化出的对象Date d2;d1.Init(2026,9,10);d1.Print();d2.Init(2026,10,1);d2.Print();return0;}2.2 对象大小类实例化出的每个对象都有独立的数据空间所以对象中肯定包含成员变量那么成员函数是否包含呢首先函数被编译之后就是一段指令在对象中是无法存储的这些指令存储在一个单独的空间(代码段那么对象中非要存储的话只能是成员函数的指针那么再来分析一下这些指针到底有没有必要存储呢Date实例化对象的时候d1和d2都有各自独立的成员变量_year、_month、_day存储各自的数据我们用汇编语言就可以很直观的看到不同的对象在调用相同的函数的时候其实函数的指针都是相同的存储在对象中就浪费了。如果用Date实例化100个对象那么成员函数指针就会被调用100次太过于浪费如图可以知道对象只存储成员变量C规定类实例化的对象也要符合内存对齐的规则内存对齐规则第一个成员在与结构体偏移量为0的地址处其它成员变量要对齐到某个数字的整数倍地址处注意对齐数编译器默认的对齐数与成员变量的较小值VS默认的对齐数为8结构体的总大小为最大对齐数(默认对齐数与所有类型变量的最大者取较小值)的整数倍如果是嵌套了结构体的情况嵌套的结构体对齐到自己最大对齐数的整数倍处结构体的整体大小就是最大对齐数的整数倍#includeiostreamusingnamespacestd;classA{public:voidDate(intyear,intmonth,intdat);private:chara;intb;};classB{public:voidDate(intyear,intmonth,intday);private://成员变量为空};classC{//类为空};intmain(){coutsizeof(A)endl;coutsizeof(B)endl;coutsizeof(C)endl;return0;}OK看上面这串代码首先看A这个类成员变量有两个char占一个字节int占四个字节。所以在所有类型的变量中最大值就是4那他的最大对齐数就是4存放的时候也是如下存放那再来看类B和C那我们可以看到B和C是有一个共同的特点就是没有成员变量我们知道在类中成员函数不占大小只有成员变量才占大小那B和C的大小是0吗我们可以来验证一下可以看到B和C这两个类的大小实际是1那为什么一个变量也没有就是要给一个字节的大小呢其实就是为了证明这个类是存在过的纯粹就是为了占位标志对象的存在3.this指针ok 那我们看上面我们已经写过的代码Date这个类中是有两个函数的分别是Init和Print函数体中其实是没有对对象的区分的那问题就来了在调用Init或者是Print的时候怎么区分调用的是d1还是d2呢这时候就需要用到this指针了编译器完成编译之后类的成员函数默认会在形参的第一个位置增加一个当前类型的指针就叫做this指。比如我们上面写过Date类的Init的真实类型为void Init(Date *const this,int year,int month,int day)类的成员函数中访问成员变量本质都是通过this指针访问的如Init函数中给_year赋值,this-_year year;C规定不能在形参和实参中出现this指针但是在函数体中是可以体现使用this指针的#includeiostreamusingnamespacestd;classDate{public://在这里加入this指针就会报错voidInit(Date*constthis,intyear,intmonth,intday){_yearyear;_monthmonth;_dayday;}voidPrint(){cout_year/_month/_dayendl;}private:int_year;int_month;int_day;};intmain(){Date d1;d1.Init(2026,9,11);d1.Print();return0;}#includeiostreamusingnamespacestd;classDate{public://在这里加入this指针就会报错//void Init(Date* const this, int year, int month, int day)voidInit(intyear,intmonth,intday){_yearyear;this-_monthmonth;//体现在函数体中是可以的this-_dayday;}voidPrint(){cout_year/_month/_dayendl;}private:int_year;int_month;int_day;};intmain(){Date d1;d1.Init(2026,9,11);d1.Print();return0;}OK那接下来我们来检验一下前面的知识掌握的如何1.下面程序编译运行结果是A、编译报错 B、运行崩溃 C、正常运行#includeiostreamusingnamespacestd;classA{public:voidPrint(){coutA::Print()endl;}private:int_a;};intmain(){A*pnullptr;p-Print();return0;}答案是C那就会有疑惑了为什么这个指针是空指针还可以正常运行呢我们之前说过函数的地址没有存在于对象中是在编译的时候确定的函数地址。因为 Print() 函数内部不需要访问任何成员变量也就是不需要解引用 this 指针所以即使 this 是空指针也不会发生内存访问错误下面程序编译运行结果是A、编译报错 B、运行崩溃 C、正常运行#includeiostreamusingnamespacestd;classA{public:voidPrint(){coutA::Print()endl;cout_aendl;}private:int_a;};intmain(){A*pnullptr;p-Print();return0;}这个代码乍一看和上面的代码好像一样但是是有差异的因为函数内部要访问成员变量 _a需要解引用 this 指针。而此时传入的 this 指针也就是 p是空指针导致非法内存访问程序崩溃。所以答案是Bthis指针是存在于哪个区域( )A. 栈 B.堆 C.静态区 D.常量区 E.对象里面答案是Athis指针是存在于栈里的4.C和C语言实现栈对比面对对象的三大特性封装、继承、多态那在这一部分我们可以初步地了解一下封装通过下面两段代码的对比可以感受到C相较C在实现Stack的形态上还是发生了很多变化的但是底层逻辑倒是没有发生太大改变C的数据和函数都放到了类里而且通过访问限定符限制了访问不能直接通过对象来更改数据这就体现了C封装的特性。这个是最重要的变化这里封装从本质也是一种更为严格的管理避免出现一些乱访问修改的问题C有一些相对方便的语法比如Init给的缺省参数就会方便很多成员函数每次不需要传对象的地址因为this指针隐含的传递了方便了不少而且使用的时候不需要用typedef直接用类名就方便很多这是用C语言实现栈#define_CRT_SECURE_NO_WARNINGS#includestdio.h#includestdlib.h#includestdbool.h#includeassert.htypedefintSTDataType;typedefstructStack{STDataType*a;inttop;intcapacity;}ST;voidSTInit(ST*ps){assert(ps);ps-aNULL;ps-top0;ps-capacity0;}voidSTDestroy(ST*ps){assert(ps);free(ps-a);ps-aNULL;ps-topps-capacity0;}voidSTPush(ST*ps,STDataType x){assert(ps);// 满了 扩容if(ps-topps-capacity){intnewcapacityps-capacity0?4:ps-capacity*2;STDataType*tmp(STDataType*)realloc(ps-a,newcapacity*sizeof(STDataType));if(tmpNULL){perror(realloc fail);return;}ps-atmp;ps-capacitynewcapacity;}ps-a[ps-top]x;ps-top;}boolSTEmpty(ST*ps){assert(ps);returnps-top0;}voidSTPop(ST*ps){assert(ps);assert(!STEmpty(ps));ps-top--;}STDataTypeSTTop(ST*ps){assert(ps);assert(!STEmpty(ps));returnps-a[ps-top-1];}intSTSize(ST*ps){assert(ps);returnps-top;}intmain(){ST s;STInit(s);STPush(s,1);STPush(s,2);STPush(s,3);STPush(s,4);while(!STEmpty(s)){printf(%d\n,STTop(s));STPop(s);}STDestroy(s);return0;}用C实现#includeiostreamusingnamespacestd;typedefintSTDataType;classStack{public:// 成员函数voidInit(intn4){_a(STDataType*)malloc(sizeof(STDataType)*n);if(nullptr_a){perror(malloc申请空间失败);return;}_capacityn;_top0;}voidPush(STDataType x){if(_top_capacity){intnewcapacity_capacity*2;STDataType*tmp(STDataType*)realloc(_a,newcapacity*sizeof(STDataType));if(tmpNULL){perror(realloc fail);return;}_atmp;_capacitynewcapacity;}_a[_top]x;}voidPop(){assert(_top0);--_top;}boolEmpty(){return_top0;}intTop(){assert(_top0);return_a[_top-1];}voidDestroy(){free(_a);_anullptr;_top_capacity0;}private:// 成员变量STDataType*_a;size_t _capacity;size_t _top;};intmain(){Stack s;s.Init();s.Push(1);s.Push(2);s.Push(3);s.Push(4);while(!s.Empty()){printf(%d\n,s.Top());s.Pop();}s.Destroy();return0;}

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价