简介:面向对象技术是一种重要的软件开发思想,C++作为支持这种技术的编程语言,不仅继承了C语言的特性,还加入了面向对象的封装、继承和多态。本课件系列由北京邮电大学房鸣老师提供,深入讲解了C++面向对象编程的基础和高级概念。学生将通过学习课件中的讲义、设计模式和案例分析,掌握类的设计、模板、异常处理等关键技能,以及如何高效构建和维护复杂系统。
1. 面向对象技术概述
面向对象技术是现代软件开发中不可或缺的一部分,它的核心理念是将问题域中的事物抽象为具有属性和行为的对象,并通过对象间的交互来解决复杂问题。面向对象技术允许开发者构建易于理解和维护的软件系统,而其主要特征——封装、继承和多态,共同支撑起了面向对象编程的基础。
在本章中,我们先从基础概念讲起,逐步深入,揭示面向对象技术的原理和应用。首先,我们会介绍面向对象的基本思想,然后探讨它的关键特性。通过这些概念的解析,读者可以建立起初步的面向对象思维模型,为进一步学习面向对象编程语言如C++奠定坚实的理论基础。随后,我们将分别探讨这些核心概念在C++中的实现方式,以及它们如何影响软件的结构和设计。
2. C++编程语言特性
2.1 C++基本语法
C++是一种静态类型、编译式、通用的编程语言,它支持多种编程范式,如过程化、面向对象和泛型编程。它继承了C语言的高效性、灵活性和低级操作能力,同时增加了面向对象编程和异常处理等特性。
2.1.1 标识符、关键字和数据类型
在C++中,标识符是用于变量、函数、类、对象等的名称。标识符的命名遵循特定规则:必须以字母或下划线开头,后面可以跟字母、数字或下划线。标识符对大小写敏感,且不能与C++中的关键字相同。
C++的关键字有特定的含义和用途,比如 if
、 else
用于条件控制, class
用于定义类等。完整的C++关键字列表可以在语言标准中找到。
数据类型是定义变量所占用内存的大小和它可以存储的数据种类。C++的数据类型可以分为基本数据类型(如 int
、 float
)、构造类型(如数组和结构体)、指针类型和空类型( void
)。
下面是一个简单的C++程序示例,展示了标识符、关键字和数据类型的使用:
#include <iostream>
using namespace std;
int main() {
int number = 10; // 定义了一个整型变量number
float realNumber = 3.14f; // 定义了一个浮点型变量realNumber
cout << "Number is: " << number << endl;
cout << "Real number is: " << realNumber << endl;
return 0;
}
2.1.2 表达式和运算符
表达式是由变量、常量、运算符和函数调用组成的序列,它计算出一个值、一个对象或一个函数。运算符是用于执行数学或逻辑运算的特殊符号或关键词。C++支持丰富的运算符,包括算术运算符(如 +
、 -
)、关系运算符(如 ==
、 !=
)、逻辑运算符(如 &&
、 ||
)等。
例如,在下面的代码中,我们使用了算术和赋值运算符:
#include <iostream>
using namespace std;
int main() {
int a = 10, b = 20;
int sum = a + b; // 算术运算符 '+'
cout << "Sum is: " << sum << endl;
a += 5; // 赋值运算符 '+=' 等同于 a = a + 5;
cout << "After addition, a is: " << a << endl;
return 0;
}
在表达式中,运算符的优先级决定了运算的顺序。比如在没有括号的情况下,乘除运算符的优先级高于加减运算符。
2.2 C++的控制结构
控制结构允许我们控制程序的执行流程。C++提供了条件语句、循环控制语句和跳转语句来改变程序执行的顺序。
2.2.1 条件语句
条件语句允许根据不同的条件执行不同的代码块。C++中最常见的条件语句有 if
语句、 if-else
语句和 switch
语句。
#include <iostream>
using namespace std;
int main() {
int value = 5;
if(value > 0) {
cout << "The value is positive." << endl;
} else {
cout << "The value is non-positive." << endl;
}
switch(value) {
case 5:
cout << "The value is five." << endl;
break;
default:
cout << "The value is not five." << endl;
}
return 0;
}
在这个例子中,我们首先使用 if-else
语句判断变量 value
的值。之后使用 switch
语句根据 value
的值输出不同的信息。
2.2.2 循环控制
循环控制语句允许根据特定条件重复执行一段代码。常见的循环控制语句有 for
循环、 while
循环和 do-while
循环。
#include <iostream>
using namespace std;
int main() {
// for循环示例
for(int i = 0; i < 5; i++) {
cout << "The value of i is: " << i << endl;
}
// while循环示例
int j = 0;
while(j < 5) {
cout << "The value of j is: " << j << endl;
j++;
}
// do-while循环示例
int k = 0;
do {
cout << "The value of k is: " << k << endl;
k++;
} while(k < 5);
return 0;
}
在本段代码中,我们分别展示了 for
、 while
和 do-while
循环的使用,它们都实现了相同的功能,即重复输出数字0到4。
2.3 C++的函数特性
函数是执行特定任务的代码块,它们具有名称、返回类型和参数列表。C++中的函数可以有多种类型,可以根据需要进行定义和声明。
2.3.1 函数定义和声明
函数定义提供了函数的实现,包含返回类型、函数名、参数列表和函数体。函数声明则用于告知编译器函数的名称、返回类型和参数类型,但不提供函数体。
#include <iostream>
using namespace std;
// 函数声明
int max(int a, int b);
int main() {
cout << "The maximum value is: " << max(10, 20) << endl;
return 0;
}
// 函数定义
int max(int a, int b) {
return (a > b) ? a : b;
}
在这个例子中,我们先声明了 max
函数,然后在 main
函数中调用它。随后,我们提供了 max
函数的定义。
2.3.2 函数重载和默认参数
函数重载允许我们创建多个具有相同名称但参数列表不同的函数。编译器会根据提供的参数类型和数量来选择正确的函数。
#include <iostream>
using namespace std;
// 函数重载
int sum(int a, int b) {
return a + b;
}
double sum(double a, double b) {
return a + b;
}
int main() {
cout << sum(10, 20) << endl; // 使用int类型的sum函数
cout << sum(10.5, 20.5) << endl; // 使用double类型的sum函数
return 0;
}
函数的默认参数提供了一种便利,允许调用函数时省略参数。如果省略,则使用默认值。
#include <iostream>
using namespace std;
// 函数默认参数
int printInfo(int id, string name = "Unknown") {
cout << "ID: " << id << " Name: " << name << endl;
return 0;
}
int main() {
printInfo(1); // 使用默认参数 "Unknown"
printInfo(2, "Alice");
return 0;
}
通过本章节的介绍,我们可以看到C++编程语言的基本语法特性,它们为构建复杂的面向对象程序奠定了坚实的基础。
3. 封装、继承和多态概念
在软件工程中,封装、继承和多态是面向对象编程的三大核心概念,这三者紧密相连,共同构成了面向对象编程的基础理论。了解和掌握这些概念是进行有效编程的关键。本章将深入探讨这些概念的细节,并展示其在C++中的实现。
3.1 封装的概念与实现
封装是面向对象编程的基本特征之一,它涉及到将数据(属性)和操作数据的代码(方法)捆绑在一起,形成一个独立的单元——类。封装的目的是隐藏类的实现细节,提供接口供外部调用,确保内部逻辑不会被外部环境直接访问和修改,从而增加软件的模块化和安全性。
3.1.1 访问控制符的使用
在C++中,访问控制符用于指定成员的可见性,分别是 private
、 protected
和 public
。
-
private
:私有成员只能被类内的函数和友元访问,无法直接被外部访问。 -
protected
:受保护的成员可以被派生类访问,但不能被外部访问。 -
public
:公有成员既可以在类内访问,也可以在类外通过对象访问。
通常,类的属性设为 private
,以防止外部直接修改数据;而成员函数(方法)则设为 public
,以便外部调用。
示例代码展示如何使用访问控制符:
class MyClass {
private:
int privateVar; // 私有成员变量
protected:
int protectedVar; // 受保护成员变量
public:
int publicVar; // 公有成员变量
void setPrivateVar(int value) { // 公有成员函数
privateVar = value;
}
void show() { // 公有成员函数
cout << "privateVar = " << privateVar << endl;
cout << "protectedVar = " << protectedVar << endl;
cout << "publicVar = " << publicVar << endl;
}
};
3.1.2 类的实现细节封装
在封装的上下文中,实现细节的封装是指将类的实现隐藏起来,即隐藏了类的数据成员和成员函数的实现细节。在C++中,可以通过将成员函数定义在类的外部来实现这一点。
class Complex {
private:
double real, imag; // 私有成员变量
public:
Complex(double r = 0.0, double i = 0.0) { // 构造函数
real = r;
imag = i;
}
// 成员函数声明
double getReal();
double getImag();
void setReal(double r);
void setImag(double i);
};
// 成员函数的定义
double Complex::getReal() {
return real;
}
double Complex::getImag() {
return imag;
}
void Complex::setReal(double r) {
real = r;
}
void Complex::setImag(double i) {
imag = i;
}
通过这种方式,即使在类外部,我们也可以通过公有成员函数来访问和修改私有属性,从而隐藏了类的内部实现。
封装不仅有助于维护代码,还可以在不改变接口的情况下改变类的内部实现。这种灵活性允许开发者优化性能而不影响使用类的外部代码。
3.2 继承的概念与实现
继承是面向对象编程中用于实现代码复用和建立类之间层次关系的机制。通过继承,一个类可以继承另一个类的属性和方法,这样可以避免重复代码并让程序结构更清晰。
3.2.1 继承的类型和访问权限
在C++中,继承主要有三种类型:公有继承( public
)、保护继承( protected
)和私有继承( private
)。继承类型决定了基类成员在派生类中的访问权限。
- 公有继承(
public
):基类的公有成员和保护成员在派生类中保持原有的访问权限;基类的私有成员虽然不可直接访问,但可以通过基类的公有和保护成员函数访问。 - 保护继承(
protected
):基类的公有和保护成员在派生类中变为保护成员。 - 私有继承(
private
):基类的公有和保护成员在派生类中变为私有成员。
3.2.2 基类和派生类的构造和析构顺序
在C++中,基类和派生类对象的构造和析构顺序是由它们在内存中的布局决定的。构造函数的调用顺序是先基类后派生类,析构函数的调用顺序则相反。
class Base {
public:
Base() { cout << "Base constructor" << endl; }
~Base() { cout << "Base destructor" << endl; }
};
class Derived : public Base {
public:
Derived() { cout << "Derived constructor" << endl; }
~Derived() { cout << "Derived destructor" << endl; }
};
int main() {
Derived d;
return 0;
}
运行上述代码,输出结果将是:
Base constructor
Derived constructor
Derived destructor
Base destructor
此顺序确保了在派生类对象存在期间,基类的成员始终是有效和可用的。
继承关系增加了类的复用性,并且使得派生类能够继承基类的功能,同时也能够添加或覆盖基类的某些成员以实现新的功能或行为。
3.3 多态的概念与实现
多态是面向对象编程中的一个高级概念,允许不同类的对象对同一消息做出响应。在C++中,多态主要通过虚函数实现。虚函数允许派生类重写基类中的方法,从而根据对象的实际类型来调用相应的方法实现。
3.3.1 多态的原理和应用
多态的实现依赖于继承和虚函数。基类中的函数被声明为 virtual
后,派生类就可以重写该函数。在编译时,并不决定调用哪个函数版本,而是在运行时根据对象的实际类型来动态绑定相应的函数。
class Base {
public:
virtual void display() {
cout << "Base display function" << endl;
}
};
class Derived : public Base {
public:
void display() override {
cout << "Derived display function" << endl;
}
};
int main() {
Base* bptr; // 指向基类的指针
Base base;
Derived derived;
bptr = &base;
bptr->display(); // 输出 "Base display function"
bptr = &derived;
bptr->display(); // 输出 "Derived display function"
return 0;
}
在这个例子中,尽管 bptr
是一个指向基类的指针,通过多态的机制,实际调用的是 display()
函数的正确版本。
3.3.2 虚函数和纯虚函数的使用
虚函数是实现多态的关键。如果一个函数在基类中被声明为虚函数,在派生类中即使不使用 override
关键字,该函数也会成为重写的虚函数。
当基类中的虚函数声明为 = 0
时,该函数成为纯虚函数。这意味着基类不能直接提供函数体,而是必须由派生类来实现。含有纯虚函数的类称为抽象类,不能直接实例化。
class Base {
public:
virtual void show() = 0; // 纯虚函数
};
class Derived : public Base {
public:
void show() override {
cout << "Derived show function implementation" << endl;
}
};
多态和虚函数使程序能够以统一的接口操作不同类型对象,增加了程序的灵活性和扩展性。
在本章中,我们探讨了封装、继承和多态这三个面向对象编程的核心概念。通过具体代码示例和深入分析,我们可以看到这些概念如何在C++中得以实现,并理解它们对软件开发的重要性。下一章将讨论如何进行面向对象设计,以更好地实现类的设计和实例化。
4. 类的设计和实例化
4.1 类的设计原则
4.1.1 类的职责和接口设计
在面向对象编程中,一个类的职责是指它应该做什么,而接口设计则是如何对外呈现类的功能。在设计类时,遵循单一职责原则(Single Responsibility Principle, SRP)是至关重要的。这意味着一个类应该只有一个引起变化的原因,即它只有一个职责。
为了确保类的职责明确,我们可以设计清晰和直观的接口。接口可以是类的公共成员函数,也可以是类提供的服务集合。良好的接口设计应该具备以下特征:
- 最小化暴露 :类只提供完成其职责所需的方法。
- 抽象性 :接口应该隐藏实现细节,对外只暴露功能。
- 一致性 :类的公共成员函数应该遵循统一的命名约定和行为模式。
4.1.2 类的封装和抽象级别
封装是面向对象编程的核心概念之一,它意味着将数据(属性)和代码(行为)绑定在一起,并对外隐藏内部实现细节。封装还可以通过访问控制符(如 private
、 protected
和 public
)来实现,这些控制符决定成员变量和方法的可见性。
抽象级别则与类的复杂度和易用性密切相关。高层次的抽象可以隐藏更复杂的实现细节,只向用户展示操作接口,而低层次的抽象则提供更多实现细节。
在设计类时,应根据应用场景确定适当的抽象级别。过高或过低的抽象级别都可能导致类的使用不便或难以维护。
4.2 类的实例化和使用
4.2.1 对象的创建和生命周期管理
在C++中,对象的创建通常是通过构造函数来完成的。构造函数是一种特殊类型的成员函数,它在对象创建时自动调用,用于初始化对象的状态。对象的生命周期从构造函数被调用开始,到析构函数执行完毕结束。
C++提供了两种创建对象的方式:栈上创建和堆上创建。
-
栈上创建 :在栈上创建对象,其生命周期与创建它的作用域相关联。当作用域结束时,对象会自动被销毁。
cpp { MyClass obj; // 栈上创建对象 } // 当作用域结束时,obj被销毁
-
堆上创建 :在堆上创建对象,程序员需要使用
new
操作符,而对象的销毁则需要显式调用delete
。cpp MyClass* obj = new MyClass(); // 堆上创建对象 delete obj; // 显式销毁对象
正确的管理对象的生命周期对于避免资源泄露和确保程序稳定运行至关重要。
4.2.2 对象之间的交互方式
对象间的交互是通过成员函数的调用来实现的。成员函数可以是类的公共接口,也可以是受保护或私有的内部实现。对象之间可以有多种交互方式:
- 消息传递 :对象间通过调用彼此的公共接口来传递信息。
- 依赖 :一个类依赖于另一个类的接口。
- 关联 :对象之间具有关联关系,但不共享状态。
- 聚合 :一个对象包含另一个对象作为其一部分,但不拥有它。
- 组合 :一个对象包含另一个对象作为其一部分,并拥有它。
- 继承 :子类继承父类的属性和方法,实现代码复用。
在设计对象间的交互时,应当尽量减少耦合,提高系统的可维护性和可扩展性。
classDiagram
class ClassA {
<<interface>>
+methodA()
}
class ClassB {
+methodB()
}
class ClassC {
+methodC()
}
ClassA <|-- ClassB
ClassB "1" -- "0..*" ClassC : uses >
在上述类图中, ClassA
定义了一个接口, ClassB
实现了该接口,并且使用了 ClassC
。对象间的交互关系被清晰地表示出来。
通过细致的类设计和正确的对象实例化,我们可以构建出高效、可维护且易于扩展的软件系统。在下一章中,我们将深入探讨构造函数和析构函数的使用,这些是C++中管理对象生命周期的关键机制。
5. 构造函数与析构函数的使用
在C++中,构造函数和析构函数是类中两个特殊的成员函数,分别用于创建和销毁对象。它们是面向对象编程中不可或缺的一部分,用于初始化对象状态和释放资源。理解构造函数和析构函数的使用对于深入学习和应用C++类至关重要。
5.1 构造函数的作用和类型
构造函数是一类特殊的成员函数,它具有与类名相同的名称,并且没有返回类型。构造函数的主要作用是在对象创建时自动执行,用来初始化对象的数据成员,或者为对象分配资源。
5.1.1 默认构造函数和参数化构造函数
默认构造函数是在没有任何参数的情况下调用的构造函数。如果没有显式定义任何构造函数,编译器会提供一个默认的无参构造函数,它会为对象的数据成员提供默认值。
class Example {
public:
Example() {
// 默认构造函数
}
};
Example obj; // 调用默认构造函数
参数化构造函数允许在创建对象时提供参数,用于初始化对象的状态。
class Example {
public:
int value;
Example(int val) : value(val) {
// 参数化构造函数
}
};
Example obj(42); // 调用参数化构造函数,value 初始化为 42
5.1.2 拷贝构造函数的特殊性
拷贝构造函数是一种特殊的构造函数,它用于创建一个新对象作为现有对象的副本。拷贝构造函数的参数是对现有对象的引用(通常使用常量引用)。
class Example {
public:
int value;
Example(const Example& other) : value(other.value) {
// 拷贝构造函数
}
};
Example obj1(42);
Example obj2(obj1); // 调用拷贝构造函数
5.2 析构函数的作用和时机
析构函数的作用与构造函数相反,它在对象生命周期结束时被调用,用于执行清理工作,比如释放分配的资源。析构函数的名称是在类名前加上一个波浪号(~)。
5.2.1 析构函数的定义和特点
析构函数没有返回类型,也没有参数,且一个类只能有一个析构函数。
class Example {
public:
~Example() {
// 析构函数
}
};
5.2.2 析构函数在继承中的特殊问题
在继承关系中,当基类指针指向派生类对象时,如果基类的析构函数不是虚函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数,这可能导致资源未被正确释放。因此,建议将基类的析构函数声明为虚函数。
class Base {
public:
virtual ~Base() {
// 虚析构函数
}
};
class Derived : public Base {
public:
~Derived() {
// 派生类析构函数
}
};
Base* ptr = new Derived();
delete ptr; // 先调用Derived析构函数,再调用Base析构函数
通过本章内容的介绍,我们已经了解了构造函数和析构函数的基本概念、作用以及如何定义不同类型的构造函数。接下来,我们将深入探讨它们在具体使用中的细节和最佳实践。
6. 访问控制与对象关系
6.1 访问控制符的详细解析
6.1.1 private、protected和public的区别与应用
在C++中,访问控制是通过访问控制符来实现的,主要包括三种:private、protected和public。这些访问控制符定义了类成员的访问权限,它们可以控制不同部分对类成员的访问,从而影响类的封装性和继承性。
- private成员 :私有成员只能在类的内部被访问,类的外部以及派生类都不能直接访问私有成员。这种访问限制有助于隐藏实现细节,确保类的内部状态不会被外部代码直接修改。
- protected成员 :保护成员对于类的派生类是可访问的,但对类的外部是不可访问的。保护成员既能够提供对基类成员的封装,又允许派生类对这些成员进行访问和修改。
- public成员 :公有成员在类的外部也是可以访问的,这意味着它们不提供封装性。通常,类的公有接口包括函数和方法,它们定义了类的外部用户能够执行的操作。
合理的访问控制符使用能够提高代码的安全性和可维护性。例如,将数据成员设为private可以防止外部错误地修改对象的状态,而将接口函数设为public则允许对象的用户通过标准的接口与对象进行交互。
6.1.2 访问控制在继承中的影响
在继承体系中,访问控制同样扮演重要角色,因为它决定了派生类如何访问基类成员。
- 基类的private成员 :基类的私有成员不能在派生类中直接访问,但派生类可以通过基类提供的公有或保护成员函数来间接访问这些私有成员。
- 基类的protected成员 :基类的保护成员在派生类中仍然保持为保护状态,这允许派生类访问基类的保护成员,但不允许派生类的实例访问。
- 基类的public成员 :基类的公有成员在派生类中依旧是公有的,这意味着它们可以直接被派生类外部的代码访问。
访问控制在继承中的应用,使得设计者能够通过访问权限来控制派生类的实现细节,同时允许派生类访问和继承那些应当共享的成员。
6.2 对象间的关系
6.2.1 对象的组合和聚合
在面向对象编程中,组合和聚合是对象之间常见的两种关系。
- 组合(Composition) :是一种更强的对象关联形式。在组合关系中,一个对象持有另一个对象的实例作为其成员变量。组合通常意味着部分与整体的关系,部分对象不能独立于整体对象存在。例如,汽车和引擎的关系就是组合关系。
- 聚合(Aggregation) :与组合类似,但聚合表示的关系更为松散,被称为"拥有"关系。在聚合关系中,整体对象包含部分对象的引用,但部分对象可以独立于整体对象存在。例如,大学和系的关系可以被看作是聚合关系。
组合和聚合的主要区别在于对象生命周期的控制以及整体和部分的依赖关系。组合中的整体负责部分的创建和销毁,而聚合中的整体不控制部分的生命周期。
6.2.2 关联、依赖和继承的关系对比
在面向对象设计中,关联、依赖和继承是三种不同的关系类型,它们在对象之间建立了不同的连接方式。
- 关联(Association) :表示一种连接,其中对象之间存在一种“使用”关系,但没有严格的包含或控制关系。关联可以是双向的也可以是单向的,例如老师和学生之间的关系就是一种双向的关联关系。
- 依赖(Dependency) :是一种更弱的关系,表示一个对象依赖于另一个对象提供的服务。依赖通常出现在方法的参数、局部变量和方法的返回类型中。例如,一个类的方法需要另一个类的实例作为参数,那么该方法就依赖于那个类。
- 继承(Inheritance) :是一种特殊的关系,其中派生类继承基类的属性和行为。继承表示的是一种“是一个”的关系,例如“汽车是一个交通工具”。
理解这些关系有助于设计更加清晰、灵活和可扩展的面向对象系统。通过合理应用这些关系,可以提高代码的复用性,降低模块间的耦合度,从而提升整个系统的可维护性和可扩展性。
7. 模板、异常处理和STL
7.1 C++模板的原理和应用
模板是C++泛型编程的核心,它允许程序员编写与数据类型无关的代码。通过模板,可以创建可重用的类和函数,这些类和函数可以处理任何类型的数据。
7.1.1 函数模板和类模板的定义和使用
函数模板通过在函数定义中引入类型参数,使得函数能够接受不同数据类型的参数。下面是一个简单的函数模板示例:
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
int main() {
std::cout << max(10, 20) << std::endl; // 输出: 20
std::cout << max(3.14, 2.71) << std::endl; // 输出: 3.14
return 0;
}
类模板则用于创建可以处理不同数据类型的类。类模板定义了一个蓝图,可以实例化为具体的数据类型。
template <typename T>
class Pair {
private:
T first;
T second;
public:
Pair(T a, T b) : first(a), second(b) {}
void print() {
std::cout << "(" << first << ", " << second << ")" << std::endl;
}
};
int main() {
Pair<int> p(10, 20);
p.print(); // 输出: (10, 20)
Pair<std::string> sp("Hello", "World");
sp.print(); // 输出: (Hello, World)
return 0;
}
7.1.2 模板特化和模板元编程基础
模板特化允许为特定类型提供特殊的模板实现。这在处理特殊类型或者优化性能时非常有用。
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
template <>
const char* max<const char*>(const char* a, const char* b) {
return (strcmp(a, b) > 0) ? a : b;
}
int main() {
std::cout << max(10, 20) << std::endl; // 输出: 20
std::cout << max("Hello", "World") << std::endl; // 输出: World
return 0;
}
模板元编程是一种编译时计算技术。通过模板特化和递归模板实例化,可以在编译时解决复杂的计算问题。
7.2 异常处理机制
异常处理是C++中处理程序运行时错误的一种机制。它允许程序在检测到错误时抛出异常,并由异常处理器捕获和处理这些异常。
7.2.1 异常抛出和捕获的机制
异常可以使用 throw
语句抛出,然后使用 try
和 catch
块捕获。
#include <iostream>
int divide(int a, int b) {
if (b == 0)
throw std::invalid_argument("Division by zero error");
return a / b;
}
int main() {
try {
std::cout << divide(10, 0) << std::endl;
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
7.2.2 自定义异常类和异常安全编程
自定义异常类需要继承自 std::exception
或其子类,从而允许使用标准的 what()
方法提供错误信息。
#include <iostream>
#include <stdexcept>
class MyException : public std::exception {
public:
const char* what() const throw() {
return "MyException occurred";
}
};
void riskyFunction() {
throw MyException();
}
int main() {
try {
riskyFunction();
} catch (const MyException& e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
异常安全编程确保异常发生时资源得到正确释放,避免内存泄漏等问题。一个常见策略是使用RAII(资源获取即初始化)来管理资源。
7.3 标准模板库(STL)的使用
STL是C++标准库中提供的一个泛型库,它包含数据结构(如列表、队列和栈)、迭代器和算法。
7.3.1 STL容器的分类和使用
STL容器可以分为序列容器(如 vector
, list
, deque
)和关联容器(如 set
, map
)等。
#include <iostream>
#include <vector>
#include <list>
#include <set>
#include <map>
int main() {
std::vector<int> vec {1, 2, 3, 4, 5};
std::list<int> lst {6, 7, 8, 9, 10};
std::set<int> st {10, 9, 8, 7, 6};
std::map<int, std::string> mp {{1, "one"}, {2, "two"}, {3, "three"}};
for (int v : vec) {
std::cout << v << ' ';
}
std::cout << std::endl;
return 0;
}
7.3.2 STL算法的使用和自定义
STL算法定义了一组函数式接口,用于数据集合的各种操作,如排序( sort
)、搜索( find
)和元素变换( transform
)等。
#include <iostream>
#include <algorithm>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
std::sort(numbers.begin(), numbers.end()); // 排序
std::cout << numbers[0] << std::endl; // 输出: 1
return 0;
}
使用STL算法可以极大地简化代码,提高效率。对于自定义算法,可以通过学习现有的STL算法来创建更加通用和可复用的解决方案。
通过本章的介绍,我们可以看到模板、异常处理和STL为C++程序带来了更高的灵活性、稳定性和效率。在实际开发中,合理地运用这些特性能够显著提升代码质量,并且让复杂问题的处理变得简单。
简介:面向对象技术是一种重要的软件开发思想,C++作为支持这种技术的编程语言,不仅继承了C语言的特性,还加入了面向对象的封装、继承和多态。本课件系列由北京邮电大学房鸣老师提供,深入讲解了C++面向对象编程的基础和高级概念。学生将通过学习课件中的讲义、设计模式和案例分析,掌握类的设计、模板、异常处理等关键技能,以及如何高效构建和维护复杂系统。