大学计算机专业面向对象程序设计教程(C++)——基于电子工业出版社《面向对象程序设计教程——C++》(西安理工大学教材)——期末看完心里有底!
面向对象程序设计教程——C++ 知识文档
第1章 面向对象方法学
1.1 面向对象方法学的发展
面向对象方法学是一种软件开发方法,它将现实世界中的事物抽象为对象,通过对象之间的交互来模拟现实世界的行为。面向对象方法学的发展经历了以下几个阶段:
- 萌芽阶段(20世纪60年代):Simula语言的出现,首次引入了类和对象的概念。
- 形成阶段(20世纪70年代):Smalltalk语言的开发,完善了面向对象的基本概念和方法。
- 成熟阶段(20世纪80年代至今):C++、Java、C#等面向对象语言的出现和广泛应用,面向对象方法学成为主流的软件开发方法。
1.2 面向对象方法学的概述
面向对象方法学包括三个主要阶段:面向对象分析(OOA)、面向对象设计(OOD)和面向对象实现(OOI)。
1.2.1 面向对象分析
面向对象分析的主要任务是分析用户需求,识别问题域中的对象,确定对象的属性和行为,以及对象之间的关系。主要步骤包括:
- 需求获取:收集和分析用户需求,确定系统的功能和性能要求。
- 问题域分析:识别问题域中的对象、属性和行为。
- 建立分析模型:使用UML(统一建模语言)等工具建立对象模型、动态模型和功能模型。
1.2.2 面向对象设计
面向对象设计的主要任务是根据分析模型,设计系统的架构和详细实现方案。主要步骤包括:
- 系统架构设计:确定系统的整体结构,包括子系统划分、模块设计和接口定义。
- 详细设计:设计类的具体实现,包括属性、方法、继承关系和多态机制。
- 数据库设计:设计系统的数据存储结构,包括表结构、索引和关系。
1.2.3 面向对象实现
面向对象实现的主要任务是根据设计方案,使用面向对象语言编写代码,实现系统功能。主要步骤包括:
- 代码编写:使用C++、Java等面向对象语言编写代码。
- 测试:进行单元测试、集成测试和系统测试,确保系统功能正确。
- 部署:将系统部署到目标环境,确保系统正常运行。
1.3 面向对象程序设计的特性
面向对象程序设计具有以下四个核心特性:
1.3.1 抽象
抽象是指从具体事物中提取共同的、本质的特征,忽略非本质的细节。在面向对象程序设计中,抽象通过类来实现,类定义了对象的共同属性和行为。
代码示例:
// 抽象类:图形classShape{public:// 纯虚函数:计算面积virtualdoublearea()const=0;// 纯虚函数:计算周长virtualdoubleperimeter()const=0;};1.3.2 封装
封装是指将对象的属性和行为封装在一起,对外部隐藏对象的内部实现细节,只通过公共接口与外部交互。在面向对象程序设计中,封装通过访问控制符(public、protected、private)来实现。
代码示例:
// 封装示例:学生类classStudent{private:// 私有属性:姓名、年龄、学号 string name;int age; string id;public:// 公共接口:设置和获取属性voidsetName(string n){ name = n;} string getName()const{return name;}voidsetAge(int a){ age = a;}intgetAge()const{return age;}voidsetId(string i){ id = i;} string getId()const{return id;}};1.3.3 继承
继承是指从已有的类派生出新的类,新类继承父类的属性和行为,并可以添加新的属性和行为或重写父类的行为。在面向对象程序设计中,继承通过类的派生关系来实现。
代码示例:
// 基类:图形classShape{public:virtualdoublearea()const=0;virtualdoubleperimeter()const=0;};// 派生类:矩形,继承自ShapeclassRectangle:publicShape{private:double width;double height;public:Rectangle(double w,double h):width(w),height(h){}doublearea()constoverride{return width * height;}doubleperimeter()constoverride{return2*(width + height);}};// 派生类:圆形,继承自ShapeclassCircle:publicShape{private:double radius;public:Circle(double r):radius(r){}doublearea()constoverride{return3.14159* radius * radius;}doubleperimeter()constoverride{return2*3.14159* radius;}};1.3.4 多态
多态是指同一个操作作用于不同的对象时,会产生不同的行为。在面向对象程序设计中,多态通过虚函数和函数重载来实现。
代码示例:
#include<iostream>usingnamespace std;// 基类:图形classShape{public:virtualvoiddraw()const{ cout <<"绘制图形"<< endl;}};// 派生类:矩形classRectangle:publicShape{public:voiddraw()constoverride{ cout <<"绘制矩形"<< endl;}};// 派生类:圆形classCircle:publicShape{public:voiddraw()constoverride{ cout <<"绘制圆形"<< endl;}};// 测试多态intmain(){ Shape* shapes[2]; shapes[0]=newRectangle(); shapes[1]=newCircle();for(int i =0; i <2; i++){ shapes[i]->draw();// 多态调用}delete shapes[0];delete shapes[1];return0;}运行结果:
绘制矩形 绘制圆形 1.4 面向对象程序设计的术语
面向对象程序设计中常用的术语包括:
| 术语 | 解释 |
|---|---|
| 对象 | 现实世界中事物的抽象,具有属性和行为 |
| 类 | 对象的模板,定义了对象的属性和行为 |
| 属性 | 对象的状态,描述对象的特征 |
| 方法 | 对象的行为,描述对象可以执行的操作 |
| 继承 | 从已有的类派生出新的类 |
| 多态 | 同一个操作作用于不同的对象时产生不同的行为 |
| 封装 | 将对象的属性和行为封装在一起,对外部隐藏内部实现细节 |
| 抽象 | 从具体事物中提取共同的、本质的特征 |
| 接口 | 类对外提供的公共方法的集合 |
| 消息 | 对象之间通信的方式 |
1.5 面向对象程序设计语言
常用的面向对象程序设计语言包括:
1.5.1 C++
C++是一种混合型语言,既支持面向过程编程,又支持面向对象编程。C++的主要特点包括:
- 兼容C语言,是C语言的超集
- 支持类、继承、多态等面向对象特性
- 支持运算符重载和函数重载
- 支持模板和泛型编程
- 支持异常处理
1.5.2 Java
Java是一种纯面向对象语言,由Sun Microsystems公司开发。Java的主要特点包括:
- 纯面向对象,所有代码都必须在类中
- 跨平台,通过JVM实现"一次编写,到处运行"
- 自动内存管理,通过垃圾回收机制
- 内置网络和多线程支持
- 安全性能好
1.5.3 C#
C#是由Microsoft公司开发的一种面向对象语言,主要用于.NET平台。C#的主要特点包括:
- 语法类似于C++和Java
- 支持面向对象编程的所有特性
- 与.NET框架紧密集成
- 支持泛型编程和LINQ
- 支持异步编程
1.5.4 Python
Python是一种解释型、面向对象的脚本语言。Python的主要特点包括:
- 语法简洁明了,易于学习
- 支持面向对象编程
- 丰富的标准库和第三方库
- 动态类型,无需显式声明变量类型
- 支持多种编程范式
小结
面向对象方法学是一种以对象为中心的软件开发方法,它具有抽象、封装、继承和多态四个核心特性。面向对象方法学通过将现实世界中的事物抽象为对象,通过对象之间的交互来模拟现实世界的行为,从而提高软件的可维护性、可扩展性和可重用性。
习题1
- 什么是面向对象方法学?它的核心特性有哪些?
- 面向对象方法学包括哪三个主要阶段?每个阶段的主要任务是什么?
- 请简述面向对象程序设计语言的发展历程。
- 请比较C++、Java、C#和Python四种面向对象编程语言的特点。
- 请举例说明面向对象程序设计中的抽象、封装、继承和多态特性。
第2章 C++概述
2.1 C++发展历程与特点
2.1.1 C++的发展历程
C++语言是由Bjarne Stroustrup于1979年在贝尔实验室开始设计和开发的,其发展历程如下:
- C with Classes阶段(1979-1983):在C语言的基础上添加了类和对象的概念。
- C++阶段(1983-1989):正式命名为C++,添加了虚函数、运算符重载、引用、常量等特性。
- 标准化阶段(1989-1998):1998年,ISO发布了第一个C++标准(C++98)。
- 现代C++阶段(1998至今):后续发布了C++03、C++11、C++14、C++17、C++20等标准,不断添加新特性和改进语言设计。
2.1.2 C++的特点
C++语言具有以下特点:
- 兼容性:C++是C语言的超集,完全兼容C语言的语法和语义。
- 面向对象:支持类、继承、多态、封装等面向对象编程特性。
- 高效性:保留了C语言的高效性,可直接操作内存和硬件。
- 灵活性:支持多种编程范式,包括面向过程、面向对象和泛型编程。
- 丰富的库:提供了标准模板库(STL)等丰富的库函数。
2.2 C++程序
2.2.1 C++程序的格式与构成
C++程序的基本构成包括:
- 预处理指令:以
#开头的指令,如#include、#define等。 - 全局声明:全局变量、函数声明等。
- 函数定义:包括主函数
main()和其他自定义函数。 - 类定义:C++特有的类定义。
代码示例:一个简单的C++程序
#include<iostream>// 预处理指令usingnamespace std;// 使用标准命名空间// 全局变量int globalVar =10;// 函数声明voidprintMessage();// 类定义classMyClass{private:int value;public:MyClass(int v):value(v){}voiddisplay(){ cout <<"Value: "<< value << endl;}};// 主函数intmain(){// 局部变量int localVar =20;// 输出信息 cout <<"Global variable: "<< globalVar << endl; cout <<"Local variable: "<< localVar << endl;// 调用函数printMessage();// 创建对象 MyClass obj(30); obj.display();return0;}// 函数定义voidprintMessage(){ cout <<"Hello, C++!"<< endl;}运行结果:
Global variable: 10 Local variable: 20 Hello, C++! Value: 30 2.2.2 C++程序的编译与执行
C++程序的编译与执行过程包括以下步骤:
- 预处理:处理预处理指令,如展开头文件、替换宏定义等。
- 编译:将预处理后的源代码编译成汇编代码。
- 汇编:将汇编代码转换为机器码(目标文件)。
- 链接:将目标文件与库文件链接成可执行文件。
- 执行:运行可执行文件。
编译命令示例(使用g++编译器):
# 编译单个文件 g++ hello.cpp -o hello # 运行程序 ./hello 2.3 从C到C++
C++在C语言的基础上添加了许多新特性,同时保持了对C语言的兼容性。以下是从C到C++的主要变化:
- 类型检查更严格:C++对类型转换的检查更严格,避免了一些潜在的类型错误。
- 新增关键字:C++添加了
class、public、private、protected、virtual、inline、namespace等新关键字。 - 函数重载:C++允许同名函数具有不同的参数列表。
- 引用:C++引入了引用(reference)的概念,作为变量的别名。
- 默认参数:C++允许函数参数具有默认值。
- 内联函数:C++支持内联函数,提高函数调用效率。
- 运算符重载:C++允许重载运算符,使自定义类型的操作更直观。
- 类和对象:C++引入了类和对象的概念,支持面向对象编程。
2.4 C++的一些新特性
2.4.1 注释
C++支持两种注释方式:
- 单行注释:以
//开头,注释到行尾。 - 多行注释:以
/*开头,以*/结尾,可跨越多行。
代码示例:
// 单行注释/* * 多行注释 * 可以跨越多行 */intmain(){return0;}2.4.2 新的数据类型
C++在C语言的基础上添加了一些新的数据类型:
- bool:布尔类型,取值为
true或false。 - wchar_t:宽字符类型,用于存储Unicode字符。
- string:字符串类,提供了丰富的字符串操作方法。
- 引用类型:通过
&定义,如int& ref = var;。
代码示例:
#include<iostream>#include<string>usingnamespace std;intmain(){bool flag =true;wchar_t wideChar = L'中'; string str ="Hello, C++";int var =10;int& ref = var;// 引用 cout <<"flag: "<< flag << endl; cout <<"str: "<< str << endl; cout <<"var: "<< var << endl; cout <<"ref: "<< ref << endl; ref =20;// 修改引用,同时修改原变量 cout <<"var after change: "<< var << endl; cout <<"ref after change: "<< ref << endl;return0;}运行结果:
flag: 1 str: Hello, C++ var: 10 ref: 10 var after change: 20 ref after change: 20 2.4.3 灵活的变量说明
C++允许在程序的任何位置声明变量,而C语言要求变量声明必须在函数或代码块的开头。
代码示例:
#include<iostream>usingnamespace std;intmain(){ cout <<"Enter a number: ";int n;// 在需要时声明变量 cin >> n;for(int i =0; i < n; i++){// 在for循环中声明变量 cout << i <<" ";} cout << endl;return0;}运行结果:
Enter a number: 5 0 1 2 3 4 2.4.4 作用域运算符
C++引入了作用域运算符::,用于访问全局变量或类的静态成员。
代码示例:
#include<iostream>usingnamespace std;int x =10;// 全局变量intmain(){int x =20;// 局部变量 cout <<"Local x: "<< x << endl;// 访问局部变量 cout <<"Global x: "<<::x << endl;// 访问全局变量return0;}运行结果:
Local x: 20 Global x: 10 2.4.5 命名空间
命名空间是C++特有的特性,用于解决命名冲突问题。通过namespace关键字定义命名空间,使用using namespace或using指令引入命名空间。
代码示例:
#include<iostream>// 定义命名空间namespace MyNamespace {int value =100;voidprint(){ std::cout <<"MyNamespace::value = "<< value << std::endl;}}// 另一个命名空间namespace AnotherNamespace {int value =200;voidprint(){ std::cout <<"AnotherNamespace::value = "<< value << std::endl;}}intmain(){// 使用命名空间MyNamespace::print();AnotherNamespace::print();// 使用using声明using MyNamespace::value; std::cout <<"Using MyNamespace::value: "<< value << std::endl;// 使用using namespace指令usingnamespace AnotherNamespace; std::cout <<"Using namespace AnotherNamespace: "<< value << std::endl;return0;}运行结果:
MyNamespace::value = 100 AnotherNamespace::value = 200 Using MyNamespace::value: 100 Using namespace AnotherNamespace: 200 2.4.6 新的输入/输出
C++使用标准输入/输出流(iostream)进行输入/输出操作,相比C语言的printf和scanf,更加类型安全和灵活。
代码示例:
#include<iostream>#include<iomanip>usingnamespace std;intmain(){int a;double b; string c;// 输入 cout <<"Enter an integer: "; cin >> a; cout <<"Enter a double: "; cin >> b; cout <<"Enter a string: "; cin >> c;// 输出 cout <<"\nOutput:"<< endl; cout <<"Integer: "<< a << endl; cout <<"Double: "<< fixed <<setprecision(2)<< b << endl; cout <<"String: "<< c << endl;return0;}运行结果:
Enter an integer: 10 Enter a double: 3.14159 Enter a string: Hello Output: Integer: 10 Double: 3.14 String: Hello 2.4.7 头文件
C++标准库的头文件不再使用.h后缀,而是直接使用库名,如iostream、string等。同时,C++仍然支持C语言的头文件,但推荐使用C++风格的头文件,如cstdio、cstdlib等。
代码示例:
// C++风格的头文件#include<iostream>#include<string>#include<vector>// C风格的头文件(不推荐)#include<stdio.h>#include<stdlib.h>// C风格的头文件(推荐的C++写法)#include<cstdio>#include<cstdlib>usingnamespace std;intmain(){ string str ="Hello"; cout << str << endl;return0;}2.4.8 引用
引用是C++特有的概念,作为变量的别名,必须在定义时初始化,并且一旦初始化后不能再绑定到其他变量。
代码示例:
#include<iostream>usingnamespace std;// 引用作为函数参数voidswap(int& a,int& b){int temp = a; a = b; b = temp;}// 引用作为函数返回值int&getElement(int arr[],int index){return arr[index];}intmain(){// 基本引用int x =10;int& ref = x; cout <<"x: "<< x << endl; cout <<"ref: "<< ref << endl; ref =20; cout <<"After change:\n"; cout <<"x: "<< x << endl; cout <<"ref: "<< ref << endl;// 引用作为函数参数int a =5, b =15; cout <<"\nBefore swap:\n"; cout <<"a: "<< a <<", b: "<< b << endl;swap(a, b); cout <<"After swap:\n"; cout <<"a: "<< a <<", b: "<< b << endl;// 引用作为函数返回值int arr[5]={1,2,3,4,5};getElement(arr,2)=100;// 修改数组元素 cout <<"\nArray after modification:\n";for(int i =0; i <5; i++){ cout << arr[i]<<" ";} cout << endl;return0;}运行结果:
x: 10 ref: 10 After change: x: 20 ref: 20 Before swap: a: 5, b: 15 After swap: a: 15, b: 5 Array after modification: 1 2 100 4 5 2.5 Visual C++ 2019开发环境简介
2.5.1 Visual Studio 2019概述
Visual Studio 2019是Microsoft公司开发的集成开发环境(IDE),支持C++、C#、Python等多种编程语言。它提供了代码编辑、编译、调试、测试等一体化的开发工具。
2.5.2 创建C++项目
在Visual Studio 2019中创建C++项目的步骤:
- 打开Visual Studio 2019,点击"创建新项目"。
- 在"创建新项目"对话框中,选择"控制台应用"或"空项目"等C++项目模板。
- 输入项目名称和位置,点击"创建"。
- 在解决方案资源管理器中,右键点击"源文件",选择"添加"→"新建项",添加C++源文件。
- 编写代码,点击"生成"→"生成解决方案"编译项目。
- 点击"调试"→"开始执行(不调试)"运行程序。
2.5.3 调试工具
Visual Studio 2019提供了强大的调试工具,包括:
- 断点:在代码行设置断点,程序运行到断点处会暂停。
- 监视窗口:查看变量和表达式的值。
- 自动窗口:自动显示当前作用域中的变量。
- 局部窗口:显示当前函数中的局部变量。
- 调用堆栈:显示函数调用的层次结构。
- 内存窗口:查看内存中的数据。
小结
C++是一种功能强大的编程语言,它是C语言的超集,同时添加了面向对象编程的特性。C++具有兼容性、高效性、灵活性等特点,支持多种编程范式。本章介绍了C++的发展历程、程序结构、编译执行过程,以及C++相比于C语言的新特性,如引用、命名空间、新的输入/输出等。同时,还简要介绍了Visual C++ 2019开发环境的使用方法。
习题2
- 简述C++的发展历程。
- C++语言有哪些特点?
- 比较C++程序和C程序的结构差异。
- 简述C++程序的编译与执行过程。
- C++相比于C语言有哪些新特性?
- 什么是引用?引用与指针有什么区别?
- 什么是命名空间?它的作用是什么?
- C++的输入/输出与C语言的输入/输出有什么区别?
第3章 类与对象
3.1 类的定义
3.1.1 类定义格式
C++中,类的定义格式如下:
class 类名 {private:// 私有成员 数据成员; 成员函数;protected:// 保护成员 数据成员; 成员函数;public:// 公共成员 数据成员; 成员函数;};其中:
class是定义类的关键字类名是用户定义的标识符private、protected、public是访问控制符,用于控制成员的访问权限- 类体由花括号
{}包围,以分号;结束
代码示例:定义一个学生类
classStudent{private:// 私有数据成员 string name;int age; string id;protected:// 保护数据成员 string department;public:// 公共成员函数voidsetInfo(string n,int a, string i, string d){ name = n; age = a; id = i; department = d;}voiddisplay(){ cout <<"Name: "<< name << endl; cout <<"Age: "<< age << endl; cout <<"ID: "<< id << endl; cout <<"Department: "<< department << endl;}};3.1.2 成员函数的定义
成员函数的定义有两种方式:
- 类内定义:在类的内部直接定义成员函数,这种函数会被自动视为内联函数。
- 类外定义:在类的内部声明成员函数,在类的外部定义成员函数,需要使用作用域运算符
::。
代码示例:类外定义成员函数
classStudent{private: string name;int age;public:// 成员函数声明voidsetName(string n);voidsetAge(int a); string getName();intgetAge();};// 成员函数定义voidStudent::setName(string n){ name = n;}voidStudent::setAge(int a){ age = a;} string Student::getName(){return name;}intStudent::getAge(){return age;}3.1.3 类的作用域
类的作用域是指类中成员的可见范围。在类的作用域内,成员可以相互访问;在类的作用域外,只能通过对象或类名访问公共成员。
代码示例:
classMyClass{private:int x;public:int y;voidsetX(int value){ x = value;// 在类的作用域内,可以访问私有成员}intgetX(){return x;// 在类的作用域内,可以访问私有成员}};intmain(){ MyClass obj; obj.y =10;// 在类的作用域外,可以访问公共成员// obj.x = 20; // 错误:在类的作用域外,不能访问私有成员 obj.setX(20);// 通过公共成员函数访问私有成员 cout <<"x: "<< obj.getX()<< endl; cout <<"y: "<< obj.y << endl;return0;}3.2 对象的定义与使用
3.2.1 对象的定义
对象是类的实例,定义对象的格式如下:
类名 对象名; 类名 对象名(初始化参数);代码示例:
classStudent{private: string name;int age;public:Student(){// 无参构造函数 name =""; age =0;}Student(string n,int a){// 带参构造函数 name = n; age = a;}voiddisplay(){ cout <<"Name: "<< name << endl; cout <<"Age: "<< age << endl;}};intmain(){ Student s1;// 调用无参构造函数 Student s2("Alice",20);// 调用带参构造函数 s1.display(); s2.display();return0;}3.2.2 对象的使用
对象的使用主要包括访问对象的成员和调用对象的成员函数。访问对象成员的格式如下:
对象名.数据成员; 对象名.成员函数(参数);代码示例:
classCircle{private:double radius;public:voidsetRadius(double r){ radius = r;}doublegetRadius(){return radius;}doublearea(){return3.14159* radius * radius;}doubleperimeter(){return2*3.14159* radius;}};intmain(){ Circle c; c.setRadius(5.0);// 调用成员函数 cout <<"Radius: "<< c.getRadius()<< endl; cout <<"Area: "<< c.area()<< endl; cout <<"Perimeter: "<< c.perimeter()<< endl;return0;}3.2.3 对象的赋值
对象可以通过赋值运算符=进行赋值,将一个对象的状态复制到另一个对象。
代码示例:
classPoint{private:int x;int y;public:voidsetPoint(int a,int b){ x = a; y = b;}voiddisplay(){ cout <<"Point: ("<< x <<", "<< y <<")"<< endl;}};intmain(){ Point p1, p2; p1.setPoint(10,20); p2 = p1;// 对象赋值 p1.display(); p2.display();return0;}3.2.4 对象的生命周期
对象的生命周期是指对象从创建到销毁的过程。对象的生命周期取决于其创建方式:
- 局部对象:在函数或代码块中定义的对象,当函数或代码块执行结束时,对象被销毁。
- 全局对象:在函数外部定义的对象,程序开始时创建,程序结束时销毁。
- 静态局部对象:在函数内部使用
static关键字定义的对象,函数第一次调用时创建,程序结束时销毁。 - 动态对象:使用
new运算符创建的对象,需要使用delete运算符手动销毁。
代码示例:
#include<iostream>usingnamespace std;classTest{private:int value;public:Test(int v):value(v){ cout <<"Constructor called, value: "<< value << endl;}~Test(){ cout <<"Destructor called, value: "<< value << endl;}};// 全局对象 Test globalObj(1);voidfunc(){// 静态局部对象static Test staticObj(2);// 局部对象 Test localObj(3); cout <<"Function func() called"<< endl;}intmain(){ cout <<"Main function started"<< endl;func();func();// 第二次调用,staticObj不会重新创建// 动态对象 Test* dynamicObj =newTest(4);delete dynamicObj;// 手动销毁动态对象 cout <<"Main function ended"<< endl;return0;}运行结果:
Constructor called, value: 1 Main function started Constructor called, value: 2 Constructor called, value: 3 Function func() called Destructor called, value: 3 Constructor called, value: 3 Function func() called Destructor called, value: 3 Constructor called, value: 4 Destructor called, value: 4 Main function ended Destructor called, value: 2 Destructor called, value: 1 3.3 构造函数和析构函数
3.3.1 构造函数
构造函数是一种特殊的成员函数,用于在创建对象时初始化对象的数据成员。构造函数具有以下特点:
- 构造函数的名称与类名相同
- 构造函数没有返回值
- 构造函数可以重载
- 构造函数在创建对象时自动调用
- 如果没有定义构造函数,编译器会自动生成一个默认构造函数
代码示例:
classPerson{private: string name;int age;public:// 默认构造函数Person(){ name =""; age =0; cout <<"Default constructor called"<< endl;}// 带参构造函数Person(string n,int a){ name = n; age = a; cout <<"Parameterized constructor called"<< endl;}// 拷贝构造函数Person(const Person& p){ name = p.name; age = p.age; cout <<"Copy constructor called"<< endl;}voiddisplay(){ cout <<"Name: "<< name << endl; cout <<"Age: "<< age << endl;}};intmain(){ Person p1;// 调用默认构造函数 Person p2("Bob",30);// 调用带参构造函数 Person p3 = p2;// 调用拷贝构造函数 cout <<"\np1:"<< endl; p1.display(); cout <<"\np2:"<< endl; p2.display(); cout <<"\np3:"<< endl; p3.display();return0;}运行结果:
Default constructor called Parameterized constructor called Copy constructor called p1: Name: Age: 0 p2: Name: Bob Age: 30 p3: Name: Bob Age: 30 3.3.2 析构函数
析构函数是一种特殊的成员函数,用于在对象销毁时清理资源。析构函数具有以下特点:
- 析构函数的名称是在类名前加上波浪号
~ - 析构函数没有返回值,也没有参数
- 析构函数不能重载,一个类只能有一个析构函数
- 析构函数在对象销毁时自动调用
- 如果没有定义析构函数,编译器会自动生成一个默认析构函数
代码示例:
classMyClass{private:int* data;public:// 构造函数MyClass(int size){ data =newint[size];// 动态分配内存 cout <<"Constructor called, memory allocated"<< endl;}// 析构函数~MyClass(){delete[] data;// 释放内存 cout <<"Destructor called, memory deallocated"<< endl;}voidsetData(int index,int value){ data[index]= value;}intgetData(int index){return data[index];}};intmain(){{ MyClass obj(5);// 创建对象 obj.setData(0,10); obj.setData(1,20); cout <<"Data[0]: "<< obj.getData(0)<< endl; cout <<"Data[1]: "<< obj.getData(1)<< endl;}// 代码块结束,对象销毁,调用析构函数 cout <<"Program ended"<< endl;return0;}运行结果:
Constructor called, memory allocated Data[0]: 10 Data[1]: 20 Destructor called, memory deallocated Program ended 3.4 内存的动态分配
3.4.1 运算符new
new运算符用于动态分配内存,创建动态对象。其格式如下:
指针变量 =new 类型; 指针变量 =new 类型(初始化参数); 指针变量 =new 类型[数组大小];代码示例:
#include<iostream>usingnamespace std;classTest{private:int value;public:Test(){ value =0; cout <<"Default constructor called"<< endl;}Test(int v){ value = v; cout <<"Parameterized constructor called, value: "<< value << endl;}~Test(){ cout <<"Destructor called, value: "<< value << endl;}voidsetValue(int v){ value = v;}intgetValue(){return value;}};intmain(){// 动态分配单个对象 Test* p1 =new Test; p1->setValue(10); cout <<"p1->getValue(): "<< p1->getValue()<< endl;delete p1;// 释放内存// 动态分配带参数的对象 Test* p2 =newTest(20); cout <<"p2->getValue(): "<< p2->getValue()<< endl;delete p2;// 释放内存// 动态分配对象数组 Test* p3 =new Test[3];for(int i =0; i <3; i++){ p3[i].setValue(30+ i); cout <<"p3["<< i <<"].getValue(): "<< p3[i].getValue()<< endl;}delete[] p3;// 释放数组内存return0;}运行结果:
Default constructor called p1->getValue(): 10 Destructor called, value: 10 Parameterized constructor called, value: 20 p2->getValue(): 20 Destructor called, value: 20 Default constructor called Default constructor called Default constructor called p3[0].getValue(): 30 p3[1].getValue(): 31 p3[2].getValue(): 32 Destructor called, value: 32 Destructor called, value: 31 Destructor called, value: 30 3.4.2 运算符delete
delete运算符用于释放new运算符分配的内存。其格式如下:
delete 指针变量;// 释放单个对象delete[] 指针变量;// 释放对象数组注意事项:
- 使用
delete释放的内存必须是new分配的 - 不要重复释放同一块内存
- 释放内存后,指针变为野指针,应将其设置为
nullptr
代码示例:
#include<iostream>usingnamespace std;intmain(){// 动态分配内存int* p =newint(10); cout <<"*p: "<<*p << endl;// 释放内存delete p; p =nullptr;// 避免野指针// 动态分配数组int* arr =newint[5];for(int i =0; i <5; i++){ arr[i]= i +1; cout <<"arr["<< i <<"]: "<< arr[i]<< endl;}// 释放数组内存delete[] arr; arr =nullptr;// 避免野指针return0;}3.5 对象数组和对象指针
3.5.1 对象数组
对象数组是由多个相同类型的对象组成的数组。定义对象数组的格式如下:
类名 数组名[数组大小]; 类名 数组名[数组大小]={构造函数参数列表1, 构造函数参数列表2,...};代码示例:
#include<iostream>#include<string>usingnamespace std;classStudent{private: string name;int age;public:Student(){ name =""; age =0;}Student(string n,int a){ name = n; age = a;}voiddisplay(){ cout <<"Name: "<< name <<", Age: "<< age << endl;}};intmain(){// 定义对象数组 Student stuArray[3]={Student("Alice",20),Student("Bob",21),Student("Charlie",22)};// 访问对象数组元素for(int i =0; i <3; i++){ cout <<"Student "<< i +1<<":"<< endl; stuArray[i].display();}return0;}运行结果:
Student 1: Name: Alice, Age: 20 Student 2: Name: Bob, Age: 21 Student 3: Name: Charlie, Age: 22 3.5.2 对象指针
对象指针是指向对象的指针,可以通过对象指针访问对象的成员。定义对象指针的格式如下:
类名* 指针变量; 指针变量 =&对象名; 指针变量 =new 类名(初始化参数);代码示例:
#include<iostream>usingnamespace std;classCircle{private:double radius;public:Circle(double r):radius(r){}doublearea(){return3.14159* radius * radius;}doubleperimeter(){return2*3.14159* radius;}};intmain(){// 栈上创建对象,使用指针指向它 Circle c1(5.0); Circle* p1 =&c1; cout <<"c1 area: "<< p1->area()<< endl; cout <<"c1 perimeter: "<< p1->perimeter()<< endl;// 堆上创建对象,使用指针指向它 Circle* p2 =newCircle(10.0); cout <<"c2 area: "<< p2->area()<< endl; cout <<"c2 perimeter: "<< p2->perimeter()<< endl;delete p2;// 释放内存return0;}运行结果:
c1 area: 78.5397 c1 perimeter: 31.4159 c2 area: 314.159 c2 perimeter: 62.8318 3.5.3 自引用指针this
this是一个特殊的指针,它指向当前对象。在成员函数中,可以使用this指针访问当前对象的成员。
代码示例:
#include<iostream>usingnamespace std;classPerson{private: string name;int age;public:Person(string n,int a){this->name = n;// 使用this指针访问成员变量this->age = a;} Person&setName(string n){this->name = n;return*this;// 返回当前对象的引用} Person&setAge(int a){this->age = a;return*this;// 返回当前对象的引用}voiddisplay(){ cout <<"Name: "<<this->name <<", Age: "<<this->age << endl;}};intmain(){ Person p("Alice",20); p.display();// 链式调用 p.setName("Bob").setAge(21); p.display();return0;}运行结果:
Name: Alice, Age: 20 Name: Bob, Age: 21 小结
本章介绍了C++中类与对象的基本概念和使用方法,包括:
- 类的定义:类的定义格式、成员函数的定义、类的作用域
- 对象的定义与使用:对象的定义、使用、赋值、生命周期
- 构造函数和析构函数:构造函数的定义和使用、析构函数的定义和使用
- 内存的动态分配:
new运算符和delete运算符的使用 - 对象数组和对象指针:对象数组的定义和使用、对象指针的定义和使用、自引用指针
this
类与对象是C++面向对象编程的核心概念,通过类的封装性,可以将数据和操作数据的方法组合在一起,提高代码的可维护性和可重用性。
习题3
- 简述类的定义格式和访问控制符的作用。
- 成员函数的定义有哪两种方式?它们有什么区别?
- 什么是构造函数?构造函数有哪些特点?
- 什么是析构函数?析构函数有哪些特点?
- 简述对象的生命周期。
- 如何使用
new和delete运算符进行动态内存分配和释放? - 什么是对象数组?如何定义和使用对象数组?
- 什么是对象指针?如何通过对象指针访问对象的成员?
- 什么是
this指针?它的作用是什么?
第4章 函数
4.1 函数参数的传递机制
函数参数的传递机制有三种:值传递、指针传递和引用传递。
4.1.1 使用对象作为函数参数
当使用对象作为函数参数时,默认采用值传递方式,即创建对象的副本传递给函数。
代码示例:
#include<iostream>#include<string>usingnamespace std;classStudent{private: string name;int age;public:Student(string n,int a){ name = n; age = a; cout <<"Constructor called"<< endl;}Student(const Student& s){ name = s.name; age = s.age; cout <<"Copy constructor called"<< endl;}~Student(){ cout <<"Destructor called"<< endl;}voidsetName(string n){ name = n;}voidsetAge(int a){ age = a;}voiddisplay(){ cout <<"Name: "<< name <<", Age: "<< age << endl;}};// 值传递:对象作为函数参数voidmodifyStudent(Student s){ s.setName("Bob"); s.setAge(21); cout <<"Inside modifyStudent():"<< endl; s.display();}intmain(){ Student stu("Alice",20); cout << "Before modifyStudent(): "; stu.display();modifyStudent(stu); cout << "After modifyStudent(): "; stu.display();return0;}运行结果:
Constructor called Before modifyStudent(): Name: Alice, Age: 20 Copy constructor called Inside modifyStudent(): Name: Bob, Age: 21 Destructor called After modifyStudent(): Name: Alice, Age: 20 Destructor called 4.1.2 使用对象指针作为函数参数
使用对象指针作为函数参数时,传递的是对象的地址,不会创建对象的副本,提高了函数调用的效率。
代码示例:
#include<iostream>#include<string>usingnamespace std;classStudent{private: string name;int age;public:Student(string n,int a){ name = n; age = a; cout <<"Constructor called"<< endl;}~Student(){ cout <<"Destructor called"<< endl;}voidsetName(string n){ name = n;}voidsetAge(int a){ age = a;}voiddisplay(){ cout <<"Name: "<< name <<", Age: "<< age << endl;}};// 指针传递:对象指针作为函数参数voidmodifyStudent(Student* s){ s->setName("Bob"); s->setAge(21); cout << "Inside modifyStudent(): "; s->display();}intmain(){ Student stu("Alice",20); cout << "Before modifyStudent(): "; stu.display();modifyStudent(&stu); cout << "After modifyStudent(): "; stu.display();return0;}运行结果:
Constructor called Before modifyStudent(): Name: Alice, Age: 20 Inside modifyStudent(): Name: Bob, Age: 21 After modifyStudent(): Name: Bob, Age: 21 Destructor called 4.1.3 使用对象引用作为函数参数
使用对象引用作为函数参数时,传递的是对象的别名,不会创建对象的副本,同时使用方式更加简洁。
代码示例:
#include<iostream>#include<string>usingnamespace std;classStudent{private: string name;int age;public:Student(string n,int a){ name = n; age = a; cout <<"Constructor called"<< endl;}~Student(){ cout <<"Destructor called"<< endl;}voidsetName(string n){ name = n;}voidsetAge(int a){ age = a;}voiddisplay(){ cout <<"Name: "<< name <<", Age: "<< age << endl;}};// 引用传递:对象引用作为函数参数voidmodifyStudent(Student& s){ s.setName("Bob"); s.setAge(21); cout << "Inside modifyStudent(): "; s.display();}intmain(){ Student stu("Alice",20); cout << "Before modifyStudent(): "; stu.display();modifyStudent(stu); cout << "After modifyStudent(): "; stu.display();return0;}运行结果:
Constructor called Before modifyStudent(): Name: Alice, Age: 20 Inside modifyStudent(): Name: Bob, Age: 21 After modifyStudent(): Name: Bob, Age: 21 Destructor called 4.1.4 三种传递方式比较
| 传递方式 | 特点 | 优点 | 缺点 |
|---|---|---|---|
| 值传递 | 创建对象副本 | 不会修改原对象 | 效率低,特别是对于大对象 |
| 指针传递 | 传递对象地址 | 效率高,可修改原对象 | 语法复杂,容易出错 |
| 引用传递 | 传递对象别名 | 效率高,可修改原对象,语法简洁 | 可能意外修改原对象 |
4.2 内联函数
内联函数是一种特殊的函数,在编译时会将函数调用替换为函数体,从而减少函数调用的开销。
代码示例:
#include<iostream>usingnamespace std;// 使用inline关键字定义内联函数inlineintmax(int a,int b){return a > b ? a : b;}intmain(){int x =10, y =20; cout <<"Max: "<<max(x, y)<< endl;return0;}注意事项:
- 内联函数适用于函数体较小、调用频繁的场景
- 内联函数的定义通常放在头文件中
- 递归函数不能定义为内联函数
- 含有循环、switch等复杂结构的函数不适合定义为内联函数
4.3 函数重载
函数重载是指在同一个作用域内,定义多个同名函数,但它们的参数列表不同(参数个数、类型或顺序不同)。
4.3.1 成员函数重载
类的成员函数也可以重载。
代码示例:
#include<iostream>#include<string>usingnamespace std;classCalculator{private:int result;public:Calculator(){ result =0;}// 重载add函数:两个整数相加intadd(int a,int b){ result = a + b;return result;}// 重载add函数:三个整数相加intadd(int a,int b,int c){ result = a + b + c;return result;}// 重载add函数:两个浮点数相加doubleadd(double a,double b){double res = a + b; result =static_cast<int>(res);return res;}intgetResult(){return result;}};intmain(){ Calculator calc; cout <<"add(10, 20): "<< calc.add(10,20)<< endl; cout <<"Result: "<< calc.getResult()<< endl; cout <<"add(10, 20, 30): "<< calc.add(10,20,30)<< endl; cout <<"Result: "<< calc.getResult()<< endl; cout <<"add(10.5, 20.7): "<< calc.add(10.5,20.7)<< endl; cout <<"Result: "<< calc.getResult()<< endl;return0;}运行结果:
add(10, 20): 30 Result: 30 add(10, 20, 30): 60 Result: 60 add(10.5, 20.7): 31.2 Result: 31 4.3.2 成员函数重载的规则
成员函数重载的规则:
- 函数名必须相同
- 参数列表必须不同(参数个数、类型或顺序不同)
- 返回类型可以相同也可以不同
- 重载函数必须在同一个作用域内
4.4 函数的默认参数
函数的默认参数是指在函数声明时为参数指定默认值,调用函数时如果不提供该参数,则使用默认值。
代码示例:
#include<iostream>#include<string>usingnamespace std;// 函数声明:带有默认参数voidprintInfo(string name,int age =18, string department ="Computer Science");// 函数定义voidprintInfo(string name,int age, string department){ cout <<"Name: "<< name << endl; cout <<"Age: "<< age << endl; cout <<"Department: "<< department << endl;}intmain(){// 提供所有参数printInfo("Alice",20,"Software Engineering"); cout << endl;// 提供部分参数,使用默认值printInfo("Bob",21); cout << endl;// 只提供第一个参数,其余使用默认值printInfo("Charlie");return0;}运行结果:
Name: Alice Age: 20 Department: Software Engineering Name: Bob Age: 21 Department: Computer Science Name: Charlie Age: 18 Department: Computer Science 注意事项:
- 默认参数必须从右向左连续设置
- 函数声明和定义中只能在一处指定默认参数
- 默认参数的类型必须与参数类型匹配
4.5 友元
友元是一种特殊的机制,允许类的非成员函数或其他类访问该类的私有成员。
4.5.1 友元函数
友元函数是在类中声明的非成员函数,可以访问类的私有成员。
代码示例:
#include<iostream>usingnamespace std;classRectangle{private:double width;double height;public:Rectangle(double w,double h){ width = w; height = h;}// 声明友元函数frienddoublecalculateArea(Rectangle r);frienddoublecalculatePerimeter(Rectangle r);};// 友元函数定义:可以访问Rectangle的私有成员doublecalculateArea(Rectangle r){return r.width * r.height;}// 友元函数定义:可以访问Rectangle的私有成员doublecalculatePerimeter(Rectangle r){return2*(r.width + r.height);}intmain(){ Rectangle rect(5.0,3.0); cout <<"Area: "<<calculateArea(rect)<< endl; cout <<"Perimeter: "<<calculatePerimeter(rect)<< endl;return0;}运行结果:
Area: 15 Perimeter: 16 4.5.2 友元类
友元类是指一个类可以访问另一个类的私有成员。
代码示例:
#include<iostream>#include<string>usingnamespace std;classStudent{private: string name;int age; string id;public:Student(string n,int a, string i){ name = n; age = a; id = i;}// 声明友元类friendclassTeacher;};classTeacher{private: string name;public:Teacher(string n){ name = n;}// 可以访问Student的私有成员voidcheckStudent(Student s){ cout <<"Teacher "<< name << " checking student: "; cout <<"Name: "<< s.name << endl; cout <<"Age: "<< s.age << endl; cout <<"ID: "<< s.id << endl;}};intmain(){ Student stu("Alice",20,"20200101"); Teacher teacher("Mr. Smith"); teacher.checkStudent(stu);return0;}运行结果:
Teacher Mr. Smith checking student: Name: Alice Age: 20 ID: 20200101 4.6 静态成员
静态成员是属于类而不是对象的成员,所有对象共享同一个静态成员。
4.6.1 静态数据成员
静态数据成员是类的静态成员变量,需要在类的外部初始化。
代码示例:
#include<iostream>#include<string>usingnamespace std;classStudent{private: string name;int age;staticint count;// 静态数据成员声明public:Student(string n,int a){ name = n; age = a; count++;// 每次创建对象时,count加1}~Student(){ count--;// 每次销毁对象时,count减1}staticintgetCount(){// 静态成员函数return count;}voiddisplay(){ cout <<"Name: "<< name <<", Age: "<< age << endl;}};// 静态数据成员初始化int Student::count =0;intmain(){ cout <<"Initial count: "<<Student::getCount()<< endl; Student stu1("Alice",20); cout <<"After creating stu1, count: "<<Student::getCount()<< endl; Student stu2("Bob",21); cout <<"After creating stu2, count: "<<Student::getCount()<< endl;{ Student stu3("Charlie",22); cout <<"Inside block, count: "<<Student::getCount()<< endl;}// 代码块结束,stu3销毁 cout <<"After block, count: "<<Student::getCount()<< endl;return0;}运行结果:
Initial count: 0 After creating stu1, count: 1 After creating stu2, count: 2 Inside block, count: 3 After block, count: 2 4.6.2 静态成员函数
静态成员函数是类的静态成员,只能访问静态成员变量和其他静态成员函数,不能访问非静态成员。
代码示例:
#include<iostream>usingnamespace std;classMath{private:staticconstdouble PI;// 静态常量public:// 静态成员函数staticdoublecircleArea(double radius){return PI * radius * radius;}staticdoublecirclePerimeter(double radius){return2* PI * radius;}// 静态成员函数不能访问非静态成员/* static void test() { cout << nonStaticMember; // 错误:静态成员函数不能访问非静态成员 } */};// 静态常量初始化constdouble Math::PI =3.14159;intmain(){double r =5.0; cout <<"Radius: "<< r << endl; cout <<"Area: "<<Math::circleArea(r)<< endl; cout <<"Perimeter: "<<Math::circlePerimeter(r)<< endl;return0;}运行结果:
Radius: 5 Area: 78.5397 Perimeter: 31.4159 4.6.3 静态对象
静态对象是使用static关键字定义的对象,其生命周期从创建时开始,到程序结束时结束。
代码示例:
#include<iostream>usingnamespace std;classTest{private:int value;public:Test(int v):value(v){ cout <<"Constructor called, value: "<< value << endl;}~Test(){ cout <<"Destructor called, value: "<< value << endl;}voidsetValue(int v){ value = v;}intgetValue(){return value;}};voidfunc(){// 静态对象static Test staticObj(10);// 局部对象 Test localObj(20); cout << "Inside func(): "; cout <<"staticObj.value: "<< staticObj.getValue()<< endl; cout <<"localObj.value: "<< localObj.getValue()<< endl; staticObj.setValue(100); localObj.setValue(200);}intmain(){ cout << "First call to func(): ";func(); cout << "\nSecond call to func(): ";func(); cout <<"\nMain function ended\n";return0;}运行结果:
First call to func(): Constructor called, value: 10 Constructor called, value: 20 Inside func(): staticObj.value: 10 localObj.value: 20 Destructor called, value: 200 Second call to func(): Constructor called, value: 20 Inside func(): staticObj.value: 100 localObj.value: 20 Destructor called, value: 200 Main function ended Destructor called, value: 100 4.7 应用实例
代码示例:实现一个简单的计算器类
#include<iostream>usingnamespace std;classCalculator{private:double result;staticint count;// 静态数据成员,记录计算器使用次数public:Calculator(){ result =0; count++;}// 重载add函数doubleadd(double a,double b){ result = a + b;return result;}doubleadd(double a,double b,double c){ result = a + b + c;return result;}// 重载subtract函数doublesubtract(double a,double b){ result = a - b;return result;}// 重载multiply函数doublemultiply(double a,double b){ result = a * b;return result;}// 重载divide函数doubledivide(double a,double b){if(b !=0){ result = a / b;return result;}else{ cout <<"Error: Division by zero!"<< endl;return0;}}doublegetResult(){return result;}staticintgetCount(){return count;}};// 静态数据成员初始化int Calculator::count =0;intmain(){ Calculator calc; cout <<"Calculator count: "<<Calculator::getCount()<< endl; cout <<"10 + 20 = "<< calc.add(10,20)<< endl; cout <<"10 + 20 + 30 = "<< calc.add(10,20,30)<< endl; cout <<"50 - 20 = "<< calc.subtract(50,20)<< endl; cout <<"10 * 20 = "<< calc.multiply(10,20)<< endl; cout <<"100 / 20 = "<< calc.divide(100,20)<< endl; cout <<"100 / 0 = "<< calc.divide(100,0)<< endl;return0;}运行结果:
Calculator count: 1 10 + 20 = 30 10 + 20 + 30 = 60 50 - 20 = 30 10 * 20 = 200 100 / 20 = 5 Error: Division by zero! 100 / 0 = 0 小结
本章介绍了C++中函数的高级特性,包括:
- 函数参数的传递机制:值传递、指针传递和引用传递
- 内联函数:减少函数调用开销的特殊函数
- 函数重载:同名函数的不同实现
- 函数的默认参数:为函数参数指定默认值
- 友元:允许非成员函数或其他类访问类的私有成员
- 静态成员:属于类而不是对象的成员
这些特性使得C++函数更加灵活和强大,能够适应各种复杂的编程场景。
习题4
- 简述函数参数的三种传递机制及其特点。
- 什么是内联函数?它的作用是什么?
- 什么是函数重载?函数重载的规则是什么?
- 什么是函数的默认参数?使用默认参数时需要注意什么?
- 什么是友元函数?什么是友元类?
- 什么是静态数据成员?如何初始化静态数据成员?
- 什么是静态成员函数?它与普通成员函数有什么区别?
- 什么是静态对象?它的生命周期有什么特点?
第5章 const
5.1 const的最初动机
const关键字的引入是为了替代C语言中的#define宏定义,解决宏定义带来的问题。
5.1.1 由define引发的问题
#define宏定义存在以下问题:
- 无类型检查:宏定义是在预处理阶段进行文本替换,没有类型检查
- 作用域问题:宏定义的作用域从定义处开始到文件结束,无法限制在特定作用域内
- 优先级问题:宏定义的表达式可能因优先级问题导致计算结果错误
- 调试困难:宏定义在预处理阶段被替换,调试时无法看到宏的原始形式
代码示例:#define宏定义的问题
#include<iostream>usingnamespace std;// 宏定义:计算平方#defineSQUARE(x) x * x// 宏定义:常量#definePI3.14159intmain(){// 优先级问题int a =5; cout <<"SQUARE(a + 1) = "<<SQUARE(a +1)<< endl;// 结果是 5 + 1 * 5 + 1 = 11,不是 6 * 6 = 36// 正确的宏定义应该使用括号#defineSQUARE_CORRECT(x)(x)*(x) cout <<"SQUARE_CORRECT(a + 1) = "<<SQUARE_CORRECT(a +1)<< endl;// 结果是 (5 + 1) * (5 + 1) = 36return0;}运行结果:
SQUARE(a + 1) = 11 SQUARE_CORRECT(a + 1) = 36 5.1.2 const的优势
使用const关键字定义常量的优势:
- 类型安全:const常量有明确的类型,编译器会进行类型检查
- 作用域控制:const常量可以限制在特定的作用域内
- 无优先级问题:const常量是在编译阶段处理,不会有优先级问题
- 易于调试:const常量在调试时可以看到其原始形式
- 节省内存:const常量只存储一份,而#define宏在每个使用处都会替换
5.2 const与指针
const与指针的组合有四种形式,需要注意它们的区别。
5.2.1 指向常量的指针
指向常量的指针表示指针指向的内容是常量,不能通过指针修改该内容。
代码示例:
#include<iostream>usingnamespace std;intmain(){int a =10;constint* p =&a;// 指向常量的指针// *p = 20; // 错误:不能通过p修改a的值 a =20;// 正确:可以直接修改a的值 cout <<"a = "<< a << endl; cout <<"*p = "<<*p << endl;// 可以改变指针指向int b =30; p =&b; cout <<"*p = "<<*p << endl;return0;}运行结果:
a = 20 *p = 20 *p = 30 5.2.2 常量指针
常量指针表示指针本身是常量,不能改变指针的指向,但可以通过指针修改指向的内容。
代码示例:
#include<iostream>usingnamespace std;intmain(){int a =10;int*const p =&a;// 常量指针*p =20;// 正确:可以通过p修改a的值 cout <<"a = "<< a << endl; cout <<"*p = "<<*p << endl;// int b = 30;// p = &b; // 错误:不能改变p的指向return0;}运行结果:
a = 20 *p = 20 5.2.3 指向常量的常量指针
指向常量的常量指针表示指针本身是常量,且指向的内容也是常量,既不能改变指针的指向,也不能通过指针修改指向的内容。
代码示例:
#include<iostream>usingnamespace std;intmain(){int a =10;constint*const p =&a;// 指向常量的常量指针// *p = 20; // 错误:不能通过p修改a的值 a =20;// 正确:可以直接修改a的值 cout <<"a = "<< a << endl; cout <<"*p = "<<*p << endl;// int b = 30;// p = &b; // 错误:不能改变p的指向return0;}运行结果:
a = 20 *p = 20 5.3 const与引用
const与引用的组合主要有两种形式:引用常量和常量引用。
5.3.1 引用常量
引用常量表示引用的对象是常量,不能通过引用修改该对象的值。
代码示例:
#include<iostream>usingnamespace std;intmain(){constint a =10;constint& ref = a;// 引用常量// ref = 20; // 错误:不能通过ref修改a的值// a = 20; // 错误:a是常量,不能修改 cout <<"a = "<< a << endl; cout <<"ref = "<< ref << endl;return0;}运行结果:
a = 10 ref = 10 5.3.2 常量引用
常量引用是一种特殊的引用,可以绑定到常量或非常量对象,也可以绑定到临时对象。
代码示例:
#include<iostream>usingnamespace std;intmain(){// 绑定到非常量对象int a =10;constint& ref1 = a; cout <<"a = "<< a <<", ref1 = "<< ref1 << endl; a =20;// 可以修改a的值 cout <<"a = "<< a <<", ref1 = "<< ref1 << endl;// 绑定到常量对象constint b =30;constint& ref2 = b; cout <<"b = "<< b <<", ref2 = "<< ref2 << endl;// 绑定到临时对象constint& ref3 =40; cout <<"ref3 = "<< ref3 << endl;return0;}运行结果:
a = 10, ref1 = 10 a = 20, ref1 = 20 b = 30, ref2 = 30 ref3 = 40 5.4 const与函数
const可以应用于函数的参数、返回值和成员函数。
5.4.1 const类型的参数
使用const类型的参数可以防止函数修改参数的值,提高函数的安全性和可维护性。
代码示例:
#include<iostream>#include<string>usingnamespace std;// const参数:防止修改字符串voidprintString(const string& str){// str = "Hello World"; // 错误:不能修改const参数 cout <<"String: "<< str << endl;}// const参数:防止修改数组voidprintArray(constint arr[],int size){for(int i =0; i < size; i++){// arr[i] = 0; // 错误:不能修改const参数 cout << arr[i]<<" ";} cout << endl;}intmain(){ string s ="Hello C++";printString(s);int arr[]={1,2,3,4,5};printArray(arr,5);return0;}运行结果:
String: Hello C++ 1 2 3 4 5 5.4.2 const类型的返回值
使用const类型的返回值可以防止返回值被修改,提高函数的安全性。
代码示例:
#include<iostream>usingnamespace std;// 返回const引用,防止修改返回值constint&getMax(constint& a,constint& b){return a > b ? a : b;}intmain(){int x =10, y =20;constint& maxVal =getMax(x, y); cout <<"Max value: "<< maxVal << endl;// maxVal = 30; // 错误:不能修改const返回值return0;}运行结果:
Max value: 20 5.4.3 const在传递地址中的应用
当函数参数是指针或引用时,使用const可以防止函数修改指针或引用指向的内容。
代码示例:
#include<iostream>usingnamespace std;// const指针参数:防止修改指针指向的内容voidprintValue(constint* p){// *p = 100; // 错误:不能修改const指针指向的内容 cout <<"Value: "<<*p << endl;}// const引用参数:防止修改引用的对象voidprintValue(constint& ref){// ref = 100; // 错误:不能修改const引用的对象 cout <<"Value: "<< ref << endl;}intmain(){int a =50;printValue(&a);// 传递指针printValue(a);// 传递引用return0;}运行结果:
Value: 50 Value: 50 5.5 const与类
const在类中的应用主要包括const数据成员、const成员函数和const对象。
5.5.1 类内const局部常量
类内的const局部常量必须在构造函数的初始化列表中初始化。
代码示例:
#include<iostream>usingnamespace std;classCircle{private:constdouble PI;// const数据成员double radius;public:// 构造函数:必须在初始化列表中初始化const数据成员Circle(double r):PI(3.14159),radius(r){}doublearea(){return PI * radius * radius;}doubleperimeter(){return2* PI * radius;}voidsetRadius(double r){ radius = r;}doublegetRadius(){return radius;}};intmain(){ Circle c(5.0); cout <<"Radius: "<< c.getRadius()<< endl; cout <<"Area: "<< c.area()<< endl; cout <<"Perimeter: "<< c.perimeter()<< endl; c.setRadius(10.0); cout <<"\nAfter changing radius:\n"; cout <<"Radius: "<< c.getRadius()<< endl; cout <<"Area: "<< c.area()<< endl; cout <<"Perimeter: "<< c.perimeter()<< endl;return0;}运行结果:
Radius: 5 Area: 78.5397 Perimeter: 31.4159 After changing radius: Radius: 10 Area: 314.159 Perimeter: 62.8318 5.5.2 常对象与常成员函数
常对象是指通过const关键字定义的对象,常对象只能调用常成员函数。
代码示例:
#include<iostream>#include<string>usingnamespace std;classStudent{private: string name;int age;public:Student(string n,int a){ name = n; age = a;}// 常成员函数:不能修改类的数据成员voiddisplay()const{ cout <<"Name: "<< name << endl; cout <<"Age: "<< age << endl;}// 非常成员函数:可以修改类的数据成员voidsetName(string n){ name = n;}voidsetAge(int a){ age = a;}// 常成员函数:返回name string getName()const{return name;}// 常成员函数:返回ageintgetAge()const{return age;}};intmain(){// 非常对象 Student stu1("Alice",20); stu1.display(); stu1.setName("Bob"); stu1.setAge(21); stu1.display();// 常对象const Student stu2("Charlie",22); stu2.display();// 正确:常对象可以调用常成员函数// stu2.setName("David"); // 错误:常对象不能调用非常成员函数// stu2.setAge(23); // 错误:常对象不能调用非常成员函数 cout <<"stu2.getName(): "<< stu2.getName()<< endl;// 正确:常对象可以调用常成员函数 cout <<"stu2.getAge(): "<< stu2.getAge()<< endl;// 正确:常对象可以调用常成员函数return0;}运行结果:
Name: Alice Age: 20 Name: Bob Age: 21 Name: Charlie Age: 22 stu2.getName(): Charlie stu2.getAge(): 22 5.6 构造函数与析构函数
构造函数和析构函数是类的特殊成员函数,它们的行为与const有一定的关系。
5.6.1 构造函数与const
构造函数可以初始化const数据成员,但不能被声明为const成员函数,因为构造函数的目的是初始化对象,需要修改对象的状态。
5.6.2 析构函数与const
析构函数也不能被声明为const成员函数,因为析构函数的目的是清理对象,可能需要修改对象的状态。
小结
本章介绍了C++中const关键字的使用方法和技巧,包括:
- const的最初动机:替代#define宏定义,解决宏定义带来的问题
- const与指针:指向常量的指针、常量指针、指向常量的常量指针
- const与引用:引用常量、常量引用
- const与函数:const类型的参数、const类型的返回值、const在传递地址中的应用
- const与类:类内const局部常量、常对象与常成员函数
- 构造函数与析构函数:构造函数可以初始化const数据成员,析构函数的行为
const关键字是C++中一个非常重要的特性,合理使用const可以提高代码的安全性、可读性和可维护性。
习题5
- 简述const关键字的最初动机和优势。
- const与指针的组合有哪几种形式?它们的区别是什么?
- const与引用的组合有哪几种形式?它们的区别是什么?
- 如何在类中使用const数据成员?
- 什么是常成员函数?常成员函数有什么特点?
- 什么是常对象?常对象有什么特点?
- 构造函数和析构函数可以被声明为const成员函数吗?为什么?
- 请举例说明const在函数参数中的应用。
第6章 运算符重载
6.1 运算符重载的基本概念
运算符重载是指赋予运算符新的含义,使得用户定义的类型可以像内置类型一样使用运算符。
6.1.1 运算符重载的意义
运算符重载的意义:
- 使代码更直观:使用运算符可以使代码更加简洁和直观,提高代码的可读性
- 保持语言的一致性:用户定义的类型可以像内置类型一样使用运算符,保持语言的一致性
- 提高代码的可维护性:运算符重载可以封装复杂的操作,提高代码的可维护性
6.1.2 运算符重载的规则
运算符重载的规则:
- 不能创建新的运算符:只能重载已有的运算符
- 不能改变运算符的优先级:运算符的优先级在重载后保持不变
- 不能改变运算符的结合性:运算符的结合性在重载后保持不变
- 不能改变运算符的操作数个数:运算符的操作数个数在重载后保持不变
- 有些运算符不能重载:如
.、.*、::、?:、sizeof等
6.2 成员函数重载运算符
运算符重载可以通过成员函数或友元函数实现。成员函数重载运算符时,运算符的第一个操作数是当前对象。
6.2.1 重载单目运算符
单目运算符是只有一个操作数的运算符,如++、--、-、!等。
代码示例:重载自增运算符++
#include<iostream>usingnamespace std;classCounter{private:int value;public:Counter(int v =0):value(v){}// 重载前缀自增运算符 Counter&operator++(){++value;return*this;}// 重载后缀自增运算符 Counter operator++(int){ Counter temp =*this;++value;return temp;}intgetValue()const{return value;}voiddisplay()const{ cout <<"Value: "<< value << endl;}};intmain(){ Counter c(5); c.display();// 前缀自增++c; c.display();// 后缀自增 Counter d = c++; c.display(); d.display();return0;}运行结果:
Value: 5 Value: 6 Value: 7 Value: 6 6.2.2 重载双目运算符
双目运算符是有两个操作数的运算符,如+、-、*、/、==、!=等。
代码示例:重载加法运算符+
#include<iostream>usingnamespace std;classComplex{private:double real;double imag;public:Complex(double r =0,double i =0):real(r),imag(i){}// 重载加法运算符 Complex operator+(const Complex& other)const{returnComplex(real + other.real, imag + other.imag);}voiddisplay()const{ cout << real <<" + "<< imag <<"i"<< endl;}};intmain(){ Complex c1(1,2); Complex c2(3,4); Complex c3 = c1 + c2; cout <<"c1: "; c1.display(); cout <<"c2: "; c2.display(); cout <<"c3 = c1 + c2: "; c3.display();return0;}运行结果:
c1: 1 + 2i c2: 3 + 4i c3 = c1 + c2: 4 + 6i 6.3 友元函数重载运算符
友元函数重载运算符时,运算符的所有操作数都作为函数参数传递。
6.3.1 友元函数重载运算符的优势
友元函数重载运算符的优势:
- 可以处理左操作数不是类对象的情况:如
3 + complex - 可以保持运算符的对称性:运算符的两个操作数都作为参数传递,更加对称
代码示例:使用友元函数重载加法运算符+
#include<iostream>usingnamespace std;classComplex{private:double real;double imag;public:Complex(double r =0,double i =0):real(r),imag(i){}// 声明友元函数friend Complex operator+(const Complex& c1,const Complex& c2);friend Complex operator+(double d,const Complex& c);friend Complex operator+(const Complex& c,double d);voiddisplay()const{ cout << real <<" + "<< imag <<"i"<< endl;}};// 友元函数:重载两个Complex对象的加法 Complex operator+(const Complex& c1,const Complex& c2){returnComplex(c1.real + c2.real, c1.imag + c2.imag);}// 友元函数:重载double和Complex对象的加法 Complex operator+(double d,const Complex& c){returnComplex(d + c.real, c.imag);}// 友元函数:重载Complex对象和double的加法 Complex operator+(const Complex& c,double d){returnComplex(c.real + d, c.imag);}intmain(){ Complex c1(1,2); Complex c2(3,4);// 两个Complex对象相加 Complex c3 = c1 + c2;// double和Complex对象相加 Complex c4 =5.5+ c1;// Complex对象和double相加 Complex c5 = c1 +6.5; cout <<"c1: "; c1.display(); cout <<"c2: "; c2.display(); cout <<"c1 + c2: "; c3.display(); cout <<"5.5 + c1: "; c4.display(); cout <<"c1 + 6.5: "; c5.display();return0;}运行结果:
c1: 1 + 2i c2: 3 + 4i c1 + c2: 4 + 6i 5.5 + c1: 6.5 + 2i c1 + 6.5: 7.5 + 2i 6.4 重载赋值运算符
赋值运算符=是一个特殊的运算符,用于将一个对象的值赋给另一个对象。
代码示例:重载赋值运算符
#include<iostream>#include<string>usingnamespace std;classStudent{private: string name;int age; string id;public:Student(string n ="",int a =0, string i =""):name(n),age(a),id(i){}// 重载赋值运算符 Student&operator=(const Student& other){// 防止自赋值if(this!=&other){ name = other.name; age = other.age; id = other.id;}return*this;}voiddisplay()const{ cout <<"Name: "<< name << endl; cout <<"Age: "<< age << endl; cout <<"ID: "<< id << endl;}};intmain(){ Student stu1("Alice",20,"20200101"); Student stu2; cout << "stu1: "; stu1.display(); cout << "stu2 before assignment: "; stu2.display();// 使用赋值运算符 stu2 = stu1; cout << "stu2 after assignment: "; stu2.display();return0;}运行结果:
stu1: Name: Alice Age: 20 ID: 20200101 stu2 before assignment: Name: Age: 0 ID: stu2 after assignment: Name: Alice Age: 20 ID: 20200101 6.5 重载下标运算符
下标运算符[]用于访问数组元素,重载后可以使类的对象像数组一样使用下标访问。
代码示例:重载下标运算符
#include<iostream>usingnamespace std;classArray{private:int* data;int size;public:Array(int s =0):size(s){if(size >0){ data =newint[size];for(int i =0; i < size; i++){ data[i]=0;}}else{ data =nullptr;}}~Array(){if(data !=nullptr){delete[] data;}}// 重载下标运算符int&operator[](int index){if(index <0|| index >= size){ cout <<"Index out of bounds!"<< endl;// 返回第一个元素作为错误处理return data[0];}return data[index];}// 重载const版本的下标运算符constint&operator[](int index)const{if(index <0|| index >= size){ cout <<"Index out of bounds!"<< endl;// 返回第一个元素作为错误处理return data[0];}return data[index];}intgetSize()const{return size;}};intmain(){ Array arr(5);// 使用下标运算符赋值for(int i =0; i < arr.getSize(); i++){ arr[i]= i *10;}// 使用下标运算符取值for(int i =0; i < arr.getSize(); i++){ cout <<"arr["<< i <<"] = "<< arr[i]<< endl;}return0;}运行结果:
arr[0] = 0 arr[1] = 10 arr[2] = 20 arr[3] = 30 arr[4] = 40 6.6 重载流插入和流提取运算符
流插入运算符<<和流提取运算符>>用于输入输出操作,重载后可以直接使用cout和cin操作用户定义的类型。
代码示例:重载流插入和流提取运算符
#include<iostream>usingnamespace std;classComplex{private:double real;double imag;public:Complex(double r =0,double i =0):real(r),imag(i){}// 声明友元函数friend ostream&operator<<(ostream& os,const Complex& c);friend istream&operator>>(istream& is, Complex& c);};// 友元函数:重载流插入运算符 ostream&operator<<(ostream& os,const Complex& c){ os << c.real <<" + "<< c.imag <<"i";return os;}// 友元函数:重载流提取运算符 istream&operator>>(istream& is, Complex& c){ cout <<"Enter real part: "; is >> c.real; cout <<"Enter imaginary part: "; is >> c.imag;return is;}intmain(){ Complex c1;// 使用流提取运算符输入 cin >> c1;// 使用流插入运算符输出 cout <<"You entered: "<< c1 << endl;return0;}运行结果:
Enter real part: 3 Enter imaginary part: 4 You entered: 3 + 4i 6.7 类型转换运算符
类型转换运算符用于将用户定义的类型转换为其他类型。
代码示例:重载类型转换运算符
#include<iostream>usingnamespace std;classDistance{private:double meters;public:Distance(double m =0):meters(m){}// 重载类型转换运算符:转换为doubleoperatordouble()const{return meters;}// 重载类型转换运算符:转换为intoperatorint()const{returnstatic_cast<int>(meters);}doublegetMeters()const{return meters;}};intmain(){ Distance d(10.5);// 隐式转换为doubledouble meters = d; cout <<"Distance in meters: "<< meters << endl;// 隐式转换为intint intMeters = d; cout <<"Distance in integer meters: "<< intMeters << endl;// 显式转换double explicitDouble =static_cast<double>(d);int explicitInt =static_cast<int>(d); cout <<"Explicit double conversion: "<< explicitDouble << endl; cout <<"Explicit int conversion: "<< explicitInt << endl;return0;}运行结果:
Distance in meters: 10.5 Distance in integer meters: 10 Explicit double conversion: 10.5 Explicit int conversion: 10 6.8 运算符重载的应用实例
代码示例:实现一个有理数类,重载各种运算符
#include<iostream>usingnamespace std;classRational{private:int numerator;// 分子int denominator;// 分母// 辅助函数:约分voidreduce(){int gcd =greatestCommonDivisor(abs(numerator),abs(denominator)); numerator /= gcd; denominator /= gcd;if(denominator <0){ numerator =-numerator; denominator =-denominator;}}// 辅助函数:计算最大公约数intgreatestCommonDivisor(int a,int b){while(b !=0){int temp = b; b = a % b; a = temp;}return a;}public:Rational(int num =0,int den =1):numerator(num),denominator(den){if(denominator ==0){ cout <<"Error: Denominator cannot be zero!"<< endl; denominator =1;}reduce();}// 重载加法运算符 Rational operator+(const Rational& other)const{int num = numerator * other.denominator + other.numerator * denominator;int den = denominator * other.denominator;returnRational(num, den);}// 重载减法运算符 Rational operator-(const Rational& other)const{int num = numerator * other.denominator - other.numerator * denominator;int den = denominator * other.denominator;returnRational(num, den);}// 重载乘法运算符 Rational operator*(const Rational& other)const{int num = numerator * other.numerator;int den = denominator * other.denominator;returnRational(num, den);}// 重载除法运算符 Rational operator/(const Rational& other)const{if(other.numerator ==0){ cout <<"Error: Division by zero!"<< endl;returnRational(0,1);}int num = numerator * other.denominator;int den = denominator * other.numerator;returnRational(num, den);}// 重载相等运算符booloperator==(const Rational& other)const{return numerator == other.numerator && denominator == other.denominator;}// 重载不等运算符booloperator!=(const Rational& other)const{return!(*this== other);}// 重载小于运算符booloperator<(const Rational& other)const{return numerator * other.denominator < other.numerator * denominator;}// 重载大于运算符booloperator>(const Rational& other)const{return other <*this;}// 重载小于等于运算符booloperator<=(const Rational& other)const{return!(*this> other);}// 重载大于等于运算符booloperator>=(const Rational& other)const{return!(*this< other);}// 友元函数:重载流插入运算符friend ostream&operator<<(ostream& os,const Rational& r);// 友元函数:重载流提取运算符friend istream&operator>>(istream& is, Rational& r);};// 友元函数:重载流插入运算符 ostream&operator<<(ostream& os,const Rational& r){if(r.denominator ==1){ os << r.numerator;}else{ os << r.numerator <<"/"<< r.denominator;}return os;}// 友元函数:重载流提取运算符 istream&operator>>(istream& is, Rational& r){char slash; is >> r.numerator; is >> slash; is >> r.denominator;if(r.denominator ==0){ cout <<"Error: Denominator cannot be zero!"<< endl; r.denominator =1;} r.reduce();return is;}intmain(){ Rational r1(1,2); Rational r2(1,3); cout <<"r1 = "<< r1 << endl; cout <<"r2 = "<< r2 << endl; cout <<"r1 + r2 = "<< r1 + r2 << endl; cout <<"r1 - r2 = "<< r1 - r2 << endl; cout <<"r1 * r2 = "<< r1 * r2 << endl; cout <<"r1 / r2 = "<< r1 / r2 << endl; cout <<"r1 == r2: "<<(r1 == r2)<< endl; cout <<"r1 != r2: "<<(r1 != r2)<< endl; cout <<"r1 < r2: "<<(r1 < r2)<< endl; cout <<"r1 > r2: "<<(r1 > r2)<< endl;return0;}运行结果:
r1 = 1/2 r2 = 1/3 r1 + r2 = 5/6 r1 - r2 = 1/6 r1 * r2 = 1/6 r1 / r2 = 3/2 r1 == r2: 0 r1 != r2: 1 r1 < r2: 0 r1 > r2: 1 小结
本章介绍了C++中运算符重载的概念和应用,包括:
- 运算符重载的基本概念:运算符重载的意义和规则
- 成员函数重载运算符:重载单目运算符和双目运算符
- 友元函数重载运算符:友元函数重载运算符的优势
- 重载赋值运算符:实现对象之间的赋值操作
- 重载下标运算符:使对象像数组一样使用下标访问
- 重载流插入和流提取运算符:使用
cout和cin操作用户定义的类型 - 类型转换运算符:将用户定义的类型转换为其他类型
- 运算符重载的应用实例:实现一个有理数类,重载各种运算符
运算符重载是C++的一个重要特性,合理使用运算符重载可以使代码更加简洁、直观和易于理解。
习题6
- 简述运算符重载的意义和规则。
- 成员函数重载运算符和友元函数重载运算符有什么区别?
- 如何重载自增运算符
++的前缀和后缀形式? - 如何重载流插入运算符
<<和流提取运算符>>? - 什么是类型转换运算符?如何重载类型转换运算符?
- 哪些运算符不能重载?
- 请实现一个复数类,重载基本的算术运算符和比较运算符。
- 请实现一个字符串类,重载下标运算符和流插入/提取运算符。
第8章 模板
8.1 模板的概念
模板是C++的一种泛型编程机制,允许程序员编写与类型无关的代码,提高代码的重用性和灵活性。
8.1.1 模板的意义
模板的意义:
- 代码重用:模板允许编写与类型无关的代码,同一套代码可以处理不同类型的数据
- 类型安全:模板在编译时进行类型检查,保证类型安全
- 灵活性:模板可以根据不同的类型参数生成不同的代码版本
- 性能优化:模板在编译时展开,避免了运行时的类型检查和转换开销
8.1.2 模板的分类
模板主要分为两类:
- 函数模板:用于创建通用函数,可以处理不同类型的参数
- 类模板:用于创建通用类,可以处理不同类型的成员数据和成员函数
8.2 函数模板
函数模板是一种通用函数定义,使用类型参数来表示函数的参数类型、返回类型或局部变量类型。
8.2.1 函数模板的定义
函数模板的定义格式:
template<typenameT> 返回类型 函数名(参数列表){// 函数体}其中,typename是关键字,也可以使用class代替,T是类型参数,可以是任何合法的标识符。
代码示例:定义一个交换两个值的函数模板
#include<iostream>usingnamespace std;// 函数模板:交换两个值template<typenameT>voidswapValues(T& a, T& b){ T temp = a; a = b; b = temp;}intmain(){// 交换整数int x =10, y =20; cout <<"Before swap: x = "<< x <<", y = "<< y << endl;swapValues(x, y); cout <<"After swap: x = "<< x <<", y = "<< y << endl;// 交换浮点数double a =1.5, b =2.5; cout <<"\nBefore swap: a = "<< a <<", b = "<< b << endl;swapValues(a, b); cout <<"After swap: a = "<< a <<", b = "<< b << endl;// 交换字符串 string s1 ="Hello", s2 ="World"; cout <<"\nBefore swap: s1 = "<< s1 <<", s2 = "<< s2 << endl;swapValues(s1, s2); cout <<"After swap: s1 = "<< s1 <<", s2 = "<< s2 << endl;return0;}运行结果:
Before swap: x = 10, y = 20 After swap: x = 20, y = 10 Before swap: a = 1.5, b = 2.5 After swap: a = 2.5, b = 1.5 Before swap: s1 = Hello, s2 = World After swap: s1 = World, s2 = Hello 8.2.2 函数模板的实例化
函数模板的实例化是指编译器根据实际的类型参数生成具体的函数版本。函数模板的实例化可以是显式的或隐式的。
代码示例:函数模板的显式实例化和隐式实例化
#include<iostream>usingnamespace std;// 函数模板:求两个值的最大值template<typenameT> T maxValue(T a, T b){return a > b ? a : b;}intmain(){// 隐式实例化:编译器根据参数类型自动推断int intMax =maxValue(10,20);double doubleMax =maxValue(10.5,20.7); string stringMax =maxValue(string("Hello"),string("World")); cout <<"Max of 10 and 20: "<< intMax << endl; cout <<"Max of 10.5 and 20.7: "<< doubleMax << endl; cout <<"Max of \"Hello\" and \"World\": "<< stringMax << endl;// 显式实例化:明确指定类型参数int explicitIntMax =maxValue<int>(30,40); cout <<"Explicit max of 30 and 40: "<< explicitIntMax << endl;return0;}运行结果:
Max of 10 and 20: 20 Max of 10.5 and 20.7: 20.7 Max of "Hello" and "World": World Explicit max of 30 and 40: 40 8.2.3 函数模板的重载
函数模板可以重载,与普通函数一样,通过参数列表的不同来区分不同的重载版本。
代码示例:函数模板的重载
#include<iostream>usingnamespace std;// 函数模板:求两个值的最大值template<typenameT> T maxValue(T a, T b){ cout <<"Template version called"<< endl;return a > b ? a : b;}// 函数模板重载:求三个值的最大值template<typenameT> T maxValue(T a, T b, T c){ cout <<"Template version (3 parameters) called"<< endl; T temp = a > b ? a : b;return temp > c ? temp : c;}// 普通函数:重载函数模板,处理字符串constchar*maxValue(constchar* a,constchar* b){ cout <<"Non-template version called"<< endl;returnstrcmp(a, b)>0? a : b;}intmain(){// 调用两个参数的函数模板int intMax =maxValue(10,20); cout <<"Max of 10 and 20: "<< intMax << endl;// 调用三个参数的函数模板double doubleMax =maxValue(10.5,20.7,15.3); cout <<"Max of 10.5, 20.7, 15.3: "<< doubleMax << endl;// 调用普通函数constchar* strMax =maxValue("Hello","World"); cout <<"Max of \"Hello\" and \"World\": "<< strMax << endl;return0;}运行结果:
Template version called Max of 10 and 20: 20 Template version (3 parameters) called Max of 10.5, 20.7, 15.3: 20.7 Non-template version called Max of "Hello" and "World": World 8.3 类模板
类模板是一种通用类定义,使用类型参数来表示类的成员数据类型、成员函数的参数类型或返回类型。
8.3.1 类模板的定义
类模板的定义格式:
template<typenameT>class 类名 {private:// 成员数据 T data;public:// 成员函数 类名(T d); T getData();voidsetData(T d);};// 类模板成员函数的定义template<typenameT> 类名<T>::类名(T d):data(d){}template<typenameT> T 类名<T>::getData(){return data;}template<typenameT>void 类名<T>::setData(T d){ data = d;}代码示例:定义一个栈的类模板
#include<iostream>#include<vector>usingnamespace std;// 类模板:栈template<typenameT>classStack{private: vector<T> elements;public:// 入栈voidpush(const T& item){ elements.push_back(item);}// 出栈voidpop(){if(!empty()){ elements.pop_back();}}// 获取栈顶元素 T&top(){return elements.back();}// 检查栈是否为空boolempty()const{return elements.empty();}// 获取栈的大小 size_t size()const{return elements.size();}};intmain(){// 整型栈 Stack<int> intStack; intStack.push(10); intStack.push(20); intStack.push(30); cout <<"Int stack size: "<< intStack.size()<< endl; cout <<"Top element: "<< intStack.top()<< endl; intStack.pop(); cout <<"After pop, top element: "<< intStack.top()<< endl;// 字符串栈 Stack<string> stringStack; stringStack.push("Hello"); stringStack.push("World"); cout <<"\nString stack size: "<< stringStack.size()<< endl; cout <<"Top element: "<< stringStack.top()<< endl; stringStack.pop(); cout <<"After pop, top element: "<< stringStack.top()<< endl;return0;}运行结果:
Int stack size: 3 Top element: 30 After pop, top element: 20 String stack size: 2 Top element: World After pop, top element: Hello 8.3.2 类模板的实例化
类模板的实例化是指编译器根据实际的类型参数生成具体的类版本。类模板的实例化必须是显式的,即必须在使用时指定类型参数。
代码示例:类模板的实例化
#include<iostream>usingnamespace std;// 类模板:容器template<typenameT>classContainer{private: T value;public:Container(T v):value(v){} T getValue()const{return value;}voidsetValue(T v){ value = v;}};intmain(){// 显式实例化:指定类型参数为int Container<int>intContainer(100); cout <<"Int container value: "<< intContainer.getValue()<< endl; intContainer.setValue(200); cout <<"After setValue, int container value: "<< intContainer.getValue()<< endl;// 显式实例化:指定类型参数为double Container<double>doubleContainer(3.14); cout <<"\nDouble container value: "<< doubleContainer.getValue()<< endl; doubleContainer.setValue(6.28); cout <<"After setValue, double container value: "<< doubleContainer.getValue()<< endl;// 显式实例化:指定类型参数为string Container<string>stringContainer("Hello"); cout <<"\nString container value: "<< stringContainer.getValue()<< endl; stringContainer.setValue("World"); cout <<"After setValue, string container value: "<< stringContainer.getValue()<< endl;return0;}运行结果:
Int container value: 100 After setValue, int container value: 200 Double container value: 3.14 After setValue, double container value: 6.28 String container value: Hello After setValue, string container value: World 8.3.3 类模板的特化
类模板的特化是指为特定的类型参数提供专门的实现,以处理特殊类型的特殊需求。
代码示例:类模板的特化
#include<iostream>#include<string>usingnamespace std;// 类模板:打印值template<typenameT>classPrinter{public:voidprint(const T& value){ cout <<"Generic version: "<< value << endl;}};// 类模板特化:处理字符串类型template<>classPrinter<string>{public:voidprint(const string& value){ cout <<"String version: \""<< value <<"\""<< endl;}};// 类模板特化:处理整型template<>classPrinter<int>{public:voidprint(constint& value){ cout <<"Int version: "<< value <<" (integer)"<< endl;}};intmain(){// 使用通用版本 Printer<double> doublePrinter; doublePrinter.print(3.14);// 使用特化版本:字符串 Printer<string> stringPrinter; stringPrinter.print("Hello World");// 使用特化版本:整型 Printer<int> intPrinter; intPrinter.print(42);return0;}运行结果:
Generic version: 3.14 String version: "Hello World" Int version: 42 (integer) 8.4 模板的使用技巧
8.4.1 模板参数的默认值
可以为模板参数指定默认值,当使用模板时如果不提供类型参数,则使用默认值。
代码示例:模板参数的默认值
#include<iostream>#include<vector>usingnamespace std;// 类模板:带默认参数的容器template<typenameT=int,typenameContainer= vector<T>>classMyContainer{private: Container elements;public:voidadd(const T& item){ elements.push_back(item);}voiddisplay(){for(constauto& item : elements){ cout << item <<" ";} cout << endl;}};intmain(){// 使用默认类型参数 MyContainer<> defaultContainer; defaultContainer.add(10); defaultContainer.add(20); defaultContainer.add(30); cout <<"Default container (int, vector<int>): "<< endl; defaultContainer.display();// 指定类型参数 MyContainer<double> doubleContainer; doubleContainer.add(1.1); doubleContainer.add(2.2); doubleContainer.add(3.3); cout <<"\nDouble container (double, vector<double>): "<< endl; doubleContainer.display();return0;}运行结果:
Default container (int, vector<int>): 10 20 30 Double container (double, vector<double>): 1.1 2.2 3.3 8.4.2 模板的递归实例化
模板可以递归实例化,即模板参数可以是另一个模板实例。
代码示例:模板的递归实例化
#include<iostream>usingnamespace std;// 类模板:元组template<typenameT,typename... Args>classTuple{private: T first; Tuple<Args...> rest;public:Tuple(T f, Args... r):first(f),rest(r...){} T getFirst()const{return first;} Tuple<Args...>getRest()const{return rest;}};// 特化:处理空参数列表template<>classTuple<>public:Tuple(){}};intmain(){// 递归实例化:创建一个包含int、double、string的元组 Tuple<int,double, string>myTuple(42,3.14,"Hello"); cout <<"First element: "<< myTuple.getFirst()<< endl;// 获取剩余部分auto rest1 = myTuple.getRest(); cout <<"Second element: "<< rest1.getFirst()<< endl;auto rest2 = rest1.getRest(); cout <<"Third element: "<< rest2.getFirst()<< endl;return0;}运行结果:
First element: 42 Second element: 3.14 Third element: Hello 8.5 模板的应用实例
代码示例:实现一个通用的排序算法模板
#include<iostream>#include<vector>#include<string>usingnamespace std;// 函数模板:交换两个值template<typenameT>voidswapValues(T& a, T& b){ T temp = a; a = b; b = temp;}// 函数模板:冒泡排序template<typenameT>voidbubbleSort(vector<T>& arr){int n = arr.size();for(int i =0; i < n -1; i++){for(int j =0; j < n - i -1; j++){if(arr[j]> arr[j +1]){swapValues(arr[j], arr[j +1]);}}}}// 函数模板:打印数组template<typenameT>voidprintArray(const vector<T>& arr){for(constauto& item : arr){ cout << item <<" ";} cout << endl;}intmain(){// 排序整型数组 vector<int> intArr ={5,2,9,1,5,6}; cout <<"Before sorting (int): "<< endl;printArray(intArr);bubbleSort(intArr); cout <<"After sorting (int): "<< endl;printArray(intArr);// 排序浮点型数组 vector<double> doubleArr ={3.14,1.41,2.71,0.57,1.73}; cout <<"\nBefore sorting (double): "<< endl;printArray(doubleArr);bubbleSort(doubleArr); cout <<"After sorting (double): "<< endl;printArray(doubleArr);// 排序字符串数组 vector<string> stringArr ={"banana","apple","orange","grape","pear"}; cout <<"\nBefore sorting (string): "<< endl;printArray(stringArr);bubbleSort(stringArr); cout <<"After sorting (string): "<< endl;printArray(stringArr);return0;}运行结果:
Before sorting (int): 5 2 9 1 5 6 After sorting (int): 1 2 5 5 6 9 Before sorting (double): 3.14 1.41 2.71 0.57 1.73 After sorting (double): 0.57 1.41 1.73 2.71 3.14 Before sorting (string): banana apple orange grape pear After sorting (string): apple banana grape orange pear 小结
本章介绍了C++中模板的概念和应用,包括:
- 模板的概念:模板是一种泛型编程机制,允许编写与类型无关的代码
- 函数模板:用于创建通用函数,可以处理不同类型的参数
- 类模板:用于创建通用类,可以处理不同类型的成员数据和成员函数
- 模板的特化:为特定的类型参数提供专门的实现
- 模板的使用技巧:模板参数的默认值、模板的递归实例化等
- 模板的应用实例:实现通用的排序算法模板
模板是C++中一个强大的特性,合理使用模板可以提高代码的重用性、灵活性和性能,是现代C++编程中不可或缺的工具。
习题8
- 简述模板的概念和意义。
- 函数模板和类模板有什么区别?
- 如何定义和使用函数模板?
- 如何定义和使用类模板?
- 什么是模板特化?如何实现模板特化?
- 如何为模板参数指定默认值?
- 请实现一个通用的向量类模板,支持基本的向量运算。
- 请实现一个通用的链表类模板,支持插入、删除、查找等操作。
第9章 输入/输出流
9.1 C++流库简介
C++的输入/输出系统是建立在流的概念基础上的。流是指数据从一个地方流向另一个地方的过程,例如从键盘流向程序,从程序流向屏幕,从程序流向文件等。
9.1.1 流的基本概念
流的基本概念:
- 输入流:数据从外部设备流向程序的流,例如从键盘输入数据到程序。
- 输出流:数据从程序流向外部设备的流,例如从程序输出数据到屏幕。
- 字符流:以字符为单位处理数据的流,例如文本文件。
- 字节流:以字节为单位处理数据的流,例如二进制文件。
9.1.2 C++流库的层次结构
C++流库的层次结构:
- 基类:
ios是所有流类的基类,提供流的状态管理和格式化控制。 - 输入流类:
istream是输入流的基类,ifstream用于文件输入,istringstream用于字符串输入。 - 输出流类:
ostream是输出流的基类,ofstream用于文件输出,ostringstream用于字符串输出。 - 输入/输出流类:
iostream是输入/输出流的基类,fstream用于文件输入/输出,stringstream用于字符串输入/输出。
9.2 基本输出流
基本输出流是指标准输出流cout,用于将数据输出到屏幕。
9.2.1 基本输出操作
基本输出操作使用流插入运算符<<,可以输出各种类型的数据。
代码示例:基本输出操作
#include<iostream>usingnamespace std;intmain(){// 输出整数int x =10; cout <<"Integer: "<< x << endl;// 输出浮点数double y =3.14; cout <<"Double: "<< y << endl;// 输出字符串 cout <<"String: Hello, World!"<< endl;// 输出布尔值bool b =true; cout <<"Boolean: "<< b << endl;// 输出1表示true,0表示false// 连续输出 cout <<"x = "<< x <<", y = "<< y << endl;return0;}运行结果:
Integer: 10 Double: 3.14 String: Hello, World! Boolean: 1 x = 10, y = 3.14 9.2.2 输出流的成员函数
输出流提供了一些成员函数,用于控制输出格式和操作。
代码示例:输出流的成员函数
#include<iostream>usingnamespace std;intmain(){// put():输出单个字符 cout.put('H'); cout.put('e'); cout.put('l'); cout.put('l'); cout.put('o'); cout.put('\n');// write():输出字符串constchar* str ="Hello, C++!\n"; cout.write(str,10);// 输出前10个字符// flush():刷新输出缓冲区 cout <<"Flushing..."; cout.flush(); cout <<" Done."<< endl;return0;}运行结果:
Hello Hello, C++! Flushing... Done. 9.3 基本输入流
基本输入流是指标准输入流cin,用于从键盘读取数据。
9.3.1 基本输入操作
基本输入操作使用流提取运算符>>,可以读取各种类型的数据。
代码示例:基本输入操作
#include<iostream>usingnamespace std;intmain(){// 读取整数int x; cout <<"Enter an integer: "; cin >> x; cout <<"You entered: "<< x << endl;// 读取浮点数double y; cout <<"Enter a double: "; cin >> y; cout <<"You entered: "<< y << endl;// 读取字符串(遇到空格停止)char str[50]; cout <<"Enter a string: "; cin >> str; cout <<"You entered: "<< str << endl;return0;}运行结果:
Enter an integer: 10 You entered: 10 Enter a double: 3.14 You entered: 3.14 Enter a string: Hello World You entered: Hello 9.3.2 输入流的成员函数
输入流提供了一些成员函数,用于控制输入格式和操作。
代码示例:输入流的成员函数
#include<iostream>usingnamespace std;intmain(){// get():读取单个字符,包括空格char ch; cout <<"Enter a character: "; ch = cin.get(); cout <<"You entered: "<< ch << endl;// 忽略换行符 cin.ignore();// getline():读取一行字符串,包括空格char str[100]; cout <<"Enter a line: "; cin.getline(str,100); cout <<"You entered: "<< str << endl;// read():读取指定长度的字符char buffer[20]; cout <<"Enter some text: "; cin.read(buffer,10); buffer[10]='\0';// 添加结束符 cout <<"You entered: "<< buffer << endl;return0;}运行结果:
Enter a character: A You entered: A Enter a line: Hello World You entered: Hello World Enter some text: This is a test You entered: This is a 9.4 格式化输入/输出
格式化输入/输出是指控制输入/输出的格式,例如数值的精度、宽度、进制等。
9.4.1 格式化输出
格式化输出可以使用流操纵符或输出流的成员函数来控制。
代码示例:格式化输出
#include<iostream>#include<iomanip>usingnamespace std;intmain(){// 设置精度double pi =3.141592653589793; cout <<"Pi: "<< pi << endl; cout <<"Pi (precision 2): "<<setprecision(2)<< pi << endl; cout <<"Pi (precision 6): "<<setprecision(6)<< pi << endl; cout <<"Pi (fixed, precision 6): "<< fixed <<setprecision(6)<< pi << endl;// 设置宽度int x =10; cout <<"\nWidth 10: "<<setw(10)<< x << endl; cout <<"Width 5: "<<setw(5)<< x << endl;// 设置填充字符 cout <<"\nFill with '*': "<<setw(10)<<setfill('*')<< x << endl;// 设置进制 cout <<"\nDecimal: "<< dec << x << endl; cout <<"Octal: "<< oct << x << endl; cout <<"Hexadecimal: "<< hex << x << endl; cout <<"Hexadecimal (uppercase): "<< uppercase << x << endl;// 重置格式 cout <<resetiosflags(ios::uppercase); cout <<"Hexadecimal (lowercase): "<< hex << x << endl;return0;}运行结果:
Pi: 3.14159 Pi (precision 2): 3.1 Pi (precision 6): 3.14159 Pi (fixed, precision 6): 3.141593 Width 10: 10 Width 5: 10 Fill with '*': *******10 Decimal: 10 Octal: 12 Hexadecimal: a Hexadecimal (uppercase): A Hexadecimal (lowercase): a 9.4.2 格式化输入
格式化输入可以使用流操纵符或输入流的成员函数来控制。
代码示例:格式化输入
#include<iostream>usingnamespace std;intmain(){// 读取不同进制的整数int x; cout <<"Enter an octal number: "; cin >> oct >> x; cout <<"Octal: "<< oct << x <<", Decimal: "<< dec << x << endl; cout <<"Enter a hexadecimal number: "; cin >> hex >> x; cout <<"Hexadecimal: "<< hex << x <<", Decimal: "<< dec << x << endl;return0;}运行结果:
Enter an octal number: 12 Octal: 12, Decimal: 10 Enter a hexadecimal number: a Hexadecimal: a, Decimal: 10 9.5 文件输入/输出
文件输入/输出是指从文件读取数据或向文件写入数据。
9.5.1 文件输出
文件输出使用ofstream类,用于向文件写入数据。
代码示例:文件输出
#include<iostream>#include<fstream>usingnamespace std;intmain(){// 创建输出文件流对象 ofstream outFile("example.txt");// 检查文件是否打开成功if(!outFile){ cerr <<"Error opening file!"<< endl;return1;}// 向文件写入数据 outFile <<"Hello, File!"<< endl; outFile <<"Integer: "<<10<< endl; outFile <<"Double: "<<3.14<< endl;// 关闭文件 outFile.close(); cout <<"Data written to file successfully!"<< endl;return0;}运行结果:
Data written to file successfully! 生成的example.txt文件内容:
Hello, File! Integer: 10 Double: 3.14 9.5.2 文件输入
文件输入使用ifstream类,用于从文件读取数据。
代码示例:文件输入
#include<iostream>#include<fstream>usingnamespace std;intmain(){// 创建输入文件流对象 ifstream inFile("example.txt");// 检查文件是否打开成功if(!inFile){ cerr <<"Error opening file!"<< endl;return1;}// 从文件读取数据 string line;while(getline(inFile, line)){ cout << line << endl;}// 关闭文件 inFile.close();return0;}运行结果:
Hello, File! Integer: 10 Double: 3.14 9.5.3 文件输入/输出
文件输入/输出使用fstream类,用于同时进行文件的读取和写入操作。
代码示例:文件输入/输出
#include<iostream>#include<fstream>usingnamespace std;intmain(){// 创建输入/输出文件流对象,以追加模式打开 fstream file("example.txt", ios::in | ios::out | ios::app);// 检查文件是否打开成功if(!file){ cerr <<"Error opening file!"<< endl;return1;}// 向文件追加数据 file <<"Appended line: "<<"Hello again!"<< endl;// 重新定位到文件开头 file.seekg(0, ios::beg);// 读取文件内容 string line;while(getline(file, line)){ cout << line << endl;}// 关闭文件 file.close();return0;}运行结果:
Hello, File! Integer: 10 Double: 3.14 Appended line: Hello again! 9.6 字符串流
字符串流是指以字符串为输入/输出对象的流,使用istringstream、ostringstream和stringstream类。
9.6.1 输出字符串流
输出字符串流使用ostringstream类,用于将数据写入字符串。
代码示例:输出字符串流
#include<iostream>#include<sstream>usingnamespace std;intmain(){// 创建输出字符串流对象 ostringstream oss;// 向字符串流写入数据 oss <<"Name: "<<"Alice"<< endl; oss <<"Age: "<<20<< endl; oss <<"GPA: "<<3.8<< endl;// 获取字符串 string str = oss.str(); cout <<"String stream content:\n"<< str << endl;return0;}运行结果:
String stream content: Name: Alice Age: 20 GPA: 3.8 9.6.2 输入字符串流
输入字符串流使用istringstream类,用于从字符串读取数据。
代码示例:输入字符串流
#include<iostream>#include<sstream>usingnamespace std;intmain(){// 源字符串 string str ="Alice 20 3.8";// 创建输入字符串流对象 istringstream iss(str);// 从字符串流读取数据 string name;int age;double gpa; iss >> name >> age >> gpa;// 输出读取的数据 cout <<"Name: "<< name << endl; cout <<"Age: "<< age << endl; cout <<"GPA: "<< gpa << endl;return0;}运行结果:
Name: Alice Age: 20 GPA: 3.8 9.6.3 输入/输出字符串流
输入/输出字符串流使用stringstream类,用于同时进行字符串的读取和写入操作。
代码示例:输入/输出字符串流
#include<iostream>#include<sstream>usingnamespace std;intmain(){// 创建输入/输出字符串流对象 stringstream ss;// 向字符串流写入数据 ss <<"Hello, "<<"World!"<<" "<<123<< endl;// 获取字符串 string str = ss.str(); cout <<"String content: "<< str;// 清空字符串流 ss.str("");// 重新写入数据 ss <<"New content: "<<3.14<< endl; cout <<"New string content: "<< ss.str();return0;}运行结果:
String content: Hello, World! 123 New string content: New content: 3.14 9.7 流的状态控制
流的状态控制是指检查和控制流的状态,例如是否到达文件末尾、是否发生错误等。
9.7.1 流的状态标志
流的状态标志包括:
good():流状态正常eof():到达文件末尾fail():发生非致命错误bad():发生致命错误rdstate():返回流的当前状态clear():清除流的错误状态setstate():设置流的错误状态
代码示例:流的状态控制
#include<iostream>#include<fstream>usingnamespace std;intmain(){// 尝试打开不存在的文件 ifstream inFile("nonexistent.txt");// 检查流状态if(!inFile.good()){ cout <<"Stream state: "<< inFile.rdstate()<< endl;if(inFile.eof()){ cout <<"End of file reached."<< endl;}if(inFile.fail()){ cout <<"Non-fatal error occurred."<< endl;}if(inFile.bad()){ cout <<"Fatal error occurred."<< endl;}// 清除错误状态 inFile.clear(); cout <<"After clear, stream state: "<< inFile.rdstate()<< endl;}// 关闭文件 inFile.close();return0;}运行结果:
Stream state: 6 Non-fatal error occurred. After clear, stream state: 0 9.8 流的应用实例
代码示例:实现一个简单的文件复制程序
#include<iostream>#include<fstream>#include<string>usingnamespace std;intmain(){// 输入源文件和目标文件 string sourceFile, targetFile; cout <<"Enter source file: ";getline(cin, sourceFile); cout <<"Enter target file: ";getline(cin, targetFile);// 打开源文件 ifstream inFile(sourceFile);if(!inFile){ cerr <<"Error opening source file!"<< endl;return1;}// 打开目标文件 ofstream outFile(targetFile);if(!outFile){ cerr <<"Error opening target file!"<< endl; inFile.close();return1;}// 复制文件内容 string line;while(getline(inFile, line)){ outFile << line << endl;}// 关闭文件 inFile.close(); outFile.close(); cout <<"File copied successfully!"<< endl;return0;}运行结果:
Enter source file: example.txt Enter target file: copy.txt File copied successfully! 小结
本章介绍了C++中输入/输出流的概念和应用,包括:
- C++流库简介:流的基本概念和流库的层次结构
- 基本输出流:标准输出流
cout的基本操作和成员函数 - 基本输入流:标准输入流
cin的基本操作和成员函数 - 格式化输入/输出:控制输入/输出格式的方法
- 文件输入/输出:使用
ifstream、ofstream和fstream进行文件操作 - 字符串流:使用
istringstream、ostringstream和stringstream进行字符串操作 - 流的状态控制:检查和控制流的状态
- 流的应用实例:实现一个简单的文件复制程序
C++的输入/输出流系统是一个功能强大、灵活多样的系统,掌握它对于编写实用的C++程序非常重要。
习题9
- 简述C++流库的层次结构。
- 如何进行基本的输出操作?
- 如何进行基本的输入操作?
- 如何控制输出格式?
- 如何进行文件的输入/输出操作?
- 如何使用字符串流?
- 如何检查和控制流的状态?
- 请实现一个程序,从文件读取数据,进行处理后再写入另一个文件。
第7章 组合、继承与多态
7.1 组合
组合是指一个类包含另一个类的对象作为其成员,这种关系称为“has-a”关系。
代码示例:
#include<iostream>#include<string>usingnamespace std;// 日期类classDate{private:int year;int month;int day;public:Date(int y,int m,int d){ year = y; month = m; day = d;}voiddisplay(){ cout << year <<"-"<< month <<"-"<< day;}};// 学生类,包含Date对象作为成员classStudent{private: string name;int age; Date birthday;// 组合:Student has a Datepublic:// 构造函数:需要初始化Date成员Student(string n,int a,int y,int m,int d):name(n),age(a),birthday(y, m, d){}voiddisplay(){ cout <<"Name: "<< name << endl; cout <<"Age: "<< age << endl; cout <<"Birthday: "; birthday.display(); cout << endl;}};intmain(){ Student stu("Alice",20,2004,5,15); stu.display();return0;}运行结果:
Name: Alice Age: 20 Birthday: 2004-5-15 7.2 继承
继承是指一个类从另一个类派生,继承基类的属性和方法,这种关系称为“is-a”关系。
7.2.1 继承的基本概念
代码示例:
#include<iostream>#include<string>usingnamespace std;// 基类:PersonclassPerson{private: string name;int age;public:Person(string n,int a){ name = n; age = a;}voidsetName(string n){ name = n;}voidsetAge(int a){ age = a;} string getName(){return name;}intgetAge(){return age;}voiddisplay(){ cout <<"Name: "<< name << endl; cout <<"Age: "<< age << endl;}};// 派生类:Student,继承自PersonclassStudent:publicPerson{private: string id; string department;public:// 构造函数:需要调用基类构造函数Student(string n,int a, string i, string d):Person(n, a){ id = i; department = d;}voidsetId(string i){ id = i;}voidsetDepartment(string d){ department = d;} string getId(){return id;} string getDepartment(){return department;}// 重写基类的display方法voiddisplay(){Person::display();// 调用基类的display方法 cout <<"ID: "<< id << endl; cout <<"Department: "<< department << endl;}};intmain(){ Student stu("Alice",20,"20200101","Computer Science"); stu.display();// 访问继承的方法 stu.setName("Bob"); stu.setAge(21); cout <<"\nAfter modification:\n"; stu.display();return0;}运行结果:
Name: Alice Age: 20 ID: 20200101 Department: Computer Science After modification: Name: Bob Age: 21 ID: 20200101 Department: Computer Science 7.2.2 继承的访问控制
继承的访问控制规则:
| 基类成员访问权限 | 公有继承(public) | 保护继承(protected) | 私有继承(private) |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可访问 | 不可访问 | 不可访问 |
代码示例:
#include<iostream>usingnamespace std;// 基类classBase{private:int privateMember;protected:int protectedMember;public:int publicMember;Base(){ privateMember =10; protectedMember =20; publicMember =30;}voidaccessBaseMembers(){ cout <<"Base::privateMember: "<< privateMember << endl; cout <<"Base::protectedMember: "<< protectedMember << endl; cout <<"Base::publicMember: "<< publicMember << endl;}};// 公有继承classPublicDerived:publicBase{public:voidaccessMembers(){// cout << "privateMember: " << privateMember; // 错误:不可访问 cout <<"protectedMember: "<< protectedMember << endl;// 可访问 cout <<"publicMember: "<< publicMember << endl;// 可访问}};// 保护继承classProtectedDerived:protectedBase{public:voidaccessMembers(){// cout << "privateMember: " << privateMember; // 错误:不可访问 cout <<"protectedMember: "<< protectedMember << endl;// 可访问 cout <<"publicMember: "<< publicMember << endl;// 可访问}};// 私有继承classPrivateDerived:privateBase{public:voidaccessMembers(){// cout << "privateMember: " << privateMember; // 错误:不可访问 cout <<"protectedMember: "<< protectedMember << endl;// 可访问 cout <<"publicMember: "<< publicMember << endl;// 可访问}};intmain(){ PublicDerived pubObj; cout <<"PublicDerived:\n"; pubObj.accessMembers(); cout <<"pubObj.publicMember: "<< pubObj.publicMember << endl;// 可访问 ProtectedDerived proObj; cout <<"\nProtectedDerived:\n"; proObj.accessMembers();// cout << "proObj.publicMember: " << proObj.publicMember; // 错误:不可访问 PrivateDerived priObj; cout <<"\nPrivateDerived:\n"; priObj.accessMembers();// cout << "priObj.publicMember: " << priObj.publicMember; // 错误:不可访问return0;}运行结果:
PublicDerived: protectedMember: 20 publicMember: 30 pubObj.publicMember: 30 ProtectedDerived: protectedMember: 20 publicMember: 30 PrivateDerived: protectedMember: 20 publicMember: 30 7.2.3 多继承
多继承是指一个类从多个基类派生,继承多个基类的属性和方法。
代码示例:
#include<iostream>#include<string>usingnamespace std;// 基类1:PersonclassPerson{private: string name;int age;public:Person(string n,int a){ name = n; age = a;}voiddisplayPerson(){ cout <<"Name: "<< name << endl; cout <<"Age: "<< age << endl;}};// 基类2:StudentInfoclassStudentInfo{private: string id; string department;public:StudentInfo(string i, string d){ id = i; department = d;}voiddisplayStudentInfo(){ cout <<"ID: "<< id << endl; cout <<"Department: "<< department << endl;}};// 派生类:Student,继承自Person和StudentInfoclassStudent:publicPerson,publicStudentInfo{private:double gpa;public:// 构造函数:需要调用所有基类的构造函数Student(string n,int a, string i, string d,double g):Person(n, a),StudentInfo(i, d){ gpa = g;}voidsetGPA(double g){ gpa = g;}doublegetGPA(){return gpa;}voiddisplay(){displayPerson();displayStudentInfo(); cout <<"GPA: "<< gpa << endl;}};intmain(){ Student stu("Alice",20,"20200101","Computer Science",3.8); stu.display();return0;}运行结果:
Name: Alice Age: 20 ID: 20200101 Department: Computer Science GPA: 3.8 7.3 继承与组合中的构造和析构
在继承和组合中,构造函数和析构函数的调用顺序:
- 构造函数调用顺序:
- 基类构造函数(按照继承顺序)
- 成员对象构造函数(按照声明顺序)
- 派生类构造函数
- 析构函数调用顺序:
- 派生类析构函数
- 成员对象析构函数(按照声明的相反顺序)
- 基类析构函数(按照继承的相反顺序)
代码示例:
#include<iostream>usingnamespace std;// 基类1classBase1{public:Base1(){ cout <<"Base1 constructor called"<< endl;}~Base1(){ cout <<"Base1 destructor called"<< endl;}};// 基类2classBase2{public:Base2(){ cout <<"Base2 constructor called"<< endl;}~Base2(){ cout <<"Base2 destructor called"<< endl;}};// 成员类classMember{public:Member(){ cout <<"Member constructor called"<< endl;}~Member(){ cout <<"Member destructor called"<< endl;}};// 派生类classDerived:publicBase1,publicBase2{private: Member member;public:Derived(){ cout <<"Derived constructor called"<< endl;}~Derived(){ cout <<"Derived destructor called"<< endl;}};intmain(){ Derived obj; cout <<"\nObject created\n";return0;}运行结果:
Base1 constructor called Base2 constructor called Member constructor called Derived constructor called Object created Derived destructor called Member destructor called Base2 destructor called Base1 destructor called 7.4 多态
多态是指同一个操作作用于不同的对象时,会产生不同的行为。
7.4.1 静态多态
静态多态是指在编译时确定调用哪个函数,通过函数重载和运算符重载实现。
代码示例:函数重载(静态多态)
#include<iostream>usingnamespace std;// 函数重载intadd(int a,int b){return a + b;}doubleadd(double a,double b){return a + b;}intadd(int a,int b,int c){return a + b + c;}intmain(){ cout <<"add(10, 20): "<<add(10,20)<< endl; cout <<"add(10.5, 20.7): "<<add(10.5,20.7)<< endl; cout <<"add(10, 20, 30): "<<add(10,20,30)<< endl;return0;}运行结果:
add(10, 20): 30 add(10.5, 20.7): 31.2 add(10, 20, 30): 60 7.4.2 动态多态
动态多态是指在运行时确定调用哪个函数,通过虚函数和继承实现。
代码示例:
#include<iostream>#include<string>usingnamespace std;// 基类:ShapeclassShape{public:// 虚函数virtualvoiddraw(){ cout <<"Drawing a shape"<< endl;}// 虚析构函数virtual~Shape(){ cout <<"Shape destructor called"<< endl;}};// 派生类:CircleclassCircle:publicShape{private:double radius;public:Circle(double r):radius(r){}// 重写虚函数voiddraw()override{ cout <<"Drawing a circle with radius "<< radius << endl;}~Circle(){ cout <<"Circle destructor called"<< endl;}};// 派生类:RectangleclassRectangle:publicShape{private:double width;double height;public:Rectangle(double w,double h):width(w),height(h){}// 重写虚函数voiddraw()override{ cout <<"Drawing a rectangle with width "<< width <<" and height "<< height << endl;}~Rectangle(){ cout <<"Rectangle destructor called"<< endl;}};intmain(){// 基类指针指向派生类对象 Shape* shape1 =newCircle(5.0); Shape* shape2 =newRectangle(10.0,20.0);// 多态调用:运行时确定调用哪个draw()方法 shape1->draw(); shape2->draw();// 释放内存delete shape1;delete shape2;return0;}运行结果:
Drawing a circle with radius 5 Drawing a rectangle with width 10 and height 20 Circle destructor called Shape destructor called Rectangle destructor called Shape destructor called 7.5 虚函数
虚函数是在基类中声明的,在派生类中可以重写的函数,用于实现动态多态。
7.5.1 虚函数的定义和使用
代码示例:
#include<iostream>usingnamespace std;classAnimal{public:// 虚函数virtualvoidmakeSound(){ cout <<"Animal makes sound"<< endl;}};classDog:publicAnimal{public:// 重写虚函数voidmakeSound()override{ cout <<"Dog barks"<< endl;}};classCat:publicAnimal{public:// 重写虚函数voidmakeSound()override{ cout <<"Cat meows"<< endl;}};voidanimalSound(Animal* animal){// 多态调用 animal->makeSound();}intmain(){ Animal animal; Dog dog; Cat cat;animalSound(&animal);animalSound(&dog);animalSound(&cat);return0;}运行结果:
Animal makes sound Dog barks Cat meows 7.5.2 纯虚函数和抽象类
纯虚函数是在基类中声明的,没有实现的虚函数,用于定义接口。包含纯虚函数的类称为抽象类,不能实例化。
代码示例:
#include<iostream>#include<string>usingnamespace std;// 抽象类:ShapeclassShape{public:// 纯虚函数virtualdoublearea()const=0;virtualdoubleperimeter()const=0;virtualvoiddisplay()const=0;// 虚析构函数virtual~Shape(){}};// 派生类:CircleclassCircle:publicShape{private:double radius;public:Circle(double r):radius(r){}doublearea()constoverride{return3.14159* radius * radius;}doubleperimeter()constoverride{return2*3.14159* radius;}voiddisplay()constoverride{ cout <<"Circle: radius = "<< radius << endl; cout <<"Area: "<<area()<< endl; cout <<"Perimeter: "<<perimeter()<< endl;}};// 派生类:RectangleclassRectangle:publicShape{private:double width;double height;public:Rectangle(double w,double h):width(w),height(h){}doublearea()constoverride{return width * height;}doubleperimeter()constoverride{return2*(width + height);}voiddisplay()constoverride{ cout <<"Rectangle:,Area: "<<area()<< endl; cout <<"Perimeter: "<<perimeter()<< endl;}};intmain(){// Shape shape; // 错误:抽象类不能实例化 Shape* shape1 =newCircle(5.0); Shape* shape2 =newRectangle(10.0,20.0); shape1->display(); cout << endl; shape2->display();delete shape1;delete shape2;return0;}运行结果:
Circle: radius = 5 Area: 78.5397 Perimeter: 31.4159 Rectangle: width = 10, height = 20 Area: 200 Perimeter: 60 7.6 虚函数的工作原理
虚函数的工作原理是通过虚函数表(vtable)实现的:
- 每个包含虚函数的类都有一个虚函数表,存储该类所有虚函数的地址。
- 每个对象都有一个虚指针(vptr),指向所属类的虚函数表。
- 当调用虚函数时,通过虚指针找到虚函数表,然后调用相应的函数。
7.7 多重继承与菱形继承
菱形继承是指一个类从两个不同的基类派生,而这两个基类又从同一个基类派生的情况,可能会导致二义性。
代码示例:使用虚继承解决菱形继承问题
#include<iostream>usingnamespace std;// 基类:BaseclassBase{private:int value;public:Base(int v):value(v){ cout <<"Base constructor called"<< endl;}voiddisplay(){ cout <<"Base::value: "<< value << endl;}};// 虚继承:Derived1classDerived1:virtualpublicBase{public:Derived1(int v):Base(v){ cout <<"Derived1 constructor called"<< endl;}};// 虚继承:Derived2classDerived2:virtualpublicBase{public:Derived2(int v):Base(v){ cout <<"Derived2 constructor called"<< endl;}};// 派生类:Derived3,从Derived1和Derived2派生classDerived3:publicDerived1,publicDerived2{public:// 需要直接调用Base的构造函数Derived3(int v):Base(v),Derived1(v),Derived2(v){ cout <<"Derived3 constructor called"<< endl;}};intmain(){ Derived3 obj(100); obj.display();// 正确:不会产生二义性return0;}运行结果:
Base constructor called Derived1 constructor called Derived2 constructor called Derived3 constructor called Base::value: 100 7.8 应用实例
代码示例:实现一个简单的图形类层次结构
#include<iostream>#include<vector>usingnamespace std;// 抽象类:ShapeclassShape{public:virtualdoublearea()const=0;virtualdoubleperimeter()const=0;virtualvoiddisplay()const=0;virtual~Shape(){}};// 派生类:CircleclassCircle:publicShape{private:double radius;public:Circle(double r):radius(r){}doublearea()constoverride{return3.14159* radius * radius;}doubleperimeter()constoverride{return2*3.14159* radius;}voiddisplay()constoverride{ cout <<"Circle: radius = "<< radius << endl; cout <<"Area: "<<area()<< endl; cout <<"Perimeter: "<<perimeter()<< endl;}};// 派生类:RectangleclassRectangle:publicShape{private:double width;double height;public:Rectangle(double w,double h):width(w),height(h){}doublearea()constoverride{return width * height;}doubleperimeter()constoverride{return2*(width + height);}voiddisplay()constoverride{ cout <<"Rectangle:,Area: "<<area()<< endl; cout <<"Perimeter: "<<perimeter()<< endl;}};// 派生类:TriangleclassTriangle:publicShape{private:double a, b, c;// 三边长度public:Triangle(double x,double y,double z):a(x),b(y),c(z){}doublearea()constoverride{// 使用海伦公式计算面积double s =(a + b + c)/2;returnsqrt(s *(s - a)*(s - b)*(s - c));}doubleperimeter()constoverride{return a + b + c;}voiddisplay()constoverride{ cout <<"Triangle: a = "<< a <<", b = "<< b <<", c = "<< c << endl; cout <<"Area: "<<area()<< endl; cout <<"Perimeter: "<<perimeter()<< endl;}};intmain(){// 创建图形对象 vector<Shape*> shapes; shapes.push_back(newCircle(5.0)); shapes.push_back(newRectangle(10.0,20.0)); shapes.push_back(newTriangle(3.0,4.0,5.0));// 多态调用for(constauto& shape : shapes){ shape->display(); cout << endl;}// 释放内存for(constauto& shape : shapes){delete shape;}return0;}运行结果:
Circle: radius = 5 Area: 78.5397 Perimeter: 31.4159 Rectangle: width = 10, height = 20 Area: 200 Perimeter: 60 Triangle: a = 3, b = 4, c = 5 Area: 6 Perimeter: 12 小结
本章介绍了C++中面向对象编程的核心概念:组合、继承与多态。
- 组合:一个类包含另一个类的对象作为其成员,实现“has-a”关系。
- 继承:一个类从另一个类派生,继承基类的属性和方法,实现“is-a”关系。
- 多态:同一个操作作用于不同的对象时,会产生不同的行为。
- 静态多态:编译时确定,通过函数重载和运算符重载实现。
- 动态多态:运行时确定,通过虚函数和继承实现。
- 虚函数:在基类中声明的,在派生类中可以重写的函数,用于实现动态多态。
- 纯虚函数和抽象类:纯虚函数是没有实现的虚函数,包含纯虚函数的类称为抽象类,不能实例化。
- 多重继承与菱形继承:一个类从多个基类派生,可能会导致二义性,可使用虚继承解决。
这些概念是面向对象编程的基础,掌握它们对于理解和使用C++进行面向对象编程至关重要。
习题7
- 什么是组合?什么是继承?它们的区别是什么?
- 简述继承的访问控制规则。
- 什么是多态?静态多态和动态多态的区别是什么?
- 什么是虚函数?如何实现虚函数?
- 什么是纯虚函数?什么是抽象类?
- 什么是菱形继承?如何解决菱形继承问题?
- 简述构造函数和析构函数在继承和组合中的调用顺序。
第8章 模板
8.1 模板的概念
模板是C++泛型编程的基础,它允许我们编写与类型无关的代码,提高代码的可重用性和灵活性。模板分为函数模板和类模板两种。
模板的基本思想:将类型作为参数传递给模板,编译器根据实际类型生成相应的代码。
8.2 函数模板
函数模板是一种通用函数,可以处理不同类型的参数。
8.2.1 函数模板的定义
函数模板的定义格式如下:
template<typenameT> 返回类型 函数名(参数列表){// 函数体}其中,typename是关键字,用于声明模板参数,也可以使用class关键字。
代码示例:定义一个通用的最大值函数
#include<iostream>usingnamespace std;// 函数模板:求两个值的最大值template<typenameT> T max(T a, T b){return a > b ? a : b;}intmain(){// 调用函数模板,处理int类型int a =10, b =20; cout <<"max("<< a <<", "<< b <<") = "<<max(a, b)<< endl;// 调用函数模板,处理double类型double c =3.14, d =2.71; cout <<"max("<< c <<", "<< d <<") = "<<max(c, d)<< endl;// 调用函数模板,处理char类型char e ='A', f ='B'; cout <<"max("<< e <<", "<< f <<") = "<<max(e, f)<< endl;return0;}运行结果:
max(10, 20) = 20 max(3.14, 2.71) = 3.14 max(A, B) = B 8.2.2 函数模板的实例化
函数模板的实例化是指编译器根据实际类型生成具体的函数。实例化分为显式实例化和隐式实例化。
1. 隐式实例化:编译器根据函数调用的实参类型自动推导模板参数类型。
2. 显式实例化:手动指定模板参数类型。
代码示例:函数模板的显式实例化
#include<iostream>usingnamespace std;// 函数模板:求两个值的最大值template<typenameT> T max(T a, T b){return a > b ? a : b;}// 显式实例化函数模板templateintmax<int>(int,int);templatedoublemax<double>(double,double);intmain(){int a =10, b =20; cout <<"max("<< a <<", "<< b <<") = "<<max(a, b)<< endl;double c =3.14, d =2.71; cout <<"max("<< c <<", "<< d <<") = "<<max(c, d)<< endl;return0;}8.2.3 函数模板的重载
函数模板可以重载,包括:
- 函数模板与普通函数重载
- 函数模板之间的重载
代码示例:函数模板的重载
#include<iostream>#include<string>usingnamespace std;// 普通函数:处理字符串 string max(string a, string b){return a > b ? a : b;}// 函数模板:处理基本类型template<typenameT> T max(T a, T b){return a > b ? a : b;}// 函数模板重载:处理三个参数template<typenameT> T max(T a, T b, T c){returnmax(max(a, b), c);}intmain(){// 调用普通函数 string s1 ="hello", s2 ="world"; cout <<"max(\""<< s1 <<"\", \""<< s2 <<"\") = "<<max(s1, s2)<< endl;// 调用函数模板int a =10, b =20; cout <<"max("<< a <<", "<< b <<") = "<<max(a, b)<< endl;// 调用重载的函数模板int c =30; cout <<"max("<< a <<", "<< b <<", "<< c <<") = "<<max(a, b, c)<< endl;return0;}运行结果:
max("hello", "world") = world max(10, 20) = 20 max(10, 20, 30) = 30 8.3 类模板
类模板是一种通用类,可以处理不同类型的数据成员和成员函数。
8.3.1 类模板的定义
类模板的定义格式如下:
template<typenameT>class 类名 {private:// 数据成员 T data;public:// 成员函数 类名(T d):data(d){} T getData()const{return data;}};代码示例:定义一个通用的栈类
#include<iostream>#include<vector>usingnamespace std;// 类模板:栈template<typenameT>classStack{private: vector<T> elements;public:// 压栈voidpush(T const& item){ elements.push_back(item);}// 出栈voidpop(){if(elements.empty()){throwout_of_range("Stack is empty");} elements.pop_back();}// 获取栈顶元素 T top()const{if(elements.empty()){throwout_of_range("Stack is empty");}return elements.back();}// 检查栈是否为空boolempty()const{return elements.empty();}// 获取栈的大小 size_t size()const{return elements.size();}};intmain(){// 创建一个int类型的栈 Stack<int> intStack; intStack.push(10); intStack.push(20); intStack.push(30); cout <<"intStack size: "<< intStack.size()<< endl; cout <<"intStack top: "<< intStack.top()<< endl; intStack.pop(); cout <<"After pop, intStack top: "<< intStack.top()<< endl;// 创建一个double类型的栈 Stack<double> doubleStack; doubleStack.push(1.1); doubleStack.push(2.2); doubleStack.push(3.3); cout <<"\ndoubleStack size: "<< doubleStack.size()<< endl; cout <<"doubleStack top: "<< doubleStack.top()<< endl;return0;}运行结果:
intStack size: 3 intStack top: 30 After pop, intStack top: 20 doubleStack size: 3 doubleStack top: 3.3 8.3.2 类模板的成员函数
类模板的成员函数可以在类内部定义,也可以在类外部定义。在类外部定义时,需要指定模板参数。
代码示例:在类外部定义类模板的成员函数
#include<iostream>usingnamespace std;// 类模板:Pairtemplate<typenameT1,typenameT2>classPair{private: T1 first; T2 second;public:// 构造函数Pair(T1 f, T2 s);// 成员函数声明 T1 getFirst()const; T2 getSecond()const;voidsetFirst(T1 f);voidsetSecond(T2 s);};// 在类外部定义构造函数template<typenameT1,typenameT2>Pair<T1, T2>::Pair(T1 f, T2 s):first(f),second(s){}// 在类外部定义成员函数template<typenameT1,typenameT2> T1 Pair<T1, T2>::getFirst()const{return first;}template<typenameT1,typenameT2> T2 Pair<T1, T2>::getSecond()const{return second;}template<typenameT1,typenameT2>voidPair<T1, T2>::setFirst(T1 f){ first = f;}template<typenameT1,typenameT2>voidPair<T1, T2>::setSecond(T2 s){ second = s;}intmain(){// 创建一个Pair对象 Pair<int, string>p(1,"hello"); cout <<"First: "<< p.getFirst()<<", Second: "<< p.getSecond()<< endl;// 修改值 p.setFirst(2); p.setSecond("world"); cout <<"After modification: First: "<< p.getFirst()<<", Second: "<< p.getSecond()<< endl;// 创建另一个类型的Pair对象 Pair<double,bool>p2(3.14,true); cout <<"First: "<< p2.getFirst()<<", Second: "<< p2.getSecond()<< endl;return0;}运行结果:
First: 1, Second: hello After modification: First: 2, Second: world First: 3.14, Second: 1 8.3.3 类模板的实例化
类模板的实例化是指编译器根据实际类型生成具体的类。与函数模板不同,类模板必须显式指定模板参数类型。
代码示例:类模板的实例化
#include<iostream>usingnamespace std;// 类模板:Boxtemplate<typenameT>classBox{private: T length; T width; T height;public:Box(T l, T w, T h):length(l),width(w),height(h){} T volume()const{return length * width * height;}};intmain(){// 实例化int类型的Box Box<int>intBox(10,20,30); cout <<"intBox volume: "<< intBox.volume()<< endl;// 实例化double类型的Box Box<double>doubleBox(10.5,20.5,30.5); cout <<"doubleBox volume: "<< doubleBox.volume()<< endl;return0;}运行结果:
intBox volume: 6000 doubleBox volume: 6607.12 8.4 模板特化
模板特化是指为特定类型提供专门的实现,以处理特殊情况。模板特化分为函数模板特化和类模板特化。
8.4.1 函数模板特化
函数模板特化的格式如下:
template<> 返回类型 函数名<特化类型>(参数列表){// 特化实现}代码示例:函数模板特化
#include<iostream>#include<string>usingnamespace std;// 函数模板:求最大值template<typenameT> T max(T a, T b){return a > b ? a : b;}// 函数模板特化:处理字符串template<> string max<string>(string a, string b){ cout <<"Specialized version for string called"<< endl;return a.length()> b.length()? a : b;}intmain(){// 调用函数模板int a =10, b =20; cout <<"max("<< a <<", "<< b <<") = "<<max(a, b)<< endl;// 调用特化的函数模板 string s1 ="hello", s2 ="world"; cout <<"max(\""<< s1 <<"\", \""<< s2 <<"\") = "<<max(s1, s2)<< endl;return0;}运行结果:
max(10, 20) = 20 Specialized version for string called max("hello", "world") = world 8.4.2 类模板特化
类模板特化的格式如下:
template<>class 类名<特化类型>{// 特化实现};代码示例:类模板特化
#include<iostream>usingnamespace std;// 类模板:Printertemplate<typenameT>classPrinter{public:voidprint(T value){ cout <<"Generic Printer: "<< value << endl;}};// 类模板特化:处理bool类型template<>classPrinter<bool>{public:voidprint(bool value){ cout <<"Bool Printer: "<<(value ?"true":"false")<< endl;}};intmain(){// 使用通用模板 Printer<int> intPrinter; intPrinter.print(42); Printer<double> doublePrinter; doublePrinter.print(3.14);// 使用特化模板 Printer<bool> boolPrinter; boolPrinter.print(true); boolPrinter.print(false);return0;}运行结果:
Generic Printer: 42 Generic Printer: 3.14 Bool Printer: true Bool Printer: false 8.5 模板的默认参数
C++11及以后的版本支持为模板参数指定默认值。
代码示例:模板的默认参数
#include<iostream>usingnamespace std;// 函数模板:带默认参数template<typenameT=int> T add(T a, T b){return a + b;}// 类模板:带默认参数template<typenameT=int,int SIZE =10>classArray{private: T data[SIZE];public:Array(){for(int i =0; i < SIZE; i++){ data[i]=0;}} T&operator[](int index){return data[index];}voidprint(){for(int i =0; i < SIZE; i++){ cout << data[i]<<" ";} cout << endl;}};intmain(){// 使用默认模板参数 cout <<"add(10, 20) = "<<add(10,20)<< endl;// 显式指定模板参数 cout <<"add<double>(1.5, 2.5) = "<<add<double>(1.5,2.5)<< endl;// 使用默认模板参数的类 Array<> intArray; intArray[0]=10; intArray[1]=20; cout <<"intArray: "; intArray.print();// 显式指定模板参数的类 Array<double,5> doubleArray; doubleArray[0]=1.1; doubleArray[1]=2.2; cout <<"doubleArray: "; doubleArray.print();return0;}运行结果:
add(10, 20) = 30 add<double>(1.5, 2.5) = 4 intArray: 10 20 0 0 0 0 0 0 0 0 doubleArray: 1.1 2.2 0 0 0 8.6 模板的应用
模板在C++标准库中被广泛应用,如STL(标准模板库)中的容器、迭代器、算法等都是基于模板实现的。
代码示例:使用STL容器
#include<iostream>#include<vector>#include<list>#include<algorithm>usingnamespace std;intmain(){// 使用vector容器 vector<int> vec; vec.push_back(10); vec.push_back(20); vec.push_back(30); cout <<"vector elements: ";for(auto it = vec.begin(); it != vec.end();++it){ cout <<*it <<" ";} cout << endl;// 使用list容器 list<double> lst; lst.push_back(1.1); lst.push_back(2.2); lst.push_back(3.3); cout <<"list elements: ";for(auto it = lst.begin(); it != lst.end();++it){ cout <<*it <<" ";} cout << endl;// 使用算法 vector<int> nums ={3,1,4,1,5,9,2,6};sort(nums.begin(), nums.end()); cout <<"sorted nums: ";for(int num : nums){ cout << num <<" ";} cout << endl;return0;}运行结果:
vector elements: 10 20 30 list elements: 1.1 2.2 3.3 sorted nums: 1 1 2 3 4 5 6 9 小结
本章介绍了C++中模板的概念和使用方法,包括:
- 模板的概念:模板是泛型编程的基础,允许编写与类型无关的代码。
- 函数模板:可以处理不同类型参数的通用函数,支持实例化和重载。
- 类模板:可以处理不同类型数据成员和成员函数的通用类,需要显式实例化。
- 模板特化:为特定类型提供专门的实现,处理特殊情况。
- 模板的默认参数:为模板参数指定默认值,提高使用的灵活性。
- 模板的应用:在STL等标准库中的广泛应用。
模板是C++中非常强大的特性,掌握模板的使用对于编写高效、可重用的代码至关重要。
习题8
- 什么是模板?模板的作用是什么?
- 函数模板和类模板的区别是什么?
- 如何定义和使用函数模板?
- 如何定义和使用类模板?
- 什么是模板特化?为什么需要模板特化?
- 如何为模板参数指定默认值?
- 简述STL中模板的应用。
第9章 输入/输出流
9.1 C++流库概述
C++的输入/输出(I/O)系统是基于流(stream)的概念实现的。流是一种抽象,代表数据的流动,可以是从设备到程序的输入流,也可以是从程序到设备的输出流。
C++标准库提供了丰富的流类,主要包括:
- 基本流类:
ios:所有流类的基类istream:输入流类ostream:输出流类iostream:输入/输出流类
- 文件流类:
ifstream:文件输入流类ofstream:文件输出流类fstream:文件输入/输出流类
- 字符串流类:
istringstream:字符串输入流类ostringstream:字符串输出流类stringstream:字符串输入/输出流类
9.2 基本输出流(cout)
cout是C++标准库中最常用的输出流对象,用于向标准输出设备(通常是屏幕)输出数据。
代码示例:基本输出操作
#include<iostream>usingnamespace std;intmain(){// 输出字符串 cout <<"Hello, C++!"<< endl;// 输出变量int a =10;double b =3.14;char c ='A'; cout <<"a = "<< a << endl; cout <<"b = "<< b << endl; cout <<"c = "<< c << endl;// 连续输出 cout <<"a = "<< a <<", b = "<< b <<", c = "<< c << endl;return0;}运行结果:
Hello, C++! a = 10 b = 3.14 c = A a = 10, b = 3.14, c = A 9.3 基本输入流(cin)
cin是C++标准库中最常用的输入流对象,用于从标准输入设备(通常是键盘)读取数据。
代码示例:基本输入操作
#include<iostream>usingnamespace std;intmain(){// 输入变量int a;double b; string c; cout <<"Enter an integer: "; cin >> a; cout <<"Enter a double: "; cin >> b; cout <<"Enter a string: "; cin >> c;// 输出输入的值 cout <<"You entered: "<< endl; cout <<"a = "<< a << endl; cout <<"b = "<< b << endl; cout <<"c = "<< c << endl;return0;}运行结果:
Enter an integer: 10 Enter a double: 3.14 Enter a string: Hello You entered: a = 10 b = 3.14 c = Hello 9.4 格式化输入/输出
C++提供了多种方式来控制输入/输出的格式,包括:
- 格式控制符:在
<iomanip>头文件中定义 - 成员函数:流对象的成员函数
9.4.1 格式控制符
代码示例:使用格式控制符
#include<iostream>#include<iomanip>usingnamespace std;intmain(){double pi =3.141592653589793;int num =42;// 设置精度 cout <<"pi = "<< pi << endl; cout <<"pi (precision 2): "<<setprecision(2)<< pi << endl; cout <<"pi (precision 10): "<<setprecision(10)<< pi << endl;// 设置宽度 cout <<"\nWidth formatting:"<< endl; cout <<"num: "<< num << endl; cout <<"num (width 10): "<<setw(10)<< num << endl; cout <<"num (width 10, left): "<<setw(10)<< left << num << endl;// 设置填充 cout <<"\nFill formatting:"<< endl; cout <<"num (width 10, fill with *): "<<setw(10)<<setfill('*')<< num << endl;// 进制转换 cout <<"\nNumber systems:"<< endl; cout <<"Decimal: "<< num << endl; cout <<"Octal: "<< oct << num << endl; cout <<"Hexadecimal: "<< hex << num << endl; cout <<"Hexadecimal (uppercase): "<< hex << uppercase << num << endl;return0;}运行结果:
pi = 3.14159 pi (precision 2): 3.1 pi (precision 10): 3.141592654 Width formatting: num: 42 num (width 10): 42 num (width 10, left): 42 Fill formatting: num (width 10, fill with *): ********42 Number systems: Decimal: 42 Octal: 52 Hexadecimal: 2a Hexadecimal (uppercase): 2A 9.4.2 流成员函数
代码示例:使用流成员函数
#include<iostream>usingnamespace std;intmain(){double pi =3.141592653589793;int num =42;// 设置精度 cout <<"pi = "<< pi << endl; cout.precision(2); cout <<"pi (precision 2): "<< pi << endl; cout.precision(10); cout <<"pi (precision 10): "<< pi << endl;// 设置宽度 cout <<"\nWidth formatting:"<< endl; cout <<"num: "<< num << endl; cout.width(10); cout <<"num (width 10): "<< num << endl;// 设置填充 cout <<"\nFill formatting:"<< endl; cout.width(10); cout.fill('*'); cout <<"num (width 10, fill with *): "<< num << endl;return0;}运行结果:
pi = 3.14159 pi (precision 2): 3.1 pi (precision 10): 3.141592654 Width formatting: num: 42 num (width 10): 42 Fill formatting: num (width 10, fill with *): ********42 9.5 文件输入/输出
C++的文件操作通过文件流类实现,主要包括ifstream、ofstream和fstream。
9.5.1 文件输出
代码示例:写入文件
#include<iostream>#include<fstream>usingnamespace std;intmain(){// 创建文件输出流对象 ofstream outFile("example.txt");// 检查文件是否成功打开if(!outFile){ cerr <<"Failed to open file for writing!"<< endl;return1;}// 写入数据 outFile <<"Hello, File!"<< endl; outFile <<"This is a test."<< endl; outFile <<"Number: "<<42<< endl; outFile <<"Pi: "<<3.14159<< endl;// 关闭文件 outFile.close(); cout <<"Data written to file successfully."<< endl;return0;}运行结果:
Data written to file successfully. 生成的文件内容 (example.txt):
Hello, File! This is a test. Number: 42 Pi: 3.14159 9.5.2 文件输入
代码示例:读取文件
#include<iostream>#include<fstream>#include<string>usingnamespace std;intmain(){// 创建文件输入流对象 ifstream inFile("example.txt");// 检查文件是否成功打开if(!inFile){ cerr <<"Failed to open file for reading!"<< endl;return1;}// 读取数据 string line; cout <<"File content:"<< endl; cout <<"-------------------"<< endl;// 逐行读取while(getline(inFile, line)){ cout << line << endl;}// 关闭文件 inFile.close();return0;}运行结果:
File content: ------------------- Hello, File! This is a test. Number: 42 Pi: 3.14159 9.5.3 同时读写文件
代码示例:同时读写文件
#include<iostream>#include<fstream>usingnamespace std;intmain(){// 创建文件输入/输出流对象 fstream file("example.txt", ios::in | ios::out | ios::app);// 检查文件是否成功打开if(!file){ cerr <<"Failed to open file!"<< endl;return1;}// 追加数据 file <<"\nAppended line."<< endl; file <<"Another appended line."<< endl;// 重新定位到文件开头 file.seekg(0, ios::beg);// 读取文件内容 string line; cout <<"File content after append:"<< endl; cout <<"-------------------"<< endl;while(getline(file, line)){ cout << line << endl;}// 关闭文件 file.close();return0;}运行结果:
File content after append: ------------------- Hello, File! This is a test. Number: 42 Pi: 3.14159 Appended line. Another appended line. 9.6 字符串流
字符串流(stringstream)是一种特殊的流,用于在内存中处理字符串数据。
9.6.1 字符串输出流
代码示例:使用ostringstream
#include<iostream>#include<sstream>#include<string>usingnamespace std;intmain(){// 创建字符串输出流对象 ostringstream oss;// 写入数据 oss <<"Hello, StringStream!"<< endl; oss <<"Number: "<<42<< endl; oss <<"Pi: "<<3.14159<< endl;// 获取字符串 string result = oss.str(); cout <<"StringStream content:"<< endl; cout <<"-------------------"<< endl; cout << result;return0;}运行结果:
StringStream content: ------------------- Hello, StringStream! Number: 42 Pi: 3.14159 9.6.2 字符串输入流
代码示例:使用istringstream
#include<iostream>#include<sstream>#include<string>usingnamespace std;intmain(){// 原始字符串 string data ="10 3.14 Hello";// 创建字符串输入流对象 istringstream iss(data);// 读取数据int num;double pi; string str; iss >> num >> pi >> str; cout <<"Parsed data:"<< endl; cout <<"-------------------"<< endl; cout <<"Integer: "<< num << endl; cout <<"Double: "<< pi << endl; cout <<"String: "<< str << endl;return0;}运行结果:
Parsed data: ------------------- Integer: 10 Double: 3.14 String: Hello 9.7 流状态控制
流对象有一个状态,可以通过状态标志和成员函数来检查和控制。
流状态标志:
good():流状态正常eof():到达文件结束fail():输入/输出操作失败bad():流已损坏
代码示例:流状态控制
#include<iostream>#include<fstream>usingnamespace std;intmain(){// 尝试打开不存在的文件 ifstream inFile("non_existent_file.txt");// 检查流状态if(!inFile){ cout <<"File open failed."<< endl; cout <<"good(): "<< inFile.good()<< endl; cout <<"eof(): "<< inFile.eof()<< endl; cout <<"fail(): "<< inFile.fail()<< endl; cout <<"bad(): "<< inFile.bad()<< endl;}// 清除错误状态 inFile.clear(); cout <<"\nAfter clear():"<< endl; cout <<"good(): "<< inFile.good()<< endl; cout <<"fail(): "<< inFile.fail()<< endl;// 关闭文件 inFile.close();return0;}运行结果:
File open failed. good(): 0 eof(): 0 fail(): 1 bad(): 0 After clear(): good(): 1 fail(): 0 9.8 文件复制程序
代码示例:实现文件复制功能
#include<iostream>#include<fstream>#include<string>usingnamespace std;boolcopyFile(const string& source,const string& destination){// 打开源文件 ifstream inFile(source, ios::binary);if(!inFile){ cerr <<"Failed to open source file: "<< source << endl;returnfalse;}// 打开目标文件 ofstream outFile(destination, ios::binary);if(!outFile){ cerr <<"Failed to open destination file: "<< destination << endl; inFile.close();returnfalse;}// 复制文件内容char buffer[1024];while(inFile.read(buffer,sizeof(buffer))){ outFile.write(buffer, inFile.gcount());}// 处理剩余部分 outFile.write(buffer, inFile.gcount());// 关闭文件 inFile.close(); outFile.close(); cout <<"File copied successfully from "<< source <<" to "<< destination << endl;returntrue;}intmain(){ string source ="example.txt"; string destination ="example_copy.txt";copyFile(source, destination);return0;}运行结果:
File copied successfully from example.txt to example_copy.txt 小结
本章介绍了C++的输入/输出流系统,包括:
- C++流库概述:流的概念和主要流类
- 基本输出流(cout):向标准输出设备输出数据
- 基本输入流(cin):从标准输入设备读取数据
- 格式化输入/输出:使用格式控制符和流成员函数控制输出格式
- 文件输入/输出:使用文件流类读写文件
- 字符串流:在内存中处理字符串数据
- 流状态控制:检查和控制流的状态
- 文件复制程序:实际应用示例
输入/输出是程序与外部世界交互的重要方式,掌握C++的流操作对于编写实用的程序至关重要。
习题9
- 简述C++流库的基本结构。
- 如何使用cout进行基本输出?
- 如何使用cin进行基本输入?
- 如何控制输出的格式?请举例说明。
- 如何读写文件?请举例说明。
- 什么是字符串流?它的作用是什么?
- 如何检查和控制流的状态?
- 请实现一个程序,读取一个文本文件,统计其中的单词数和行数。
第10章 异常处理
10.1 异常处理的概念
异常处理是C++中处理运行时错误的机制,它允许程序在遇到错误时,将错误信息传递给上层调用者,而不是直接终止程序。异常处理的核心思想是:将错误检测与错误处理分离。
异常处理的优点:
- 提高程序的健壮性和可靠性
- 使错误处理代码与正常代码分离,提高代码的可读性
- 允许在适当的层次处理错误
- 支持错误的传递和转换
10.2 异常的抛出与捕获
10.2.1 异常的抛出
使用throw关键字抛出异常,可以抛出任何类型的值作为异常。
代码示例:抛出异常
#include<iostream>usingnamespace std;intdivide(int a,int b){if(b ==0){// 抛出异常throw"Division by zero!";}return a / b;}intmain(){int x =10, y =0;try{int result =divide(x, y); cout <<"Result: "<< result << endl;}catch(constchar* msg){// 捕获异常 cerr <<"Error: "<< msg << endl;} cout <<"Program continues..."<< endl;return0;}运行结果:
Error: Division by zero! Program continues... 10.2.2 异常的捕获
使用try-catch语句块捕获异常,try块包含可能抛出异常的代码,catch块用于处理异常。
代码示例:捕获不同类型的异常
#include<iostream>#include<string>usingnamespace std;voidprocess(int type){switch(type){case1:throw100;// 抛出int类型异常case2:throw3.14;// 抛出double类型异常case3:throwstring("Error message");// 抛出string类型异常default: cout <<"No exception thrown"<< endl;}}intmain(){// 测试不同类型的异常for(int i =1; i <=4; i++){ cout <<"\nTesting case "<< i <<":"<< endl;try{process(i);}catch(int e){ cout <<"Caught int exception: "<< e << endl;}catch(double e){ cout <<"Caught double exception: "<< e << endl;}catch(const string& e){ cout <<"Caught string exception: "<< e << endl;}catch(...){// 捕获所有其他类型的异常 cout <<"Caught unknown exception"<< endl;}}return0;}运行结果:
Testing case 1: Caught int exception: 100 Testing case 2: Caught double exception: 3.14 Testing case 3: Caught string exception: Error message Testing case 4: No exception thrown 10.3 异常处理的机制
10.3.1 异常的传播
当异常被抛出后,如果当前函数没有捕获它,异常会向上传播到调用者函数,直到找到合适的catch块。如果一直没有找到,程序会调用terminate()函数终止。
代码示例:异常的传播
#include<iostream>usingnamespace std;voidlevel3(){ cout <<"In level3()"<< endl;throw"Exception from level3";}voidlevel2(){ cout <<"In level2(), calling level3()"<< endl;level3(); cout <<"Back to level2()"<< endl;// 不会执行}voidlevel1(){ cout <<"In level1(), calling level2()"<< endl;try{level2();}catch(constchar* msg){ cout <<"Caught in level1(): "<< msg << endl;// 重新抛出异常throw;} cout <<"Back to level1()"<< endl;// 不会执行}intmain(){ cout <<"In main(), calling level1()"<< endl;try{level1();}catch(constchar* msg){ cout <<"Caught in main(): "<< msg << endl;} cout <<"Back to main()"<< endl;return0;}运行结果:
In main(), calling level1() In level1(), calling level2() In level2(), calling level3() In level3() Caught in level1(): Exception from level3 Caught in main(): Exception from level3 Back to main() 10.3.2 异常与构造函数/析构函数
当异常发生时,C++会自动调用已构造对象的析构函数,确保资源的正确释放。
代码示例:异常与析构函数
#include<iostream>usingnamespace std;classResource{private: string name;public:Resource(string n):name(n){ cout <<"Resource "<< name <<" acquired"<< endl;}~Resource(){ cout <<"Resource "<< name <<" released"<< endl;}voiduse(){throw"Error using resource";}};intmain(){try{ Resource r1("File"); Resource r2("Network"); r1.use();// 抛出异常 Resource r3("Database");// 不会构造}catch(constchar* msg){ cout <<"Caught exception: "<< msg << endl;} cout <<"Program continues..."<< endl;return0;}运行结果:
Resource File acquired Resource Network acquired Resource Network released Resource File released Caught exception: Error using resource Program continues... 10.4 标准异常
C++标准库提供了一系列标准异常类,它们都派生自exception类。标准异常主要分为两类:
- 逻辑错误:在程序运行前可以检测到的错误,如
logic_error及其派生类 - 运行时错误:在程序运行时发生的错误,如
runtime_error及其派生类
常见的标准异常:
bad_alloc:内存分配失败bad_cast:类型转换失败out_of_range:索引越界invalid_argument:无效参数length_error:长度错误range_error:范围错误
代码示例:使用标准异常
#include<iostream>#include<stdexcept>#include<vector>usingnamespace std;voidtestStandardExceptions(){// 测试out_of_range vector<int> vec ={1,2,3};try{ cout << vec.at(5)<< endl;// 越界访问}catch(const out_of_range& e){ cout <<"out_of_range: "<< e.what()<< endl;}// 测试invalid_argumenttry{throwinvalid_argument("Invalid argument");}catch(const invalid_argument& e){ cout <<"invalid_argument: "<< e.what()<< endl;}// 测试runtime_errortry{throwruntime_error("Runtime error");}catch(const runtime_error& e){ cout <<"runtime_error: "<< e.what()<< endl;}// 测试通用异常try{throwexception();}catch(const exception& e){ cout <<"exception: "<< e.what()<< endl;}}intmain(){testStandardExceptions();return0;}运行结果:
out_of_range: vector::_M_range_check: __n (which is 5) >= this->size() (which is 3) invalid_argument: Invalid argument runtime_error: Runtime error exception: std::exception 10.5 自定义异常
可以通过继承标准异常类来定义自定义异常,通常继承自exception或其派生类。
代码示例:自定义异常
#include<iostream>#include<stdexcept>#include<string>usingnamespace std;// 自定义异常类classMyException:publicexception{private: string message;public:MyException(const string& msg):message(msg){}// 重写what()方法constchar*what()constnoexceptoverride{return message.c_str();}};// 另一个自定义异常类classNetworkException:publicruntime_error{public:NetworkException(const string& msg):runtime_error(msg){}};voidtestCustomExceptions(){// 测试MyExceptiontry{throwMyException("Custom exception occurred");}catch(const MyException& e){ cout <<"Caught MyException: "<< e.what()<< endl;}// 测试NetworkExceptiontry{throwNetworkException("Network connection failed");}catch(const NetworkException& e){ cout <<"Caught NetworkException: "<< e.what()<< endl;}// 测试通过基类捕获try{throwNetworkException("Another network error");}catch(const runtime_error& e){ cout <<"Caught via runtime_error: "<< e.what()<< endl;}}intmain(){testCustomExceptions();return0;}运行结果:
Caught MyException: Custom exception occurred Caught NetworkException: Network connection failed Caught via runtime_error: Another network error 10.6 异常处理的最佳实践
- 只在异常情况下使用异常:不要将异常用于常规的控制流
- 抛出有意义的异常:异常应该包含足够的信息,便于调试和处理
- 捕获适当的异常:使用具体的异常类型,避免使用通用的
catch(...) - 及时释放资源:使用RAII(资源获取即初始化)技术管理资源
- 不要在析构函数中抛出异常:可能导致程序终止
- 记录异常信息:在适当的层次记录异常信息
- 考虑异常的性能影响:异常处理可能会影响程序性能
代码示例:异常处理的最佳实践
#include<iostream>#include<fstream>#include<stdexcept>#include<string>usingnamespace std;// 使用RAII管理文件资源classFileGuard{private: ifstream& file;public:FileGuard(ifstream& f):file(f){}~FileGuard(){if(file.is_open()){ file.close(); cout <<"File closed"<< endl;}}}; string readFile(const string& filename){ ifstream file(filename);if(!file){throwruntime_error("Failed to open file: "+ filename);}// 使用FileGuard确保文件关闭 FileGuard guard(file); string content; string line;while(getline(file, line)){ content += line +"\n";}if(content.empty()){throwruntime_error("File is empty: "+ filename);}return content;}intmain(){try{ string content =readFile("example.txt"); cout <<"File content:"<< endl; cout << content;}catch(const runtime_error& e){ cerr <<"Error: "<< e.what()<< endl;}catch(const exception& e){ cerr <<"Unexpected error: "<< e.what()<< endl;}catch(...){ cerr <<"Unknown error occurred"<< endl;}return0;}运行结果:
File content: Hello, File! This is a test. Number: 42 Pi: 3.14159 Appended line. Another appended line. File closed 10.7 异常处理的应用实例
代码示例:实现一个安全的栈类
#include<iostream>#include<vector>#include<stdexcept>usingnamespace std;// 安全的栈类template<typenameT>classSafeStack{private: vector<T> elements;public:// 压栈voidpush(const T& item){ elements.push_back(item);}// 出栈voidpop(){if(empty()){throwunderflow_error("Stack underflow");} elements.pop_back();}// 获取栈顶元素 T&top(){if(empty()){throwunderflow_error("Stack underflow");}return elements.back();}// 获取栈顶元素(const版本)const T&top()const{if(empty()){throwunderflow_error("Stack underflow");}return elements.back();}// 检查栈是否为空boolempty()const{return elements.empty();}// 获取栈的大小 size_t size()const{return elements.size();}};intmain(){ SafeStack<int> stack;try{// 测试空栈操作 stack.pop();}catch(const underflow_error& e){ cout <<"Error: "<< e.what()<< endl;}try{// 测试正常操作 stack.push(10); stack.push(20); stack.push(30); cout <<"Stack size: "<< stack.size()<< endl; cout <<"Top element: "<< stack.top()<< endl; stack.pop(); cout <<"After pop, top: "<< stack.top()<< endl; stack.pop(); stack.pop(); cout <<"After all pops, stack empty: "<< stack.empty()<< endl;// 测试再次空栈操作 stack.top();}catch(const exception& e){ cout <<"Error: "<< e.what()<< endl;}return0;}运行结果:
Error: Stack underflow Stack size: 3 Top element: 30 After pop, top: 20 After all pops, stack empty: 1 Error: Stack underflow 小结
本章介绍了C++的异常处理机制,包括:
- 异常处理的概念:异常处理的基本思想和优点
- 异常的抛出与捕获:使用
throw抛出异常,使用try-catch捕获异常 - 异常处理的机制:异常的传播、重新抛出异常
- 标准异常:C++标准库提供的异常类
- 自定义异常:通过继承标准异常类定义自定义异常
- 异常处理的最佳实践:异常处理的原则和技巧
- 异常处理的应用实例:安全栈类的实现
异常处理是C++中处理错误的重要机制,合理使用异常处理可以提高程序的健壮性和可靠性,使代码更加清晰和可维护。
习题10
- 什么是异常处理?异常处理的优点是什么?
- 如何抛出和捕获异常?
- 异常是如何传播的?
- 当异常发生时,构造函数和析构函数会如何处理?
- 简述C++标准异常的层次结构。
- 如何定义和使用自定义异常?
- 异常处理的最佳实践有哪些?
- 请实现一个带有异常处理的除法函数,处理除数为零的情况。
第11章 Visual C++ 2019开发环境简介
11.1 Visual Studio 2019概述
Visual Studio 2019是Microsoft公司开发的集成开发环境(IDE),支持多种编程语言,包括C++、C#、Python等。它提供了代码编辑、编译、调试、测试等一体化的开发工具,是C++开发的主流环境之一。
Visual Studio 2019的主要版本:
- Community:免费版本,适合学生、开源开发者和个人开发者
- Professional:专业版本,适合专业开发者和小团队
- Enterprise:企业版本,适合大型企业和团队
11.2 Visual Studio 2019的安装
11.2.1 系统要求
- 操作系统:Windows 10版本1507或更高版本,Windows Server 2016或更高版本
- 处理器:1.8 GHz或更快的处理器,推荐多核处理器
- 内存:至少4 GB RAM,推荐8 GB或更多
- 硬盘空间:至少800 MB可用空间,推荐SSD
- 显示器:1024x768分辨率或更高
11.2.2 安装步骤
- 下载安装程序:
- 访问Visual Studio官方网站(https://visualstudio.microsoft.com/)
- 下载Visual Studio 2019安装程序
- 运行安装程序:
- 双击安装程序启动
- 选择"继续"开始安装
- 选择工作负载:
- 在"工作负载"选项卡中,选择"使用C++的桌面开发"
- 可根据需要选择其他工作负载
- 选择组件:
- 在"单个组件"选项卡中,选择需要的组件
- 推荐选择:
- MSVC v142 - VS 2019 C++ x64/x86生成工具
- Windows 10 SDK
- C++分析工具
- 开始安装:
- 点击"安装"按钮开始安装
- 等待安装完成,可能需要重启电脑
11.3 创建C++项目
11.3.1 创建控制台应用项目
步骤:
- 启动Visual Studio 2019
- 选择"创建新项目"
- 选择项目模板:
- 在"创建新项目"对话框中,搜索并选择"控制台应用"
- 点击"下一步"
- 配置项目:
- 输入项目名称(如"HelloWorld")
- 选择项目位置
- 点击"创建"
- 查看项目结构:
- 解决方案资源管理器中会显示项目文件
- 默认生成
HelloWorld.cpp文件,包含基本的控制台应用代码
代码示例:默认生成的控制台应用代码
#include<iostream>intmain(){ std::cout <<"Hello World!"<< std::endl;return0;}11.3.2 创建空项目
步骤:
- 启动Visual Studio 2019
- 选择"创建新项目"
- 选择项目模板:
- 在"创建新项目"对话框中,搜索并选择"空项目"
- 点击"下一步"
- 配置项目:
- 输入项目名称
- 选择项目位置
- 点击"创建"
- 添加源文件:
- 在解决方案资源管理器中,右键点击"源文件"
- 选择"添加" → “新建项”
- 选择"C++文件(.cpp)"
- 输入文件名
- 点击"添加"
11.4 编辑和编译C++程序
11.4.1 代码编辑
Visual Studio 2019提供了强大的代码编辑功能:
- 语法高亮:不同语法元素使用不同颜色显示
- 代码自动完成:输入代码时自动提示可能的选项
- 错误提示:实时检查代码错误并提示
- 代码格式化:自动调整代码格式
- 代码导航:快速跳转到函数定义、变量声明等
11.4.2 编译程序
编译步骤:
- 构建解决方案:
- 点击"生成"菜单 → “生成解决方案”
- 或使用快捷键
Ctrl+Shift+B
- 查看编译结果:
- 编译结果显示在"输出"窗口中
- 如果有错误,会在"错误列表"窗口中显示
常见编译错误:
- 语法错误:如缺少分号、括号不匹配等
- 未定义的标识符:如变量或函数未声明
- 类型不匹配:如赋值时类型不一致
- 链接错误:如函数定义未找到
11.5 运行和调试程序
11.5.1 运行程序
运行步骤:
- 启动调试:
- 点击"调试"菜单 → “开始调试”
- 或使用快捷键
F5
- 开始执行(不调试):
- 点击"调试"菜单 → “开始执行(不调试)”
- 或使用快捷键
Ctrl+F5
- 查看运行结果:
- 程序输出显示在控制台窗口中
11.5.2 调试工具
Visual Studio 2019提供了强大的调试工具:
- 断点:
- 在代码行左侧点击设置断点
- 程序运行到断点处会暂停
- 监视窗口:
- 查看变量和表达式的值
- 可以添加、删除监视项
- 自动窗口:
- 自动显示当前作用域中的变量
- 局部窗口:
- 显示当前函数中的局部变量
- 调用堆栈:
- 显示函数调用的层次结构
- 内存窗口:
- 查看内存中的数据
- 调试控制台:
- 执行调试命令
- 查看调试信息
调试步骤:
- 设置断点:在需要调试的代码行设置断点
- 启动调试:按
F5启动调试 - 单步执行:
F10:单步跳过(不进入函数)F11:单步进入(进入函数)Shift+F11:单步跳出(从函数返回)
- 查看变量:在监视窗口或局部窗口中查看变量值
- 继续执行:按
F5继续执行到下一个断点 - 停止调试:按
Shift+F5停止调试
代码示例:使用调试工具调试程序
#include<iostream>usingnamespace std;intfactorial(int n){if(n <=1){return1;}return n *factorial(n -1);// 在这行设置断点}intmain(){int n =5;int result =factorial(n); cout <<"Factorial of "<< n <<" is "<< result << endl;return0;}11.6 项目管理和配置
11.6.1 解决方案和项目
- 解决方案:包含一个或多个项目的容器
- 项目:包含源代码、资源文件等的集合
管理解决方案和项目:
- 添加新项目:
- 右键点击解决方案 → “添加” → “新建项目”
- 添加现有项目:
- 右键点击解决方案 → “添加” → “现有项目”
- 移除项目:
- 右键点击项目 → “移除”
11.6.2 项目属性
查看和修改项目属性:
- 打开项目属性:
- 右键点击项目 → “属性”
- 常用属性设置:
- 配置属性 → 常规:设置输出目录、中间目录等
- 配置属性 → C/C++ → 常规:设置附加包含目录
- 配置属性 → C/C++ → 预处理器:设置预处理器定义
- 配置属性 → C/C++ → 代码生成:设置运行库、优化等
- 配置属性 → 链接器 → 常规:设置附加库目录
- 配置属性 → 链接器 → 输入:设置附加依赖项
11.6.3 版本控制
Visual Studio 2019集成了Git版本控制:
- 初始化Git仓库:
- 右键点击解决方案 → “创建Git存储库”
- 提交更改:
- 在"团队资源管理器"窗口中,查看更改并提交
- 分支管理:
- 创建、切换、合并分支
- 远程仓库:
- 连接到GitHub、Azure DevOps等远程仓库
11.7 实用技巧和快捷键
11.7.1 常用快捷键
| 快捷键 | 功能 |
|---|---|
Ctrl+Shift+B | 生成解决方案 |
F5 | 开始调试 |
Ctrl+F5 | 开始执行(不调试) |
F10 | 单步跳过 |
F11 | 单步进入 |
Shift+F11 | 单步跳出 |
Shift+F5 | 停止调试 |
Ctrl+K, Ctrl+C | 注释选定代码 |
Ctrl+K, Ctrl+U | 取消注释选定代码 |
Ctrl+F | 查找 |
Ctrl+H | 替换 |
Ctrl+G | 转到行 |
F12 | 转到定义 |
Shift+F12 | 查找所有引用 |
11.7.2 实用技巧
- 代码片段:
- 输入代码片段缩写,如
for、if、class等,然后按Tab键自动展开
- 输入代码片段缩写,如
- 智能提示:
- 输入代码时,Visual Studio会提供智能提示,按
Tab或Enter接受
- 输入代码时,Visual Studio会提供智能提示,按
- 代码重构:
- 右键点击代码 → “重构”,可以重命名变量、提取函数等
- 错误列表:
- 点击"视图"菜单 → “错误列表”,查看编译错误和警告
- 输出窗口:
- 点击"视图"菜单 → “输出”,查看编译和调试输出
- 代码地图:
- 点击"体系结构"菜单 → “生成代码地图”,查看代码结构
11.8 常见问题及解决方案
11.8.1 安装问题
- 安装失败:
- 确保系统满足最低要求
- 关闭所有正在运行的程序
- 以管理员身份运行安装程序
- 检查网络连接
- 缺少组件:
- 重新运行安装程序,添加缺少的工作负载和组件
11.8.2 编译问题
- 无法找到头文件:
- 在项目属性中添加包含目录
- 检查头文件路径是否正确
- 链接错误:
- 检查函数定义是否存在
- 确保所有必要的库都已链接
- 编译速度慢:
- 启用增量编译
- 减少预编译头文件大小
- 清理解决方案并重新生成
11.8.3 调试问题
- 断点未命中:
- 确保程序已重新编译
- 检查断点是否在执行路径上
- 确保调试配置正确
- 变量值不正确:
- 检查变量作用域
- 确保变量已初始化
- 检查内存是否损坏
小结
本章介绍了Visual Studio 2019开发环境的使用方法,包括:
- Visual Studio 2019概述:版本、系统要求等
- 安装步骤:下载、安装过程
- 创建项目:控制台应用、空项目等
- 编辑和编译:代码编辑功能、编译步骤
- 运行和调试:运行方式、调试工具
- 项目管理:解决方案管理、项目属性配置
- 实用技巧:快捷键、代码片段等
- 常见问题:安装、编译、调试中的常见问题及解决方案
Visual Studio 2019是一个功能强大的集成开发环境,掌握它的使用方法对于C++开发至关重要。通过本章的学习,读者应该能够熟练使用Visual Studio 2019进行C++程序的开发、调试和管理。
习题11
- 简述Visual Studio 2019的主要版本和特点。
- 如何在Visual Studio 2019中创建C++控制台应用项目?
- 简述Visual Studio 2019的代码编辑功能。
- 如何编译和运行C++程序?
- 简述Visual Studio 2019的调试工具和使用方法。
- 如何配置项目属性?
- 列出几个常用的Visual Studio 2019快捷键。
- 如何解决Visual Studio 2019中的常见编译错误?
第12章 综合实例
12.1 学生管理系统
12.1.1 系统功能
- 学生信息的添加、修改、删除和查询
- 学生成绩的管理
- 数据的保存和加载
12.1.2 系统设计
类结构:
- Person类:基类,包含基本个人信息
- Student类:派生类,继承自Person,包含学生特有的信息
- Course类:课程类,包含课程信息
- StudentManager类:学生管理类,负责学生信息的管理
代码示例:学生管理系统
#include<iostream>#include<vector>#include<string>#include<fstream>#include<algorithm>#include<stdexcept>usingnamespace std;// 基类:PersonclassPerson{protected: string name;int age; string id;public:Person(const string& n,int a,const string& i):name(n),age(a),id(i){}virtual~Person(){}// 虚函数:显示信息virtualvoiddisplay()const{ cout <<"Name: "<< name << endl; cout <<"Age: "<< age << endl; cout <<"ID: "<< id << endl;}// 获取属性 string getName()const{return name;}intgetAge()const{return age;} string getId()const{return id;}// 设置属性voidsetName(const string& n){ name = n;}voidsetAge(int a){ age = a;}voidsetId(const string& i){ id = i;}};// 课程类classCourse{private: string courseName;double score;public:Course(const string& cn,double s):courseName(cn),score(s){}// 获取属性 string getCourseName()const{return courseName;}doublegetScore()const{return score;}// 设置属性voidsetCourseName(const string& cn){ courseName = cn;}voidsetScore(double s){if(s <0|| s >100){throwinvalid_argument("Score must be between 0 and 100");} score = s;}// 显示信息voiddisplay()const{ cout <<"Course: "<< courseName <<", Score: "<< score << endl;}};// 派生类:StudentclassStudent:publicPerson{private: string department; vector<Course> courses;public:Student(const string& n,int a,const string& i,const string& d):Person(n, a, i),department(d){}// 重写虚函数:显示信息voiddisplay()constoverride{Person::display(); cout <<"Department: "<< department << endl; cout <<"Courses:"<< endl;for(constauto& course : courses){ cout <<" "; course.display();}}// 获取属性 string getDepartment()const{return department;}const vector<Course>&getCourses()const{return courses;}// 设置属性voidsetDepartment(const string& d){ department = d;}// 课程管理voidaddCourse(const Course& course){ courses.push_back(course);}voidremoveCourse(const string& courseName){auto it =find_if(courses.begin(), courses.end(),[&courseName](const Course& c){return c.getCourseName()== courseName;});if(it != courses.end()){ courses.erase(it);}else{throwruntime_error("Course not found");}}voidupdateCourseScore(const string& courseName,double score){auto it =find_if(courses.begin(), courses.end(),[&courseName](const Course& c){return c.getCourseName()== courseName;});if(it != courses.end()){ it->setScore(score);}else{throwruntime_error("Course not found");}}// 计算平均成绩doublecalculateAverageScore()const{if(courses.empty()){return0.0;}double sum =0.0;for(constauto& course : courses){ sum += course.getScore();}return sum / courses.size();}};// 学生管理类classStudentManager{private: vector<Student> students; string filename;public:StudentManager(const string& fn ="students.txt"):filename(fn){loadFromFile();}~StudentManager(){saveToFile();}// 添加学生voidaddStudent(const Student& student){// 检查ID是否重复auto it =find_if(students.begin(), students.end(),[&student](const Student& s){return s.getId()== student.getId();});if(it != students.end()){throwruntime_error("Student with this ID already exists");} students.push_back(student); cout <<"Student added successfully."<< endl;}// 删除学生voidremoveStudent(const string& id){auto it =find_if(students.begin(), students.end(),[&id](const Student& s){return s.getId()== id;});if(it != students.end()){ students.erase(it); cout <<"Student removed successfully."<< endl;}else{throwruntime_error("Student not found");}}// 修改学生信息voidupdateStudent(const string& id,const Student& newStudent){auto it =find_if(students.begin(), students.end(),[&id](const Student& s){return s.getId()== id;});if(it != students.end()){*it = newStudent; cout <<"Student updated successfully."<< endl;}else{throwruntime_error("Student not found");}}// 查询学生const Student*findStudent(const string& id)const{auto it =find_if(students.begin(), students.end(),[&id](const Student& s){return s.getId()== id;});if(it != students.end()){return&(*it);}returnnullptr;}// 显示所有学生voiddisplayAllStudents()const{if(students.empty()){ cout <<"No students found."<< endl;return;}for(size_t i =0; i < students.size(); i++){ cout <<"\nStudent "<< i +1<<":"<< endl; cout <<"-------------------"<< endl; students[i].display();}}// 按平均成绩排序voidsortByAverageScore(){sort(students.begin(), students.end(),[](const Student& s1,const Student& s2){return s1.calculateAverageScore()> s2.calculateAverageScore();}); cout <<"Students sorted by average score."<< endl;}// 保存数据到文件voidsaveToFile(){ ofstream outFile(filename);if(!outFile){ cerr <<"Failed to open file for writing."<< endl;return;}for(constauto& student : students){ outFile << student.getName()<<" "<< student.getAge()<<" "<< student.getId()<<" "<< student.getDepartment()<< endl;constauto& courses = student.getCourses(); outFile << courses.size()<< endl;for(constauto& course : courses){ outFile << course.getCourseName()<<" "<< course.getScore()<< endl;}} outFile.close(); cout <<"Data saved to file."<< endl;}// 从文件加载数据voidloadFromFile(){ ifstream inFile(filename);if(!inFile){// 文件不存在,创建空文件 ofstream outFile(filename); outFile.close();return;} students.clear(); string name, id, department, courseName;int age, courseCount;double score;while(inFile >> name >> age >> id >> department){ Student student(name, age, id, department); inFile >> courseCount;for(int i =0; i < courseCount; i++){ inFile >> courseName >> score; student.addCourse(Course(courseName, score));} students.push_back(student);} inFile.close(); cout <<"Data loaded from file."<< endl;}};// 菜单函数voidshowMenu(){ cout <<"\nStudent Management System"<< endl; cout <<"1. Add Student"<< endl; cout <<"2. Remove Student"<< endl; cout <<"3. Update Student"<< endl; cout <<"4. Find Student"<< endl; cout <<"5. Display All Students"<< endl; cout <<"6. Sort By Average Score"<< endl; cout <<"7. Exit"<< endl; cout <<"Enter your choice: ";}// 主函数intmain(){ StudentManager manager;int choice;do{showMenu(); cin >> choice; cin.ignore();// 忽略换行符try{switch(choice){case1:{// 添加学生 string name, id, department;int age; cout <<"Enter name: ";getline(cin, name); cout <<"Enter age: "; cin >> age; cin.ignore(); cout <<"Enter ID: ";getline(cin, id); cout <<"Enter department: ";getline(cin, department); Student student(name, age, id, department);// 添加课程char addCourse; cout <<"Add courses? (y/n): "; cin >> addCourse; cin.ignore();while(addCourse =='y'|| addCourse =='Y'){ string courseName;double score; cout <<"Enter course name: ";getline(cin, courseName); cout <<"Enter score: "; cin >> score; cin.ignore(); student.addCourse(Course(courseName, score)); cout <<"Add another course? (y/n): "; cin >> addCourse; cin.ignore();} manager.addStudent(student);break;}case2:{// 删除学生 string id; cout <<"Enter student ID: ";getline(cin, id); manager.removeStudent(id);break;}case3:{// 修改学生 string id; cout <<"Enter student ID: ";getline(cin, id);const Student* student = manager.findStudent(id);if(!student){ cout <<"Student not found."<< endl;break;}// 显示当前信息 cout <<"Current student information:"<< endl; student->display();// 输入新信息 string name, newId, department;int age; cout <<"Enter new name: ";getline(cin, name); cout <<"Enter new age: "; cin >> age; cin.ignore(); cout <<"Enter new ID: ";getline(cin, newId); cout <<"Enter new department: ";getline(cin, department); Student newStudent(name, age, newId, department);// 添加课程char addCourse; cout <<"Add courses? (y/n): "; cin >> addCourse; cin.ignore();while(addCourse =='y'|| addCourse =='Y'){ string courseName;double score; cout <<"Enter course name: ";getline(cin, courseName); cout <<"Enter score: "; cin >> score; cin.ignore(); newStudent.addCourse(Course(courseName, score)); cout <<"Add another course? (y/n): "; cin >> addCourse; cin.ignore();} manager.updateStudent(id, newStudent);break;}case4:{// 查询学生 string id; cout <<"Enter student ID: ";getline(cin, id);const Student* student = manager.findStudent(id);if(student){ cout <<"Student found:"<< endl; student->display(); cout <<"Average score: "<< student->calculateAverageScore()<< endl;}else{ cout <<"Student not found."<< endl;}break;}case5:{// 显示所有学生 manager.displayAllStudents();break;}case6:{// 按平均成绩排序 manager.sortByAverageScore(); manager.displayAllStudents();break;}case7:{// 退出 cout <<"Exiting..."<< endl;break;}default: cout <<"Invalid choice. Please try again."<< endl;}}catch(const exception& e){ cerr <<"Error: "<< e.what()<< endl; cin.clear(); cin.ignore(1000,'\n');}}while(choice !=7);return0;}运行结果:
Data loaded from file. Student Management System 1. Add Student 2. Remove Student 3. Update Student 4. Find Student 5. Display All Students 6. Sort By Average Score 7. Exit Enter your choice: 1 Enter name: Alice Enter age: 20 Enter ID: 20200101 Enter department: Computer Science Add courses? (y/n): y Enter course name: Mathematics Enter score: 95 Add another course? (y/n): y Enter course name: English Enter score: 88 Add another course? (y/n): n Student added successfully. Data saved to file. Student Management System 1. Add Student 2. Remove Student 3. Update Student 4. Find Student 5. Display All Students 6. Sort By Average Score 7. Exit Enter your choice: 5 Student 1: ------------------- Name: Alice Age: 20 ID: 20200101 Department: Computer Science Courses: Course: Mathematics, Score: 95 Course: English, Score: 88 Data saved to file. Student Management System 1. Add Student 2. Remove Student 3. Update Student 4. Find Student 5. Display All Students 6. Sort By Average Score 7. Exit Enter your choice: 7 Exiting... Data saved to file. 12.2 图形类层次结构
12.2.1 系统功能
- 计算不同图形的面积和周长
- 显示图形信息
- 支持多种图形类型
12.2.2 系统设计
类结构:
- Shape类:抽象基类,定义图形的基本接口
- Circle类:派生类,继承自Shape,表示圆形
- Rectangle类:派生类,继承自Shape,表示矩形
- Triangle类:派生类,继承自Shape,表示三角形
- ShapeManager类:图形管理类,负责图形的管理
代码示例:图形类层次结构
#include<iostream>#include<vector>#include<string>#include<memory>#include<cmath>#include<stdexcept>usingnamespace std;// 抽象基类:ShapeclassShape{public:virtual~Shape(){}// 纯虚函数:计算面积virtualdoublearea()const=0;// 纯虚函数:计算周长virtualdoubleperimeter()const=0;// 纯虚函数:显示信息virtualvoiddisplay()const=0;// 纯虚函数:获取类型virtual string getType()const=0;};// 圆形类classCircle:publicShape{private:double radius;public:Circle(double r){if(r <=0){throwinvalid_argument("Radius must be positive");} radius = r;}doublearea()constoverride{return M_PI * radius * radius;}doubleperimeter()constoverride{return2* M_PI * radius;}voiddisplay()constoverride{ cout <<"Circle:"<< endl; cout <<"Radius: "<< radius << endl; cout <<"Area: "<<area()<< endl; cout <<"Perimeter: "<<perimeter()<< endl;} string getType()constoverride{return"Circle";}// 获取半径doublegetRadius()const{return radius;}// 设置半径voidsetRadius(double r){if(r <=0){throwinvalid_argument("Radius must be positive");} radius = r;}};// 矩形类classRectangle:publicShape{private:double width;double height;public:Rectangle(double w,double h){if(w <=0|| h <=0){throwinvalid_argument("Width and height must be positive");} width = w; height = h;}doublearea()constoverride{return width * height;}doubleperimeter()constoverride{return2*(width + height);}voiddisplay()constoverride{ cout <<"Rectangle:"<< endl; cout <<"Width: "<< width << endl; cout <<"Height: "<< height << endl; cout <<"Area: "<<area()<< endl; cout <<"Perimeter: "<<perimeter()<< endl;} string getType()constoverride{return"Rectangle";}// 获取属性doublegetWidth()const{return width;}doublegetHeight()const{return height;}// 设置属性voidsetWidth(double w){if(w <=0){throwinvalid_argument("Width must be positive");} width = w;}voidsetHeight(double h){if(h <=0){throwinvalid_argument("Height must be positive");} height = h;}};// 三角形类classTriangle:publicShape{private:double a, b, c;// 三边长度public:Triangle(double x,double y,double z){if(x <=0|| y <=0|| z <=0){throwinvalid_argument("All sides must be positive");}if(x + y <= z || x + z <= y || y + z <= x){throwinvalid_argument("Invalid triangle: sum of any two sides must be greater than the third side");} a = x; b = y; c = z;}doublearea()constoverride{// 使用海伦公式计算面积double s =(a + b + c)/2;returnsqrt(s *(s - a)*(s - b)*(s - c));}doubleperimeter()constoverride{return a + b + c;}voiddisplay()constoverride{ cout <<"Triangle:"<< endl; cout <<"Sides: "<< a <<", "<< b <<", "<< c << endl; cout <<"Area: "<<area()<< endl; cout <<"Perimeter: "<<perimeter()<< endl;} string getType()constoverride{return"Triangle";}// 获取属性doublegetSideA()const{return a;}doublegetSideB()const{return b;}doublegetSideC()const{return c;}// 设置属性voidsetSides(double x,double y,double z){if(x <=0|| y <=0|| z <=0){throwinvalid_argument("All sides must be positive");}if(x + y <= z || x + z <= y || y + z <= x){throwinvalid_argument("Invalid triangle: sum of any two sides must be greater than the third side");} a = x; b = y; c = z;}};// 图形管理类classShapeManager{private: vector<unique_ptr<Shape>> shapes;public:// 添加图形voidaddShape(unique_ptr<Shape> shape){ shapes.push_back(move(shape)); cout <<"Shape added successfully."<< endl;}// 删除图形voidremoveShape(int index){if(index <0|| index >= shapes.size()){throwout_of_range("Invalid shape index");} shapes.erase(shapes.begin()+ index); cout <<"Shape removed successfully."<< endl;}// 显示所有图形voiddisplayAllShapes()const{if(shapes.empty()){ cout <<"No shapes found."<< endl;return;}for(size_t i =0; i < shapes.size(); i++){ cout <<"\nShape "<< i +1<<":"<< endl; cout <<"-------------------"<< endl; shapes[i]->display();}}// 计算所有图形的总面积doublecalculateTotalArea()const{double totalArea =0.0;for(constauto& shape : shapes){ totalArea += shape->area();}return totalArea;}// 计算所有图形的总周长doublecalculateTotalPerimeter()const{double totalPerimeter =0.0;for(constauto& shape : shapes){ totalPerimeter += shape->perimeter();}return totalPerimeter;}// 按面积排序voidsortByArea(){sort(shapes.begin(), shapes.end(),[](const unique_ptr<Shape>& s1,const unique_ptr<Shape>& s2){return s1->area()> s2->area();}); cout <<"Shapes sorted by area."<< endl;}};// 菜单函数voidshowMenu(){ cout <<"\nShape Management System"<< endl; cout <<"1. Add Circle"<< endl; cout <<"2. Add Rectangle"<< endl; cout <<"3. Add Triangle"<< endl; cout <<"4. Remove Shape"<< endl; cout <<"5. Display All Shapes"<< endl; cout <<"6. Calculate Total Area"<< endl; cout <<"7. Calculate Total Perimeter"<< endl; cout <<"8. Sort By Area"<< endl; cout <<"9. Exit"<< endl; cout <<"Enter your choice: ";}// 主函数intmain(){ ShapeManager manager;int choice;do{showMenu(); cin >> choice;try{switch(choice){case1:{// 添加圆形double radius; cout <<"Enter radius: "; cin >> radius; manager.addShape(make_unique<Circle>(radius));break;}case2:{// 添加矩形double width, height; cout <<"Enter width: "; cin >> width; cout <<"Enter height: "; cin >> height; manager.addShape(make_unique<Rectangle>(width, height));break;}case3:{// 添加三角形double a, b, c; cout <<"Enter side a: "; cin >> a; cout <<"Enter side b: "; cin >> b; cout <<"Enter side c: "; cin >> c; manager.addShape(make_unique<Triangle>(a, b, c));break;}case4:{// 删除图形int index; cout <<"Enter shape index (1-based): "; cin >> index; manager.removeShape(index -1);break;}case5:{// 显示所有图形 manager.displayAllShapes();break;}case6:{// 计算总面积double totalArea = manager.calculateTotalArea(); cout <<"Total area: "<< totalArea << endl;break;}case7:{// 计算总周长double totalPerimeter = manager.calculateTotalPerimeter(); cout <<"Total perimeter: "<< totalPerimeter << endl;break;}case8:{// 按面积排序 manager.sortByArea(); manager.displayAllShapes();break;}case9:{// 退出 cout <<"Exiting..."<< endl;break;}default: cout <<"Invalid choice. Please try again."<< endl;}}catch(const exception& e){ cerr <<"Error: "<< e.what()<< endl; cin.clear(); cin.ignore(1000,'\n');}}while(choice !=9);return0;}运行结果:
Shape Management System 1. Add Circle 2. Add Rectangle 3. Add Triangle 4. Remove Shape 5. Display All Shapes 6. Calculate Total Area 7. Calculate Total Perimeter 8. Sort By Area 9. Exit Enter your choice: 1 Enter radius: 5 Shape added successfully. Shape Management System 1. Add Circle 2. Add Rectangle 3. Add Triangle 4. Remove Shape 5. Display All Shapes 6. Calculate Total Area 7. Calculate Total Perimeter 8. Sort By Area 9. Exit Enter your choice: 2 Enter width: 10 Enter height: 20 Shape added successfully. Shape Management System 1. Add Circle 2. Add Rectangle 3. Add Triangle 4. Remove Shape 5. Display All Shapes 6. Calculate Total Area 7. Calculate Total Perimeter 8. Sort By Area 9. Exit Enter your choice: 3 Enter side a: 3 Enter side b: 4 Enter side c: 5 Shape added successfully. Shape Management System 1. Add Circle 2. Add Rectangle 3. Add Triangle 4. Remove Shape 5. Display All Shapes 6. Calculate Total Area 7. Calculate Total Perimeter 8. Sort By Area 9. Exit Enter your choice: 5 Shape 1: ------------------- Circle: Radius: 5 Area: 78.5398 Perimeter: 31.4159 Shape 2: ------------------- Rectangle: Width: 10 Height: 20 Area: 200 Perimeter: 60 Shape 3: ------------------- Triangle: Sides: 3, 4, 5 Area: 6 Perimeter: 12 Shape Management System 1. Add Circle 2. Add Rectangle 3. Add Triangle 4. Remove Shape 5. Display All Shapes 6. Calculate Total Area 7. Calculate Total Perimeter 8. Sort By Area 9. Exit Enter your choice: 8 Shapes sorted by area. Shape 1: ------------------- Rectangle: Width: 10 Height: 20 Area: 200 Perimeter: 60 Shape 2: ------------------- Circle: Radius: 5 Area: 78.5398 Perimeter: 31.4159 Shape 3: ------------------- Triangle: Sides: 3, 4, 5 Area: 6 Perimeter: 12 Shape Management System 1. Add Circle 2. Add Rectangle 3. Add Triangle 4. Remove Shape 5. Display All Shapes 6. Calculate Total Area 7. Calculate Total Perimeter 8. Sort By Area 9. Exit Enter your choice: 9 Exiting... 12.3 文件加密工具
12.3.1 系统功能
- 文件的加密和解密
- 支持多种加密算法
- 简单的用户界面
12.3.2 系统设计
类结构:
- Encryptor类:抽象基类,定义加密接口
- CaesarEncryptor类:派生类,实现凯撒加密
- XorEncryptor类:派生类,实现异或加密
- FileEncryptor类:文件加密类,负责文件的加密和解密
代码示例:文件加密工具
#include<iostream>#include<fstream>#include<string>#include<stdexcept>usingnamespace std;// 抽象基类:EncryptorclassEncryptor{public:virtual~Encryptor(){}// 纯虚函数:加密virtual string encrypt(const string& plaintext)const=0;// 纯虚函数:解密virtual string decrypt(const string& ciphertext)const=0;// 虚函数:获取加密算法名称virtual string getAlgorithmName()const=0;};// 凯撒加密类classCaesarEncryptor:publicEncryptor{private:int shift;public:CaesarEncryptor(int s):shift(s){// 确保shift在合理范围内 shift = shift %26;if(shift <0){ shift +=26;}} string encrypt(const string& plaintext)constoverride{ string ciphertext;for(char c : plaintext){if(isalpha(c)){char base =islower(c)?'a':'A'; c =static_cast<char>((c - base + shift)%26+ base);} ciphertext += c;}return ciphertext;} string decrypt(const string& ciphertext)constoverride{ string plaintext;int reverseShift =26- shift;for(char c : ciphertext){if(isalpha(c)){char base =islower(c)?'a':'A'; c =static_cast<char>((c - base + reverseShift)%26+ base);} plaintext += c;}return plaintext;} string getAlgorithmName()constoverride{return"Caesar Cipher";}// 获取shift值intgetShift()const{return shift;}};// 异或加密类classXorEncryptor:publicEncryptor{private:char key;public:XorEncryptor(char k):key(k){} string encrypt(const string& plaintext)constoverride{ string ciphertext;for(char c : plaintext){ c ^= key; ciphertext += c;}return ciphertext;} string decrypt(const string& ciphertext)constoverride{// 异或加密的解密与加密相同returnencrypt(ciphertext);} string getAlgorithmName()constoverride{return"XOR Cipher";}// 获取密钥chargetKey()const{return key;}};// 文件加密类classFileEncryptor{private: Encryptor* encryptor;public:FileEncryptor(Encryptor* e):encryptor(e){if(!encryptor){throwinvalid_argument("Encryptor cannot be null");}}// 加密文件voidencryptFile(const string& inputFile,const string& outputFile)const{ ifstream inFile(inputFile, ios::binary);if(!inFile){throwruntime_error("Failed to open input file: "+ inputFile);} ofstream outFile(outputFile, ios::binary);if(!outFile){throwruntime_error("Failed to open output file: "+ outputFile);}// 读取文件内容 string content((istreambuf_iterator<char>(inFile)),istreambuf_iterator<char>()); inFile.close();// 加密内容 string encryptedContent = encryptor->encrypt(content);// 写入加密内容 outFile.write(encryptedContent.c_str(), encryptedContent.size()); outFile.close(); cout <<"File encrypted successfully using "<< encryptor->getAlgorithmName()<< endl; cout <<"Input file: "<< inputFile << endl; cout <<"Output file: "<< outputFile << endl;}// 解密文件voiddecryptFile(const string& inputFile,const string& outputFile)const{ ifstream inFile(inputFile, ios::binary);if(!inFile){throwruntime_error("Failed to open input file: "+ inputFile);} ofstream outFile(outputFile, ios::binary);if(!outFile){throwruntime_error("Failed to open output file: "+ outputFile);}// 读取文件内容 string content((istreambuf_iterator<char>(inFile)),istreambuf_iterator<char>()); inFile.close();// 解密内容 string decryptedContent = encryptor->decrypt(content);// 写入解密内容 outFile.write(decryptedContent.c_str(), decryptedContent.size()); outFile.close(); cout <<"File decrypted successfully using "<< encryptor->getAlgorithmName()<< endl; cout <<"Input file: "<< inputFile << endl; cout <<"Output file: "<< outputFile << endl;}};// 菜单函数voidshowMenu(){ cout <<"\nFile Encryption Tool"<< endl; cout <<"1. Caesar Cipher Encryption"<< endl; cout <<"2. Caesar Cipher Decryption"<< endl; cout <<"3. XOR Cipher Encryption"<< endl; cout <<"4. XOR Cipher Decryption"<< endl; cout <<"5. Exit"<< endl; cout <<"Enter your choice: ";}// 主函数intmain(){int choice;do{showMenu(); cin >> choice; cin.ignore();// 忽略换行符try{switch(choice){case1:{// 凯撒加密int shift; string inputFile, outputFile; cout <<"Enter shift value: "; cin >> shift; cin.ignore(); cout <<"Enter input file path: ";getline(cin, inputFile); cout <<"Enter output file path: ";getline(cin, outputFile); CaesarEncryptor encryptor(shift); FileEncryptor fileEncryptor(&encryptor); fileEncryptor.encryptFile(inputFile, outputFile);break;}case2:{// 凯撒解密int shift; string inputFile, outputFile; cout <<"Enter shift value: "; cin >> shift; cin.ignore(); cout <<"Enter input file path: ";getline(cin, inputFile); cout <<"Enter output file path: ";getline(cin, outputFile); CaesarEncryptor encryptor(shift); FileEncryptor fileEncryptor(&encryptor); fileEncryptor.decryptFile(inputFile, outputFile);break;}case3:{// 异或加密char key; string inputFile, outputFile; cout <<"Enter encryption key (single character): "; cin >> key; cin.ignore(); cout <<"Enter input file path: ";getline(cin, inputFile); cout <<"Enter output file path: ";getline(cin, outputFile); XorEncryptor encryptor(key); FileEncryptor fileEncryptor(&encryptor); fileEncryptor.encryptFile(inputFile, outputFile);break;}case4:{// 异或解密char key; string inputFile, outputFile; cout <<"Enter encryption key (single character): "; cin >> key; cin.ignore(); cout <<"Enter input file path: ";getline(cin, inputFile); cout <<"Enter output file path: ";getline(cin, outputFile); XorEncryptor encryptor(key); FileEncryptor fileEncryptor(&encryptor); fileEncryptor.decryptFile(inputFile, outputFile);break;}case5:{// 退出 cout <<"Exiting..."<< endl;break;}default: cout <<"Invalid choice. Please try again."<< endl;}}catch(const exception& e){ cerr <<"Error: "<< e.what()<< endl; cin.clear(); cin.ignore(1000,'\n');}}while(choice !=5);return0;}运行结果:
File Encryption Tool 1. Caesar Cipher Encryption 2. Caesar Cipher Decryption 3. XOR Cipher Encryption 4. XOR Cipher Decryption 5. Exit Enter your choice: 1 Enter shift value: 3 Enter input file path: example.txt Enter output file path: example_encrypted.txt File encrypted successfully using Caesar Cipher Input file: example.txt Output file: example_encrypted.txt File Encryption Tool 1. Caesar Cipher Encryption 2. Caesar Cipher Decryption 3. XOR Cipher Encryption 4. XOR Cipher Decryption 5. Exit Enter your choice: 2 Enter shift value: 3 Enter input file path: example_encrypted.txt Enter output file path: example_decrypted.txt File decrypted successfully using Caesar Cipher Input file: example_encrypted.txt Output file: example_decrypted.txt File Encryption Tool 1. Caesar Cipher Encryption 2. Caesar Cipher Decryption 3. XOR Cipher Encryption 4. XOR Cipher Decryption 5. Exit Enter your choice: 5 Exiting... 小结
本章介绍了三个综合实例,展示了C++面向对象编程的实际应用:
- 学生管理系统:
- 展示了类的继承、多态、异常处理、文件I/O等知识点
- 实现了学生信息的添加、修改、删除、查询等功能
- 支持数据的保存和加载
- 图形类层次结构:
- 展示了抽象类、虚函数、多态、异常处理等知识点
- 实现了圆形、矩形、三角形等图形的面积和周长计算
- 支持图形的管理和排序
- 文件加密工具:
- 展示了抽象类、虚函数、多态、文件I/O等知识点
- 实现了凯撒加密和异或加密两种加密算法
- 支持文件的加密和解密
这些实例涵盖了C++面向对象编程的核心知识点,通过实际应用展示了如何使用这些知识点解决实际问题。通过学习这些实例,读者可以更好地理解和掌握C++面向对象编程的精髓,提高编程能力和解决实际问题的能力。
习题12
- 简述学生管理系统的设计思路和实现方法。
- 简述图形类层次结构的设计思路和实现方法。
- 简述文件加密工具的设计思路和实现方法。
- 在学生管理系统中,如何实现数据的保存和加载?
- 在图形类层次结构中,如何实现多态?
- 在文件加密工具中,如何实现不同加密算法的切换?
- 请设计一个简单的图书管理系统,包含图书的添加、修改、删除和查询功能。
- 请设计一个简单的计算器,支持基本的算术运算和三角函数运算。