第七章 面向对象基础:类与对象、访问修饰符、构造函数与析构函数、this指针、友元函数/类

好,咱们今天来聊聊C++面向对象编程的核心。说实话,我当年从C语言转到C++时,最兴奋的就是这个“类”的概念。它让代码不再是冷冰冰的函数堆砌,而是有了“生命”和“关系”。

面向对象编程,说白了就是把现实世界的东西抽象成代码里的“对象”。你想想看,一辆车有颜色、有速度,能跑能停——这不就是数据加行为吗?C++里的类,就是用来干这个的。

核心思想:类 = 数据成员 + 成员函数。对象 = 类的具体实例。

第七章:面向对象基础 知识体系 类与对象 访问修饰符 public / private / protected 构造 & 析构函数 初始化 / 清理资源 this 指针 指向对象自身的指针 友元函数 / 友元类 打破封装,访问私有成员

7.1 类与对象:从结构体到类的进化

如果你学过C语言,一定用过struct。C++的class,其实就是struct的“超级升级版”。区别在哪?struct默认所有成员是公开的,class默认是私有的。但这不是重点。

重点在于:类把数据和操作数据的方法打包在了一起。这叫“封装”。

// 一个简单的类定义
class Car {
    // 数据成员
    string brand;
    int speed;
    
public:
    // 成员函数
    void setBrand(const string& b) { brand = b; }
    void accelerate(int delta) { speed += delta; }
    int getSpeed() const { return speed; }
};

// 使用对象
Car myCar;
myCar.setBrand("Tesla");
myCar.accelerate(30);
cout << myCar.getSpeed() << endl;  // 输出 30

我个人习惯把类的声明放在头文件里,实现放在cpp文件里。这样结构清晰,编译也快。你在项目里最好也养成这个习惯。

7.2 访问修饰符:谁可以碰我的数据?

访问修饰符有三个:public、private、protected。我简单说说我的理解:

  • public:谁都能访问。就像你家大门,快递员都能敲。
  • private:只有类内部能访问。就像你的保险柜,只有你自己知道密码。
  • protected:自己和子类能访问。就像家里的工具箱,你和孩子能用,外人不行。

我的建议:数据成员一律设为private,通过public的getter/setter来访问。这不是矫情,是为了以后好维护。我在项目中见过太多直接暴露成员变量导致后期改得想哭的代码。

class BankAccount {
private:
    double balance;  // 私有,外部不能直接改
    
public:
    void deposit(double amount) {
        if (amount > 0) balance += amount;
    }
    
    double getBalance() const {
        return balance;
    }
};

7.3 构造函数与析构函数:对象的生与死

构造函数在对象创建时自动调用,用来初始化数据。析构函数在对象销毁时自动调用,用来释放资源。

嗯,这里要注意:如果你没写构造函数,编译器会给你一个默认的。但一旦你写了任何构造函数,默认的那个就没了。

class FileHandler {
private:
    FILE* file;
    
public:
    // 构造函数:打开文件
    FileHandler(const char* filename) {
        file = fopen(filename, "r");
        if (!file) {
            cerr << "文件打开失败!" << endl;
        }
    }
    
    // 析构函数:关闭文件
    ~FileHandler() {
        if (file) {
            fclose(file);
            cout << "文件已关闭" << endl;
        }
    }
    
    void readData() {
        // 读取文件内容...
    }
};

// 使用
{
    FileHandler fh("data.txt");
    fh.readData();
}  // 这里fh离开作用域,析构函数自动调用

避坑指南:我曾经在项目中忘记给包含动态内存的类写析构函数,结果内存泄漏了整整一周才查出来。记住:如果你的类里有new出来的资源,一定要写析构函数来delete。这就是“RAII”原则——资源获取即初始化,析构时自动释放。

7.4 this指针:我就是我

this是一个指向当前对象的指针。它在每个成员函数里都隐式可用。什么时候显式用它?最常见的情况是参数名和成员变量名冲突时。

class Person {
private:
    string name;
    int age;
    
public:
    // 参数名和成员名一样,用this区分
    void setName(const string& name) {
        this->name = name;  // 左边的name是成员,右边的是参数
    }
    
    // 链式调用:返回当前对象的引用
    Person& setAge(int age) {
        this->age = age;
        return *this;  // 返回对象本身
    }
    
    void print() const {
        cout << "Name: " << this->name << ", Age: " << this->age << endl;
    }
};

// 链式调用示例
Person p;
p.setName("Alice").setAge(25).print();  // 输出:Name: Alice, Age: 25

你想想看,如果没有this指针,成员函数怎么知道自己在操作哪个对象?其实每个成员函数调用时,编译器都会偷偷把对象的地址传进去——就是this。

7.5 友元函数与友元类:打破规则的特权

友元,说白了就是“我信任你,你可以看我的私有数据”。友元函数不是类的成员函数,但它可以访问类的私有成员。友元类里的所有成员函数都可以访问当前类的私有成员。

重要提醒:友元破坏了封装性,能不用就别用。我只有在运算符重载或者两个类需要紧密协作时才会考虑它。

class Matrix;  // 前向声明

class Vector {
private:
    double x, y, z;
    
public:
    Vector(double x, double y, double z) : x(x), y(y), z(z) {}
    
    // 声明友元函数
    friend double dotProduct(const Vector& v, const Vector& w);
    
    // 声明友元类
    friend class Matrix;
};

// 友元函数实现:可以访问Vector的私有成员
double dotProduct(const Vector& v, const Vector& w) {
    return v.x * w.x + v.y * w.y + v.z * w.z;
}

class Matrix {
private:
    double data[3][3];
    
public:
    // 友元类中的函数可以访问Vector的私有成员
    Vector transform(const Vector& v) {
        return Vector(
            data[0][0]*v.x + data[0][1]*v.y + data[0][2]*v.z,
            data[1][0]*v.x + data[1][1]*v.y + data[1][2]*v.z,
            data[2][0]*v.x + data[2][1]*v.y + data[2][2]*v.z
        );
    }
};

我的经验:友元类通常用在迭代器模式中。比如一个Container类,它的Iterator需要访问Container的内部数据。这时候把Iterator声明为友元类,既保持了封装,又提供了必要的访问权限。

7.6 综合示例:一个简单的Student类

咱们把今天学的知识点串起来,写一个完整的例子:

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

class Student {
private:
    string name;
    int id;
    vector<double> scores;
    
public:
    // 构造函数
    Student(const string& name, int id) 
        : name(name), id(id) {
        cout << "学生 " << name << " 已创建" << endl;
    }
    
    // 析构函数
    ~Student() {
        cout << "学生 " << name << " 已销毁" << endl;
    }
    
    // setter/getter
    void setName(const string& name) { this->name = name; }
    string getName() const { return name; }
    
    void addScore(double score) {
        if (score >= 0 && score <= 100) {
            scores.push_back(score);
        }
    }
    
    double getAverage() const {
        if (scores.empty()) return 0.0;
        double sum = 0;
        for (double s : scores) {
            sum += s;
        }
        return sum / scores.size();
    }
    
    // 友元函数:比较两个学生的平均分
    friend bool compareAverage(const Student& a, const Student& b);
};

// 友元函数实现
bool compareAverage(const Student& a, const Student& b) {
    return a.getAverage() > b.getAverage();
}

int main() {
    Student s1("张三", 1001);
    Student s2("李四", 1002);
    
    s1.addScore(85);
    s1.addScore(92);
    s1.addScore(78);
    
    s2.addScore(88);
    s2.addScore(76);
    s2.addScore(95);
    
    cout << s1.getName() << " 平均分: " << s1.getAverage() << endl;
    cout << s2.getName() << " 平均分: " << s2.getAverage() << endl;
    
    if (compareAverage(s1, s2)) {
        cout << s1.getName() << " 成绩更好" << endl;
    } else {
        cout << s2.getName() << " 成绩更好" << endl;
    }
    
    return 0;
}

这个例子涵盖了类定义、构造函数、析构函数、this指针、友元函数。你可以在自己的编译器上跑跑看,感受一下对象的生命周期。

最后提醒一句:面向对象不是银弹。我见过有人为了用面向对象而用面向对象,把简单的逻辑搞得无比复杂。记住:封装、继承、多态是工具,不是目的。该用struct就用struct,该用class就用class,别为了炫技而过度设计。


公众号:蓝海资料掘金营,微信deep3321