
想象一栋摩天大楼——建筑师不会把钢筋水泥随意堆在工地上,而是先绘制精密的蓝图,明确每个房间的功能和连接方式。在C++中,class(类)正是这样的"建筑蓝图",而对象则是根据蓝图建造的"房子"。今天,让我们一起推开面向对象编程的大门,看看如何用C++的封装思想为代码建造坚固的"大厦"。
许多同学从C语言转向C++时,最大的困惑是:为什么有了struct还要引入class?记得我第一次接触C++时,也对这种"重复造轮子"的做法感到不解。直到有一天,我尝试用C语言实现一个数据结构,才真正体会到C++设计的精妙。
在C语言中,struct仅能打包数据:
// C语言风格
struct Student {
char name[20];
int age;
float score;
};C++让struct脱胎换骨,不仅能包含数据,还能拥有"行为"(方法):
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++的设计哲学是"默认保守"——除非明确声明,否则不对外开放。
封装是面向对象的基石。它像保险箱一样保护内部数据,只通过特定"钥匙孔"(接口)与外界交互。我们用public和private访问限定符实现这一思想:
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;
}
};这种设计有两大优势:
balance,避免设置为负数等非法操作#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指针作为第一个参数。这个指针指向调用函数的对象本身,让我们能在函数内部访问对象的成员变量。
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++提供了构造函数——一种特殊成员函数,在对象创建时自动调用:
class Car {
private:
string brand;
int year;
double mileage;
public:
// 构造函数:无返回值,函数名与类名相同
Car(string b, int y, double m) {
brand = b;
year = y;
mileage = m;
}
};构造函数的关键特性:
void都不需要)代码演示:汽车类,展示构造函数的使用
#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语言实现:
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++实现:
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++后,这种问题几乎消失了——析构函数会在对象销毁时自动调用,像一个尽职的管家,确保离开前关好所有门窗。
你可能会好奇:对象在内存中占多大空间?哪些成员会被存储在对象中?
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指针是成员函数的隐式参数,指向调用函数的对象当你把数据和操作数据的方法捆绑在一起,代码不再是散落的零件,而变成了有生命力的对象。正如一栋设计精良的大厦,每个房间都有其特定用途,且通过走廊和电梯有序连接。
思考题:如果一个类既没有定义构造函数,也没有定义析构函数,但包含一个指针成员指向动态分配的内存,会发生什么问题?
下篇预告:当类包含指针成员时,简单的对象复制会导致灾难性后果!我们将深入浅拷贝陷阱,揭秘析构函数如何安全释放资源,以及初始化列表为何是构造函数的最佳搭档。准备迎接对象生命周期的完整掌控!