在 C++ 面向对象编程中,类和对象是构建程序的基石。类是对现实世界中事物的抽象描述,而对象则是类的具体实例。理解类和对象的概念及关系,是掌握面向对象编程思想的第一步。本文将系统讲解类的定义、对象的创建与使用,以及封装、this 指针等核心知识点,帮你建立对 C++ 面向对象编程的基础认知。
一、类:现实事物的 “抽象模板”
类(Class)是一种用户自定义的数据类型,它封装了事物的属性(数据) 和行为(操作数据的方法),是对一类事物共同特征的抽象。
1. 类的定义:成员变量与成员函数
类的定义通过class关键字实现,基本语法如下:
class 类名 { // 访问限定符:public(公有)、private(私有)、protected(保护)public: // 成员函数(行为):对属性的操作 返回值类型 函数名(参数列表) { // 函数体 }private: // 成员变量(属性):事物的状态数据 数据类型 变量名;};
示例:定义一个 “学生” 类(Student)
class Student {public: // 成员函数:设置学生信息 void setInfo(const std::string& name, int age, double score) { _name = name; _age = age; _score = score; } // 成员函数:打印学生信息 void printInfo() { std::cout << "姓名:" << _name << ",年龄:" << _age << ",成绩:" << _score << std::endl; }private: // 成员变量:学生的属性 std::string _name; // 姓名 int _age; // 年龄 double _score; // 成绩};
- 访问限定符的作用:
- public:类外可直接访问(如Student类的setInfo和printInfo函数)。
- private:仅类内成员可访问,类外不可直接访问(如_name、_age等成员变量,保证数据安全性)。
- protected:用于继承场景,子类可访问,类外不可访问(后续继承章节详解)。
2. 类的声明与定义分离
当类的成员函数实现复杂时,通常将类的声明放在头文件(.h) 中,成员函数的定义放在源文件(.cpp)
示例:分离式定义
- Student.h(头文件,类声明):
#pragma once // 防止头文件重复包含#include <string>#include <iostream>class Student {public: void setInfo(const std::string& name, int age, double score); void printInfo();private: std::string _name; int _age; double _score;};
- Student.cpp(源文件,成员函数定义):
#include "Student.h"// 用Student::限定函数属于Student类void Student::setInfo(const std::string& name, int age, double score) { _name = name; _age = age; _score = score;}void Student::printInfo() { std::cout << "姓名:" << _name << ",年龄:" << _age << ",成绩:" << _score << std::endl;}
二、对象:类的 “具体实例”
对象(Object)是类的实例化结果,就像 “学生类” 可以实例化出 “张三”“李四” 等具体学生对象。每个对象拥有独立的成员变量,但共享类的成员函数(节省内存)。
1. 对象的创建与初始化
创建对象的语法与定义基本数据类型变量类似,可通过默认构造函数或自定义构造函数初始化(构造函数将在下文详解)。
示例:创建 Student 类的对象
#include "Student.h"int main() { // 创建对象:栈上实例化 Student stu1; // 调用默认构造函数(编译器自动生成) stu1.setInfo("张三", 18, 90.5); // 调用成员函数 // 另一种创建方式:堆上实例化(需手动释放) Student* stu2 = new Student; stu2->setInfo("李四", 19, 88.0); // 指针访问成员函数用-> // 访问对象的成员 stu1.printInfo(); // 栈对象用.访问成员 stu2->printInfo(); // 堆对象用->访问成员 delete stu2; // 释放堆对象 return 0;}
- 对象的内存布局:每个对象仅存储成员变量(成员函数存放在类的代码段,被所有对象共享)。例如,Student对象的内存中仅包含_name、_age、_score三个成员变量。
2. 构造函数:对象的 “初始化器”
构造函数是一种特殊的成员函数,用于对象创建时的初始化工作,其名称与类名相同,无返回值,且在对象创建时自动调用。
构造函数的特性:
- 默认构造函数:若类中未定义任何构造函数,编译器会自动生成默认构造函数(无参);若定义了其他构造函数,默认构造函数需手动定义。
- 重载特性:支持函数重载(可定义多个参数不同的构造函数)。
示例:为 Student 类定义构造函数
class Student {public: // 无参构造函数(默认构造函数) Student() { _name = "未知"; _age = 0; _score = 0.0; } // 带参构造函数(重载) Student(const std::string& name, int age, double score) { _name = name; _age = age; _score = score; } // 其他成员函数...private: std::string _name; int _age; double _score;};// 使用构造函数初始化对象int main() { Student stu1; // 调用无参构造 Student stu2("王五", 20, 95.0); // 调用带参构造 Student* stu3 = new Student("赵六", 17, 80.0); // 堆对象初始化 return 0;}
3. 析构函数:对象的 “清理器”
析构函数与构造函数对应,用于对象销毁时的资源清理(如释放动态分配的内存),其名称为~类名,无参数、无返回值,在对象销毁时自动调用。
示例:为包含动态内存的类定义析构函数
class Array {public: // 构造函数:分配动态内存 Array(int size) { _size = size; _data = new int[size]; // 动态分配数组 } // 析构函数:释放内存 ~Array() { if (_data) { delete[] _data; // 释放动态内存 _data = nullptr; _size = 0; } }private: int* _data; // 动态数组 int _size;};
- 析构函数的调用时机:栈对象在离开作用域时调用,堆对象在delete时调用。
三、封装:面向对象的核心特性之一
封装(Encapsulation)是指将类的成员变量隐藏在类的内部(通过private限定),仅通过公有成员函数对外提供访问接口,从而实现数据的安全性和接口的规范性。
1. 封装的意义
- 数据保护:防止类外直接修改成员变量(如学生的成绩不能随意修改,需通过setScore函数进行合法性检查)。
void setScore(double score) { if (score >= 0 && score <= 100) { // 合法性检查 _score = score; } else { std::cout << "成绩无效!" << std::endl; }}
- 接口统一:对外提供明确的成员函数接口,隐藏内部实现细节(如修改内部数据结构时,只要接口不变,外部代码无需修改)。
四、this 指针:成员函数的 “隐式参数”
在成员函数中,编译器会隐式传递一个指向当前对象的指针(this指针),用于区分不同对象的成员变量。
this 指针的特性:
- 隐式存在:成员函数的参数列表中隐含类名* const this,无需手动声明。
- 指向当前对象:this指针指向调用该成员函数的对象。
- 不可修改:this是常量指针(*const),不能指向其他对象。
示例:this 指针的显式使用
class Student {public: void setName(const std::string& name) { // this->_name表示当前对象的成员变量 this->_name = name; // 等价于_name = name(编译器自动补充this) } // 比较两个学生的年龄 bool isOlderThan(const Student& other) { return this->_age > other._age; // this指向当前对象,other是另一个对象 }private: std::string _name; int _age;};
- 解决命名冲突:当成员函数的参数与成员变量同名时,需用this指针区分:
void setAge(int age) { this->age = age; // 参数age与成员变量age同名,用this区分}
五、常成员与常对象:数据的 “只读保护”
为了保护对象的数据不被修改,C++ 提供了常成员函数和常对象的机制。
1. 常成员函数
在成员函数的参数列表后加const,表示该函数不能修改成员变量(this指针类型变为const 类名* const),语法:
返回值类型 函数名(参数列表) const { // 只能访问成员变量,不能修改}
示例:常成员函数的使用
class Student {public: // 常成员函数:仅读取数据,不修改 std::string getName() const { return _name; // 允许访问 // _name = "新名字"; // 错误:常成员函数不能修改成员变量 }private: std::string _name;};
2. 常对象
用const修饰的对象为常对象,常对象只能调用常成员函数(防止修改数据):
const Student stu("张三", 18, 90); // 常对象stu.getName(); // 正确:调用常成员函数stu.setName("李四"); // 错误:常对象不能调用非const成员函数
总结:类和对象是面向对象编程的基石
类通过封装属性和行为,实现了对现实事物的抽象;对象作为类的实例,是程序中具体操作的实体。构造函数和析构函数负责对象的初始化与资源清理,this 指针解决了成员函数对不同对象的区分问题,而封装、常成员等机制则保证了数据的安全性和接口的规范性。
理解类和对象的核心概念,是学习继承、多态等高级面向对象特性的基础。在实际编程中,合理设计类的成员变量和成员函数,遵循封装原则,能大幅提升代码的可读性和可维护性。