C到C++的丝滑过度(类和对象篇上)

📅 2026/8/12 17:19:18
C到C++的丝滑过度(类和对象篇上)
类和对象目录1、类的定义2、访问限定符3、类域4.1 类的实例化4.2 对象的大小5、this指针1、类的定义类的定义格式 class Name{ } ; class为定义类的关键字Name为类的名字{ }中为类的主体。类中内容称为类的成员类中的变量称为类的属性或成员变量;类中的函数称为类方法或者成员函数。C中struct也可以定义类C兼容C中struct的用法同时struct升级成了类明显的变化是在C语言中只能存放变量不能写函数所有成员默认公开而C中struct中可以定义函数一般情况下我们还是推荐用class定义类。定义在类里面的成员函数默认为inline。可以成员变量加一个特殊标识用来区分成员变量。下面是类的定义与 struct 对比的示意图class 定义类成员变量属性成员函数方法默认 privatestruct 定义类成员变量成员函数默认 public类域中查找2、访问限定符(public、private、protected访问限定符是实现C 这门面向对象语言封装特性的语法手段之一C一种实现封装的方式只开放安全的函数给外部调用防止外部随意篡改内部数据public修饰的成员在类外可以直接被访问protected和private修饰的成员在类外不能被直接访问区别在继承章节才能体现。访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止如果后面没有访问限定符作用域就到 } 即类结束。class定义成员没有被访问限定符修饰时默认为privatestruct默认为public。一般成员变量都会被限制为private/protected需要给别人使用的成员函数会用public。3、 类域类定义了⼀个新的作用域类的所有成员都在类的作用域中在类体外定义成员时需要使用::作用域操作符指明成员属于哪个类域。类域影响的是编译的查找规则下⾯程序中Init如果不指定类域Name那么编译器就把Init当成全局函数那么编译时找不到array等成员的声明/定义在哪里就会报错。指定类Name就是知道Init是成员函数当前域找不到的array等成员就会到类域中去查找。#includeiostreamusingnamespacestd;className{public:voidInit(intn4);private:int*array;size_t capacity;size_t top;};voidName::Init(intn){array(int*)malloc(sizeof(int)*n);if(nullptrarray){perror(malloc申请空间失败);return;}capacityn;top0;}intmain(){Name name;name.Init();return0;}下面是访问限定符的作用域示意图类作用域public类外可直接访问private类外不可直接访问protected类外不可直接访问继承章节体现区别4.1 类的实例化用类的类型在物理内存中创建对象的过程称为类实例化出对象。类是对象进⾏⼀种抽象描述是⼀个模型⼀样的东西限定了类有哪些成员变量这些成员变量只是声明没有分配空间用类实例化出对象时才会分配空间。打个比方类是房子图纸实例化对象是建的房子//名为Date类实例化对象d1、d2Date d1;Date d2;下面是类实例化过程的示意图实例化实例化类图纸/模型对象 d1对象 d2分配内存空间存储成员变量4.2 对象的大小一个类对象的内存里只存储成员变量不存储成员函数。首先函数被编译后是⼀段指令对象中没办法存储这些指令存储在⼀个单独的区域(代码段)那么对象中非要存储的话只能是成员函数的指针。那么对象中是否有存储指针的必要呢如果Date实例化d1和d2两个对象d1和d2都有各自独立的成员变量_year/_month/_day存储各自的数据但是d1和d2的成员函数指针却是⼀样的存储在对象中就浪费了一个没有任何成员变量、成员函数的空类实例化对象大小为 1 字节原因 C 要求每个对象在内存中拥有唯一地址编译器自动填充 1 个占位字节C规定类实例化的对象要符合内存对齐的规则结构体 / 类第一个成员偏移量固定为 0紧跟结构体起始地址。除首成员外每个变量必须存放在自身对齐数的整数倍地址。当前成员对齐数 min(编译器默认对齐数, 该成员自身类型大小)VS 默认对齐数 8Linux gcc 默认对齐数 8。结构体最终总字节数必须是整个结构体最大对齐数的整数倍不足则末尾填充空白字节填充位。如果嵌套了结构体的情况嵌套的结构体对齐到自己的最大对齐数的整数倍处外层结构体整体大小取所有成员含嵌套结构体最大对齐数的整数倍。下面是对象内存布局与内存对齐规则的示意图对象内存只存储成员变量不存储成员函数函数指令存于代码段遵循内存对齐规则首成员偏移量为0对齐数 min(编译器默认对齐数, 成员类型大小)总字节数为最大对齐数的整数倍5、 this指针假设Date类中有Init与Print两个成员函数函数体中没有关于不同对象的区分那当d1调用Init和Print函数时该函数是如何知道应该访问的是d1对象还是d2对象呢那么这里就要C给的⼀个隐含的this指针解决这里的问题编译器编译后类的成员函数默认都会在形参第⼀个位置增加⼀个当前类类型的指针叫做this指针。比如Date类的Init写的是void Init(int year, int month, int day)但编译后真实原型为void Init(Date* const this, int year, int month, int day)类的成员函数中访问成员变量本质都是通过this指针访问的如Init函数中给_year赋值this-_year yearC规定不能在实参和形参的位置显式的写this指针(编译时编译器会处理)但是可以在函数体内显式使用this指针1.下⾯程序编译运行结果是A、编译报错 B、运行崩溃 C、正常运行#includeiostreamusingnamespacestd;classA{public:voidPrint(){coutA::Print()endl;}private:int_a;};intmain(){A*pnullptr;p-Print();return0;}A* p nullptr; p-Print(); 语法完全合法编译器仅做语法检查不会校验指针是否为空。非静态成员函数Print底层参数为void Print(A* const this)调用p-Print()等价于Print(nullptr)只是把空指针传入this形参语法无错误。运行不会报错函数体完全没有访问任何成员变量没有使用this-_a、this-xxx这类操作。this虽然是空指针但代码全程没有解引用this不会触发空指针访问异常程序可以正常打印字符串后结束。2.下⾯程序编译运行结果是A、编译报错 B、运行崩溃 C、正常运行#includeiostreamusingnamespacestd;classA{public:voidPrint(){coutA::Print()endl;cout_aendl;}private:int_a;};intmain(){A*pnullptr;p-Print();return0;}函数内部有 cout _a endl ; _a 等价于 this-_a这里要通过 this地址 0非法保护内存去访问成员变量属于空指针解引用操作系统触发内存访问异常程序直接崩溃。下面是 this 指针的工作原理示意图否未访问成员变量是访问成员变量d1 调用 Print()编译器传入 this d1d2 调用 Print()编译器传入 this d2Print 函数体函数体是否解引用 this正常运行空指针解引用程序崩溃