恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
C++入门到精通:类和对象(上)全方位解析
首页
资讯中心
/
C++入门到精通:类和对象(上)全方位解析
C++入门到精通:类和对象(上)全方位解析
发布时间:2026/9/25 20:05:59
前言你是否曾好奇过为什么 C 中 struct 和 class 都能定义类它们之间到底有什么区别类又是如何在内存中活起来的如果你对这些问题感到困惑那么这篇文章正是为你准备的。本文将带你深入理解 C 的类和对象你将学到类的定义与访问限定符掌握 public、private、protected 的用法与区别inline 的奥秘了解它如何提升性能并为编译器创造更多优化空间类域与作用域理解 :: 作用域操作符的作用类实例化与内存布局弄清对象在内存中如何分配空间、大小如何计算this 指针揭秘成员函数是如何找到它操作的那个对象的。让我们开始吧类的定义在C中class是类的一个关键字后面跟着的是类的名称{}中的是类的内容类定义结束后后面要加分号。例如classstack{.....};类中可以放函数和变量分别称为成员函数成员变量这也是C中的struct与C中的class的区别之一为什么说是C中的呢因为在C中struct升级成为了类也可以放函数但是C中的struct和class在访问限定符中还有差异。访问限定符访问限定符有三个public,private,protected.访问限定符的出现是为了选择性的将类中的成员让外部访问、使用。public修饰的成员外部可以直接访问private和protected修饰的成员不可被外部直接访问可以用public修饰的函数来间接访问private和protected修饰的一般用private和protected修饰的是成员变量这种间接访问可以有效的保护成员变量的数值不被篡改。所以一般成员变量用private修饰而提供给外部使用的函数用public修饰。访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌如果后⾯没有访问限定符作⽤域就到 }即类结束。例如classStack{public:// 成员函数voidInit(intn4);private:// 成员变量int*array;size_t capacity;size_t top;};刚刚我们说到C中class和struct在访问限定符中有差异在class中若没有写任何访问限定符则里面的成员默认是被private修饰的而struct默认是被public修饰的。class和struct中的成员函数默认为inline.inline那么什么是inline呢inline是一个关键字在普通函数调用时CPU 需要做一系列操作保存当前状态、压栈传参、跳转到函数地址、执行函数体、返回恢复现场。这些操作虽然单次开销很小但在高频调用时积累的开销是非常大的为此就有了inlineinline的作用就是建议不是命令编译器直接将这个函数复制到调用处这样可以消除调用开销并且这个复制到调用处这个操作是在编译期不是运行期不存在运行时的额外开销那么为什么inline只是给编译器一个建议而不是命令呢我觉得这样的处理方式更加灵活这让编译器根据这个函数代码量来进行判断是否要进行这个操作若代码量小并且高频调用那就进行操作若代码量大或者函数只调用几次甚至一次那么就不会进行这个操作这样的处理方式更加灵活。另外这里如果是I/O流的话是不会内联展开的I/O流操作的开销比函数调用的开销大得多。inline 还有一个更大的作用——它能为编译器创造更多的优化空间。为什么这么说呢我们不妨先看一段代码inlineintsquare(intx){returnx*x;}intmain(){intasquare(5);// 内联后变成 int a 5 * 5;// 编译器进一步常量折叠 → int a 25;// 连乘法都省了}为什么Inline会让编译器做出这种操作在没有Inline时每个 .cpp 文件是独立编译的一个翻译单元。编译 main.cpp 时编译器只知道 square 的函数签名返回 int接收一个 int但看不到函数体。它只能生成一条 call square 指令函数栈帧中把 5 压栈传参跳转到 square 的地址执行等返回后再继续。这对于编译器所能做的优化非常有限而有inline时编译器能看到这个函数的主体内容所能看到的更多编译器所能做的优化自然就越多。给个表格更加直观类域类定义了⼀个新的作⽤域类的所有成员都在类的作⽤域中在类体外定义成员时需要使⽤ :: 作⽤域操作符指明成员属于哪个类域。例如#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;}这里的Init函数需要指明是stack里的不然会当作全局函数里面的成员变量也就找不到了Stack::这样子来指明。另外类域影响的是编译的查找规则在对Init函数指定了是stack类域之后里面的成员变量若在当前域中找不到就会在指定的类域中查找。类实例化对象⽤类类型在物理内存中创建对象的过程称为类实例化出对象。类中的成员变量只是先声明并没有分配空间只有类实例化出对象时才会给这些成员变量分配空间⼀个类可以实例化出多个对象实例化出的对象 占⽤实际的物理空间存储类成员变量。例如classstack{public://提供给外部使用的函数private://成员变量。此时还未分配空间};intmain(){stack s1;stack s2;//这时s1,s2中的成员变量才分配了空间//...return0;}我们可以把内存空间比作房子而类就是房子的设计图纸。图纸上虽然标注了每个房间的位置和大小但在房子真正建成之前这些房间并不实际存在。同样地类中的成员变量在实例化之前只是声明并不会占用内存空间只有当类实例化出对象时才会为这些成员变量真正分配空间。实例化对象的大小像s1,s2这样实例化出的对象在内存中是独立存在的互不干扰对象里只存储成员变量不存储成员函数成员函数是这些实例化出的对象共享的没有必要都存储一份。若是都存储的话会造成极大的空间浪费。另外类实例化出的对象也要符合内存对齐规则在结构体中讲解过。内存对齐规则• 第⼀个成员在与结构体偏移量为0的地址处。• 其他成员变量要对⻬到某个数字对⻬数的整数倍的地址处。• 注意对⻬数 编译器默认的⼀个对⻬数 与 该成员⼤⼩的较⼩值。• VS中默认的对⻬数为8• 结构体总⼤⼩为最⼤对⻬数所有变量类型最⼤者与默认对⻬参数取最⼩的整数倍。• 如果嵌套了结构体的情况嵌套的结构体对⻬到⾃⼰的最⼤对⻬数的整数倍处结构体的整体⼤⼩就是所有最⼤对⻬数含嵌套结构体的对⻬数的整数倍。我们再来看段代码classB{public:voidPrint(){//...}};classC{};从 a 可以看出它符合内存对齐规则。那么问题来了b 和 c 明明都没有成员变量为什么还会各自分配一个字节的空间呢其实这个字节的作用是为了标明对象的存在——即使类中没有成员变量实例化出的对象也需要在内存中占据一个明确的地址以便编译器能够区分不同的对象。那么问题来了若是这个函数没有存储进类里面那函数怎么知道它操作的是哪个对象的数据呢这里就体现出了this指针的作用了。this指针编译器在编译成员函数时会自动在第一个形参的位置加上this指针指向调用这个函数的对象。例如d1.Init(2024,9,22);// 实际编译后变成Init(d1, 2024, 9, 22)// 函数内部this-_year year; 这里的 this 就是 d1另外this指针只会在类中的非静态成员函数中自动加上静态成员函数是不会的全局函数不会有还有友元函数也不会有友元函数虽然能访问类的私有成员但它不是类的成员函数本质上是一个普通的全局函数所以没有 this。classDate{friendvoidPrint(constDated);// 声明友元private:int_year;};voidPrint(constDated){// 没有 this必须通过参数传入对象coutd._yearendl;// 通过参数 d 访问}给张表格理解更加清晰。另外C规定不能在实参和形参的位置显⽰的写this指针(编译时编译器会处理)但是可以在函数体内显⽰使⽤this指针。例如classDate{public:// void Init(Date* const this, int year, int month, int day)voidInit(intyear,intmonth,intday){// 编译报错error C2106: “”: 左操作数必须为左值// this nullptr;// this-_year year;_yearyear;this-_monthmonth;this-_dayday;}private://...题目我们看两个题目1.下⾯程序编译运⾏结果是A、编译报错 B、运⾏崩溃 C、正常运⾏#includeiostreamusingnamespacestd;classA{public:voidPrint(){coutA::Print()endl;}private:int_a;};intmain(){A*pnullptr;p-Print();return0;}这里是正常运行先看这个Print这个函数它是个普通成员函数在编译阶段已经确定了调用地址在调用Print这个函数时会被当做A::Printp这样处理虽然p是个nullptr传给Print里的this指针但是函数里面根本没有访问成员变量所以就不会由空指针解引用的问题所以就能正常运行。那相反的只要这个函数内部由访问成员变量这个操作那么就会导致空指针解引用从而会导致运行崩溃。再来看一道题。3. this指针存在内存哪个区域的 ()A. 栈 B.堆 C.静态区 D.常量区 E.对象⾥⾯解答this指针是存储再对象里面的它指向当前调用成员函数的对象实例。它并不是一个独立存储在栈、堆或静态区的变量而是由编译器在每次调用非静态成员函数时自动传递的其本质是“指向当前对象”的指针。所以this指针实际上是对象本身的一部分this指针不是一个局部变量不是动态申请的也不是一个常量数据不是全局或静态变量。所以this指针是存储在对象里面的。结束语到这里我们已经完整地走过了 C 类和对象上的整个旅程从类的定义与访问限定符到 inline 的优化奥秘从类域与作用域到实例化对象的内存布局与大小计算最后还深入剖析了 this 指针的底层原理并通过两道经典题目加深了理解。回顾整篇文章有几个核心要点值得牢记类是设计图对象是建好的房子——类只是声明实例化后才真正分配内存成员函数不占对象空间——它们由所有对象共享通过 this 指针找到操作的目标内存对齐与空对象的一个字节——这些细节决定了对象在内存中的真实大小this 指针是编译器自动注入的隐藏参数——它让成员函数知道我在操作谁。掌握了这些基础你就已经为后续学习构造函数、析构函数、拷贝构造、运算符重载等面向对象的高级特性打下了坚实的基础。类和对象是 C 面向对象编程的基石理解得越透彻后面的路就走得越稳。希望这篇文章对你有所帮助也欢迎在评论区留言交流你的疑问或心得。我们下一篇再见