C++ 友元、异常
摘要:本文介绍了C++中的友元机制和异常处理。友元允许特定函数或类访问其他类的私有成员,包括友元函数、友元类和友元成员函数三种形式,通过代码示例展示了其语法和应用场景。异常处理部分详细讲解了try-catch-throw机制、标准异常类、自定义异常实现原理,并强调了异常安全(RAII原则)和注意事项。两种机制都需谨慎使用:友元可能破坏封装性,异常处理则需权衡性能与代码安全性。文章最后通过文件处理的
·
一、什么是友元(friend)
友元是一种特殊的关系,允许某个函数或类访问另一个类的私有(private)和保护(protected)成员。
友元不是类的成员,但拥有类的访问权限。
二、友元的类型
-
友元函数
普通函数或类外的函数,被声明为某类的 friend,可以访问该类的私有成员。 -
友元类
某个类被声明为另一个类的 friend,可以访问该类的所有私有和保护成员。 -
友元成员函数
某个类的成员函数被声明为另一个类的 friend,可以访问该类的私有和保护成员。
三、语法与代码示例
1. 友元函数
#include <iostream>
class Box {
private:
int length;
public:
Box(int l) : length(l) {}
friend void printLength(const Box& b); // 声明友元函数
};
void printLength(const Box& b) {
std::cout << "Length: " << b.length << std::endl; // 访问私有成员
}
int main() {
Box box(10);
printLength(box);
return 0;
}
2. 友元类
#include <iostream>
class Box;
class BoxHelper {
public:
void showLength(const Box& b);
};
class Box {
private:
int length;
public:
Box(int l) : length(l) {}
friend class BoxHelper; // 声明 BoxHelper 为友元类
};
void BoxHelper::showLength(const Box& b) {
std::cout << "Length: " << b.length << std::endl; // 访问私有成员
}
int main() {
Box box(20);
BoxHelper helper;
helper.showLength(box);
return 0;
}
3. 友元成员函数
#include <iostream>
class Box;
class BoxHelper {
public:
void showLength(const Box& b);
};
class Box {
private:
int length;
public:
Box(int l) : length(l) {}
friend void BoxHelper::showLength(const Box& b); // 只授权某成员函数为友元
};
void BoxHelper::showLength(const Box& b) {
std::cout << "Length: " << b.length << std::endl;
}
int main() {
Box box(30);
BoxHelper helper;
helper.showLength(box);
return 0;
}
四、底层实现原理
- 友元不是成员:友元函数/类不是该类的成员,不受类的访问控制限制。
- 编译期处理:编译器在编译时赋予友元访问权限,生成的代码可以直接访问私有成员,无需额外运行时机制。
- 不破坏封装:友元机制只在特定场景下开放访问权限,不影响类的整体封装性。
五、应用场景
- 操作符重载:如
operator<<
,需要访问私有成员但不是成员函数。 - 类间紧密协作:如数据结构的节点类和管理类。
- 调试/测试:辅助函数需要访问私有成员。
- 实现某些设计模式:如工厂模式、代理模式等。
六、注意事项
- 滥用友元会破坏封装性,应谨慎使用。
- 友元关系是单向的,A是B的友元,B不一定能访问A的私有成员。
- 友元声明可以在类的任何位置(public/private/protected),但访问权限一样。
七、补充:友元与继承
- 友元关系不会被继承,子类不会自动成为父类的友元。
八、C++ 异常机制简介
C++ 异常用于在程序运行过程中处理错误和异常情况,实现错误检测与处理的分离。异常机制基于三个关键字:
try
:用于包裹可能发生异常的代码块。throw
:用于抛出异常对象。catch
:用于捕获和处理异常。
九、基本语法和代码示例
1. 基本用法
#include <iostream>
int divide(int a, int b) {
if (b == 0)
throw "除数不能为0"; // 抛出异常(字符串字面量)
return a / b;
}
int main() {
try {
std::cout << divide(10, 0) << std::endl;
} catch (const char* msg) {
std::cout << "异常: " << msg << std::endl;
}
return 0;
}
2. 抛出标准异常对象
C++ 标准库定义了一系列异常类(如 std::exception
及其派生类):
#include <iostream>
#include <stdexcept>
int divide(int a, int b) {
if (b == 0)
throw std::runtime_error("除数不能为0");
return a / b;
}
int main() {
try {
std::cout << divide(10, 0) << std::endl;
} catch (const std::exception& e) {
std::cout << "异常: " << e.what() << std::endl;
}
return 0;
}
3. 捕获所有异常
try {
// code
} catch (...) {
std::cout << "捕获到未知异常!" << std::endl;
}
十、异常类层次结构
C++ 标准库异常类位于 <exception>
头文件:
std::exception
(基类)std::logic_error
std::invalid_argument
std::domain_error
std::length_error
std::out_of_range
std::runtime_error
std::overflow_error
std::underflow_error
std::range_error
你可以自定义异常类:
class MyException : public std::exception {
public:
const char* what() const noexcept override {
return "自定义异常";
}
};
十一、底层实现原理(简述)
- try-catch块:编译器为每个 try 块生成异常处理表。
- throw:抛出异常时,编译器会搜索匹配的 catch 块,并展开栈(调用析构函数,释放资源),直到找到匹配的处理器。
- 资源释放:保证局部对象的析构函数被调用(RAII原则)。
- 性能:异常处理机制有一定开销,通常只用于错误处理,不用于普通流程控制。
十二、应用场景
- 文件、网络、数据库等 I/O 操作失败
- 内存分配失败(
new
抛出std::bad_alloc
) - 算法或业务逻辑错误(如除零、越界)
- 标准库容器操作(如访问非法索引)
- 需要保证资源安全释放时(RAII)
十三、异常安全(Exception Safety)
- 基本保证:异常发生时,程序状态不变,不泄漏资源。
- 强保证:异常发生时,操作要么成功,要么无副作用。
- 无异常保证:承诺不会抛出异常(如
noexcept
)。
void foo() noexcept; // 承诺不抛异常
十四、注意事项
- 不建议抛出基本类型(如 int、char*),推荐抛出异常类。
- 析构函数中不建议抛异常,否则可能导致程序终止。
- 异常处理会影响性能,关键代码可考虑使用错误码而不是异常。
- C++ 异常与 C 的
setjmp/longjmp
不兼容。
十五、补充:异常与 RAII
异常发生时,局部对象会自动析构,资源安全释放:
#include <iostream>
class File {
public:
File() { std::cout << "打开文件\n"; }
~File() { std::cout << "关闭文件\n"; }
};
int main() {
try {
File f;
throw std::runtime_error("出错了");
} catch (...) {
std::cout << "异常被捕获\n";
}
// 输出顺序:打开文件 -> 关闭文件 -> 异常被捕获
}
创作不易、点赞关注、持续创作!!!
更多推荐
所有评论(0)