C++面向对象3——C++面向对象的权限、引用与指针
C++面向对象的权限
#include <iostream>using namespace std;class bank{private:double balance;int account_number;string account_name;public:void open_account(string name,double balance,int number);void deposit_money();void withdraw_money();void display_account();bank(){balance = 0;account_number = 0;account_name = ""; }
};void bank::open_account(string name, double bal, int number) {account_name = name;balance = bal;account_number = number;cout << "Account opened successfully!" << endl;display_account();
}void bank::deposit_money() {double amount;cout << "Enter amount to deposit: ";cin >> amount;balance += amount;cout << "Deposit successful!" << endl;display_account();
}void bank::withdraw_money() {double amount;cout << "Enter amount to withdraw: ";cin >> amount;if (amount > balance) {cout << "Insufficient balance!" << endl;} else {balance -= amount;cout << "Withdrawal successful!" << endl;}display_account();
}void bank::display_account() {cout << "Account Name: " << account_name << endl;cout << "Account Number: " << account_number << endl;cout << "Balance: " << balance << endl;
}int main()
{bank b;b.open_account("John Doe", 1000.0, 123456);b.deposit_money();b.withdraw_money(); b.display_account();return 0;
}
这段代码通过访问权限(private
和 public
)实现了面向对象编程的封装特性。以下是详细分析:
1. 访问权限设计
class bank{private:double balance; // 账户余额int account_number; // 账户号码string account_name; // 账户名public:// 公有方法(接口)void open_account(...);void deposit_money();void withdraw_money();void display_account();bank(); // 默认构造函数
};
private
成员:balance
、account_number
、account_name
是核心数据,被声明为private
。- 外部无法直接访问或修改这些数据,只能通过类的公有方法操作。
public
方法:- 提供了用户与账户交互的唯一途径(如开户、存款、取款)。
- 确保数据的完整性和安全性(例如,取款时检查余额)。
2. 封装的优势
① 数据保护
- 外部无法直接修改
balance
:bank b; b.balance = 1000000; // 错误!private 成员不可访问
- 必须通过
deposit_money()
或withdraw_money()
操作余额,确保业务逻辑(如余额检查)被执行。
② 实现细节隐藏
- 用户不需要知道
balance
如何存储或计算,只需调用display_account()
查看信息。 - 未来若需要修改内部实现(如改用
long long
存储金额),只需调整类的内部代码,外部调用无需改变。
3. 方法的访问权限控制
构造函数 bank()
- 初始化所有
private
成员为默认值,防止未初始化的数据被使用。
open_account(...)
- 负责安全地初始化账户信息,避免外部直接设置
private
成员。 - 例如:
account_number
可能需要遵循特定格式或唯一性规则,通过方法封装可确保规则被执行。
deposit_money()
- 验证存款金额的合法性(虽然当前代码未检查负数,但可在此处添加逻辑)。
- 直接修改
balance
会绕过验证逻辑。
withdraw_money()
- 关键业务逻辑:检查余额是否充足。
- 若允许外部直接修改
balance
,可能导致透支(如balance = -1000
)。
4. 潜在改进建议
① 增加只读访问器(Getter)
public:double get_balance() const { return balance; }int get_account_number() const { return account_number; }string get_account_name() const { return account_name; }
- 优势:允许外部读取数据但禁止修改,增强封装性。
const
修饰:确保方法不修改对象状态。
② 限制构造函数参数
public:bank(string name, double initial_balance, int number) {open_account(name, initial_balance, number);}
- 优势:强制用户在创建对象时提供必要信息,避免创建无效账户。
③ 异常处理
void withdraw_money() {double amount;cout << "Enter amount to withdraw: ";cin >> amount;if (amount <= 0 || amount > balance) {throw invalid_argument("Invalid withdrawal amount"); // 抛出异常}balance -= amount;cout << "Withdrawal successful!" << endl;display_account();
}
- 优势:更健壮的错误处理,避免程序崩溃。
5. 总结
- 封装原则:通过
private
数据和public
方法,确保数据只能通过受控接口访问。 - 安全性:防止外部直接操作敏感数据(如余额),降低错误和恶意修改的风险。
- 可维护性:内部实现可以独立修改,不影响外部代码。
该代码通过合理的访问权限设计,实现了一个安全且易于使用的银行账户类。
引用与指针的区别
在C++中,引用(Reference) 和 指针(Pointer) 都用于间接访问对象,但它们的语法、语义和使用场景有本质区别。以下是核心辨析:
1. 基本语法与定义
指针
int x = 10;
int* ptr = &x; // 指针存储变量的地址
*ptr = 20; // 通过解引用修改x的值
- 本质:变量,存储内存地址。
- 声明:使用
*
(如int* ptr
)。 - 操作:通过
&
取地址,*
解引用。
引用
int x = 10;
int& ref = x; // 引用是x的别名
ref = 20; // 直接修改x的值
- 本质:对象的别名,与原变量绑定到同一内存地址。
- 声明:使用
&
(如int& ref
)。 - 操作:直接使用引用名,无需显式解引用。
2. 核心区别
特性 | 指针 | 引用 |
---|---|---|
是否可空 | 可以为 nullptr (空指针) | 必须初始化,不能为 nullptr |
是否可变 | 可以指向不同对象(可重新赋值) | 一旦初始化,不能更改绑定对象 |
内存占用 | 通常占4/8字节(取决于架构) | 不占额外内存(编译器实现可能不同) |
解引用语法 | 需要显式使用 * (如 *ptr ) | 直接使用引用名(如 ref ) |
多级间接性 | 支持多级指针(如 int** pp ) | 不支持多级引用 |
自增/自减语义 | 移动指针地址(如 ptr++ ) | 修改绑定对象的值(如 ref++ ) |
3. 使用场景对比
指针的典型场景
- 动态内存分配:
int* ptr = new int(10); // 必须用指针管理堆内存 delete ptr;
- 需要重新指向不同对象:
void swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp; }
- 允许空值语义:
void process(int* ptr) {if (ptr != nullptr) { // 必须检查空指针// ...} }
引用的典型场景
- 函数参数传递(避免拷贝):
void print(const std::string& str) { // 常量引用避免拷贝std::cout << str << std::endl; }
- 操作符重载(如
=
、[]
):std::vector<int> vec = {1, 2, 3}; vec[0] = 10; // operator[] 返回引用,允许修改原对象
- 范围for循环:
for (int& num : vec) { // 引用允许修改容器元素num *= 2; }
4. 安全性与陷阱
指针的风险
- 空指针解引用:
int* ptr = nullptr; *ptr = 10; // 运行时崩溃!
- 野指针:
int* ptr = new int(10); delete ptr; *ptr = 20; // 访问已释放的内存
- 内存泄漏:
void leak() {int* ptr = new int(10);// 未delete ptr }
引用的风险
- 悬挂引用:
int& ref = *new int(10); // 引用堆对象 delete &ref; ref = 20; // 访问已释放的内存
- 隐式生命周期延长(临时对象):
const std::string& ref = "hello"; // 临时对象的引用 // 语句结束后临时对象销毁,ref变为悬挂引用
5. 何时选择指针或引用?
场景 | 优先选择指针 | 优先选择引用 |
---|---|---|
需要表示“无对象”状态(空值) | ✅ | ❌(引用必须绑定对象) |
需要重新指向不同对象 | ✅ | ❌(引用不可变) |
实现数据结构(如链表、树) | ✅(节点通过指针连接) | ❌ |
函数参数需要默认值 | ✅(如 void func(int* p = nullptr) ) | ❌ |
重载操作符(如 operator[] ) | ❌ | ✅(返回引用允许赋值) |
避免拷贝大型对象 | ✅(指针传递) | ✅(引用传递) |
6. C++11 后的改进
-
智能指针(如
std::unique_ptr
、std::shared_ptr
):std::unique_ptr<int> ptr = std::make_unique<int>(10); // 自动内存管理
- 优势:避免手动
delete
,防止内存泄漏。
- 优势:避免手动
-
右值引用(
&&
):void func(std::string&& str) { // 接收右值引用(临时对象)// 可用于移动语义 }
- 优势:实现高效的资源转移(如
std::vector
的push_back
)。
- 优势:实现高效的资源转移(如
总结
- 指针:灵活但危险,适合动态内存管理、需要空值或重新赋值的场景。
- 引用:安全且简洁,适合作为函数参数或返回值,避免拷贝并提供自然的语法。
合理选择引用和指针,可以提高代码的安全性、可读性和性能。
一个使用引用修改函数值的例子
#include <iostream>
using namespace std;int a[4]={1,2,3,4};int& set_a(int i){return a[i];
}
int main()
{int b = set_a(0);int& c = set_a(1);int d = 9;set_a(3) = d;cout << "b: " << b << endl;cout << "c: " << c << endl;for (int i = 0; i < 4; i++){cout << a[i] <<endl;}c=10;cout << "c: " << c << endl;for (int i = 0; i < 4; i++){cout << a[i] <<endl;}int* p;p=&c;*p=999;cout << "p: " << *p << endl;for (int i = 0; i < 4; i++){cout << a[i] <<endl;}return 0;
}
这段代码通过引用(Reference)直接操作数组元素,展示了引用的强大之处。以下是详细分析:
1. 核心函数 set_a(int i)
int& set_a(int i){return a[i]; // 返回全局数组a的第i个元素的引用
}
- 功能:返回数组
a
中索引为i
的元素的引用(即内存地址的别名)。 - 关键:通过返回引用,外部代码可直接修改原数组元素。
2. 变量初始化与引用绑定
① int b = set_a(0);
set_a(0)
返回a[0]
的引用(值为1
)。b
是值拷贝:b
被初始化为1
,后续修改a[0]
不会影响b
。
② int& c = set_a(1);
set_a(1)
返回a[1]
的引用。c
直接绑定到a[1]
:c
是a[1]
的别名,修改c
等同于修改a[1]
。
3. 通过引用修改数组元素
① set_a(3) = d;
- 等价于
a[3] = d
(即a[3] = 9
)。 - 输出验证:
b: 1 // b保持初始值1 c: 2 // c绑定到a[1],当前值为2 a[0]: 1 a[1]: 2 a[2]: 3 a[3]: 9 // 被修改为9
② c = 10;
- 直接修改
a[1]
(因为c
是a[1]
的引用)。 - 输出验证:
c: 10 // c的值变为10 a[0]: 1 a[1]: 10 // 被修改为10 a[2]: 3 a[3]: 9
③ 通过指针修改 c
int* p = &c; // p指向c(即a[1]的地址)
*p = 999; // 修改p指向的内存(即a[1])
- 输出验证:
p: 999 // p指向的值为999 a[0]: 1 a[1]: 999 // 被修改为999 a[2]: 3 a[3]: 9
4. 内存与引用关系图
全局数组 a:
+---+-------+---+---+
| 1 |2→999 | 3 | 9 |
+---+-------+---+---+↑ ↑ ↑| ↓ |b c,p d=9
b
:独立变量,值为1
(a[0]
的初始值)。c
和p
:均指向a[1]
的内存地址,修改它们会直接影响a[1]
。
5. 潜在风险与注意事项
① 越界访问
set_a(10) = 5; // 访问a[10],导致未定义行为!
- 原因:
set_a
未检查索引合法性,可能访问非法内存。
② 悬挂引用
int& ref = set_a(0); // 绑定到a[0]
// 如果后续a被销毁或重新分配,ref将变为悬挂引用
6. 改进建议
① 添加边界检查
int& set_a(int i) {if (i < 0 || i >= 4) {throw std::out_of_range("Index out of range");}return a[i];
}
② 使用更安全的容器(如 std::vector
)
#include <vector>
std::vector<int> a = {1, 2, 3, 4};int& set_a(int i) {return a.at(i); // at()方法会自动检查边界
}
7. 关键知识点
- 引用的本质:对象的别名,与原对象共享同一内存地址。
- 返回引用的函数:可用于直接修改目标对象(如数组元素、类成员)。
- 引用与指针的关系:引用一旦绑定无法更改,而指针可重新指向其他对象。
通过引用,代码可以实现简洁且高效的内存操作,但需谨慎处理边界和生命周期问题。