文章目录

  • 前言
  • 一、核心概念:类与对象的关系(必懂)
  • 二、类的定义:语法详解(入门必写)
  • 三、对象的实例化与使用(实战入门)
  • 四、类的核心特性:构造函数与析构函数(重中之重)
  • 五、常见误区:新手必避的3个坑
  • 六、实战案例:完整的类与对象代码
  • 七、总结:类与对象核心要点

前言

C++作为面向对象编程(OOP)的经典语言,“类与对象”是其核心基石,也是区别于C语言(面向过程)的关键所在。很多新手入门时会被“类”“对象”“封装”“成员函数”这些概念绕晕,其实只要抓住“类是模板,对象是实例”这一核心,就能轻松打通任督二脉。今天这篇博客,从基础概念到语法细节,再到实战案例,帮你彻底掌握C++类与对象。


一、核心概念:类与对象的关系(必懂)

先举一个生活化的例子,帮你快速理解:

我们可以把“汽车”理解为一个——它是一个抽象的模板,定义了汽车的共同属性(颜色、品牌、排量)和行为(启动、刹车、加速);而你家的那辆黑色特斯拉Model 3,就是这个类的一个对象——它是模板的具体实例,有明确的属性值(黑色、特斯拉、2.0T),也能执行具体的行为(踩油门加速、踩刹车停车)。

用专业的话总结:

  • 类(Class):是一种用户自定义的数据类型,封装了数据(属性)和操作数据的函数(行为),是对象的“蓝图”或“模板”。

  • 对象(Object):是类的实例化结果,是具体的、可操作的实体。一个类可以实例化出无数个对象,每个对象拥有独立的属性值,但共享类的行为(成员函数)。

一句话记住:类是抽象的,对象是具体的;类是模板,对象是模板造出来的“产品”


二、类的定义:语法详解(入门必写)

C++中,类的定义使用class关键字,语法结构清晰,主要包含三部分:访问权限修饰符、成员属性、成员函数。

1. 基本语法格式

class 类名 {
    // 访问权限修饰符:public(公有)、private(私有)、protected(保护)
public:
    // 成员属性(数据):类的特征
    数据类型 属性名1;
    数据类型 属性名2;
    
    // 成员函数(行为):操作属性的函数
    返回值类型 函数名1(参数列表) {
        // 函数体:实现具体功能
    }
    返回值类型 函数名2(参数列表); // 声明,实现可以写在类外
private:
    // 私有属性/函数:只能在类内部访问
    数据类型 私有属性;
}; // 注意:类定义结尾必须加分号

2. 访问权限修饰符(重点)

C++通过访问权限修饰符,实现“封装”特性——隐藏类的内部细节,只对外提供可访问的接口,提升代码安全性和可维护性。三种修饰符的区别如下:

修饰符

访问范围

常用场景

public(公有)

类内部、类外部、子类都可访问

成员函数(对外接口)、需要外部访问的属性

private(私有)

仅类内部可访问

成员属性(隐藏细节)、内部辅助函数

protected(保护)

类内部、子类可访问,类外部不可访问

继承场景中,子类需要访问的父类属性/函数

注意:如果不写访问权限修饰符,类的默认访问权限是 private(结构体struct默认是public)。

3. 类成员函数的两种实现方式

成员函数的实现有两种方式,根据需求选择即可:

方式1:类内实现(inline内联函数)
class Student {
public:
    // 类内实现成员函数
    void showInfo() {
        cout << "姓名:" << name << ",年龄:" << age << endl;
    }
private:
    string name;
    int age;
};
方式2:类外实现(需加作用域解析符::)

函数声明写在类内,实现写在类外,需要使用 类名::函数名 指明函数属于哪个类,避免命名冲突。

#include <iostream>
#include <string>
using namespace std;

// 类的声明
class Student {
public:
    void showInfo(); // 声明成员函数
private:
    string name;
    int age;
};

// 类外实现成员函数,::是作用域解析符
void Student::showInfo() {
    cout << "姓名:" << name << ",年龄:" << age << endl;
}

三、对象的实例化与使用(实战入门)

类定义完成后,需要实例化对象才能使用类的属性和成员函数。对象的实例化有两种方式:栈上实例化、堆上实例化(和内存管理结合,重点掌握)。

1. 栈上实例化(常用,自动管理生命周期)

语法:类名 对象名;,和定义普通变量类似,生命周期由编译器自动管理(函数结束后自动销毁)。

int main() {
    // 栈上实例化对象
    Student stu;
    
    // 访问对象的公有成员(私有成员无法直接访问)
    // 给公有属性赋值(若属性是private,需通过成员函数赋值)
    stu.name = "张三"; // 错误:name是private,外部不可访问
    stu.showInfo(); // 正确:showInfo是public,可外部调用
    
    return 0;
}

2. 堆上实例化(动态分配,手动管理生命周期)

语法:类名* 对象指针 = new 类名();,和动态内存分配结合,对象在堆区,需手动用delete释放,否则会内存泄漏。

int main() {
    // 堆上实例化对象,返回指针
    Student* pStu = new Student();
    
    // 通过指针访问成员:-> 运算符
    pStu->showInfo();
    
    // 手动释放内存,避免泄漏
    delete pStu;
    pStu = NULL; // 避免野指针
    
    return 0;
}

3. 访问对象成员的两种方式

  • 对象名访问:对象名.成员属性/成员函数(栈上对象常用);

  • 指针访问:对象指针->成员属性/成员函数(堆上对象常用)。


四、类的核心特性:构造函数与析构函数(重中之重)

构造函数和析构函数是类的特殊成员函数,无需手动调用,由编译器自动执行,负责对象的初始化和清理工作,是面向对象编程的“灵魂”。

1. 构造函数(初始化对象)

作用:对象实例化时,自动调用,用于初始化对象的成员属性(避免属性是随机值)。

核心特点:

  • 函数名和类名完全一致,无返回值(甚至不能写void);

  • 可以重载(多个构造函数,参数列表不同);

  • 若未手动定义,编译器会自动生成一个“默认构造函数”(无参数、无函数体);

  • 常用场景:给成员属性赋初始值。

示例:

class Student {
public:
    // 1. 默认构造函数(无参数)
    Student() {
        name = "未知";
        age = 0;
    }
    
    // 2. 带参数的构造函数(重载)
    Student(string n, int a) {
        name = n;
        age = a;
    }
    
    // 3. 拷贝构造函数(用一个对象初始化另一个对象)
    Student(const Student& stu) {
        name = stu.name;
        age = stu.age;
    }
    
    void showInfo() {
        cout << "姓名:" << name << ",年龄:" << age << endl;
    }
private:
    string name;
    int age;
};

int main() {
    Student stu1; // 调用默认构造函数
    Student stu2("张三", 18); // 调用带参数构造函数
    Student stu3 = stu2; // 调用拷贝构造函数
    stu1.showInfo(); // 输出:姓名:未知,年龄:0
    stu2.showInfo(); // 输出:姓名:张三,年龄:18
    stu3.showInfo(); // 输出:姓名:张三,年龄:18
    return 0;
}

2. 析构函数(清理对象)

作用:对象销毁时(生命周期结束),自动调用,用于清理对象占用的资源(如堆内存、文件句柄等)。

核心特点:

  • 函数名是~类名,无返回值,无参数(不能重载);

  • 若未手动定义,编译器会自动生成默认析构函数(无函数体);

  • 重点:若对象有堆区资源(如new分配的内存),必须手动定义析构函数,释放堆内存,否则会内存泄漏。

示例(带堆内存的析构函数):

class Student {
public:
    // 构造函数:在堆区分配内存
    Student(string n, int a) {
        name = new string(n); // 堆区分配字符串
        age = a;
    }
    
    // 析构函数:释放堆区内存
    ~Student() {
        if (name != NULL) {
            delete name; // 释放堆区内存
            name = NULL; // 避免野指针
        }
        cout << "析构函数调用,释放资源" << endl;
    }
    
    void showInfo() {
        cout << "姓名:" << *name << ",年龄:" << age << endl;
    }
private:
    string* name; // 指针,指向堆区内存
    int age;
};

int main() {
    Student* pStu = new Student("张三", 18);
    pStu->showInfo();
    delete pStu; // 手动释放对象,会调用析构函数
    return 0;
}

五、常见误区:新手必避的3个坑

误区1:混淆类和对象

错误认知:类可以直接使用成员属性/函数;正确做法:类是模板,必须实例化对象后,通过对象才能访问成员。

误区2:私有成员可以直接访问

错误认知:对象可以直接修改private属性;正确做法:私有成员只能在类内部访问,外部需通过公有成员函数(setter/getter)操作。

// 正确的setter/getter写法
class Student {
public:
    // setter:设置私有属性值
    void setName(string n) {
        name = n;
    }
    // getter:获取私有属性值
    string getName() {
        return name;
    }
private:
    string name;
};

int main() {
    Student stu;
    stu.setName("张三"); // 正确:通过setter设置
    cout << stu.getName(); // 正确:通过getter获取
    return 0;
}

误区3:忘记释放堆上的对象

错误做法:new实例化对象后,不delete;正确做法:堆上对象必须手动delete,否则会导致内存泄漏,尤其在循环、分支中,要确保每个new都有对应的delete。


六、实战案例:完整的类与对象代码

结合以上知识点,写一个完整的“学生类”案例,涵盖类定义、构造/析构、对象使用、setter/getter:

#include <iostream>
#include <string>
using namespace std;

// 学生类定义
class Student {
public:
    // 构造函数(重载)
    Student() {
        name = "未知";
        age = 0;
        score = 0.0;
    }
    
    Student(string n, int a, double s) {
        name = n;
        age = a;
        score = s;
    }
    
    // 拷贝构造函数
    Student(const Student& stu) {
        name = stu.name;
        age = stu.age;
        score = stu.score;
    }
    
    // 析构函数
    ~Student() {
        cout << name << "的对象已销毁" << endl;
    }
    
    // setter/getter函数
    void setName(string n) { name = n; }
    string getName() { return name; }
    void setAge(int a) { age = a; }
    int getAge() { return age; }
    void setScore(double s) { score = s; }
    double getScore() { return score; }
    
    // 成员函数:展示学生信息
    void showInfo() {
        cout << "姓名:" << name << ",年龄:" << age << ",成绩:" << score << endl;
    }
    
    // 成员函数:判断成绩是否及格
    bool isPass() {
        return score >= 60;
    }
private:
    // 私有成员属性
    string name;
    int age;
    double score;
};

// 主函数:测试类与对象
int main() {
    // 栈上实例化对象
    Student stu1("张三", 18, 90.5);
    stu1.showInfo();
    cout << "是否及格:" << (stu1.isPass() ? "是" : "否") << endl;
    
    // 堆上实例化对象
    Student* pStu2 = new Student("李四", 17, 58.0);
    pStu2->showInfo();
    cout << "是否及格:" << (pStu2->isPass() ? "是" : "否") << endl;
    
    // 拷贝对象
    Student stu3 = stu1;
    stu3.setName("王五");
    stu3.setScore(85.0);
    stu3.showInfo();
    
    // 释放堆上对象
    delete pStu2;
    pStu2 = NULL;
    
    return 0;
}

运行结果:

姓名:张三,年龄:18,成绩:90.5
是否及格:是
姓名:李四,年龄:17,成绩:58.0
是否及格:否
姓名:王五,年龄:18,成绩:85.0
李四的对象已销毁
王五的对象已销毁
张三的对象已销毁

注意:栈上对象的销毁顺序和实例化顺序相反(先实例化的后销毁),堆上对象手动delete后立即销毁。


七、总结:类与对象核心要点

1. 核心关系:类是模板,对象是实例;类抽象,对象具体;

2. 类的结构:访问权限(public/private/protected)+ 成员属性 + 成员函数;

3. 对象实例化:栈上(自动管理)、堆上(手动delete);

4. 特殊函数:构造函数(初始化,可重载)、析构函数(清理,不可重载);

5. 核心原则:封装(隐藏私有细节,提供公有接口),提升代码安全性和可维护性。

更多推荐