👋 欢迎阅读
🎯 欢迎来到「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的默认访问权限为private,struct为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默认public,class默认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++ 的学习之路上越走越稳,早日掌握更多编程技巧!下次见 🚀✨