11_C++类和对象(上):封装与对象的生命周期

0 阅读5分钟

C++类和对象(上):封装与对象的生命周期


面向对象编程(OOP)是 C++ 区别于 C 语言最核心的设计范式。在 C++ 中,类(class) 是构造对象的蓝图,对象(object) 是类的具体实例。这一章我们从最基础的封装开始,逐步深入到对象从创建到销毁的完整生命周期。


一、封装:类的骨架

1.1 为什么需要封装

写 C 程序时,数据和操作数据的函数是分开的。你定义结构体存储数据,然后编写一组函数来操作它。这种方式有一个隐患:调用者可以绕过你的函数,直接修改结构体内部的数据

封装(Encapsulation)解决的就是这个问题——把数据和对数据的操作捆绑在一起,并对外隐藏实现细节

1.2 类的定义与基本使用

C++ 中定义类使用 class 关键字,类中的变量称为 成员变量(属性),函数称为 成员函数(方法)

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

class Clock {
public:
    int hour;
    int minute;
    int second;

    void setTime(int h, int m, int s) {
        hour   = h;
        minute = m;
        second = s;
    }

    void showTime() {
        cout << hour << ":" << minute << ":" << second << endl;
    }
};

int main() {
    Clock c;
    c.setTime(10, 30, 45);
    c.showTime();
    c.hour = 12;
    c.showTime();
    return 0;
}

1.3 访问权限:public / protected / private

限定符类内访问派生类访问类外访问
public允许允许允许
protected允许允许禁止
private允许禁止禁止
class BankAccount {
public:
    void deposit(double amount) {
        if (amount > 0) balance += amount;
    }
    double getBalance() { return balance; }
private:
    double balance = 0.0;
};

int main() {
    BankAccount acc;
    acc.deposit(1000);
    cout << acc.getBalance() << endl;
    return 0;
}

1.4 struct 与 class 的区别

C++ 中 structclass 唯一的区别是默认访问权限。

struct Point {
    int x;  // 默认 public
    int y;
};

class Rect {
    int w;  // 默认 private
    int h;
};

1.5 成员属性私有化实践

将所有成员变量设为 private,通过 public 的 getter/setter 访问,可以在 setter 中加入校验逻辑。

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

class Student {
public:
    void setName(const string& name) { m_name = name; }
    string getName() const { return m_name; }

    void setAge(int age) {
        if (age >= 3 && age <= 100) m_age = age;
        else cout << "年龄不合法" << endl;
    }
    int getAge() const { return m_age; }

    void setScore(double s) {
        if (s >= 0 && s <= 100) m_score = s;
        else cout << "成绩不合法" << endl;
    }
    double getScore() const { return m_score; }

    void show() const {
        cout << m_name << "," << m_age << "岁,成绩" << m_score << endl;
    }

private:
    string m_name;
    int    m_age   = 0;
    double m_score = 0.0;
};

int main() {
    Student stu;
    stu.setName("小明");
    stu.setAge(20);
    stu.setScore(95.5);
    stu.show();
    stu.setAge(200);
    return 0;
}

二、对象的初始化和清理

对象从创建到销毁有一个完整的生命周期。C++ 通过构造函数和析构函数让这个生命周期变得可控。

2.1 构造函数与析构函数

  • 构造函数(Constructor):对象创建时自动调用,用于初始化。
  • 析构函数(Destructor):对象销毁时自动调用,用于清理资源。
#include <iostream>
using namespace std;

class Timer {
public:
    Timer() {
        startTime = 0;
        cout << "Timer 构造" << endl;
    }
    ~Timer() {
        cout << "Timer 析构" << endl;
    }
private:
    int startTime;
};

void test() {
    Timer t;
}

int main() {
    cout << "--- start ---" << endl;
    test();
    cout << "--- end ---" << endl;
    return 0;
}

如果类中没有定义构造和析构,编译器会隐式生成一个空实现。每个对象都有构造和析构,即使你没写。

2.2 构造函数的分类与调用

构造函数分为三类:

  1. 无参构造(默认构造)Person()
  2. 有参构造Person(int age)
  3. 拷贝构造Person(const Person& p)
#include <iostream>
#include <string>
using namespace std;

class Person {
public:
    Person() : name("未命名"), age(0) {
        cout << "无参构造" << endl;
    }
    Person(const string& n, int a) : name(n), age(a) {
        cout << "有参构造" << endl;
    }
    Person(const Person& p) : name(p.name + "(副本)"), age(p.age) {
        cout << "拷贝构造" << endl;
    }
    ~Person() { }
    void show() const {
        cout << name << "," << age << "岁" << endl;
    }
private:
    string name;
    int age;
};

int main() {
    Person p1;
    Person p2("张三", 25);
    Person p3(p2);
    Person p4 = {"王五", 28};
    p1.show();
    p2.show();
    p3.show();
    return 0;
}

注意Person p1(); 会被编译器解释为函数声明。无参构造必须写为 Person p1;

2.3 拷贝构造的调用时机

值传递、值返回、用已有对象构造新对象时都会触发拷贝构造。

2.4 深拷贝与浅拷贝

浅拷贝只复制指针值,导致两个对象指向同一块堆内存,销毁时 double free。深拷贝重新开辟内存并复制内容,使每个对象独立管理资源。

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

class StringBox {
public:
    StringBox(const char* str) {
        len = strlen(str);
        data = new char[len + 1];
        strcpy(data, str);
        cout << "构造" << endl;
    }

    StringBox(const StringBox& s) {
        len = s.len;
        data = new char[len + 1];
        strcpy(data, s.data);
        cout << "深拷贝" << endl;
    }

    ~StringBox() { delete[] data; }

private:
    char* data;
    int len;
};

int main() {
    StringBox s1("Hello");
    StringBox s2(s1);
    return 0;
}

2.5 初始化列表

初始化列表在构造函数体执行之前初始化成员变量。const 成员、引用成员、无默认构造的组合类成员必须使用。

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

class Engine {
public:
    Engine(int hp) : horsepower(hp) { }
private:
    int horsepower;
};

class Car {
public:
    Car(const string& brand, int hp)
        : m_brand(brand)
        , m_engine(hp)
        , m_year(2024)
    { }
    void info() const {
        cout << m_brand << "," << m_year << "年款" << endl;
    }
private:
    string m_brand;
    Engine m_engine;
    const int m_year;
};

int main() {
    Car car("丰田", 200);
    car.info();
    return 0;
}

三、对象数组与对象指针

3.1 对象数组

Point arr1[3];
Point arr2[3] = { Point(1,2), Point(3,4), Point(5,6) };
Point* arr3 = new Point[3];
delete[] arr3;

3.2 对象指针

Rect r1(4, 5);
Rect* ptr = &r1;
cout << ptr->area() << endl;

Rect* heap = new Rect(3, 6);
delete heap;

四、综合练习:影片租借系统

运用封装、构造/析构、深拷贝和对象数组知识。

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

class Movie {
public:
    Movie() : title(nullptr), duration(0), price(0) {}
    Movie(const char* t, int d, double p)
        : duration(d), price(p) {
        title = new char[strlen(t) + 1];
        strcpy(title, t);
    }
    Movie(const Movie& m) : duration(m.duration), price(m.price) {
        title = new char[strlen(m.title) + 1];
        strcpy(title, m.title);
    }
    ~Movie() { delete[] title; }
    void show() const {
        cout << "《" << title << "》 " << duration
             << "分钟 " << price << "元/天" << endl;
    }
    double getPrice() const { return price; }
private:
    char* title;
    int duration;
    double price;
};

class RentalStore {
public:
    RentalStore() : count(0) {}
    void addMovie(const Movie& m) {
        if (count < 10) movies[count++] = m;
    }
    double calcTotalRent(int days) const {
        double total = 0;
        for (int i = 0; i < count; ++i)
            total += movies[i].getPrice() * days;
        return total;
    }
    void showAll() const {
        cout << "\n=== 影片仓库 ===" << endl;
        for (int i = 0; i < count; ++i) {
            cout << i + 1 << ". ";
            movies[i].show();
        }
    }
private:
    Movie movies[10];
    int count;
};

int main() {
    Movie m1("流浪地球", 125, 3.5);
    Movie m2("让子弹飞", 132, 2.8);
    Movie m3("千与千寻", 124, 4.0);
    RentalStore store;
    store.addMovie(m1);
    store.addMovie(m2);
    store.addMovie(m3);
    store.showAll();
    cout << "\n租借3天,总价" << store.calcTotalRent(3) << "元" << endl;
    return 0;
}

本章小结

要点说明
封装将数据和行为捆绑,通过访问权限控制对外接口
public/protected/private三种访问级别的含义与用法
构造函数无参、有参、拷贝三种构造及调用方式
深拷贝 vs 浅拷贝指针成员必须深拷贝,否则 double free
初始化列表const/引用/无默认构造的组合类必须使用
对象数组与指针栈上数组、堆上数组、对象指针的基本操作