前面我们用 struct 定义过结构体,把相关的数据打包在一起。但结构体只能存数据,不能定义行为。类(class)是 C++ 面向对象编程的核心------它把数据和操作数据的函数封装在一起,形成一个「对象」。
1. 从结构体到类
1.1 结构体的局限
cpp
struct Student {
string name;
int age;
double score;
};
// 使用时,数据和操作是分离的
Student s = {"Alice", 20, 95.5};
cout << s.name << " " << s.score << endl;
// 如果要判断是否及格,需要写一个独立函数
bool isPass(const Student& s) {
return s.score >= 60;
}
问题是:isPass 和 Student 没有任何语言层面的关联,代码组织很松散。
1.2 类的定义
类把数据(成员变量)和操作(成员函数)封装在一起:
cpp
class Student {
public:
// 成员变量
string name;
int age;
double score;
// 成员函数
bool isPass() const {
return score >= 60;
}
void printInfo() const {
cout << "姓名:" << name
<< ",年龄:" << age
<< ",成绩:" << score << endl;
}
};
int main() {
Student s;
s.name = "Alice";
s.age = 20;
s.score = 95.5;
s.printInfo(); // 输出:姓名:Alice,年龄:20,成绩:95.5
cout << (s.isPass() ? "及格" : "不及格") << endl;
}
1.3 class vs struct
在 C++ 中,class 和 struct 几乎完全一样,唯一的区别是默认访问权限:
cpp
struct MyStruct {
int x; // 默认 public
};
class MyClass {
int x; // 默认 private
};
惯例:
- 只有数据、没有行为的简单结构 → 用
struct - 有封装、有行为的复杂类型 → 用
class
2. 访问控制
2.1 三种访问权限
cpp
class Account {
public:
// 公有:任何地方都能访问
string owner;
void deposit(double amount) {
balance += amount;
}
protected:
// 保护:本类和子类能访问,外部不能
string accountType;
private:
// 私有:只有本类的成员函数能访问
double balance = 0;
void internalTransfer() {
// 只有类内部能调用
}
};
| 访问权限 | 类内部 | 子类 | 外部 |
|---|---|---|---|
| public | ✅ | ✅ | ✅ |
| protected | ✅ | ✅ | ❌ |
| private | ✅ | ❌ | ❌ |
2.2 为什么要封装?
cpp
class BankAccount {
private:
double balance; // 私有:防止外部直接修改
public:
// 通过公有接口操作,可以加入验证逻辑
void deposit(double amount) {
if (amount <= 0) {
cout << "存款金额必须为正数!" << endl;
return;
}
balance += amount;
}
bool withdraw(double amount) {
if (amount > balance) {
cout << "余额不足!" << endl;
return false;
}
balance -= amount;
return true;
}
double getBalance() const {
return balance; // 只读访问
}
};
如果 balance 是 public 的,外部代码可以直接写 account.balance = -1000000,绕过所有验证。封装就是把数据藏起来,只暴露安全的操作接口。
3. 构造函数与析构函数
3.1 构造函数
构造函数在对象创建时自动调用,用于初始化成员变量:
cpp
class Student {
private:
string name;
int age;
double score;
public:
// 构造函数:与类同名,没有返回值
Student(const string& n, int a, double s) {
name = n;
age = a;
score = s;
}
void printInfo() const {
cout << name << "," << age << " 岁,成绩:" << score << endl;
}
};
int main() {
Student s("Alice", 20, 95.5); // 调用构造函数
s.printInfo();
}
3.2 初始化列表
更高效的初始化方式是使用初始化列表:
cpp
class Student {
private:
string name;
int age;
double score;
public:
// 初始化列表:在构造函数体执行之前就完成初始化
Student(const string& n, int a, double s)
: name(n), age(a), score(s) {
// 构造函数体(可以为空)
}
};
初始化列表 vs 赋值:
- 初始化列表:直接调用成员的构造函数,一步到位
- 赋值:先调用默认构造函数创建对象,再赋值,多了一步
对于 string、vector 等类类型,初始化列表效率更高。对于 int、double 等基本类型,两者没有区别。
3.3 默认构造函数
如果不提供任何参数就能调用的构造函数,就是默认构造函数:
cpp
class Point {
private:
int x, y;
public:
// 默认构造函数
Point() : x(0), y(0) {}
// 带参数的构造函数
Point(int x, int y) : x(x), y(y) {}
};
Point p1; // 调用默认构造函数,x=0, y=0
Point p2(3, 4); // 调用带参数的构造函数
如果你定义了任何构造函数,编译器就不会自动生成默认构造函数。如果你还需要默认构造函数,需要显式定义。
3.4 构造函数重载
一个类可以有多个构造函数(重载):
cpp
class Rectangle {
private:
double width, height;
public:
// 默认构造:正方形
Rectangle() : width(1), height(1) {}
// 带一个参数:正方形
Rectangle(double size) : width(size), height(size) {}
// 带两个参数:长方形
Rectangle(double w, double h) : width(w), height(h) {}
double area() const {
return width * height;
}
};
Rectangle r1; // 面积 = 1
Rectangle r2(5); // 面积 = 25(正方形)
Rectangle r3(3, 4); // 面积 = 12(长方形)
3.5 析构函数
析构函数在对象销毁时自动调用,用于释放资源:
cpp
class DynamicArray {
private:
int* data;
int size;
public:
DynamicArray(int n) : size(n) {
data = new int[n]; // 动态分配内存
cout << "构造:分配了 " << n << " 个 int 的内存" << endl;
}
~DynamicArray() { // 析构函数:波浪号 + 类名
delete[] data; // 释放内存
cout << "析构:释放了内存" << endl;
}
void set(int index, int value) {
if (index >= 0 && index < size) {
data[index] = value;
}
}
int get(int index) const {
if (index >= 0 && index < size) {
return data[index];
}
return -1;
}
};
int main() {
{
DynamicArray arr(5); // 调用构造函数
arr.set(0, 42);
cout << arr.get(0) << endl;
} // arr 离开作用域,自动调用析构函数
return 0;
}
输出:
构造:分配了 5 个 int 的内存
42
析构:释放了内存
4. this 指针
每个成员函数都有一个隐含的 this 指针,指向调用该函数的对象:
cpp
class Counter {
private:
int count;
public:
Counter(int count) : count(count) {} // 参数名和成员变量同名
// 当参数名和成员变量同名时,用 this 区分
void setCount(int count) {
this->count = count; // this->count 是成员变量,count 是参数
}
Counter& increment() {
count++;
return *this; // 返回自身引用,支持链式调用
}
void print() const {
cout << "count = " << count << endl;
}
};
int main() {
Counter c(0);
c.increment().increment().increment(); // 链式调用
c.print(); // 输出:count = 3
}
5. 静态成员
5.1 静态成员变量
静态成员变量属于类而不是某个对象,所有对象共享同一份:
cpp
class Student {
private:
string name;
static int totalCount; // 静态成员变量声明
public:
Student(const string& n) : name(n) {
totalCount++; // 每创建一个对象,计数 +1
}
~Student() {
totalCount--;
}
static int getCount() { // 静态成员函数
return totalCount;
}
};
// 静态成员变量需要在类外定义
int Student::totalCount = 0;
int main() {
Student s1("Alice");
Student s2("Bob");
cout << "学生数量:" << Student::getCount() << endl; // 输出 2
{
Student s3("Charlie");
cout << "学生数量:" << Student::getCount() << endl; // 输出 3
} // s3 销毁
cout << "学生数量:" << Student::getCount() << endl; // 输出 2
}
5.2 静态成员函数
静态成员函数没有 this 指针,只能访问静态成员变量:
cpp
class MathUtils {
public:
static int max(int a, int b) {
return a > b ? a : b;
}
static int min(int a, int b) {
return a < b ? a : b;
}
};
// 通过类名直接调用,不需要创建对象
int result = MathUtils::max(10, 20);
6. 友元
有时候你需要让某个外部函数或其他类访问本类的私有成员。friend 关键字就是为此设计的:
cpp
class Box {
private:
double width;
public:
Box(double w) : width(w) {}
// 声明友元函数
friend void printWidth(const Box& b);
};
// 友元函数可以访问私有成员
void printWidth(const Box& b) {
cout << "宽度:" << b.width << endl; // ✅ 可以访问 private
}
注意:友元破坏了封装性,应该谨慎使用。
7. 完整示例:简易银行账户
cpp
#include <iostream>
#include <string>
using namespace std;
class BankAccount {
private:
string owner;
double balance;
static int accountCount;
public:
// 构造函数
BankAccount(const string& name, double initial = 0)
: owner(name), balance(initial) {
accountCount++;
cout << "账户创建成功:" << owner << endl;
}
// 析构函数
~BankAccount() {
accountCount--;
cout << "账户注销:" << owner << endl;
}
// 存款
void deposit(double amount) {
if (amount <= 0) {
cout << "存款金额必须为正数!" << endl;
return;
}
balance += amount;
cout << owner << " 存入 " << amount << " 元,余额:" << balance << " 元" << endl;
}
// 取款
bool withdraw(double amount) {
if (amount <= 0) {
cout << "取款金额必须为正数!" << endl;
return false;
}
if (amount > balance) {
cout << "余额不足!当前余额:" << balance << " 元" << endl;
return false;
}
balance -= amount;
cout << owner << " 取出 " << amount << " 元,余额:" << balance << " 元" << endl;
return true;
}
// 转账
bool transfer(BankAccount& to, double amount) {
if (withdraw(amount)) {
to.deposit(amount);
cout << "转账成功:" << owner << " → " << to.owner << ",金额:" << amount << " 元" << endl;
return true;
}
return false;
}
// 查询余额
double getBalance() const {
return balance;
}
// 获取账户数量
static int getAccountCount() {
return accountCount;
}
// 打印信息
void printInfo() const {
cout << "账户:" << owner << ",余额:" << balance << " 元" << endl;
}
};
int BankAccount::accountCount = 0;
int main() {
BankAccount alice("Alice", 1000);
BankAccount bob("Bob", 500);
alice.deposit(200);
bob.withdraw(100);
alice.transfer(bob, 300);
cout << "\n--- 账户信息 ---" << endl;
alice.printInfo();
bob.printInfo();
cout << "总账户数:" << BankAccount::getAccountCount() << endl;
return 0;
}
输出:
账户创建成功:Alice
账户创建成功:Bob
Alice 存入 200 元,余额:1200 元
Bob 取出 100 元,余额:400 元
Alice 取出 300 元,余额:900 元
Bob 存入 300 元,余额:700 元
转账成功:Alice → Bob,金额:300 元
--- 账户信息 ---
账户:Alice,余额:900 元
账户:Bob,余额:700 元
总账户数:2
账户注销:Bob
账户注销:Alice
8. 小结
本节我们学习了 C++ 类与对象的基础:
- 类把数据和操作封装在一起,是面向对象的基本单元
- 访问控制(public、protected、private)保护数据不被随意修改
- 构造函数 在对象创建时初始化,析构函数在对象销毁时清理资源
- 初始化列表比赋值更高效
- this 指针指向当前对象,支持链式调用
- 静态成员属于类而非对象,所有对象共享
学习建议:
- 理解封装的意义:隐藏实现细节,暴露安全接口
- 养成使用初始化列表的习惯
- 如果类管理了动态资源(new),一定要写析构函数
- 不修改对象的成员函数都声明为 const
下一节我们将学习运算符重载------让你的自定义类型像内置类型一样使用运算符。