C++基础(三)
·
1. 多态
• 多态基本的概念(polymorphic):
• 同一个行为具有多个不同表现形式或形态的能力。通俗讲就是:用相同的接口访问不同的实现,或者说允许使用统一的接口来处理不同的子类对象。
• 多态的类型
• 编译时多态(静态多态):静态多态是指程序在编译阶段就确定要调用哪个函数或使用哪个实现。
• 实现方式:
• 函数重载:同一作用域内,函数名相同但参数列表不同。
• 运算符重载:给已有运算符赋予新的功能。
• 模板:使用泛型编程。
• 运行时多态(动态多态):动态多态是指程序在运行时才确定要调用哪个函数实现。
• 必要条件:
• 通过虚函数和继承实现。
• 使用基类指针或引用操作调用子类的函数。
1.1 如何实现多态
• 使用虚函数(Virtual Function):
• 我们在基类中定义一个虚函数,这个函数可以在任何子类中被“重写“。
• 使用关键字virtual来声明。
• 创建派生类并重写虚函数:
• 在子类中,我们提供该虚函数的具体实现。
• 通过基类的引用或指针调用虚函数:
• 当我们使用基类类型的指针或引用来调用虚函数时,实际调用的是该对象的实际类型(子类)中的函数版本。
1.2 抽象类
• 抽象类基本概念:抽象类是至少包含一个纯虚函数的类,它不能被实例化,只能作为其他类的基类。
• 抽象类的特点:
• 至少包含一个纯虚函数:
• 抽象类至少有一个纯虚函数。这是一种特殊的虚函数,在抽象类中没有具体实现,而是留给派生类去实现。
• 纯虚函数的声明方式是在函数声明的末尾加上 = 0 。
• 不能直接实例化:
• 由于抽象类不完整,所以不能直接创建它的对象。就像不能直接使用“交通工具”的概念去任何地方,你需要一个具体的交通工具。
• 用于提供基础结构:
• 抽象类的主要目的是为派生类提供一个共同的基础结构,确保所有派生类都有一致的接口和行为。
virtual void 函数名() = 0;//定义纯虚函数
• 核心要点:
- 定义:包含至少一个纯虚函数 = 0
- 不能实例化:不能创建抽象类的对象
- 派生类必须实现:派生类必须实现所有纯虚函数,否则也是抽象类
- 支持多态:通过基类指针/引用可以实现运行时多态
1.3 接口
• 在C++中,虽然没有像其他编程语言那样(比如 Java 中的接口Interface)直接定义接口的关键字,但可以通过抽象类和纯虚函数的方式来实现接口的概念。
• 接口通常用于定义类应该实现的方法(声明方法),但不提供具体实现。这样的实现方式允许多个类共享相同的接口,同时让每个类根据需要去实现这些接口。
• 一个类作为接口可以通过以下步骤来实现:
1. 定义抽象类:创建一个包含纯虚函数的抽象类,这些函数构成了接口的一部分。这些函数在抽象类中只有声明而没有具体的实现。
2. 子类实现接口:子类继承抽象类,并实现其中的纯虚函数。
• 抽象类和接口的区别:
| 特性 | 抽象类 | 接口(纯抽象类) |
|
纯虚函数 |
可以有 |
必须全部是 |
|
普通虚函数 |
可以有 |
不应该有 |
|
普通函数 |
可以有 |
不应该有 |
|
成员变量 |
可以有 |
不应该有 |
|
构造函数 |
可以有 |
不应该有 |
|
用途 |
代码复用 + 接口定义 |
纯接口定义 |
|
继承关系 |
"是一个"关系 |
“具有能力”关系 |
2. 友元
2.1 什么是友元
• 在C++中,友元(friend)关键字用于给特定的外部函数或类访问某类的私有(private)和保护 (protected)成员的权限。友元关系不是相互的,也不是可继承的。这意味着被声明为友元的函数或类可以访问原始类的私有和保护成员,但原始类不能访问友元的私有成员,除非它们也被声明为友元。
• 友元主要有三种类型:
1. 友元函数:一个普通函数(不是类的成员函数)可以被声明为一个类的友元函数。它可以访问该类的所有成员(公有、保护和私有)。
2. 友元类:一个类可以被声明为另一个类的友元。这意味着友元类的所有成员函数都可以访问原始类的保护和私有成员。
3. 友元成员函数:一个类的成员函数可以被声明为另一个类的友元。这意味着该成员函数可以访问另一个类的保护和私有成员。
• 为什么使用友元
• 友元提供了一种机制,允许某些外部函数或类直接访问类的私有或保护成员。
2.2 友元函数
• 示例:
• 下面是一个展示友元函数的例子:在这个例子中, showValue 函数被声明为 MyClass 的友元函数,它可以访问 MyClass 的私有成员 value 。
#include <iostream>
using namespace std;
class MyClass{
private:
int value;
public:
MyClass(int v) : value(v){}
//声明友元函数
friend void showValue(MyClass& obj);
};
//友元函数的定义
void showValue(MyClass& obj){
cout << "value of MyClass: " << obj.value << endl;
}
int main()
{
MyClass m1(42);
showValue(m1); //可以访问MyClass的私有成员
return 0;
}
• 友元函数实现运算符重载,如重载输入输出运算符( << 和 >> )。
• 在C++中,重载输入输出运算符( << 和 >> )通常需要使用友元函数。这是因为输入输出运算符通常需要访问类的私有成员,同时又需要符合标准库中流对象(如 std::ostream 和 std::istream)的用法。
• 示例
• 假设我们有一个 Point 类,表示二维空间中的一个点,我们想要重载 << 和 >> 运算符以输出和输入点的坐标。
#include <iostream>
using namespace std;
class Point{
private:
int x,y;
public:
Point() : x(0),y(0){}
Point(int x,int y) : x(x),y(y){}
//重载 << 运算符(输出)
friend ostream& operator << (ostream& os,Point point){
os << "(" << point.x << "," << point.y << ")";
return os;
}
//重载 >> 运算符(输入)
friend istream& operator >> (istream& is,Point& point){
is >> point.x >> point.y;
return is;
}
};
int main()
{
Point p1(1,2);
//输出
cout << "Point p1:" << p1 << endl;
//输入
Point p2;
cout << "Input your Point:";
cin >> p2;
cout << "Point p2:" << p2 << endl;
return 0;
}
• 在这个例子中:
• << 运算符用于将 Point 对象的内容输出到输出流(如 std::cout )。
• >> 运算符用于从输入流(如 std::cin )中读取值到 Point 对象。
• 这两个运算符都被声明为 Point 类的友元函数,以便它们可以访问类的私有成员变量x和y。
• 注意事项
• 虽然友元提供了强大的功能,但也应谨慎使用:
• 过度使用友元可能会破坏封装,使得代码难以维护和理解。
• 保持友元的数量最小,仅在确实需要时使用,以维持良好的封装性。
2.3 友元类
• 在C++中,一个类可以被声明为另一个类的友元类(friend class)。当一个类被声明为另一个类的友元类时,它可以访问后者的所有成员,包括私有(private)和保护(protected)成员。
• 友元类是一种强大的特性,它允许在保持封装性的同时,提供对类内部的深入访问。然而,由于它允许对另一个类的内部细节进行直接操作,因此应谨慎使用,以免破坏封装性。
• 示例:假设我们有两个类 ClassA 和 ClassB 。我们可以使ClassB成为 ClassA 的友元类,这样 ClassB就可以访问 ClassA 的所有成员,包括私有成员。
#include <iostream>
using namespace std;
class ClassA{
private:
int value;
public:
ClassA(int v) : value(v){}
// 声明 ClassB 为友元类
friend class ClassB;
};
class ClassB{
public:
void showValue(ClassA& a){
// 可以访问 ClassA 的私有成员
cout << "Value of ClassA: " << a.value << endl;
}
};
int main()
{
ClassA a(100);
ClassB b;
b.showValue(a);
return 0;
}
• 在这个示例中,ClassB能够访问ClassA 的私有成员 value ,因为它被声明为 ClassA 的友元类。
• 注意事项:
• 谨慎使用:友元类应该谨慎使用,因为它们可能会破坏对象的封装性和隐藏性。
• 非相互性:如果 ClassA 是 ClassB 的友元类,这并不意味着 ClassB 自动成为 ClassA 的友元类。友元关系是单向的。
• 非继承性:友元关系不会被继承。如果类 C 继承自类 B ,并且 B 是 A 的友元类,那么 C 不自动成为 A 的友元类,除非 A 明确声明 C 为友元类。
• 友元类提供了一种机制,允许其他类访问私有成员,但应在不破坏封装性的前提下谨慎使用。
2.4 友元成员函数
• 在C++中,除了可以将整个类或单独的函数声明为友元外,还可以将特定类中的某个成员函数单独声明为另一个类的友元。这样做可以让这个成员函数访问另一个类的所有成员(包括私有和受保护的成员), 而无需将整个类声明为友元,从而提供了更细粒度的访问控制。
• 示例:
假设我们有两个类 ClassA 和 ClassB ,我们希望 ClassB 的一个特定成员函数 showValue 能够访问ClassA 的私有成员。我们可以将 ClassB 的 showValue 函数声明为 ClassA 的友元。
#include <iostream>
using namespace std;
class ClassA;//前向声明
class ClassB{
public:
void showValue(ClassA& a);
};
class ClassA{
private:
int value;
public:
ClassA(int v) : value(v){}
// 声明 ClassB 的成员函数为友元
friend void ClassB::showValue(ClassA& a);
};
//这个函数实现一定要等ClassA类后面,不然会报错
void ClassB::showValue(ClassA &a){
// 访问 ClassA 的私有成员
cout << "Value of ClassA: " << a.value << endl;
}
int main()
{
ClassA a(100);
ClassB b;
b.showValue(a);
return 0;
}
• 在这个示例中, ClassB::showValue 成为 ClassA 的友元函数。这意味着 showValue 可以访问
ClassA 的私有成员 value 。注意,要提供了 ClassA的前向声明,这是必须的。
• 注意事项:
• 精确控制:与将整个类声明为友元相比,将特定成员函数声明为友元可以更精确地控制访问权限。
• 破坏封装:即使是单个成员函数,过度使用友元也可能破坏类的封装性。应当谨慎使用。
• 前向声明:在一个类中声明另一个类的成员函数为友元时,可能需要前向声明另一个类,特别是在两个类相互引用对方的成员函数时。
• 友元成员函数是C++中一种强大的特性,允许开发者在保持类封装性的同时提供必要的访问权限。然而, 应当谨慎使用,以保持代码的清晰性和维护性。
3. 模板(泛型)
3.1 类模板
• 在C++中,模板(Template)是一种通用的编程工具,允许程序员编写泛型代码,使得类或函数能够适用于多种不同的数据类型而不需要重复编写相似的代码。C++ 提供了两种主要类型的模板:类模板和函数模板。
• 类模板(Class Templates):
• 类模板允许定义通用的类,其中某些类型可以作为参数。这样的类可以处理不同类型的数据,而不需要为每个数据类型编写单独的类。
#include <iostream>
#include <string>
using namespace std;
// 定义一个通用的类模板
template <typename T>
class MyTemplate{
private:
T data;
public:
MyTemplate(T d) : data(d){}
T getData(){
return data;
}
};
int main()
{
// 使用类模板创建对象
MyTemplate<int> intT(5);
MyTemplate<string> stringT("hello");
// 调用类模板中的函数
cout << intT.getData() << endl; // 输出 5
cout << stringT.getData() << endl; // 输出 Hello
return 0;
}
3.2 函数模板
• 函数模板允许编写通用的函数,可以处理多种不同类型的数据。
#include <iostream>
using namespace std;
template <typename T>
// 定义一个通用的函数模板
T add(T a,T b){//可以处理不同类型的加法
return a + b;
}
int main()
{
// 使用函数模板调用通用函数
double resultDouble = add(3.4,2.5);
int resultInt = add(1,2);
// 输出函数模板调用结果
std::cout << "Result 1: " << resultDouble << std::endl;
std::cout << "Result 2: " << resultInt << std::endl;
return 0;
}
• 模板提供了一种在编写代码时更具通用性的方法,能够处理不同类型的数据而无需为每种类型编写特定的函数或类。通过使用模板,可以提高代码的重用性和可维护性
3.3 模板特化
• 模板特化(Template Specialization)是C++中模板的一个概念,它允许针对特定的数据类型或特定的模板参数提供定制化的实现。模板特化允许您为模板提供一个特殊的实现,以覆盖或扩展默认的模板行为。
• 有两种类型的模板特化:完全特化(Full Specialization)和部分特化(Partial Specialization)。
• 完全特化(Full Specialization):
• 完全特化是对模板中的所有模板参数都进行特化的情况。在完全特化中,模板参数被指定为特定的类型,为特定的类型提供独特的实现。
• 以下是一个示例,演示了对模板函数的完全特化:
#include <iostream>
#include <string.h>
using namespace std;
// 定义一个通用的模板函数
template <typename T>
T maximum(T a,T b){
return (a > b) ? a : b;
}
// 对模板函数进行完全特化,针对 char* 类型
template <>
const char * maximum<const char *>(const char * a,const char * b){
return strcmp(a,b) > 0 ? a : b;
}
int main()
{
int intMax = maximum(3, 5); //调用模板函数,通用模板:适合所有类型
std::cout << "Maximum of integers: " << intMax << std::endl;
const char* charMax = maximum("apple", "orange"); //调用特化的函数,完全特化
std::cout << "Maximum of strings: " << charMax << std::endl;
return 0;
}
• 在这个示例中, maximum是一个模板函数,可以比较不同类型的数据,并返回较大的值。然后,为了特化针对 const char* 类型,我们提供了一个特化版本的 maximum 函数,该函数使用 strcmp 函数来比较字符串,并返回较大的字符串。
• 部分特化(Partial Specialization):
• 部分特化是指对模板中的部分参数进行特化,允许更具体地特化某些模板参数。这通常在模板类中使用。
• 以下是一个示例,展示了对模板类的部分特化:
#include <iostream>
using namespace std;
// 定义一个通用的模板类
template <typename T , typename U>
class MyTemplate{
public:
void display() {
cout << "Generic Display" << endl;
}
};
//对模板类进行部分特化,针对特定的类型组合
template <typename T>
class MyTemplate<T,int>{
public:
void display() {
cout << "Specialized Display for T and int" << endl;
}
};
int main()
{
MyTemplate<float,double> obj1;
obj1.display();
MyTemplate<float,int> obj2;
obj2.display();
//obj1 是普通情况,obj2 触发了"当第二个类型是int"的特殊处理规则
return 0;
}
• 在这个示例中, MyTemplate 是一个模板类,然后我们对 MyTemplate 进行部分特化,当第二个模板参数是 int 类型时,提供了特殊的实现。因此,对于特定的类型组合,我们可以提供自定义的实现。
4. 标准模板库STL
4.1 vector
• 在C++的标准模板库(STL)中, std::vector 是一种动态数组容器。它提供了动态大小的数组功能, 能够在运行时根据需要自动调整大小,允许在其尾部高效地进行元素的插入和删除操作。
• 特点:
1. 动态大小:std::vector允许动态增加或减少其大小。它会自动处理内存分配和释放,无需手动
管理内存。
2. 随机访问: 支持使用索引进行快速的随机访问,因为它底层基于连续的内存块。
3. 尾部插入/删除: 在数组的尾部插入或删除元素的操作非常高效,时间复杂度为常数时。
4. 连续内存存储: std::vector 中的元素在内存中是连续存储的,这有助于提高访问速度和缓存利
用率。
5. 动态增长策略: 当向 std::vector 添加元素时,如果当前容量不足,它会动态地重新分配更大的
内存空间,并将现有元素复制到新的内存位置。这种动态增长策略确保了插入操作的高效性。
• std::vector 的缺点是,在执行插入或删除操作时,如果不是在容器的末尾进行,可能会导致较高的时间复杂度,因为需要移动后续元素。
• 以下是一个使用 C++ STL 中的 vector 容器的简单示例:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
//创建一个vector容器
vector<int> myVector;
//向vector容器尾部加入数据
myVector.push_back(3);
myVector.push_back(7);
myVector.push_back(12);
// 使用迭代器遍历 vector 容器并输出其中的元素
// 在 C++ 中,auto 是一个关键字,用于声明变量时的类型推断。
// 它允许编译器根据变量的初始化表达式推断出变量的类型,从而简化代码书写过程。
cout << "Vector element: ";
for(auto it = myVector.begin();it != myVector.end();it++){
cout << *it << endl;
}
// 获取 vector 容器的大小和访问特定位置的元素
cout << "Vector size: " << myVector.size() << endl;
cout << "Element at index 1: " << myVector[1] << endl;
cout << "Element at index 2: " << myVector[2] << endl;
// 修改特定位置的元素
myVector[2] = 20;
cout << "Element at index 2: " << myVector[2] << endl;
//循环遍历 vector 并输出元素
for(int num : myVector){
std::cout << num << endl;
}
// 清空 vector 容器
myVector.clear();
// 检查 vector 是否为空
if(myVector.empty()){
cout << "Vector is empty." << endl;
}else {
cout << "Vector is not empty." << endl;
}
return 0;
}
4.2 list
• STL中的 list 是双向链表(doubly linked list)的实现,它是 C++ 标准模板库中的一个容器,提供了一种能够高效进行插入、删除操作的数据结构。与 vector 不同, list 不支持随机访问,但它允许在任意位置快速插入和删除元素。
• 以下是关于 std::list 的一些特点和说明:
• 双向链表结构: std::list 使用双向链表来组织其元素,每个节点都包含指向前一个节点和后一
个节点的指针,因此在任意位置进行插入和删除操作的开销较小。
• 不支持随机访问: 与 vector 不同, list 不支持通过索引直接访问元素,因为它不具备随机访问
能力。要访问 list 中的元素,需要使用迭代器进行顺序遍历。
• 动态大小调整: list 具有动态大小调整的特性,可以动态增加或减少元素的数量。对于大量的插
入和删除操作, list 往往比 vector 更高效。
• 迭代器操作: 使用迭代器可以对 list 中的元素进行访问、插入和删除。 list 提供了begin() 、 end() 、 rbegin() 、 rend() 等迭代器相关方法,支持正向和逆向迭代。
• 插入和删除操作效率高: 在 list 中,在任意位置进行插入和删除操作的时间复杂度是 O(1),因为
只需要调整相邻节点的指针,无需移动大量元素。
• 空间开销: 相比于 vector , list 需要额外的空间来存储指向前一个和后一个节点的指针,可能
会导致更高的存储开销。
• 下面是一个简单的示例,演示了如何使用 STL 中的 std::list 容器。在这个案例中,我们创建了一个std::list 来存储整数,并展示了一些基本的操作,如插入、删除、迭代等。
#include <iostream>
#include <list>
using namespace std;
int main()
{
//创建一个vector容器
list<int> mylist;
//向list容器尾部加入数据
mylist.push_back(10);
mylist.push_back(20);
mylist.push_back(30);
//向list容器头部加入数据
mylist.push_front(5);
mylist.push_front(15);
// 使用迭代器遍历 list 容器并输出其中的元素
// 在 C++ 中,auto 是一个关键字,用于声明变量时的类型推断。
// 它允许编译器根据变量的初始化表达式推断出变量的类型,从而简化代码书写过程。
cout << "List element: ";
for(auto it = mylist.begin();it != mylist.end();it++){
cout << *it << endl;
}
// 删除 list 中特定的元素值
mylist.remove(20);
//循环遍历 list 并输出元素
for(int num : mylist){
std::cout << num << endl;
}
// 清空 list 容器
//mylist.clear();
// 检查 list 是否为空
if(mylist.empty()){
cout << "list is empty." << endl;
}else {
cout << "list is not empty." << endl;
}
return 0;
}
4.3 set
• std::set 是 C++ 标准模板库中的关联容器,用于存储唯一值的集合。它基于红黑树实现,保持了元素的有序性,且不允许重复的元素存在。
• 以下是关于 std::set 的一些特点和说明:
• 唯一性:std::set中的元素是唯一的,不允许有重复的元素存在。当尝试向 set 中插入重复的元素时,新元素将不会被插入。
• 有序性: std::set 中的元素是根据元素值进行排序的,这使得元素按照一定顺序存储,并且支持
对元素的快速搜索。
• 红黑树实现: std::set 的底层实现通常是基于红黑树的,这保证了插入、删除和查找操作的时间
复杂度为对数时间(O(log n))。
• 动态操作: 可以对 std::set 进行动态操作,如插入、删除和查找元素。插入和删除操作的性能较
好,不会影响其他元素的位置。
• 以下是 std::set 常用的一些方法:
• insert() : 向 set 中插入一个元素。
• erase() : 删除 set 中指定值的元素。
• find() : 查找指定值在 set 中的位置。
• size() : 返回 set 中元素的数量。
• empty() : 检查 set 是否为空。
• clear() : 清空 set 中的所有元素。
下面是一个简单的示例,演示了如何使用 std::set:
#include <iostream>
#include <set>
using namespace std;
int main()
{
// 创建一个存储 int 类型值的 set 容器
set<int> myset;
//向set容器加入数据
myset.insert(10);
myset.insert(20);
myset.insert(30);
// 尝试插入重复元素
myset.insert(20);// 不会插入重复的元素
// 使用迭代器遍历 set 容器并输出其中的元素
// 在 C++ 中,auto 是一个关键字,用于声明变量时的类型推断。
// 它允许编译器根据变量的初始化表达式推断出变量的类型,从而简化代码书写过程。
cout << "set element: ";
for(auto it = myset.begin();it != myset.end();it++){
cout << *it << endl;
}
// 查找特定值在 set 中的位置
int searchValue = 200;
auto found = myset.find(searchValue);
if(found != myset.end()){
cout << "Found " << searchValue << " in the set." << endl;
}else {
cout << searchValue << " not found in the set." << endl;
}
// 删除特定值的元素
myset.erase(10);
cout << "set element: ";
for(auto it = myset.begin();it != myset.end();it++){
cout << *it << endl;
}
// 清空 set 容器
//myset.clear();
// 检查 set 是否为空
if(myset.empty()){
cout << "set is empty." << endl;
}else {
cout << "set is not empty." << endl;
}
return 0;
}
4.4 map
• std::map 是 C++ 标准模板库中的关联容器,用于存储键值对。它基于红黑树实现,保持了元素的有序性,其中每个元素都是一个键值对,键和值之间存在映射关系。
• 以下是关于 std::map 的一些特点和说明:
• 有序性: std::map 中的元素是根据键值排序的,这使得元素按照一定顺序存储,并且支持对元素
的快速搜索。
• 唯一键: std::map 中的键是唯一的,每个键对应一个值。如果尝试使用相同的键向 map 中插入
值,则会更新键对应的值。
• 红黑树实现: std::map 的底层实现通常是基于红黑树的,这保证了插入、删除和查找操作的时间
复杂度为对数时间(O(log n))。
• 动态操作: 可以对 std::map 进行动态操作,如插入、删除和查找键值对。插入和删除操作的性能较好,不会影响其他元素的位置。
• 以下是 std::map 常用的一些方法:
• insert() : 向 map 中插入一个键值对。
• erase() : 删除 map 中指定键的键值对。
• find() : 查找指定键在 map 中的位置。
• operator[] : 通过键访问对应的值。
• size() : 返回 map 中键值对的数量。
• empty() : 检查 map 是否为空。
• clear() : 清空 map 中的所有键值对。
• 下面是一个简单的示例,演示了如何使用 std::map :
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
// 创建一个存储 string 类型键和 int 类型值的 map 容器
map<string,int> mymap;
// 向 map 中插入键值对
mymap["alice"] = 25;
mymap["bob"] = 30;
mymap["jack"] = 20;
// 使用迭代器遍历 map 容器并输出其中的元素
// 在 C++ 中,auto 是一个关键字,用于声明变量时的类型推断。
// 它允许编译器根据变量的初始化表达式推断出变量的类型,从而简化代码书写过程。
cout << "Map element: ";
for(auto it = mymap.begin();it != mymap.end();it++){
cout << it->first << ":" << it->second << endl;
}
// 查找特定键在 map 中的位置
string searchValue = "bob";
auto found = mymap.find(searchValue);
if(found != mymap.end()){
cout << "Found " << searchValue << " with value:" << found->second << endl;
}else {
cout << searchValue << " not found in the set." << endl;
}
// 删除特定键的键值对
mymap.erase("jack");
cout << "Map element: ";
for(auto it = mymap.begin();it != mymap.end();it++){
cout << it->first << ":" << it->second << endl;
}
//清空map容器
//mymap.clear();
// 检查 map 是否为空
if(mymap.empty()){
cout << "map is empty." << endl;
}else {
cout << "map is not empty." << endl;
}
return 0;
}
5. 异常
5.1 基本异常
• 在C++中,异常处理是一种机制,用于处理程序在运行时发生的异常情况。异常是指程序执行期间发生的意外事件,比如除以零、访问无效的内存地址等。通过使用异常处理机制,可以使程序更健壮,并能够处理这些意外情况,避免程序崩溃或产生不可预测的结果。
• 在 C++ 中,异常处理通常包括以下关键词和概念:
• try-catch 块: try 块用于标识可能会引发异常的代码块,而 catch 块用于捕获和处理异常。
catch块可以针对不同类型的异常进行处理。
• throw 关键词:throw 用于在程序中显式抛出异常。当发生异常情况时,可以使用 throw 来抛出
一个特定的异常类型。
• 异常类型:异常可以是任何类型的数据,但通常是标准库中的异常类或自定义的异常类。标准库提
供了一些常见的异常类,如 std::exception 及其派生类,用于表示不同类型的异常情况。
下面是一个简单的示例,演示了异常处理的基本用法:
#include <iostream>
using namespace std;
void divide(int number1,int number2){
try {
if(number2 == 0){
//除数不能为0
throw "Division by zero is not allowed!";
}
int ret = number1 / number2;
cout << "The result is " << ret << endl;
} catch (const char* errorMessage) {
cout << "Exception caught is " << errorMessage << endl;
}
}
int main()
{
int a = 10;
int b = 0;
divide(a, b);
return 0;
}
• 在这个示例中, divide() 函数尝试对number1除以number2进行除法运算。如果number2为零,就会抛出一个字符串类型的异常。在 main() 函数中调用 divide() 函数时,由于 b 的值为零,因此会抛出异常,然后在 catch 块中捕获并处理异常,输出错误消息。
• 在实际的程序开发中,可以根据具体情况设计和抛出自定义的异常类,以及使用多个 catch 块来处理不同类型的异常,使程序能够更好地处理各种异常情况。
5.2 自定义异常
• 在 C++ 中,你可以通过继承标准库的std::exception 类或其派生类来自定义异常类。自定义异常类通常用于表示特定类型的异常情况,并允许你提供有关异常的额外信息。
• 以下是一个示例,演示了如何创建自定义的异常类:
#include <iostream>
#include <exception>
using namespace std;
//自定义异常类,继承自std::exception
class MyException : public exception {
private:
const char * message;//异常消息
public:
MyException(const char* msg) : message(msg){} // 构造函数,接受异常消息作为参数
// 重写基类的what()方法,返回异常消息
const char *what() const throw() {
return message;
}
};
// 一个函数,演示如何抛出自定义异常
void myFunction(){
// 在这个示例中,函数总是抛出自定义异常
throw MyException("This is a custom exception!");
}
int main()
{
try {
myFunction();
} catch (MyException e) {
cout << "Caught MyException: " << e.what() << endl;
}
return 0;
}
• 在这个示例中, MyException 类继承自 std::exception 类,并重写了基类的 what() 方法,以返回
异常消息。在 myFunction() 中,我们抛出了一个 MyException 类型的异常,并在 main() 函数中的
try-catch 块中捕获并处理该异常。
• 通过自定义异常类,可以根据需要添加其他成员变量、方法或构造函数,以便更好地描述和处理特定类型的异常情况。这种方式可以提高程序的可读性和可维护性,并允许你更精确地控制异常处理。
更多推荐


所有评论(0)