首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【C++类与对象·上】从结构体到类:C++封装思想入门

【C++类与对象·上】从结构体到类:C++封装思想入门

作者头像
给东岸来杯冷咖啡
发布2026-01-12 20:25:11
发布2026-01-12 20:25:11
1000
举报

引言

想象一栋摩天大楼——建筑师不会把钢筋水泥随意堆在工地上,而是先绘制精密的蓝图,明确每个房间的功能和连接方式。在C++中,class(类)正是这样的"建筑蓝图",而对象则是根据蓝图建造的"房子"。今天,让我们一起推开面向对象编程的大门,看看如何用C++的封装思想为代码建造坚固的"大厦"。

从C到C++:struct的华丽蜕变

许多同学从C语言转向C++时,最大的困惑是:为什么有了struct还要引入class?记得我第一次接触C++时,也对这种"重复造轮子"的做法感到不解。直到有一天,我尝试用C语言实现一个数据结构,才真正体会到C++设计的精妙。

在C语言中,struct仅能打包数据:

代码语言:javascript
复制
// C语言风格
struct Student {
    char name[20];
    int age;
    float score;
};

C++让struct脱胎换骨,不仅能包含数据,还能拥有"行为"(方法):

代码语言:javascript
复制
struct Student {
    char name[20];
    int age;
    
    // 方法!C语言无法做到
    void introduce() {
        std::cout << "我是" << name << ",今年" << age << "岁\n";
    }
};

更关键的是,C++引入了class关键字,它与struct几乎相同,唯一区别是默认访问权限

  • class默认成员为private(私有)
  • struct默认成员为public(公有)

踩坑经历:刚学C++时,我常常忘记在class中添加public关键字,导致所有成员函数都无法在类外调用,编译器报错看得我一头雾水。后来才明白,C++的设计哲学是"默认保守"——除非明确声明,否则不对外开放。

封装:数据与行为的完美融合

封装是面向对象的基石。它像保险箱一样保护内部数据,只通过特定"钥匙孔"(接口)与外界交互。我们用publicprivate访问限定符实现这一思想:

代码语言:javascript
复制
class BankAccount {
private:
    double balance;  // 私有成员,外部无法直接访问
    
public:
    // 公有接口,控制对私有数据的访问
    void deposit(double amount) {
        if (amount > 0) balance += amount;
    }
    
    bool withdraw(double amount) {
        if (amount > 0 && balance >= amount) {
            balance -= amount;
            return true;
        }
        return false;
    }
    
    double getBalance() const { 
        return balance; 
    }
};

这种设计有两大优势:

  1. 数据安全:防止外部代码直接修改balance,避免设置为负数等非法操作
  2. 行为封装:将相关操作(存款、取款)与数据绑定在一起,代码更清晰

代码演示:银行账户类,展示封装的力量

代码语言:javascript
复制
#include <iostream>
using namespace std;

class BankAccount {
private:
    string accountNumber;
    double balance;
    
public:
    // 构造函数:初始化账户
    BankAccount(string number, double initialBalance = 0.0)
        : accountNumber(number), balance(initialBalance) {
        cout << "账户 " << accountNumber << " 已创建,初始余额: " << balance << endl;
    }
    
    // 存款
    void deposit(double amount) {
        if (amount <= 0) {
            cout << "错误:存款金额必须大于0" << endl;
            return;
        }
        balance += amount;
        cout << "存入 " << amount << ",新余额: " << balance << endl;
    }
    
    // 取款
    bool withdraw(double amount) {
        if (amount <= 0) {
            cout << "错误:取款金额必须大于0" << endl;
            return false;
        }
        if (balance < amount) {
            cout << "错误:余额不足,当前余额: " << balance << endl;
            return false;
        }
        balance -= amount;
        cout << "取出 " << amount << ",新余额: " << balance << endl;
        return true;
    }
    
    // 获取余额
    double getBalance() const {
        return balance;
    }
    
    // 显示账户信息
    void display() const {
        cout << "账户: " << accountNumber << ", 余额: " << balance << endl;
    }
};

int main() {
    // 创建账户
    BankAccount account1("ACCT-001", 1000.0);
    account1.display();
    
    // 存款
    account1.deposit(500.0);
    
    // 取款
    account1.withdraw(200.0);
    
    // 尝试非法操作(无法直接访问私有成员)
    // account1.balance = -10000; // 编译错误!无法访问私有成员
    
    cout << "当前余额: " << account1.getBalance() << endl;
    
    return 0;
}

类域与成员函数

当你在类内定义函数时,编译器会为每个成员函数隐式添加一个this指针作为第一个参数。这个指针指向调用函数的对象本身,让我们能在函数内部访问对象的成员变量。

代码语言:javascript
复制
class Rectangle {
private:
    int width, height;
    
public:
    void setSize(int width, int height) {
        // this->width 指成员变量,width指参数
        this->width = width;
        this->height = height;
    }
};

this指针是C++实现面向对象的关键机制,它让我们可以在同一个类的多个对象之间区分各自的成员变量。当你调用obj.method()时,编译器将其转换为method(&obj),隐式传递对象地址。

易错点警告:如果你尝试通过空指针调用成员函数,当函数内部使用了成员变量时会导致程序崩溃。但如果成员函数不访问任何成员变量,空指针调用可能"侥幸"成功,这是危险的未定义行为!

构造函数:对象的"出生证明"

当你创建对象时,常常需要初始化其成员变量。C++提供了构造函数——一种特殊成员函数,在对象创建时自动调用:

代码语言:javascript
复制
class Car {
private:
    string brand;
    int year;
    double mileage;
    
public:
    // 构造函数:无返回值,函数名与类名相同
    Car(string b, int y, double m) {
        brand = b;
        year = y;
        mileage = m;
    }
};

构造函数的关键特性:

  • 无返回类型(连void都不需要)
  • 函数名必须与类名相同
  • 可以重载(多个不同参数列表的构造函数)
  • 如果不定义任何构造函数,编译器会自动生成一个默认构造函数(无参)
  • 一旦定义了任何构造函数,编译器就不再自动生成默认构造函数

代码演示:汽车类,展示构造函数的使用

代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;

class Car {
private:
    string brand;
    int year;
    double mileage;
    
public:
    // 带参数的构造函数
    Car(string b, int y, double m) {
        brand = b;
        year = y;
        mileage = m;
        cout << "新车 " << brand << " 出厂啦!" << endl;
    }
    
    // 无参构造函数(默认构造函数)
    Car() {
        brand = "未知品牌";
        year = 2023;
        mileage = 0.0;
        cout << "一辆未知品牌的新车出厂了!" << endl;
    }
    
    // 显示汽车信息
    void displayInfo() {
        cout << "品牌: " << brand 
             << ", 年份: " << year 
             << ", 里程: " << mileage << "万公里" << endl;
    }
    
    // 行驶指定里程
    void drive(double distance) {
        if (distance < 0) {
            cout << "错误:行驶距离不能为负数" << endl;
            return;
        }
        mileage += distance;
        cout << brand << " 行驶了 " << distance << " 万公里,总里程: " << mileage << " 万公里" << endl;
    }
};

int main() {
    // 调用带参数构造函数
    Car myCar("Toyota", 2022, 1.5);
    myCar.displayInfo();
    myCar.drive(0.3);
    
    // 调用默认构造函数
    Car newCar;
    newCar.displayInfo();
    
    return 0;
}

C与C++实现对比:封装的力量

为了真正理解封装的价值,让我们对比C语言和C++实现相同功能的代码。假设我们要实现一个栈数据结构:

C语言实现

代码语言:javascript
复制
typedef struct Stack {
    int* array;
    size_t top;
    size_t capacity;
} Stack;

void StackInit(Stack* ps, int capacity);
void StackPush(Stack* ps, int value);
int StackPop(Stack* ps);
// 还需要更多函数...

C++实现:

代码语言:javascript
复制
class Stack {
private:
    int* array;
    size_t top;
    size_t capacity;
    
public:
    Stack(int capacity = 4);  // 构造函数替代Init
    void push(int value);
    int pop();  // 自动检查栈是否为空
    ~Stack();  // 析构函数自动清理资源
};

C++的优势显而易见:

  • 资源自动管理:构造函数初始化资源,析构函数自动清理
  • 接口清晰:所有操作都通过成员函数调用,无需传递对象指针
  • 安全性高:内部数据被保护,外部无法直接修改
  • 代码简洁:减少参数传递,提高可读性

小忠告:在C语言中,我经常忘记调用StackDestroy释放内存,导致内存泄漏。转到C++后,这种问题几乎消失了——析构函数会在对象销毁时自动调用,像一个尽职的管家,确保离开前关好所有门窗。

对象大小与内存布局

你可能会好奇:对象在内存中占多大空间?哪些成员会被存储在对象中?

  • 对象中只存储非静态成员变量,不存储成员函数
  • 成员函数共享一份代码,存储在代码段(全局唯一)
  • 对象大小遵循内存对齐规则
代码语言:javascript
复制
class EmptyClass {
    // 即使没有成员变量,空类的大小也是1字节
};

class WithMembers {
private:
    char c;    // 1字节
    int i;     // 4字节
    double d;  // 8字节
    // 总大小不一定是1+4+8=13,需考虑内存对齐
};

C++规定,即使是空类,sizeof也会返回1。这是因为每个对象必须有独一无二的地址,就像即使空房间也得有门牌号一样。

小结与预告

今天我们迈出了面向对象编程的第一步:

  • class是数据与行为的封装体,通过public/private控制访问权限
  • this指针是成员函数的隐式参数,指向调用函数的对象
  • 构造函数在对象创建时自动调用,负责初始化成员变量
  • 封装使代码更安全、更易维护,对象像有自我意识的实体

当你把数据和操作数据的方法捆绑在一起,代码不再是散落的零件,而变成了有生命力的对象。正如一栋设计精良的大厦,每个房间都有其特定用途,且通过走廊和电梯有序连接。

思考题:如果一个类既没有定义构造函数,也没有定义析构函数,但包含一个指针成员指向动态分配的内存,会发生什么问题?

下篇预告:当类包含指针成员时,简单的对象复制会导致灾难性后果!我们将深入浅拷贝陷阱,揭秘析构函数如何安全释放资源,以及初始化列表为何是构造函数的最佳搭档。准备迎接对象生命周期的完整掌控!

下一期【C++类与对象·中】对象生命周期:拷贝、析构与资源管理

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-11-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • 从C到C++:struct的华丽蜕变
  • 封装:数据与行为的完美融合
    • 代码演示:银行账户类,展示封装的力量
  • 类域与成员函数
  • 构造函数:对象的"出生证明"
  • C与C++实现对比:封装的力量
    • 对象大小与内存布局
  • 小结与预告
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档