10.1 过程性编程与面向对象编程
10.1.1 两种编程范式的对比
过程性编程(C风格): 面向对象编程(C++):
┌─────────────────┐ ┌─────────────────────┐
│ 数据(变量) │ │ 类(Class) │
│ + │ → │ ┌────────────────┐ │
│ 函数(操作) │ │ │ 数据(属性) │ │
└─────────────────┘ │ │ 函数(方法) │ │
│ └────────────────┘ │
└─────────────────────┘
数据和操作是分离的 数据和操作封装在一起
OOP 三大核心特性:
- 封装(Encapsulation):将数据和操作绑定,隐藏实现细节
- 继承(Inheritance):从已有类派生新类,复用代码
- 多态(Polymorphism):同一接口,不同实现
10.2 类的基本概念
10.2.1 类的定义
cpp
// stock.h -- 股票类的头文件
#ifndef STOCK_H_
#define STOCK_H_
#include <string>
class Stock // 类定义
{
private: // 私有成员(外部不可直接访问)
std::string company; // 公司名称
long shares; // 持股数量
double share_val; // 每股价格
double total_val; // 总价值
void set_tot() // 私有成员函数
{
total_val = shares * share_val;
}
public: // 公有成员(外部可以访问)
// 构造函数
Stock(const std::string& co, long n, double pr);
// 默认构造函数
Stock();
// 析构函数
~Stock();
// 成员函数(方法)
void buy(long num, double price);
void sell(long num, double price);
void update(double price);
void show() const; // const成员函数:不修改对象
// 访问器(getter)
const std::string& getCompany() const { return company; }
long getShares() const { return shares; }
double getShareVal() const { return share_val; }
double getTotalVal() const { return total_val; }
};
#endif
10.2.2 类的实现
cpp
// stock.cpp -- 股票类的实现
#include <iostream>
#include "stock.h"
// 构造函数:使用初始化列表(推荐)
Stock::Stock(const std::string& co, long n, double pr)
: company(co), shares(0), share_val(0), total_val(0)
{
if (n < 0)
{
std::cout << "持股数量不能为负,设为0" << std::endl;
shares = 0;
}
else
shares = n;
share_val = pr;
set_tot();
std::cout << "构造函数:创建 " << company << " 对象" << std::endl;
}
// 默认构造函数
Stock::Stock()
: company("无名"), shares(0), share_val(0), total_val(0)
{
std::cout << "默认构造函数被调用" << std::endl;
}
// 析构函数
Stock::~Stock()
{
std::cout << "析构函数:销毁 " << company << " 对象" << std::endl;
}
// 买入股票
void Stock::buy(long num, double price)
{
if (num < 0)
{
std::cout << "买入数量不能为负" << std::endl;
return;
}
shares += num;
share_val = price;
set_tot();
}
// 卖出股票
void Stock::sell(long num, double price)
{
using namespace std;
if (num < 0)
{
cout << "卖出数量不能为负" << endl;
return;
}
if (num > shares)
{
cout << "持股不足,无法卖出" << endl;
return;
}
shares -= num;
share_val = price;
set_tot();
}
// 更新股价
void Stock::update(double price)
{
share_val = price;
set_tot();
}
// 显示信息(const成员函数)
void Stock::show() const
{
using namespace std;
cout << "公司:" << company << endl;
cout << "持股:" << shares << " 股" << endl;
cout << "股价:" << share_val << " 元/股" << endl;
cout << "总值:" << total_val << " 元" << endl;
}
10.2.3 使用类
cpp
// usestock.cpp -- 使用Stock类
#include <iostream>
#include "stock.h"
int main()
{
using namespace std;
cout << "===== 创建股票对象 =====" << endl;
// 方式1:调用有参构造函数
Stock fluffy("腾讯控股", 100, 350.0);
fluffy.show();
cout << "\n===== 买入操作 =====" << endl;
fluffy.buy(200, 360.0);
fluffy.show();
cout << "\n===== 卖出操作 =====" << endl;
fluffy.sell(50, 380.0);
fluffy.show();
cout << "\n===== 更新股价 =====" << endl;
fluffy.update(400.0);
fluffy.show();
// 方式2:默认构造函数
Stock empty;
empty.show();
// 方式3:C++11 列表初始化
Stock bayer = {"拜耳制药", 50, 120.0};
cout << "\n===== 程序结束,对象销毁 =====" << endl;
return 0;
}
输出:
===== 创建股票对象 =====
构造函数:创建 腾讯控股 对象
公司:腾讯控股
持股:100 股
股价:350 元/股
总值:35000 元
===== 买入操作 =====
公司:腾讯控股
持股:300 股
股价:360 元/股
总值:108000 元
...
===== 程序结束,对象销毁 =====
析构函数:销毁 拜耳制药 对象
析构函数:销毁 无名 对象
析构函数:销毁 腾讯控股 对象
10.3 类的访问控制
10.3.1 public、private、protected
cpp
// access_control.cpp -- 访问控制示例
#include <iostream>
#include <string>
class BankAccount
{
private:
// 私有成员:只有类的成员函数可以访问
std::string owner;
double balance;
std::string password;
// 私有辅助函数
bool validatePassword(const std::string& pwd) const
{
return pwd == password;
}
public:
// 公有成员:任何代码都可以访问
BankAccount(const std::string& name,
double initial,
const std::string& pwd)
: owner(name), balance(initial), password(pwd) {}
// 存款
void deposit(double amount)
{
if (amount > 0)
balance += amount;
}
// 取款(需要密码验证)
bool withdraw(double amount, const std::string& pwd)
{
if (!validatePassword(pwd))
{
std::cout << "密码错误!" << std::endl;
return false;
}
if (amount > balance)
{
std::cout << "余额不足!" << std::endl;
return false;
}
balance -= amount;
return true;
}
// 查询余额
double getBalance() const { return balance; }
std::string getOwner() const { return owner; }
};
int main()
{
using namespace std;
BankAccount acc("张三", 10000.0, "123456");
// ✅ 可以访问public成员
acc.deposit(5000);
cout << "余额:" << acc.getBalance() << endl; // 15000
acc.withdraw(3000, "123456");
cout << "取款后余额:" << acc.getBalance() << endl; // 12000
acc.withdraw(1000, "wrong"); // 密码错误
// ❌ 不能访问private成员
// acc.balance = 999999; // 编译错误!
// acc.password = "hack"; // 编译错误!
return 0;
}
💡 访问控制总结:
private:只有类自身的成员函数可以访问(默认)public:任何代码都可以访问protected:类自身和派生类可以访问(继承章节详述)
10.3.2 struct 与 class 的区别
cpp
// struct 默认成员是 public
struct MyStruct {
int x; // 默认 public
int y; // 默认 public
};
// class 默认成员是 private
class MyClass {
int x; // 默认 private
int y; // 默认 private
public:
int z; // 显式 public
};
int main()
{
MyStruct s;
s.x = 10; // ✅ 可以访问
MyClass c;
// c.x = 10; // ❌ 错误:x是private
c.z = 30; // ✅ 可以访问
return 0;
}
10.4 构造函数
10.4.1 构造函数的特点
cpp
// constructor_demo.cpp -- 构造函数详解
#include <iostream>
#include <string>
class Person
{
private:
std::string name;
int age;
double height;
public:
// 1. 默认构造函数(无参数)
Person()
: name("未知"), age(0), height(0.0)
{
std::cout << "默认构造函数" << std::endl;
}
// 2. 有参构造函数
Person(const std::string& n, int a, double h)
: name(n), age(a), height(h)
{
std::cout << "有参构造函数:" << name << std::endl;
}
// 3. 单参数构造函数(可用于隐式转换,加explicit防止)
explicit Person(const std::string& n)
: name(n), age(0), height(0.0)
{
std::cout << "单参数构造函数:" << name << std::endl;
}
void show() const
{
std::cout << "姓名:" << name
<< " 年龄:" << age
<< " 身高:" << height << std::endl;
}
};
int main()
{
using namespace std;
Person p1; // 默认构造函数
Person p2("李四", 25, 175.5); // 有参构造函数
Person p3(string("王五")); // 单参数构造函数
// explicit 防止隐式转换
// Person p4 = "赵六"; // ❌ 错误:explicit阻止隐式转换
Person p4 = Person(string("赵六")); // ✅ 显式转换
p1.show();
p2.show();
p3.show();
p4.show();
return 0;
}
10.4.2 成员初始化列表
cpp
// init_list.cpp -- 成员初始化列表
#include <iostream>
#include <string>
class Rectangle
{
private:
const int id; // const成员必须用初始化列表
int& ref; // 引用成员必须用初始化列表
double width;
double height;
std::string label;
public:
// 初始化列表:在函数体执行前初始化成员
// 顺序应与成员声明顺序一致
Rectangle(int i, int& r, double w, double h, const std::string& l)
: id(i), ref(r), width(w), height(h), label(l)
{
// 此时成员已经初始化完毕
std::cout << "创建矩形 #" << id << std::endl;
}
double area() const { return width * height; }
double perimeter() const { return 2 * (width + height); }
void show() const
{
std::cout << "矩形 #" << id << " [" << label << "]"
<< " 宽=" << width << " 高=" << height
<< " 面积=" << area() << std::endl;
}
};
int main()
{
int refVal = 100;
Rectangle r1(1, refVal, 5.0, 3.0, "客厅");
Rectangle r2(2, refVal, 8.0, 4.0, "卧室");
r1.show();
r2.show();
return 0;
}
💡 必须使用初始化列表的情况:
const成员变量- 引用(
&)成员变量- 没有默认构造函数的类类型成员
- 基类构造函数(继承时)
10.4.3 拷贝构造函数
cpp
// copy_constructor.cpp -- 拷贝构造函数
#include <iostream>
#include <string>
class MyString
{
private:
char* data;
int length;
public:
// 普通构造函数
MyString(const char* str = "")
{
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
std::cout << "构造:\"" << data << "\"" << std::endl;
}
// 拷贝构造函数(深拷贝)
MyString(const MyString& other)
{
length = other.length;
data = new char[length + 1]; // 分配新内存
strcpy(data, other.data); // 复制内容
std::cout << "拷贝构造:\"" << data << "\"" << std::endl;
}
// 析构函数
~MyString()
{
std::cout << "析构:\"" << data << "\"" << std::endl;
delete[] data;
}
void show() const
{
std::cout << "字符串:\"" << data << "\" 长度:" << length << std::endl;
}
};
int main()
{
MyString s1("Hello");
MyString s2 = s1; // 调用拷贝构造函数
MyString s3(s1); // 同上
s1.show();
s2.show();
s3.show();
return 0;
}
⚠️ 浅拷贝 vs 深拷贝:
- 浅拷贝(默认):复制指针值,两个对象共享同一块内存,析构时会双重释放!
- 深拷贝:分配新内存并复制内容,两个对象独立,安全。
- 含有指针成员的类必须自定义拷贝构造函数实现深拷贝。
10.5 析构函数
cpp
// destructor_demo.cpp -- 析构函数详解
#include <iostream>
#include <string>
class Resource
{
private:
std::string name;
int* data;
int size;
public:
Resource(const std::string& n, int sz)
: name(n), size(sz)
{
data = new int[size]; // 分配资源
for (int i = 0; i < size; i++)
data[i] = i * 10;
std::cout << "[" << name << "] 资源已分配(" << size << "个int)" << std::endl;
}
// 析构函数:释放资源
~Resource()
{
delete[] data; // 释放动态内存
std::cout << "[" << name << "] 资源已释放" << std::endl;
}
void show() const
{
std::cout << "[" << name << "] 数据:";
for (int i = 0; i < size; i++)
std::cout << data[i] << " ";
std::cout << std::endl;
}
};
void demoScope()
{
std::cout << "--- 进入函数 ---" << std::endl;
Resource r("函数内资源", 3); // 构造
r.show();
std::cout << "--- 离开函数 ---" << std::endl;
} // r 在这里自动析构
int main()
{
std::cout << "=== 程序开始 ===" << std::endl;
demoScope(); // 函数内的对象自动析构
std::cout << "\n--- 动态分配 ---" << std::endl;
Resource* p = new Resource("堆上资源", 5);
p->show();
delete p; // 必须手动析构!
std::cout << "\n=== 程序结束 ===" << std::endl;
return 0;
}
输出:
=== 程序开始 ===
--- 进入函数 ---
[函数内资源] 资源已分配(3个int)
[函数内资源] 数据:0 10 20
--- 离开函数 ---
[函数内资源] 资源已释放
--- 动态分配 ---
[堆上资源] 资源已分配(5个int)
[堆上资源] 数据:0 10 20 30 40
[堆上资源] 资源已释放
=== 程序结束 ===
10.6 const 成员函数
cpp
// const_member.cpp -- const成员函数
#include <iostream>
class Circle
{
private:
double radius;
public:
Circle(double r) : radius(r) {}
// const成员函数:承诺不修改对象的任何成员
double getRadius() const { return radius; }
double area() const { return 3.14159 * radius * radius; }
double perimeter() const { return 2 * 3.14159 * radius; }
// 非const成员函数:可以修改成员
void setRadius(double r)
{
if (r > 0) radius = r;
}
void show() const
{
std::cout << "半径:" << radius
<< " 面积:" << area()
<< " 周长:" << perimeter() << std::endl;
}
};
int main()
{
Circle c1(5.0);
c1.show();
c1.setRadius(8.0);
c1.show();
// const对象只能调用const成员函数
const Circle c2(3.0);
c2.show(); // ✅ const函数
c2.getRadius(); // ✅ const函数
// c2.setRadius(4.0); // ❌ 错误:const对象不能调用非const函数
return 0;
}
💡 const成员函数规则:
- 在函数声明和定义末尾加
constconst对象只能调用const成员函数const成员函数不能修改 成员变量(除非成员是mutable)- 建议:所有不修改对象的成员函数都加
const
10.7 this 指针
cpp
// this_pointer.cpp -- this指针示例
#include <iostream>
#include <string>
class Builder
{
private:
std::string name;
int age;
std::string city;
public:
Builder() : name(""), age(0), city("") {}
// 返回 *this 实现链式调用
Builder& setName(const std::string& n)
{
this->name = n; // this->name 等价于 name
return *this; // 返回当前对象的引用
}
Builder& setAge(int a)
{
age = a;
return *this;
}
Builder& setCity(const std::string& c)
{
city = c;
return *this;
}
void show() const
{
std::cout << "姓名:" << name
<< " 年龄:" << age
<< " 城市:" << city << std::endl;
}
// this用于区分同名参数和成员
void setInfo(std::string name, int age)
{
this->name = name; // this->name是成员,name是参数
this->age = age;
}
};
// 比较两个对象,返回较大的那个
class Value
{
public:
int val;
Value(int v) : val(v) {}
const Value& max(const Value& other) const
{
return (val > other.val) ? *this : other;
}
};
int main()
{
using namespace std;
// 链式调用(Builder模式)
Builder b;
b.setName("张三").setAge(25).setCity("北京"); // 链式调用
b.show();
// this用于比较
Value v1(10), v2(20);
const Value& bigger = v1.max(v2);
cout << "较大值:" << bigger.val << endl; // 20
return 0;
}
10.8 对象数组
cpp
// object_array.cpp -- 对象数组示例
#include <iostream>
#include <string>
class Student
{
private:
std::string name;
int score;
public:
// 默认构造函数(对象数组必须有)
Student() : name("未知"), score(0) {}
Student(const std::string& n, int s)
: name(n), score(s) {}
void setInfo(const std::string& n, int s)
{
name = n; score = s;
}
void show() const
{
std::cout << "姓名:" << name
<< " 成绩:" << score << std::endl;
}
int getScore() const { return score; }
const std::string& getName() const { return name; }
};
int main()
{
using namespace std;
// 方式1:默认构造函数创建数组
Student class1[3];
class1[0].setInfo("张三", 85);
class1[1].setInfo("李四", 92);
class1[2].setInfo("王五", 78);
// 方式2:初始化列表
Student class2[3] = {
{"赵六", 90},
{"钱七", 88},
{"孙八", 95}
};
cout << "班级1:" << endl;
for (int i = 0; i < 3; i++)
class1[i].show();
cout << "\n班级2:" << endl;
for (int i = 0; i < 3; i++)
class2[i].show();
// 找最高分
int best = 0;
for (int i = 1; i < 3; i++)
if (class2[i].getScore() > class2[best].getScore())
best = i;
cout << "\n最高分:" << class2[best].getName()
<< "(" << class2[best].getScore() << "分)" << endl;
// 动态对象数组
int n = 2;
Student* dynamic = new Student[n];
dynamic[0].setInfo("动态1", 80);
dynamic[1].setInfo("动态2", 85);
for (int i = 0; i < n; i++)
dynamic[i].show();
delete[] dynamic;
return 0;
}
10.9 类作用域
cpp
// class_scope.cpp -- 类作用域示例
#include <iostream>
class MyClass
{
public:
// 类作用域内的枚举(C++11推荐用enum class)
enum Color { RED, GREEN, BLUE };
enum class Size { SMALL, MEDIUM, LARGE }; // 强类型枚举
// 类内静态常量(C++11)
static const int MAX_COUNT = 100;
static constexpr double PI = 3.14159;
// 类内类型别名
using ValueType = double;
private:
Color color;
Size size;
ValueType value;
public:
MyClass(Color c, Size s, ValueType v)
: color(c), size(s), value(v) {}
void show() const
{
std::cout << "颜色:" << color
<< " 大小:" << static_cast<int>(size)
<< " 值:" << value << std::endl;
}
};
// 静态成员在类外初始化(非const静态成员)
// int MyClass::MAX_COUNT = 100; // 如果不是const
int main()
{
using namespace std;
// 访问类作用域内的枚举
MyClass obj(MyClass::RED, MyClass::Size::LARGE, 3.14);
obj.show();
cout << "MAX_COUNT = " << MyClass::MAX_COUNT << endl;
cout << "PI = " << MyClass::PI << endl;
return 0;
}
10.10 综合示例:完整的银行账户类
cpp
// bank_account.cpp -- 综合示例:银行账户类
#include <iostream>
#include <string>
#include <vector>
#include <iomanip>
class Transaction
{
public:
enum class Type { DEPOSIT, WITHDRAW, TRANSFER };
Type type;
double amount;
std::string description;
Transaction(Type t, double a, const std::string& desc)
: type(t), amount(a), description(desc) {}
void show() const
{
std::string typeStr;
switch (type)
{
case Type::DEPOSIT: typeStr = "存款"; break;
case Type::WITHDRAW: typeStr = "取款"; break;
case Type::TRANSFER: typeStr = "转账"; break;
}
std::cout << std::setw(6) << typeStr
<< " | " << std::setw(10) << amount
<< " | " << description << std::endl;
}
};
class BankAccount
{
private:
std::string owner;
std::string accountNo;
double balance;
std::vector<Transaction> history;
static int accountCounter; // 静态成员:所有对象共享
void addHistory(Transaction::Type type,
double amount,
const std::string& desc)
{
history.emplace_back(type, amount, desc);
}
public:
BankAccount(const std::string& name, double initial = 0.0)
: owner(name), balance(initial)
{
accountNo = "ACC" + std::to_string(++accountCounter);
if (initial > 0)
addHistory(Transaction::Type::DEPOSIT, initial, "开户存款");
std::cout << "账户创建:" << accountNo
<< " 户主:" << owner << std::endl;
}
~BankAccount()
{
std::cout << "账户注销:" << accountNo << std::endl;
}
// 存款
bool deposit(double amount, const std::string& desc = "存款")
{
if (amount <= 0)
{
std::cout << "存款金额必须大于0" << std::endl;
return false;
}
balance += amount;
addHistory(Transaction::Type::DEPOSIT, amount, desc);
return true;
}
// 取款
bool withdraw(double amount, const std::string& desc = "取款")
{
if (amount <= 0)
{
std::cout << "取款金额必须大于0" << std::endl;
return false;
}
if (amount > balance)
{
std::cout << "余额不足!当前余额:" << balance << std::endl;
return false;
}
balance -= amount;
addHistory(Transaction::Type::WITHDRAW, amount, desc);
return true;
}
// 转账
bool transfer(BankAccount& target, double amount)
{
std::string desc = "转账至" + target.accountNo;
if (withdraw(amount, desc))
{
target.deposit(amount, "来自" + accountNo + "的转账");
return true;
}
return false;
}
// 查询
double getBalance() const { return balance; }
std::string getOwner() const { return owner; }
std::string getAccountNo() const { return accountNo; }
// 打印账单
void printStatement() const
{
using namespace std;
cout << "\n===== 账户账单 =====" << endl;
cout << "账号:" << accountNo << " 户主:" << owner << endl;
cout << string(45, '-') << endl;
cout << setw(6) << "类型"
<< " | " << setw(10) << "金额"
<< " | " << "说明" << endl;
cout << string(45, '-') << endl;
for (const auto& t : history)
t.show();
cout << string(45, '-') << endl;
cout << "当前余额:" << fixed << setprecision(2)
<< balance << " 元" << endl;
}
static int getAccountCount() { return accountCounter; }
};
// 静态成员在类外初始化
int BankAccount::accountCounter = 0;
int main()
{
using namespace std;
cout << "===== 银行系统演示 =====" << endl;
BankAccount alice("张三", 10000.0);
BankAccount bob("李四", 5000.0);
cout << "\n--- 操作记录 ---" << endl;
alice.deposit(3000.0, "工资");
alice.withdraw(500.0, "购物");
alice.transfer(bob, 2000.0);
bob.withdraw(1000.0, "房租");
alice.printStatement();
bob.printStatement();
cout << "\n当前账户总数:" << BankAccount::getAccountCount() << endl;
return 0;
}
输出(部分):
===== 银行系统演示 =====
账户创建:ACC1 户主:张三
账户创建:ACC2 户主:李四
--- 操作记录 ---
===== 账户账单 =====
账号:ACC1 户主:张三
---------------------------------------------
类型 | 金额 | 说明
---------------------------------------------
存款 | 10000 | 开户存款
存款 | 3000 | 工资
取款 | 500 | 购物
取款 | 2000 | 转账至ACC2
---------------------------------------------
当前余额:10500.00 元
===== 账户账单 =====
账号:ACC2 户主:李四
---------------------------------------------
存款 | 5000 | 开户存款
存款 | 2000 | 来自ACC1的转账
取款 | 1000 | 房租
---------------------------------------------
当前余额:6000.00 元
当前账户总数:2
📝 第10章知识点总结
| 知识点 | 核心要点 |
|---|---|
| 类的定义 | class 关键字,包含数据成员和成员函数,默认 private |
| 访问控制 | private(类内)/ public(任意)/ protected(类内+派生类) |
| 构造函数 | 与类同名,无返回值,对象创建时自动调用,可重载 |
| 初始化列表 | 构造函数名() : 成员1(值1), 成员2(值2) {},const/引用成员必须用 |
| 析构函数 | ~类名(),对象销毁时自动调用,释放资源 |
| 拷贝构造函数 | 类名(const 类名& other),含指针成员必须深拷贝 |
| const成员函数 | 函数末尾加 const,不修改成员,const对象只能调用const函数 |
| this指针 | 指向当前对象,用于区分同名参数/成员,返回 *this 实现链式调用 |
| static成员 | 所有对象共享,类外初始化,用 类名:: 访问 |
| explicit | 防止单参数构造函数的隐式类型转换 |
| 对象数组 | 需要默认构造函数,可用初始化列表 |
| struct vs class | struct默认public,class默认private,其余相同 |