拓冰建站拓冰建站
首页 / 资讯中心 / 正文

【C++ 入门】类与对象:class 定义、类域、实例化与 this 指针

文章目录前言一、class 定义类c封装与访问限定符二、类域三、实例化类对象内存成员分析对齐规则四、this指针前言提示这里可以添加本文要记录的大概内容例如随着人工智能的不断发展机器学习这门技术也越来越重要很多人都开启了学习机器学习本文就介绍了机器学习的基础内容。提示以下是本篇文章正文内容下面案例可供参考一、class 定义类class是定义类的关键字Stack就是这个类的名字大括号{}里面写类里面所有内容。类写完大括号的末尾分号千万不能漏写。大括号里面的内容叫做类的成员类里面写的变量叫成员变量 / 属性类里面写的函数叫成员函数 / 成员方法写代码的习惯为了一眼分清成员变量和局部变量成员变量常常加下划线_或者用m开头比如_a、m_a。这个只是行业编码习惯C 语法没有强制要求实际工作以公司编码规范为准。在 C 里struct也能用来定义类。C 保留了 C 语言 struct 原来的用法同时把 struct 升级成类了struct 里面也可以写成员函数。不过日常写代码更推荐用 class 来定义类。直接写在类大括号内部里面实现的成员函数默认就是inline内联函数c封装与访问限定符封装是 C 的特性之一用类把对象的属性成员变量和方法成员函数打包放到一起。并且可以靠访问权限控制选择性对外暴露能用的接口。三种访问权限public公开成员类外面的代码可以直接访问。protected、private类外面都不能直接访问。目前没学继承时二者效果完全一样等到学继承的时候才会看出两者差别。访问权限的生效范围从这个权限关键字开始一直到下一个权限关键字为止后面再也没有别的权限标记就一直生效到类的大括号结束。重要默认规则class定义的类成员默认权限是 private私有struct定义的类成员默认权限是 public公开工程上常规写法成员变量一般设成private或者protected把数据保护起来对外要调用的功能函数放到public当做对外开放的接口。class类#includeiostreamusingnamespacestd;classStack{// 访问限定符class中没给的话里面默认是私有的public:// 公有一般是把成员函数公有在类外也可以直接访问// 成员函数voidInit(intn4){array(int*)malloc(sizeof(int)*n);if(arraynullptr){perror(malloc申请空间失败);}capacityn;top0;}// public到这里结束private:// 私有一般是把成员变量私有在类外不能直接访问更加规划// 成员变量int*array;size_t capacity;size_t top;};// 分号别忘记intmain(){Stack st;st.Init();return0;}struct类#includeiostreamusingnamespacestd;// 兼容C中struct的用法升级成了类就算不typedef也可以不用带前面的struct了typedefstructA{voidfunc(){}inta1;inta2;}AA;// 升级成类structB{voidInit(){}private:inta1;inta2;};intmain(){//struct A aa1;不需要这么写了AA aa1;B bb1;bb1.Init();return0;}通常情况下我们还是喜欢用class来定义类但是像链表定义节点还是用struct因为struct成员变量默认公有。二、类域类会开辟一块全新的作用域类里面所有的成员变量、成员函数全都属于这个类的作用域。如果在类的大括号外面写成员函数的实现必须用::作用域解析运算符说明这个函数归属于哪一个类。类域只影响编译阶段名字查找规则。举个例子写Init函数的时候如果不写Stack::编译器就会把Init当成普通全局函数。这时函数里面访问_arr这类类的成员编译器就找不到这些变量直接编译报错。写上Stack::Init编译器就明白这是Stack类的成员函数。在当前局部找不到变量时就会自动跑到Stack这个类作用域里面去搜寻成员变量。stack.h:#includeiostreamclassStack{public:voidInit(intn4);voidPush(intx);private:int*arr;int_n;int_x;};test.cpp:#includeiostreamusingnamespacestd;#includestack.h//一定要带Stack::去找voidStack::Init(intn){arr(int*)malloc(sizeof(int)*n);_nn;_x0;}voidStack::Push(intx){//...}intmain(){Stack st;st.Init();return0;}三、实例化拿类这个类型在内存当中实实在在创建出对象这个过程就叫用类实例化对象。类只是对对象的抽象描述相当于一张模型图纸。它只规定这个类里面有哪些成员变量这些成员仅仅是声明不会分配内存空间。只有拿类去实例化生成对象的时候才会真正分配内存。一个类可以实例化出来好多个对象。实例出来的对象占有真实的物理内存用来存放成员变量的数据。打个比方类就好比楼房的设计图纸。图纸上面画好了一共有多少房间、每个房间多大、做什么用途但是图纸本身不是房子不能住人。照着图纸盖出来一栋栋真实的房子房子才可以住人。同理类图纸本身不能存数据实例化出来的对象盖好的房子才拥有物理内存用来保存数据。classData{public:voidInit(intyear,intmonth,intday){_yearyear;_monthmonth;_dayday;}voidPrint(){cout_year/_month/_dayendl;}private:int_year;int_month;int_day;};intmain(){// 类实例化出对象d1,d2,这时才分配了空间Data d1;Data d2;// 成员函数不存在对象里面而是在代码段里通过调地址访问d1.Init(2026,9,3);d1.Print();d2.Init(2026,9,4);d2.Print();// 但是成员变量是存在对象里面的// 就像d1._year和d2._year肯定不是同一个但是d1.Init和d2.Init调的是同一个// 可以传对象也可以传类型遵守内存对齐规则coutsizeof(d1)endl;coutsizeof(d2)endl;coutsizeof(Data)endl;return0;}类对象内存成员分析每个实例化出来的对象都拥有属于自己独立的内存空间成员变量肯定是存在对象内存里面的。那成员函数会不会也存在对象里面函数编译之后就是一堆机器指令这些指令统一放在程序的代码段不会放到对象内存里。如果非要把成员函数放到对象里那就只能存函数的地址函数指针。我们再想一想存这个指针有没有必要拿Date类举例实例化出d1、d2两个对象d1和d2各自保存自己独有的_year、_month、_day成员变量。但是d1调用的Init()、Print()和d2调用的Init()、Print()是完全同一套函数代码函数地址一模一样。要是每个对象都存一遍相同的函数指针就是重复浪费内存。如果创建 100 个对象就要重复存 100 份一模一样的函数指针开销很大。再补充一点普通成员函数调用是编译链接阶段就确定好函数地址直接生成call 函数地址汇编指令运行的时候不需要再去找地址根本不用在对象里保存函数指针。只有后面要学的动态多态才需要在对象里面存虚函数地址运行时才去找函数这块后面再讲对齐规则第一个成员变量放在结构体内存偏移为 0 的地址位置。后面每一个成员变量存放的地址必须是对齐数的整数倍。对齐数怎么算取「编译器默认对齐数」和「该成员自身大小」两者中更小的那一个。VS 编译器默认对齐数是 8。结构体整体总大小必须是该结构体最大对齐数的整数倍。最大对齐数结构体里所有成员各自对齐数当中最大的那个。如果结构体里面嵌套另一个结构体被嵌套的结构体要对齐到它自身的最大对齐数的整数倍地址。外层结构体总大小要包含所有含嵌套结构体的最大对齐数并且是它的整数倍。#includeiostreamusingnamespacestd;// 计算⼀下A/B/C实例化的对象是多大classA{public:voidPrint(){cout_chendl;}private:char_ch;int_i;};classB{public:voidPrint(){//...}};classC{};intmain(){A a;B b;C c;coutsizeof(a)endl;// a的这个不用多说根据内存对齐规则就可以算出来// 因为函数不存在对象里所以b,c都是空类// 空类的大小为1,开1byte是为例占位不存在实际数据只是为了表示对象存在过coutsizeof(b)endl;coutsizeof(c)endl;// 大家可以想一下不开空间怎么表示对象存在过呢不开地址怎么给呢// 所以肯定是会给的return0;}四、this指针Date 类有Init和Print两个成员函数函数代码本身里面看不出是操作哪个对象。当d1.Init()、d2.Init()调用同一个函数代码函数怎么分得清现在操作的是 d1 还是 d2C 靠隐藏的 this 指针解决这个问题。编译器处理成员函数的时候会自动在形参的最前面偷偷加上一个当前类的指针参数就是 this 指针。比如我们写的voidInit(intyear,intmonth,intday)编译器处理之后真实函数原型是voidInit(Date*constthis,intyear,intmonth,intday)成员函数里面去读写成员变量本质全部都是通过 this 指针访问。写_year year;编译器实际翻译成this-_year year;。调用d1.Init(2026,9,3)时编译器会把d1作为实参传给 this调用d2.Init(...)就传d2。函数靠 this 指针就能找到当前是哪个对象的成员变量。语法规则不能在函数声明、调用传参的地方手动写 this这个是编译器自动处理。但是在成员函数的函数体内部我们可以手动写上this-显式使用 this 指针。#includeiostreamusingnamespacestd;classData{public:// 成员函数// 原本应该是这样的// void Init(Data* const this,int year,int month,int day)--但是不能这么写出来voidInit(intyear,intmonth,intday){// 函数内部是可以写出来的this-_yearyear;_monthmonth;_dayday;}voidPrint(){cout_year/_month/_dayendl;}private:int_year;int_day;int_month;};intmain(){Data d1;Data d2;// d1.Init(d1,2026,9,1);--不能这么写出来d1.Init(2026,9,1);d2.Init(2026,9,3);d1.Print();d2.Print();return0;}1.下⾯程序编译运⾏结果是A、编译报错 B、运⾏崩溃C、正常运⾏//题目1--C.正常运行#includeiostreamusingnamespacestd;classA{public:voidPrint(){coutthisendl;coutA::Print()endl;}private:int_a;};intmain(){A*pnullptr;p-Print();//call 地址//因为这里的函数地址并不存在对象之中(而是直接通过地址调用的),this传过去也没影响//所以这里不存在空指针的解引用//如果改成这样呢(*p).Print();//也是一样的可以正常运行这里转成反汇编看比较好实际上是直接调用函数的//注意:不要只看语法的表象。return0;}2.下⾯程序编译运⾏结果是A、编译报错B、运⾏崩溃C、正常运⾏//题目2--B.运行崩溃#includeiostreamusingnamespacestd;classA{public:voidPrint(){coutthisendl;coutA::Print()endl;cout_aendl;//调用函数执行到这里时就有问题了//_a是成员变量是存在对象里的所以会出现问题。}private:int_a;};intmain(){A*pnullptr;p-Print();return0;}还有一点是this是存在栈里面的就像形参一样类似于局部变量。但是this也是很有可能存在寄存器里的
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门