在 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 指针解决了成员函数对不同对象的区分问题,而封装、常成员等机制则保证了数据的安全性和接口的规范性。

理解类和对象的核心概念,是学习继承、多态等高级面向对象特性的基础。在实际编程中,合理设计类的成员变量和成员函数,遵循封装原则,能大幅提升代码的可读性和可维护性。