尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

C++(类与对象)(上)

C++(类与对象)(上) 欢迎阅读 欢迎来到「C 类和对象上」入门讲解本文将带你从面向过程与面向对象的对比出发逐步深入理解类的引入、访问限定符、类的作用域、实例化、对象模型以及 this 指针为你揭开 C 面向对象编程的第一层面纱。在开始之前建议你先了解背景知识如果你熟悉 C 语言的结构体struct那么 C 的类class就是它的“升级版”——不仅能够包含成员变量还能包含成员函数并支持更严格的访问控制从而更好地实现封装与数据隐藏。明确学习目标掌握以下七个核心知识点——①面向过程和面向对象初步认识理解两者的设计理念差异——前者关注“怎么做”算法数据结构后者关注“谁来做”对象交互②类的初步认识掌握 C 中 class 的定义方式了解类将变量和函数封装在一起的基本形式③类的访问限定符及封装熟悉 public、private、protected 的作用理解封装的核心意义——隐藏内部实现细节仅暴露必要接口④类的作用域明确类定义了一个独立的作用域类内成员函数可以相互访问类外则需通过对象或作用域解析符::来访问⑤类的实例化理解类是对对象的抽象描述不占用内存空间通过类创建对象实例化后对象才实际占用内存⑥类对象模型了解对象在内存中如何存储——成员变量占用独立空间成员函数存放在代码段公共代码区以及内存对齐对对象大小的影响⑦this 指针掌握 this 指针的隐式存在理解它指向当前对象、用于区分同名成员变量和参数以及它在成员函数调用中的传递机制。一.面向过程和面向对象初步认识C语言是面向过程的关注的是过程分析出求解问题的步骤通过函数调用逐步解决问题。C是基于面向对象的关注的是对象将一件事情拆分成不同的对象靠对象之间的交互完成。二.类的初步认识C语言结构体中只能定义变量在C中结构体内不仅可以定义变量也可以定义函数。不过C不太经常用struct毕竟新事物一般要配上新名字之所以改变struct的功能是因为C它对类的理解也是一种结构体因此才会对struct进行升级。格式class 类名class className { // 类体由成员函数和成员变量组成 }; // 一定要注意后面的分号class为定义类的关键字className为类的名字{}中为类的主体注意类定义结束时后面分号不能省略。 类中内容称为类的成员类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。类的两种定义方式1. 声明和定义全部放在类体中需注意成员函数如果在类中定义编译器可能会将其当成内联函数处理。2. 类声明放在.h文件中成员函数定义放在.cpp文件中注意成员函数名前需要加类名::这个其实就是类域的使用和之前命名空间域一样。一般情况下更期望采用第二种方式因为它看起来更分工明确成员变量命名规则的建议// 我们看看这个函数是不是很僵硬 class Date { public: void Init(int year) { // 这里的year到底是成员变量还是函数形参 year year; } private: int year; }; // 所以一般都建议这样 class Date { public: void Init(int year) { _year year; } private: int _year; }; // 或者这样 class Date { public: void Init(int year) { mYear year; } private: int mYear; };三.类的访问限定符及封装3.1 访问限定符C实现封装的方式用类将对象的属性与方法结合在一块让对象更加完善通过访问权限选择性的将其接口提供给外部的用户使用。【访问限定符说明】1. public修饰的成员在类外可以直接被访问2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的后面会给大家讲二者的区别)3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止4. 如果后面没有访问限定符一直到 } 即类结束。5. class的默认访问权限为privatestruct为public(因为struct要兼容C)注意访问限定符只在编译时有用当数据映射到内存后没有任何访问限定符上的区别3.2 封装面向对象的三大特性封装、继承、多态。这三个点是面向对象的最重要的特性下面我们先对封装进行理解吧在类和对象阶段主要是研究类的封装特性那什么是封装呢封装将数据和操作数据的方法进行有机结合隐藏对象的属性和实现细节仅对外公开接口来和对象进行交互。封装本质上是一种管理让用户更方便使用类。库提供的函数都是一种接口我们不知道它是这么实现的我们只需要知道它有什么作用比如你买电视你总不需要知道它是这么运行的你只需要知道如何这么使用即可四.类的作用域类定义了一个新的作用域类的所有成员都在类的作用域中。在类体外定义成员时需要使用 :: 作用域操作符指明成员属于哪个类域。之前我们在讲命名空间的时候也提过一次class Person { public: void PrintPersonInfo(); private: char _name[20]; char _gender[3]; int _age; }; // 这里需要指定PrintPersonInfo是属于Person这个类域 void Person::PrintPersonInfo() { cout _name _gender _age endl; }五.类的实例化用类类型创建对象的过程称为类的实例化类是对对象进行描述的是一个模型一样的东西限定了类有哪些成员定义出一个类但是并没有分配实际的内存空间来存储它比如入学时填写的学生信息表表格就可以看成是一个类来描述具体学生信息。一个类可以实例化出多个对象实例化出的对象 占用实际的物理空间存储类成员变量int main() { Person._age 100; // 编译失败error C2059: 语法错误:“.” Person study; study._age 100; return 0; }因为类它是没有分配实际的内存空间存储因此它无法成员赋值只有类的实例化对象才有分配实际的内存空间因此才可以给成员赋值3. 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子类就像是设计图只设计出需要什么东西但是并没有实体的建筑存在同样类也只是一个设计实例化出的对象才能实际存储数据占用物理空间但是并不意味着类它是没有内存的类只是不会分配物理上的内存空间但是它在逻辑层面是有内存的图上这个类的内存就是8字节只不过物理上是不会分配而空类即无成员的类它也是有内存的是1字节仅占位物理上也同样不会分配六.类对象模型如何计算类对象的大小类中既可以有成员变量又可以有成员函数那么一个类的对象中包含了什么如何计算 一个类的大小根据图片可以看到只保存成员变量成员函数存放在公共的代码段因此一个类的大小实际就是该类中”成员变量”之和要注意内存对齐知道这个你就知道明白上面是如何算出类的大小的七.this指针7.1 this指针的引出class Date { public: void Init(int year, int month, int day) { _year year; _month month; _day day; } void Print() { cout _year - _month - _day endl; } private: int _year; // 年 int _month; // 月 int _day; // 日 }; int main() { Date d1, d2; d1.Init(2022,1,11); d2.Init(2022,1,12); d1.Print(); d2.Print(); return 0; }对于上述类有这样的一个问题Dte类中有 Init 与 Print 两个成员函数函数体中没有关于不同对象的区分那当d1调用 Init 函 数时该函数是如何知道应该设置d1对象而不是设置d2对象呢C中通过引入this指针解决该问题即C编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数让该指针指向当前对象(函数运行时调用该函数的对象)在函数体中所有“成员变量”的操作都是通过该指针去访问。只不过所有的操作对用户是透明的即用户不需要来传递编译器自动完成。7.2 this指针的特性1.this指针的类型类类型* const即成员函数中不能给this指针赋值。2. 只能在“成员函数”的内部使用3.this指针本质上是“成员函数”的形参当对象调用成员函数时将对象地址作为实参传递给 this形参。所以对象中不存储this指针。4.this指针是“成员函数”第一个隐含的指针形参一般情况由编译器通过ecx寄存器自动传递不需要用户传递 闭幕 恭喜你完成了 C 类与对象核心知识的学习今天我们系统梳理了面向过程和面向对象初步认识、类的初步认识、类的访问限定符及封装、类的作用域、类的实例化、类对象模型和this 指针这七大基础模块。为了巩固知识并进一步拓展建议你动手实践在本地 IDE 或在线编译器中编写代码尝试不同的测试用例亲身体验每个知识点的用法。深入思考面向过程和面向对象面向过程和面向对象的核心区别是什么C 兼容 C 语言如何在 C 中同时使用两种编程范式类的初步认识C 中struct和class的唯一区别是什么为什么 C 要保留struct而不是完全用class替代访问限定符及封装public、protected、private的访问范围分别是什么封装的意义是什么如何通过友元破坏封装类的作用域类域和全局域的区别是什么在类外定义成员函数时为什么要加类名::前缀类的实例化类实例化对象的内存分配时机是什么栈对象和堆对象的生命周期有何不同类对象模型空类的大小是多少为什么成员函数是否占用对象的内存空间this 指针this指针的本质是什么为什么this指针不能被修改静态成员函数为什么没有this指针延伸挑战分别用面向过程C 风格和面向对象C 类实现一个简单的学生信息管理系统包含姓名、年龄、学号对比两种设计思路。定义一个Date类包含公有成员函数和私有成员变量演示访问限定符的作用。将Date类的成员函数声明和定义分离声明放在类内定义放在类外演示类域的使用。分别创建Date类的栈对象和堆对象观察其生命周期差异。定义一个空类用sizeof计算其大小并解释结果。在Date类的成员函数中使用this指针访问成员变量对比显式使用this-和隐式访问的区别。定义一个Student类包含静态成员变量和静态成员函数体会它们与this指针的关系。如果你觉得本文对你有所帮助欢迎点赞 / 收藏关注作者获取更多题解留言交流你的疑问或优化思路深入思考参考答案面向过程和面向对象面向过程以函数为核心数据与操作分离面向对象以对象为核心将数据和方法封装在一起。C 兼容 C可以在 C 中继续写 C 风格代码同时使用类、继承、多态等面向对象特性。类的初步认识C 中struct和class的唯一区别是默认访问权限struct默认publicclass默认private。保留struct是为了兼容 C 语言同时也用于定义纯数据聚合体。访问限定符及封装public类内外均可访问protected类内和派生类可访问private仅类内可访问。封装隐藏实现细节暴露接口提高代码安全性和可维护性。类的作用域类域包含类内所有成员类外访问需用类名::限定。全局域是所有函数和变量的作用域。类外定义成员函数时必须加类名::前缀表示该函数属于类域。类的实例化类定义时不分配内存实例化创建对象时才分配。栈对象在作用域结束时自动销毁堆对象需手动delete释放。类对象模型空类的大小为 1 字节因为 C 标准规定任何对象必须有唯一地址。成员函数不占用对象内存空间它们存储在代码段所有对象共享。this 指针this是指向当前对象的常量指针ClassName* const this不能被修改。静态成员函数没有this指针因为它们属于类而不是对象无法访问非静态成员。️延伸挑战参考解法1. 面向过程 vs 面向对象——学生信息管理系统/* * 功能对比面向过程C 风格和面向对象C 类的实现方式 * 场景学生信息管理姓名、年龄、学号 */ #include iostream #include cstring using namespace std; // 面向过程C 风格 // 数据单独定义 struct StudentC { char name[20]; int age; int id; }; // 操作函数单独定义数据和操作分离 void InitStudentC(StudentC* s, const char* name, int age, int id) { strcpy(s-name, name); s-age age; s-id id; } void PrintStudentC(const StudentC* s) { cout [C] Name: s-name , Age: s-age , ID: s-id endl; } // 面向对象C 类 // 数据和方法封装在一起 class StudentCpp { private: // 成员变量通常设为私有封装 char name[20]; int age; int id; public: // 公有成员函数提供对外接口 void Init(const char* n, int a, int i) { strcpy(name, n); age a; id i; } void Print() const // const 成员函数不修改成员变量 { cout [C] Name: name , Age: age , ID: id endl; } }; int main() { // 面向过程方式 StudentC s1; InitStudentC(s1, Alice, 20, 1001); PrintStudentC(s1); // 面向对象方式 StudentCpp s2; s2.Init(Bob, 21, 1002); s2.Print(); return 0; }2. Date 类——访问限定符演示/* * 功能演示访问限定符 public 和 private 的作用 * 成员变量设为私有通过公有成员函数进行访问和修改封装 */ #include iostream using namespace std; class Date { private: // 私有成员变量类外无法直接访问封装 int _year; int _month; int _day; public: // 公有成员函数提供对私有成员的访问接口 // 设置日期 void SetDate(int year, int month, int day) { // 可以在这里添加合法性检查封装的好处 if (month 1 || month 12 || day 1 || day 31) { cout Invalid date! endl; return; } _year year; _month month; _day day; } // 打印日期const 表示不修改成员变量 void PrintDate() const { cout _year - _month - _day endl; } // 获取年份只读访问 int GetYear() const { return _year; } }; int main() { Date d; // d._year 2024; // 编译错误_year 是 private类外无法直接访问 d.SetDate(2024, 8, 5); // 通过公有接口设置 d.PrintDate(); // 输出2024-8-5 cout Year: d.GetYear() endl; // 通过公有接口读取 return 0; }3. 声明与定义分离——类域演示/* * 功能演示类外定义成员函数声明与定义分离 * 类外定义时必须加 类名:: 前缀表示该函数属于类域 * 通常将声明放在 .h 头文件定义放在 .cpp 源文件 */ #include iostream using namespace std; // ----- 类声明通常放在 .h 头文件 ----- class Date { private: int _year; int _month; int _day; public: void SetDate(int year, int month, int day); // 声明 void PrintDate() const; // 声明 }; // ----- 类外定义成员函数通常放在 .cpp 源文件 ----- // 必须加 Date:: 前缀指明属于 Date 类域 void Date::SetDate(int year, int month, int day) { _year year; _month month; _day day; } // 类外定义 const 成员函数 void Date::PrintDate() const { // 不能修改成员变量 cout _year - _month - _day endl; } int main() { Date d; d.SetDate(2024, 8, 5); // 调用类外定义的成员函数 d.PrintDate(); return 0; }4. 栈对象 vs 堆对象——生命周期对比/* * 功能演示栈对象和堆对象的生命周期差异 * 栈对象作用域结束自动销毁 * 堆对象需手动 delete 释放否则内存泄漏 */ #include iostream using namespace std; class Date { private: int _year; int _month; int _day; public: Date(int y, int m, int d) : _year(y), _month(m), _day(d) { cout Date( y , m , d ) constructed endl; } ~Date() { cout Date destroyed endl; } void Print() const { cout _year - _month - _day endl; } }; int main() { cout Stack Object endl; Date d1(2024, 8, 5); // 栈对象 d1.Print(); // d1 在 main 函数结束时自动销毁 cout \n Heap Object endl; Date* d2 new Date(2024, 8, 6); // 堆对象需手动释放 d2-Print(); // 忘记 delete d2; // 会导致内存泄漏 delete d2; // 必须手动销毁 d2 nullptr; cout \n End of main endl; return 0; }5. this 指针的显式和隐式使用/* * 功能演示 this 指针的用法 * this 指向当前对象可用于区分同名参数和成员变量 * 显式使用 this- 和隐式访问的区别 */ #include iostream using namespace std; class Date { private: int _year; int _month; int _day; public: // ----- 显式使用 this 指针 ----- void SetDate(int year, int month, int day) { // 使用 this 明确访问成员变量避免与参数名混淆 this-_year year; this-_month month; this-_day day; } // ----- 隐式使用直接访问 ----- void SetDateImplicit(int year, int month, int day) { // 编译器自动隐式使用 this-_year year _year year; _month month; _day day; } // ----- 链式调用返回 this ----- Date SetYear(int year) { this-_year year; return *this; // 返回当前对象的引用支持链式调用 } Date SetMonth(int month) { this-_month month; return *this; } Date SetDay(int day) { this-_day day; return *this; } void Print() const { cout _year - _month - _day endl; } }; int main() { Date d1, d2; // 显式使用 this d1.SetDate(2024, 8, 5); d1.Print(); // 隐式使用 this d2.SetDateImplicit(2024, 8, 6); d2.Print(); // 链式调用利用 this 返回对象自身的引用 Date d3; d3.SetYear(2024).SetMonth(12).SetDay(25); // 链式调用 d3.Print(); return 0; }7. 静态成员与 this 指针/* * 功能演示静态成员与 this 指针的关系 * 静态成员变量所有对象共享一份 * 静态成员函数没有 this 指针只能访问静态成员 * 非静态成员函数有 this 指针可以访问所有成员 */ #include iostream using namespace std; class Student { private: string _name; int _age; // 静态成员变量属于类不属于任何对象 static int _studentCount; // 统计学生总数 public: Student(const string name, int age) : _name(name), _age(age) { _studentCount; // 每创建对象计数加1 } ~Student() { _studentCount--; // 对象销毁计数减1 } // ----- 非静态成员函数有 this 指针 ----- void PrintInfo() const { // 可以访问非静态成员通过 this 指针隐式访问 cout Name: _name , Age: _age endl; // 也可以访问静态成员所有对象共享 cout Total students: _studentCount endl; } // ----- 静态成员函数没有 this 指针 ----- static int GetStudentCount() { // 不能访问非静态成员没有 this 指针指向哪个对象 // cout _name; // 编译错误静态函数不能访问非静态成员 // 只能访问静态成员 return _studentCount; } // 静态成员函数可以用于创建对象的工厂方法 static Student* CreateStudent(const string name, int age) { return new Student(name, age); } }; // 静态成员变量必须在类外初始化 int Student::_studentCount 0; int main() { cout Initial count: Student::GetStudentCount() endl; // 0 // 使用静态工厂方法创建对象 Student* s1 Student::CreateStudent(Alice, 20); Student* s2 Student::CreateStudent(Bob, 21); cout After creating 2 students: Student::GetStudentCount() endl; // 2 // 通过对象调用非静态成员函数 s1-PrintInfo(); delete s1; delete s2; cout After deleting: Student::GetStudentCount() endl; // 0 return 0; }祝你在 C 的学习之路上越走越稳早日掌握更多编程技巧下次见 ✨
返回列表