跳转至

2、C++程序设计

2.1 程序基本概念

​ C++是一种功能强大的编程语言,具有高效性、可扩展性和可移植性等优点,广泛应用于系统软件、嵌入式系统、游戏开发、科学计算等领域。以下是C++程序中的一些基本概念:

  1. 函数:C++程序由一个或多个函数组成,每个函数执行特定的任务。函数由函数名、参数列表、返回类型和函数体组成。
  2. 变量:C++程序中的变量必须先声明,然后才能使用。变量由变量名、类型和可选的初始值组成。
  3. 数据类型:C++提供了多种数据类型,包括整型、浮点型、字符型、布尔型和指针等。程序员可以根据需要选择适当的数据类型。
  4. 运算符:C++提供了多种运算符,包括算术运算符、比较运算符、逻辑运算符和位运算符等。
  5. 控制结构:C++提供了多种控制结构,包括条件语句(if-else语句)、循环语句(for循环、while循环等)和跳转语句(break语句、continue语句和return语句)等。
  6. 类和对象:C++是一种面向对象编程语言,程序员可以使用类和对象来封装数据和行为。类定义了对象的属性和方法,而对象是类的一个实例。
  7. 模板:C++提供了模板的支持,可以编写通用的代码。模板使得程序员可以编写适用于不同数据类型的代码,提高了代码的重用性和可扩展性。
  8. 异常处理:C++提供了异常处理机制,用于处理程序运行时发生的异常情况。程序员可以编写异常处理代码,以保证程序的正确性和健壮性。

2.1.1 标识符、关键字、常量、变量、字符串、 表达式的概念

在C++中,有一些基本概念是编写程序必须掌握的,包括标识符、关键字、常量、变量、字符串和表达式等。

  1. 标识符:标识符是程序中用来标识变量、函数、类等程序实体的名称。在C++中,标识符必须以字母或下划线开头,后面可以跟着任意数量的字母、数字或下划线。
  2. 关键字:C++中的关键字是具有特殊含义的单词,不能作为标识符使用。例如,int、double、if、else、for等都是C++中的关键字。
  3. 常量:常量是程序中的固定值,不能修改。C++中有多种常量,包括整数常量、浮点数常量、字符常量和布尔常量等。
  4. 变量:变量是程序中用于存储数据的内存位置,每个变量都有一个名称和类型。在C++中,变量必须先声明,然后才能使用。
  5. 字符串:字符串是一串字符序列,用于表示文本数据。在C++中,字符串必须用双引号括起来。
  6. 表达式:表达式是由运算符、操作数和函数调用组成的语句,用于计算一个值。在C++中,表达式可以包含算术运算符、比较运算符、逻辑运算符等。

2.1.2 头文件与名字空间的定义与理解

在C++中,头文件和命名空间是两个重要的概念,它们可以帮助程序员更好地组织和管理程序代码。

1.头文件:头文件是C++程序中的一种特殊文件,用于包含其他代码文件中的函数和变量。头文件通常包含函数声明、宏定义和类定义等。在C++中,头文件使用 #include 指令来引用,例如:

C++
1
#include <iostream>

这个指令告诉编译器要引用 iostream 头文件中的内容。

C++中常见的头文件可以分为标准库头文件和第三方库头文件两种。下面列出了一些常见的头文件及其作用:

  1. iostream:输入输出流的头文件,定义了cin、cout、cerr等对象,以及相关的输入输出操作符。
  2. string:字符串处理的头文件,定义了string类以及相关的操作函数。
  3. vector:动态数组的头文件,定义了vector类以及相关的操作函数。
  4. algorithm:常用算法的头文件,定义了sort、find、reverse等函数。
  5. math.h:数学函数的头文件,定义了常用的数学函数,如sin、cos、sqrt等。
  6. time.h:时间处理的头文件,定义了time_t、tm等结构体以及相关的时间处理函数。
  7. stdlib.h:标准库函数的头文件,定义了一些常用的函数,如rand、srand、malloc等。
  8. ctype.h:字符处理的头文件,定义了一些字符分类函数和字符转换函数。
  9. cstdio:C语言风格的输入输出头文件,定义了printf、scanf等函数。
  10. cstring:C语言风格的字符串处理头文件,定义了一些C语言中的字符串处理函数。

​ 在使用这些头文件时,需要注意头文件的引用顺序,以及可能存在的命名空间冲突问题。一般来说,应该尽量避免使用过多的头文件,只包含需要的头文件,以减少编译时间和代码量。

是一个非标准的头文件,它包含了所有C++标准库中的头文件。这个头文件通常被用于竞赛编程中,因为它可以方便地包含所有常用的头文件,避免了手动包含多个头文件的麻烦。在大多数编译器中,只需要包含这个头文件即可使用标准库中的所有函数和类。

​ 需要注意的是,这个头文件不是C++标准库的一部分,而是一些编译器实现的扩展。因此,在一些特殊的编译器或环境下,可能不支持这个头文件。另外,由于包含了大量的头文件,使用这个头文件可能会导致编译时间变长,并且增加二进制文件的大小。因此,在实际项目中并不推荐使用这个头文件。

2.命名空间:命名空间是C++中的一种特殊机制,用于将代码中的标识符组织在一起,避免命名冲突。命名空间可以包含变量、函数、类和其他命名空间等。在C++中,命名空间(Namespace)是一种将全局作用域划分为若干个子作用域的机制,用于避免名称冲突。C++标准库中的函数、对象和类型定义都被定义在名为std的命名空间中。

使用命名空间可以通过以下两种方式:

1.使用作用域解析运算符(::)来指定命名空间前缀,例如:

C++
1
std::cout << "Hello, world!" << std::endl;

这里,使用了std命名空间中的cout和endl对象。

2.使用using命令将命名空间引入当前作用域,例如:

C++
1
2
3
4
5
6
7
#include <iostream>
using namespace std;

int main() {
    cout << "Hello, world!" << endl;
    return 0;
}

​ 这里,使用了using namespace指令将std命名空间引入当前作用域中,因此可以直接使用std命名空间中的对象和函数,而不必每次都指定命名空间前缀。

​ 需要注意的是,在头文件中使用using namespace指令可能会带来一些问题,例如命名空间冲突等,因此推荐在函数内使用using namespace指令,而不是在头文件中使用。另外,也可以使用using声明来引入需要的名称,例如:

C++
1
2
3
4
5
6
7
8
#include <iostream>
using std::cout;
using std::endl;

int main() {
    cout << "Hello, world!" << endl;
    return 0;
}

​ 这里,使用using声明来引入std命名空间中的cout和endl对象,而不是引入整个std命名空间。这样可以避免命名空间冲突和名称污染的问题。

2.1.3 在C++开发过程中,需要经历以下几个阶段:

  1. 编写代码:使用文本编辑器或集成开发环境(IDE)编写C++源代码文件,源代码文件通常以.cpp或.c文件扩展名结尾。

  2. 编译:将源代码文件编译成目标文件,编译器将源代码文件翻译成机器语言,生成目标文件。编译过程包括词法分析、语法分析、语义分析、中间代码生成、代码优化和目标代码生成等步骤。

  3. 链接:将目标文件链接成可执行文件,链接器将目标文件中未定义的符号与其他目标文件或库文件中定义的符号进行链接,生成可执行文件。链接过程包括地址和空间分配、符号决议和重定位等步骤。

  4. 运行:运行可执行文件,操作系统加载可执行文件到内存中,执行可执行文件中的指令,完成程序运行。

  5. 调试:调试是在程序运行时检查和修复程序错误的过程。调试工具可以让开发人员设置断点、单步执行、查看变量值等操作,帮助开发人员找到程序中的错误。

另外,C++还有两种常见的编译方式:静态编译和动态编译。静态编译是将所有的库文件都编译进可执行文件中,使得可执行文件可以在不安装库文件的情况下直接运行。动态编译则是将库文件单独编译成动态链接库(.dll或.so文件),程序运行时再动态地加载所需要的库文件。静态编译可以提高程序的运行效率,但会增加可执行文件的大小;动态编译可以减小可执行文件的大小,但会降低程序的运行效率。

2.2 基本数据类型

C++中基本数据类型包括整型、浮点型、字符型和布尔型。

  1. 整型:整型表示整数,包括有符号整型和无符号整型。有符号整型可以表示负数和零,无符号整型只能表示非负数。C++中的整型包括以下几种类型:

  2. short:短整型,通常占用2个字节。

  3. int:整型,通常占用4个字节。
  4. long:长整型,通常占用4个字节或8个字节。
  5. long long:长长整型,通常占用8个字节。
  6. unsigned short:无符号短整型。
  7. unsigned int:无符号整型。
  8. unsigned long:无符号长整型。
  9. unsigned long long:无符号长长整型。

  10. 浮点型:浮点型表示实数,包括单精度浮点型和双精度浮点型。单精度浮点型通常占用4个字节,双精度浮点型通常占用8个字节。

  11. float:单精度浮点型。

  12. double:双精度浮点型。

  13. 字符型:字符型表示单个字符,通常占用1个字节。

  14. char:字符型。

  15. 布尔型:布尔型表示真或假,占用1个字节。

  16. bool:布尔型。

在C++中,还可以使用typedef关键字定义新的数据类型,可以将一个数据类型重命名为另一个名称,例如:

C++
1
2
typedef int integer;
integer a = 10;

上面的代码将int类型重命名为integer类型,并使用integer类型定义变量a。

2.3 程序基本语句

C++中的程序基本语句包括:

1.cin 语句用于从标准输入读取数据,例如:

C++
1
2
int x;
cin >> x;

这段代码会从标准输入读入一个整数,存储在变量 x 中。

2.scanf 语句也用于读取数据,它是 C 语言的标准输入函数,可以在 C++ 中使用。例如:

C++
1
2
int x;
scanf("%d", &x);

这段代码会从标准输入读入一个整数,存储在变量 x 中。

3.cout 语句用于向标准输出打印数据,例如:

C++
1
2
int x = 10;
cout << "The value of x is " << x << endl;

这段代码会输出一条消息,显示 x 的值为 10。

4.printf 语句也用于打印数据,它是 C 语言的标准输出函数,可以在 C++ 中使用。例如:

C++
1
2
int x = 10;
printf("The value of x is %d\n", x);

这段代码会输出一条消息,显示 x 的值为 10。

​ C++ 中提供了格式化输入输出的方式,可以使输出更加美观,同时也可以对输入进行格式限制。下面对 C++ 中格式化输入输出的几个关键点进行介绍。

1.格式化输入

格式化输入使用的是 scanf 函数,可以对输入的格式进行控制。scanf 函数的语法如下:

C++
1
int scanf(const char *format, ...);

其中,format 是格式字符串,用于指定输入的格式,后面的参数表示输入的变量。

格式字符串可以包含下列格式说明符:

格式说明符 描述
%d 整型
%f 浮点型
%s 字符串
%c 字符
%u 无符号整型
%e 科学计数法的浮点型
%g 自动选择浮点型的格式
%x 16 进制整型

例如,输入一个整数和一个浮点数可以使用下面的语句:

C++
1
2
3
int a;
float b;
scanf("%d%f", &a, &b);

2.格式化输出

格式化输出使用的是 printf 函数,可以对输出的格式进行控制。printf 函数的语法如下:

C++
1
int printf(const char *format, ...);

其中,format 是格式字符串,用于指定输出的格式,后面的参数表示输出的变量。

格式字符串可以包含下列格式说明符:

格式说明符 描述
%d 整型
%f 浮点型
%s 字符串
%c 字符
%u 无符号整型
%e 科学计数法的浮点型
%g 自动选择浮点型的格式
%x 16 进制整型
%p 指针

除此之外,格式字符串还可以包含一些控制输出格式的标志,例如:

  • -:左对齐输出;
  • +:在正数前面显示加号;
  • #:对于八进制或十六进制数,分别添加 0 和 0x 或 0X 前缀;
  • 0:在数字前面填充 0,而不是空格;
  • .:精度。

例如,输出一个整数和一个浮点数可以使用下面的语句:

C++
1
2
3
int a = 10;
float b = 3.14159;
printf("a = %d, b = %.2f", a, b);

输出结果为:

C++
1
a = 10, b = 3.14

3.流操作符

​ C++ 中流操作符可以用于格式化输入输出,其中 << 操作符可以用于向输出流中插入数据,而 >> 操作符则可以从输入流中提取数据。

下面是一些常用的流操作符:

  • endl:表示换行符,可以用于在输出流中换行。
  • setw(n):指定后面输出的宽度,其中 n 表示宽度值。
  • setprecision(n):设置浮点数输出的精度,其中 n 表示小数点后的位数。
  • fixed:设置浮点数输出的模式为固定精度。
  • scientific:设置浮点数输出的模式为科学计数法。

下面是一些使用流操作符的示例:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
    int num1 = 10;
    double num2 = 3.14159;

    // 使用 endl 换行
    cout << "num1 = " << num1 << endl;

    // 使用 setw 设置输出宽度
    cout << "num1 = " << setw(5) << num1 << endl;

    // 使用 setprecision 设置输出精度
    cout << "num2 = " << setprecision(3) << num2 << endl;

    // 使用 fixed 设置浮点数输出为固定精度
    cout << fixed << "num2 = " << setprecision(3) << num2 << endl;

    // 使用 scientific 设置浮点数输出为科学计数法
    cout << scientific << "num2 = " << setprecision(3) << num2 << endl;

    return 0;
}

输出结果:

C++
1
2
3
4
5
num1 = 10
num1 =    10
num2 = 3.14
num2 = 3.142
num2 = 3.142e+00

4.赋值语句用于给变量赋值,例如:

C++
1
2
int x = 10;// 赋值语句
x = x + 1;// 表达式语句

这段代码会将 x 的值增加 1。

赋值语句的执行顺序是从右向左,即先计算表达式的值,再将这个值赋给变量。例如:

C++
1
2
3
int a = 10;
int b = 20;
a = b + 5; // 先计算 b+5 的值为25,然后将25赋给a

在赋值语句中,左边的变量必须是一个可修改的左值,而右边的表达式可以是任意的右值。赋值语句的返回值是被赋的值,也就是右边表达式的值。

赋值语句也可以使用一些简化的写法,例如:

C++
1
2
a += 5; // 相当于 a = a + 5;
b *= 2; // 相当于 b = b * 2;

这些简化写法在表达式中使用时也是有效的。

多个赋值语句是指在一个语句中同时给多个变量赋值的操作,也称为链式赋值。在 C++ 中,可以使用逗号运算符 , 连接多个赋值操作,这些赋值操作按顺序依次执行,最后整个语句的结果是最后一个赋值操作的结果。

例如,下面的语句将分别给三个变量 abc 赋值:

C++
1
a = 1, b = 2, c = 3;

这条语句中包含了三个赋值操作,逗号运算符将它们连接起来。它的执行顺序是先给 a 赋值为 1,再给 b 赋值为 2,最后给 c 赋值为 3。整个语句的结果是 3。

需要注意的是,虽然逗号运算符可以连接多个赋值操作,但不建议在实际的代码中过多地使用这种方式,因为这会使代码的可读性变差,容易引入错误。通常情况下,一个语句只应该执行一个赋值操作。

5.复合语句用花括号 {} 括起来的语句块,例如:

C++
1
2
3
4
5
int x = 10;
{
    int y = 20;
    x = x + y;
}

这段代码定义了一个复合语句块,其中声明了一个变量 y,并将 x 的值增加了 y 的值。注意,y 的作用域仅限于复合语句块内部,出了复合语句块就无法访问 y 了。

2.选择语句:选择语句根据条件来选择执行哪些语句,包括if语句和switch语句。

  • if语句:

if 语句的基本语法如下:

C++
1
2
3
if (condition) {
  // 当条件成立时执行的代码
}

其中,condition 是需要判断的条件表达式,如果 condition 的值为真(即非零),则执行花括号中的代码,否则跳过该代码块。

我们可以使用 else 关键字来在 if 语句中添加一个分支。else 语句块会在 if 条件不成立时执行,如下所示:

C++
1
2
3
4
5
if (condition) {
  // 当条件成立时执行的代码
} else {
  // 当条件不成立时执行的代码
}

我们也可以使用多个 else if 分支,来判断多个条件,如下所示:

C++
1
2
3
4
5
6
7
8
9
if (condition1) {
  // 当条件 1 成立时执行的代码
} else if (condition2) {
  // 当条件 2 成立时执行的代码
} else if (condition3) {
  // 当条件 3 成立时执行的代码
} else {
  // 当所有条件都不成立时执行的代码
}

注意,在 if 条件表达式的括号中,只能放置一个表达式。如果需要判断多个条件,可以使用逻辑运算符(如 &&||)进行组合。

if 语句是 C++ 中最常用的控制语句之一,它能够帮助程序员根据不同的条件来执行不同的代码,从而实现程序的逻辑分支。

在 C++ 中,if 嵌套是指在一个 if 语句块中再嵌套一个或多个 if 语句块。if 嵌套可以让程序在特定的条件下执行不同的操作,增强程序的灵活性。if 嵌套的语法结构如下:

C++
1
2
3
4
5
6
if (condition1) {
    // if-block 1
    if (condition2) {
        // if-block 2
    }
}

在 if-block 1 中,如果条件 condition1 成立,则会执行 if-block 1 中的代码;如果条件 condition1 不成立,则不会执行 if-block 1 中的代码。在 if-block 1 中还有一个 if 语句块,即 if-block 2。如果条件 condition2 成立,则会执行 if-block 2 中的代码;如果条件 condition2 不成立,则不会执行 if-block 2 中的代码。

下面是一个简单的示例程序,用来说明 if 嵌套的应用:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
using namespace std;

int main() {
    int score;

    cout << "请输入你的分数:";
    cin >> score;

    if (score >= 0 && score <= 100) {
        if (score >= 90) {
            cout << "优秀" << endl;
        } else if (score >= 80) {
            cout << "良好" << endl;
        } else if (score >= 60) {
            cout << "及格" << endl;
        } else {
            cout << "不及格" << endl;
        }
    } else {
        cout << "输入的分数不合法" << endl;
    }

    return 0;
}

​ 在上面的示例程序中,首先提示用户输入分数,然后使用 cin 语句从标准输入流中读取用户输入的分数,并保存在变量 score 中。接着使用 if 语句判断分数是否在 0 到 100 的范围内,如果不在范围内,则输出“输入的分数不合法”,程序结束;如果在范围内,则执行 if 块中的代码,即判断分数所属的等级,并输出对应的等级。在判断等级的过程中,使用了 if 嵌套,将分数划分到不同的区间中,并输出相应的等级。

  • switch语句:
C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
switch (expression) {
    case value1:
        // 如果expression等于value1,则执行这里的语句
        break;
    case value2:
        // 如果expression等于value2,则执行这里的语句
        break;
    default:
        // 如果expression不等于任何一个case,则执行这里的语句
        break;
}

3.循环语句:循环语句可以重复执行一组语句,包括for循环、while循环和do-while循环。

  • for循环:
C++
1
2
3
for (int i = 0; i < 10; i++) {
    // 循环执行这里的语句
}
  • while循环:
C++
1
2
3
while (condition) {
    // 循环执行这里的语句
}
  • do-while循环:
C++
1
2
3
do {
    // 循环执行这里的语句
} while (condition);

多重循环嵌套是指在一个循环语句中嵌套了另外一个或多个循环语句的结构,这种结构可以方便地遍历多维数组或处理复杂的问题。在 C++ 中,可以使用 for 或 while 语句来实现多重循环嵌套。

下面是一个嵌套的 for 循环,它可以用于遍历二维数组:

C++
1
2
3
4
5
6
7
int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        cout << a[i][j] << " ";
    }
    cout << endl;
}

这段代码会输出:

Text Only
1
2
3
1 2 3
4 5 6
7 8 9

​ 在嵌套的 for 循环中,外层循环控制行数,内层循环控制列数,这样就可以遍历整个二维数组。

​ 另外,可以在循环语句中嵌套多个循环语句,实现多重循环嵌套的效果。例如,下面的代码可以用于输出 1 到 100 之间的所有素数:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
for (int i = 2; i <= 100; i++) {
    bool is_prime = true;
    for (int j = 2; j < i; j++) {
        if (i % j == 0) {
            is_prime = false;
            break;
        }
    }
    if (is_prime) {
        cout << i << " ";
    }
}

​ 在这个例子中,外层循环从 2 到 100 遍历所有数字,内层循环用于判断当前数字是否为素数。如果当前数字能够被除了 1 和自身以外的数整除,那么它就不是素数,内层循环就会结束,控制权返回到外层循环。如果当前数字是素数,那么就会输出它的值。

4.跳转语句:跳转语句可以使程序跳过一些语句或重复执行一些语句,包括break语句、continue语句和goto语句。

  • break语句:用于跳出循环。
  • continue语句:用于结束当前循环中的一次迭代,继续下一次迭代。
  • goto语句:可以将程序跳转到指定的语句处执行。
C++
1
2
3
4
goto label;
// ...
label:
// 执行这里的语句

5.函数调用语句:函数调用语句调用函数并执行函数中的语句。

C++
1
func(); // 调用函数

6.块语句:块语句由一组语句组成,并用大括号括起来,块语句中的变量只在块语句中有效。

C++
1
2
3
4
{
    int x = 10; // 在这里定义的变量只在这个块中有效
    // ...
}
  1. 注释:注释用于对代码进行解释和说明,分为单行注释和多行注释。

  2. 单行注释:

C++
1
// 这是单行注释,注释内容在这行之后
  • 多行注释:
C++
1
2
3
4
/*
这是一个多行注释
它可以包含多行
*/

多行注释通常用于注释多行代码或者暂时禁用一些代码块。和单行注释一样,多行注释的内容会被编译器忽略,不会被编译成可执行文件中的指令。

2.4 基本运算

2.4.1 算数运算

在C++中,算术运算符可用于进行基本算术操作,如加减乘除和求余等。下面是C++中的算术运算符列表:

  • 加法(+):用于将两个值相加。
  • 减法(-):用于将两个值相减。
  • 乘法(*):用于将两个值相乘。
  • 除法(/):用于将两个值相除。
  • 取余(%):用于获取两个值(必须为整数)相除后的余数。

除法运算符需要注意的是,如果除数为0,将导致程序崩溃。对于整数的除法,C++中有两种类型:

  • 除法(/):执行整数除法,结果取整。
  • 取整除法(/):在C++11之后引入,执行整数除法,但结果向0取整。

此外,C++还提供了一些增量和减量运算符,例如:

  • 前置递增运算符(++var):将变量的值增加1,并返回新的值。
  • 后置递增运算符(var++):将变量的值增加1,但返回原始值。
  • 前置递减运算符(--var):将变量的值减少1,并返回新的值。
  • 后置递减运算符(var--):将变量的值减少1,但返回原始值。

在使用这些运算符时需要注意优先级和结合性,否则可能会导致代码出错。

2.4.2 关系运算

在C++中,关系运算符用于比较两个值之间的大小关系,其结果为bool类型的值,即true或false。下面是C++中常见的关系运算符:

  • > 大于
  • < 小于
  • >= 大于等于
  • <= 小于等于
  • == 等于
  • != 不等于

这些运算符可以用于所有基本数据类型,例如int、float、double、char等。

关系运算符的使用方法如下:

C++
1
2
3
4
5
6
7
8
9
int a = 10, b = 20;
bool result;

result = (a > b);   // false
result = (a < b);   // true
result = (a >= b);  // false
result = (a <= b);  // true
result = (a == b);  // false
result = (a != b);  // true

需要注意的是,关系运算符的优先级较低,如果在表达式中同时使用了多个运算符,建议使用括号明确优先级。同时,如果进行指针的比较,需要使用指针比较运算符<,<=,>,>=,而不能使用==!=运算符,因为指针类型在内存中并不是实际的值,而是一个地址。

2.4.3 逻辑运算

C++中的逻辑运算符有三个,分别是逻辑与(&&)、逻辑或(||)、逻辑非(!)。

​ 逻辑与(&&):表示两个条件同时成立,才会返回 true,否则返回 false。例如,表达式 x > 0 && y > 0 表示当 x 和 y 都大于 0 时,该表达式的值为 true。

​ 逻辑或(||):表示两个条件其中一个成立,就会返回 true,只有两个条件都不成立时,才会返回 false。例如,表达式 x > 0 || y > 0 表示当 x 和 y 中至少有一个大于 0 时,该表达式的值为 true。

​ 逻辑非(!):表示对条件取反,如果原来的条件为 true,则该运算符的结果为 false,反之,如果原来的条件为 false,则该运算符的结果为 true。例如,表达式 !(x > 0) 表示当 x 不大于 0 时,该表达式的值为 true。

​ 逻辑运算符一般用于控制程序流程,例如在条件语句中,根据条件的成立与否来执行不同的代码块。

​ 在 C++ 中,逻辑运算符 && 和 || 会遵循短路规则。所谓短路规则,是指在表达式中某个部分的求值结果可以确定整个表达式的值时,就可以停止对剩余部分的求值,从而节省时间和计算资源。

​ 具体来说,当使用 && 运算符时,如果左边的表达式值为 false,那么整个表达式的值一定为 false,这时就不会再对右边的表达式求值了,因为不管右边的表达式的值是 true 还是 false,整个表达式的值都是 false。

​ 类似地,当使用 || 运算符时,如果左边的表达式值为 true,那么整个表达式的值一定为 true,这时就不会再对右边的表达式求值了,因为不管右边的表达式的值是 true 还是 false,整个表达式的值都是 true。

​ 使用短路规则可以提高程序的效率,避免不必要的计算。但是需要注意的是,在某些情况下,不使用短路规则可能会更好,这时可以使用按位逻辑运算符 & 和 |。

2.4.4 变量自增与自减运算

​ 在C++中,变量的自增运算符是“++”,表示将变量的值加1,而变量的自减运算符是“--”,表示将变量的值减1。

​ 自增与自减运算可以用作前缀或后缀,前缀形式表示先进行自增或自减运算,后使用变量的值;后缀形式则表示先使用变量的值,后进行自增或自减运算。

例如,以下是一个示例程序,演示了前缀自增和后缀自增的用法:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;

int main() {
    int a = 0, b = 0;
    a++;          // 后缀自增
    ++b;          // 前缀自增
    cout << "a = " << a << endl;  // 输出 a 的值
    cout << "b = " << b << endl;  // 输出 b 的值

    int c = 0, d = 0;
    c--;          // 后缀自减
    --d;          // 前缀自减
    cout << "c = " << c << endl;  // 输出 c 的值
    cout << "d = " << d << endl;  // 输出 d 的值

    return 0;
}

输出结果为:

C++
1
2
3
4
a = 1
b = 1
c = -1
d = -1

​ 可以看到,由于使用了自增和自减运算符,变量的值被修改为了相应的值。需要注意的是,如果在表达式中同时使用前缀和后缀形式的自增或自减运算符,那么它们的执行顺序是不确定的,这可能会导致一些难以发现的错误。因此,在实际编程中,应尽量避免这种写法。

2.4.5 三目运算

​ C++中的三目运算符是一种简单的条件表达式,也称为条件运算符,使用“?:”符号表示。该运算符通常用于在一行代码中编写条件语句,以便根据条件的真假来选择不同的值。

其语法如下:

C++
1
condition ? value_if_true : value_if_false;

其中,condition是一个判断条件,如果为真,则返回value_if_true的值,否则返回value_if_false的值。

例如,下面的代码使用三目运算符来比较两个数的大小,并将结果赋值给max变量:

C++
1
2
int a = 5, b = 10;
int max = (a > b) ? a : b;

在上面的例子中,如果a大于b,则将a的值赋给max变量,否则将b的值赋给max变量。

2.4.6 位运算

C++提供了位运算符,允许直接操作变量中的二进制位。

下面是C++中的位运算符:

  • 位与(&):两个二进制数的每一位都为1时,结果为1,否则为0。
  • 位或(|):两个二进制数的每一位都为0时,结果为0,否则为1。
  • 位异或(^):两个二进制数的每一位不同时,结果为1,否则为0。
  • 取反(~):一个二进制数的每一位都取反,0变成1,1变成0。
  • 左移(<<):将一个二进制数的每一位都向左移动一定的位数,空位补0。
  • 右移(>>):将一个二进制数的每一位都向右移动一定的位数,空位补0或补1。

例如,下面的代码演示了如何使用C++位运算符计算两个数字的按位与:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <iostream>
using namespace std;

int main() {
  int a = 5;
  int b = 3;
  int c = a & b;
  cout << c << endl;
  return 0;
}

输出结果为:

Text Only
1
1

这里,5和3的二进制表示分别为101011。按位与操作会将这两个数的每一位进行比较,得到001,即1。

2.5 数学库常用函数

​ C++数学库提供了一系列常用的数学函数,如三角函数、指数函数、对数函数等。下面介绍一些常用的数学库函数:

1.abs:计算绝对值。

C++
1
2
3
int abs(int n);
long abs(long n);
double abs(double n);

2.pow:计算幂次方。

C++
1
double pow(double x, double y);

3.sqrt:计算平方根。

C++
1
double sqrt(double x);

4.exp:计算自然对数的底数 e 的幂次方。

C++
1
double exp(double x);

5.log:计算自然对数。

C++
1
double log(double x);

6.log10:计算以 10 为底的对数。

C++
1
double log10(double x);

7.sin、cos、tan:分别计算正弦、余弦、正切。

C++
1
2
3
double sin(double x);
double cos(double x);
double tan(double x);

8.asin、acos、atan:分别计算反正弦、反余弦、反正切。

C++
1
2
3
double asin(double x);
double acos(double x);
double atan(double x);

9.ceil:返回不小于参数的最小整数。

C++
1
double ceil(double x);

10.floor:返回不大于参数的最大整数。

C++
1
double floor(double x);

11.fmod:计算两个数的浮点余数。

C++
1
double fmod(double x, double y);

这些函数需要使用数学库头文件 cmath。例如:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <cmath>
#include <iostream>

using namespace std;

int main()
{
    double x = 2.0;
    double y = 3.0;

    cout << "abs(-10) = " << abs(-10) << endl;
    cout << "pow(" << x << ", " << y << ") = " << pow(x, y) << endl;
    cout << "sqrt(" << x << ") = " << sqrt(x) << endl;
    cout << "exp(" << x << ") = " << exp(x) << endl;
    cout << "log(" << x << ") = " << log(x) << endl;
    cout << "log10(" << x << ") = " << log10(x) << endl;
    cout << "sin(" << x << ") = " << sin(x) << endl;
    cout << "cos(" << x << ") = " << cos(x) << endl;
    cout << "tan(" << x << ") = " << tan(x) << endl;
    cout << "asin(" << x << ") = " << asin(x) << endl;
    cout << "acos(" << x << ") = " << acos(x) << endl;
    cout << "atan(" << x << ") = " << atan(x) << endl;
    cout << "ceil(" << x << ") = " << ceil(x) << endl;
    cout << "floor(" << x << ") = " << floor(x) << endl;
    cout << "fmod(" << x << ", " << y << ") = " << fmod(x);

    return 0;
}

另外一个例子:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <cmath>
using namespace std;

int main() {
    double num1, num2;
    cout << "请输入两个实数:";
    cin >> num1 >> num2;

    // 计算两个数的和、差、积、商、余数、平均数、最大值和最小值
    double sum = num1 + num2;
    double diff = num1 - num2;
    double product = num1 * num2;
    double quotient = num1 / num2;
    double remainder = fmod(num1, num2);
    double average = (num1 + num2) / 2;
    double max_val = max(num1, num2);
    double min_val = min(num1, num2);

    // 输出结果
    cout << "两个数的和为:" << sum << endl;
    cout << "两个数的差为:" << diff << endl;
    cout << "两个数的积为:" << product << endl;
    cout << "两个数的商为:" << quotient << endl;
    cout << "两个数的余数为:" << remainder << endl;
    cout << "两个数的平均数为:" << average << endl;
    cout << "两个数中的最大值为:" << max_val << endl;
    cout << "两个数中的最小值为:" << min_val << endl;

    return 0;
}

​ 这个程序输入两个实数,然后计算它们的和、差、积、商、余数、平均数、最大值和最小值,最后输出这些结果。这个程序使用了 cmath 头文件中的 fmod() 函数来计算两个实数的余数,同时还用到了 iostreamcmath 头文件中的其他函数和常量。

2.6 结构化程序设计

2.6.1 顺序结构、分支结构和循环结构

​ C++ 结构化程序设计是一种程序设计方法,其核心思想是将程序分解为小的、易于管理的模块,并使用良好定义的接口将它们组合起来实现更复杂的功能。这种程序设计方法的好处是提高代码的可读性、可维护性、可扩展性和可重用性。

​ 结构化程序设计的基本概念包括顺序、选择和循环控制结构。顺序结构是指程序按照编写顺序依次执行;选择结构是指根据条件选择执行不同的语句;循环结构是指根据条件循环执行相同或不同的语句。

​ 在C++中,可以使用if语句、switch语句和循环语句来实现选择和循环控制结构。这些语句提供了一种有效的方式来控制程序的流程,并在不同的情况下执行不同的操作。此外,C++还提供了一些其他的结构化编程工具,例如函数和结构体等,可以帮助程序员更好地组织代码和数据。

​ 总之,结构化程序设计是一种重要的程序设计方法,能够帮助程序员设计出更高效、更健壮、更可维护的程序,也是C++程序设计的基础。

2.6.2 自顶向下、逐步求精的模块化程序设计

​ 自顶向下、逐步求精是一种模块化程序设计方法,它是一种自然的、清晰的和易于理解的编程方式,它使得程序员可以分步解决问题,使程序更易于编写、测试和维护。

​ 自顶向下、逐步求精的基本思路是:从大到小分解问题,将一个大问题分解为若干个小问题,然后将每个小问题再分解为更小的问题,直到最小问题的规模可以通过简单的程序解决。这样,每个问题都被分解为较小的子问题,这些子问题可以更容易地解决。

​ 在自顶向下、逐步求精的模块化程序设计方法中,每个模块都是一个相对独立的程序单元,它们通过输入和输出连接起来。一个模块的输入是另一个模块的输出,这种输入和输出可以通过参数传递和返回值来实现。这样,每个模块都可以单独编写、调试和测试,使程序开发更加高效和可靠。

​ 该方法的核心思想是将大问题分解为小问题,将小问题分解为更小的问题,逐步求解,直到每个问题可以通过简单的程序单元解决。

2.6.3 流程图的概念及流程图描述

​ 流程图是一种用于描述程序或系统流程的图形化工具,通常由流程图符号和箭头组成,符号代表程序的各个组成部分,箭头表示它们之间的关系和流向。流程图可以帮助程序员理解问题和设计解决方案,以及在实现程序时跟踪和调试代码。

流程图中常用的符号包括以下几种:

  1. 开始/结束符号:表示程序的开始或结束点,通常用一个圆圈或椭圆来表示。
  2. 输入/输出符号:表示程序的输入或输出,通常用一个平行四边形来表示。
  3. 过程符号:表示程序的处理过程,通常用一个矩形来表示。
  4. 判断符号:表示程序的判断过程,通常用一个菱形来表示。
  5. 连接符号:表示程序的流程连接点,通常用一条箭头来表示。
  6. 循环符号:表示程序的循环过程,通常用一个带箭头的弧形来表示。

流程图中箭头的方向表示程序的执行顺序,箭头的起点和终点表示程序的流程连接点。

​ 流程图是一种比较通用的程序设计工具,可以用来描述各种类型的问题和程序设计方法。在设计程序时,可以先用流程图描述程序的整体流程,然后逐步将流程图细化成具体的程序代码。流程图也可以用来分析和优化程序的效率和正确性。

2.7 数组

2.7.1 数组定义,数组与数组下标的含义

​ 在 C++ 中,数组是一组相同数据类型的元素的集合。数组的每个元素可以通过索引访问,索引是一个非负整数,用于唯一标识数组中的每个元素。数组元素的类型可以是任何基本数据类型,如整数、浮点数、字符等。

在 C++ 中,要声明一个数组,需要指定数组的数据类型、数组名称和数组的大小。例如,以下是一个声明具有 5 个整数元素的数组的示例:

C++
1
int myArray[5];

​ 上面的声明将创建一个名为 myArray 的数组,该数组有 5 个整数元素。数组中的每个元素可以通过索引访问,索引从 0 开始,到 4 结束,因为数组有 5 个元素。

​ 数组中的元素可以使用数组下标访问。数组下标是一个整数,指定要访问的元素在数组中的位置。在 C++ 中,数组下标从 0 开始,这意味着第一个元素的下标为 0,第二个元素的下标为 1,以此类推。

例如,以下是如何访问数组元素的示例:

C++
1
2
3
4
5
6
7
int myArray[5] = {1, 2, 3, 4, 5}; // 定义一个数组并初始化

// 访问第一个元素
int firstElement = myArray[0];

// 访问第三个元素
int thirdElement = myArray[2];

在这个例子中,我们定义了一个名为 myArray 的数组,并使用花括号初始化了数组的值。然后,我们使用下标访问了数组的第一个元素和第三个元素。

2.7.2 数组的读入与输出

·在 C++ 中,可以使用 for 循环来读取和输出数组。下面是一个简单的示例代码,演示了如何读取和输出整型数组:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

int main() {
    int arr[5];
    cout << "Enter 5 integers: " << endl;

    // 读取数组元素
    for (int i = 0; i < 5; i++) {
        cin >> arr[i];
    }

    // 输出数组元素
    cout << "You entered: ";
    for (int i = 0; i < 5; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;

    return 0;
}

​ 在上面的示例代码中,定义了一个整型数组 arr,它有 5 个元素。然后使用 for 循环读取了用户输入的 5 个整数,并将它们存储到数组 arr 中。最后,使用另一个 for 循环输出了数组中的元素。

​ 需要注意的是,在 C++ 中,数组下标从 0 开始。也就是说,如果一个数组有 n 个元素,那么它的下标范围是 0 到 n-1。在上面的示例代码中,数组 arr 的下标范围是 0 到 4。

2.7.3 纯一维数组的综合运用

纯一维数组的综合运用可以包括数组的初始化、读入、输出、排序、插入、删除等操作。

首先是数组的初始化,可以通过循环给数组的每个元素赋值,也可以直接在定义数组时初始化。

C++
1
2
3
4
5
6
7
8
// 循环给数组赋值
int arr[10];
for (int i = 0; i < 10; i++) {
    arr[i] = i + 1;
}

// 直接在定义数组时初始化
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

然后是数组的读入和输出,可以通过循环遍历数组的每个元素,依次输入或输出。

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 读入数组
int arr[10];
for (int i = 0; i < 10; i++) {
    cin >> arr[i];
}

// 输出数组
for (int i = 0; i < 10; i++) {
    cout << arr[i] << " ";
}
cout << endl;

接下来是数组的排序,可以使用STL库中的sort函数来实现,也可以手写排序算法。

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 使用STL库中的sort函数排序
sort(arr, arr + 10);

// 手写冒泡排序算法排序
for (int i = 0; i < 10 - 1; i++) {
    for (int j = 0; j < 10 - i - 1; j++) {
        if (arr[j] > arr[j + 1]) {
            int tmp = arr[j];
            arr[j] = arr[j + 1];
            arr[j + 1] = tmp;
        }
    }
}

最后是数组的其他操作,比如查找某个元素的位置、求和、求平均值等。

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 查找某个元素的位置
int index = -1;
for (int i = 0; i < 10; i++) {
    if (arr[i] == 5) {
        index = i;
        break;
    }
}

// 求和
int sum = 0;
for (int i = 0; i < 10; i++) {
    sum += arr[i];
}

// 求平均值
double average = sum / 10.0;

对于一维数组,插入和删除操作需要对数组进行移动,因此需要考虑以下几个步骤:

  1. 对于插入操作,首先需要确定要插入的位置,将该位置之后的元素都向后移动一位,然后将需要插入的元素放在该位置即可。
  2. 对于删除操作,首先需要确定要删除的位置,将该位置之后的元素都向前移动一位,然后将数组的长度减 1 即可。

下面是一些示例代码。

1.插入操作

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
using namespace std;

const int MAXN = 1000;
int a[MAXN];
int n;

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }

    int pos, x;
    cin >> pos >> x;

    for (int i = n; i > pos; i--) {
        a[i] = a[i - 1];
    }
    a[pos] = x;
    n++;

    for (int i = 0; i < n; i++) {
        cout << a[i] << " ";
    }
    cout << endl;

    return 0;
}

2.删除操作

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;

const int MAXN = 1000;
int a[MAXN];
int n;

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }

    int pos;
    cin >> pos;

    for (int i = pos + 1; i < n; i++) {
        a[i - 1] = a[i];
    }
    n--;

    for (int i = 0; i < n; i++) {
        cout << a[i] << " ";
    }
    cout << endl;

    return 0;
}

这里的代码是比较简单的实现方式,实际上在处理大规模数据时可能需要采用更高效的算法来实现。

2.7.4 纯二维数组与多维数组的综合应用

​ 纯二维数组是指元素类型为基本数据类型的二维数组,即数组的每个元素都是一个基本数据类型的变量。多维数组则是指包含两个或以上维度的数组。

​ 在 C++ 中,可以使用多维数组来存储多维数据,如矩阵、图像等。多维数组的定义方式与一维数组类似,只需要在方括号中指定每个维度的长度即可。例如,以下是一个 3 行 4 列的二维数组的定义:

C++
1
int arr[3][4];

我们也可以使用多维数组进行输入和输出。对于二维数组,我们可以使用两层循环来遍历每个元素,如下所示:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
int arr[3][4];

// 输入二维数组
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 4; j++) {
        cin >> arr[i][j];
    }
}

// 输出二维数组
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 4; j++) {
        cout << arr[i][j] << " ";
    }
    cout << endl;
}

对于多维数组,同样可以使用多重循环来进行遍历。例如,以下是一个 2 行 3 列 4 层的三维数组的遍历:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
int arr[2][3][4];

// 输入三维数组
for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 3; j++) {
        for (int k = 0; k < 4; k++) {
            cin >> arr[i][j][k];
        }
    }
}

// 输出三维数组
for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 3; j++) {
        for (int k = 0; k < 4; k++) {
            cout << arr[i][j][k] << " ";
        }
        cout << endl;
    }
    cout << endl;
}

​ 在 C++ 中,多维数组也可以通过指针来访问,但需要注意多维数组元素的存储顺序,可以使用“行优先”或“列优先”两种方式。对于“行优先”,即先遍历第一维,再遍历第二维,以此类推;对于“列优先”,则是先遍历最后一维,再遍历倒数第二维,以此类推。

2.8 字符串的处理

2.8.1 字符数组与字符串的关系

​ 字符数组与字符串的关系是非常紧密的。在 C++ 中,字符串实际上是一个字符数组,由一个或多个字符组成。C++ 使用字符数组来存储字符串,其中每个字符都用 ASCII 码值来表示。通常情况下,字符数组以空字符('\0')结尾,以表示字符串的结束。例如,字符串 "hello" 实际上存储在一个字符数组中,形式为:

C++
1
char str[] = {'h', 'e', 'l', 'l', 'o', '\0'};

​ 由于字符串是字符数组,因此可以使用字符数组的所有操作来操作字符串。例如,可以使用下标运算符访问单个字符,可以使用循环和字符串处理函数来处理字符串,可以使用指针来遍历字符串等等。

​ C++ 标准库提供了一组字符串处理函数,例如 strcpy、strcat、strlen、strcmp 等等,这些函数可以方便地操作字符串。此外,C++ 也提供了一个 string 类型,它是一个动态的字符串,可以自动管理内存。使用 string 类型可以方便地操作字符串,避免了手动管理内存的麻烦。

2.8.2 字符数组的综合应用

​ 在 C++ 中,字符数组和字符串经常用于输入/输出、字符串匹配和其他字符串操作等方面。下面是字符数组的一些综合应用:

1.字符串输入和输出

​ 字符数组通常用于字符串的输入和输出。可以使用 cin 和 cout 对字符数组进行输入和输出。例如,下面的代码可以输入一个字符串并将其输出:

C++
1
2
3
4
char str[100];
cout << "Enter a string: ";
cin >> str;
cout << "You entered: " << str << endl;

2.字符串长度

​ 可以使用 C++ 中的 strlen 函数来获取一个字符串的长度。strlen 函数返回一个整数,表示给定字符串的字符数,不包括字符串的 null 终止符。例如,下面的代码可以输出一个字符串的长度:

C++
1
2
3
char str[100] = "Hello, world!";
int len = strlen(str);
cout << "Length of the string is: " << len << endl;

3.字符串复制

​ 可以使用 C++ 中的 strcpy 函数来将一个字符串复制到另一个字符串中。strcpy 函数接受两个参数,第一个参数是目标字符串,第二个参数是源字符串。例如,下面的代码将一个字符串复制到另一个字符串中:

C++
1
2
3
4
char str1[100] = "Hello, world!";
char str2[100];
strcpy(str2, str1);
cout << "Copied string is: " << str2 << endl;

4.字符串连接

​ 可以使用 C++ 中的 strcat 函数将两个字符串连接起来。strcat 函数接受两个参数,第一个参数是目标字符串,第二个参数是要连接到目标字符串末尾的源字符串。例如,下面的代码将两个字符串连接起来:

C++
1
2
3
4
char str1[100] = "Hello, ";
char str2[100] = "world!";
strcat(str1, str2);
cout << "Concatenated string is: " << str1 << endl;

5.字符串比较

​ 可以使用 C++ 中的 strcmp 函数来比较两个字符串。strcmp 函数接受两个参数,第一个参数是第一个要比较的字符串,第二个参数是第二个要比较的字符串。例如,下面的代码将比较两个字符串:

C++
1
2
3
4
5
6
7
8
char str1[100] = "Hello, world!";
char str2[100] = "hello, world!";
int result = strcmp(str1, str2);
if (result == 0) {
    cout << "The two strings are equal." << endl;
} else {
    cout << "The two strings are not equal." << endl;
}

2.8.3 string类定义、相关函数引用

​ 在C++中,除了使用字符数组表示字符串之外,还可以使用string类表示字符串。string类是一个非常强大的字符串处理工具,它可以轻松地完成各种字符串操作。下面是string类的定义和一些常用函数的介绍:

1.string类的定义

string类定义在头文件中,它的定义如下:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class string {
public:
    typedef size_t size_type;

    string();
    string(const char* s);
    string(const string& str);
    string(size_type n, char c);
    template <class InputIterator>
    string(InputIterator first, InputIterator last);

    string& operator=(const char* s);
    string& operator=(const string& str);
    string& operator=(char c);

    ~string();

    const char* c_str() const;
    size_type size() const;
    size_type length() const;

    string& operator+=(const char* s);
    string& operator+=(const string& str);
    string& operator+=(char c);

    string operator+(const char* s) const;
    string operator+(const string& str) const;
    string operator+(char c) const;

    bool empty() const;
};

2.string类常用函数

(1) 构造函数:

  • string():默认构造函数,构造一个空的字符串。
  • string(const char* s):用C风格字符串s构造一个字符串。
  • string(const string& str):用另一个字符串str构造一个字符串。
  • string(size_type n, char c):用字符c构造一个长度为n的字符串。
  • template string(InputIterator first, InputIterator last):用迭代器[first, last)范围内的元素构造一个字符串。

(2) 赋值运算符:

  • string& operator=(const char* s):将C风格字符串s赋值给字符串。
  • string& operator=(const string& str):将另一个字符串str赋值给字符串。
  • string& operator=(char c):将字符c赋值给字符串。

(3) 析构函数:

  • ~string():析构函数,销毁字符串对象。

(4) 成员函数:

  • const char* c_str() const:返回一个指向字符串第一个字符的指针,字符串以'\0'结尾。
  • size_type size() const:返回字符串的长度。
  • size_type length() const:返回字符串的长度。
  • bool empty() const:判断字符串是否为空。

(5) 运算符重载:

  • string& operator+=(const char* s):将C风格字符串s附加到字符串的末尾。
  • string& operator+=(const string& str):将另一个字符串str附加到字符串的末尾。
  • string& operator+=(char c):将字符c附加到字符串的末尾。
  • string operator+(const char* s) const:返回将C风格字符串s附加到字符串的末尾的结果。
  • string operator+(const string& str) const:返回将另一个字符串str附加到字符串的末尾的结果。
  • string operator+(char c) const:返回将字符c附加到字符串的末尾的结果。

下面是一些string类常用的函数:

  • length(): 返回字符串的长度。
  • size(): 返回字符串的长度,等同于length()。
  • empty(): 判断字符串是否为空,如果为空则返回true,否则返回false。
  • clear(): 清空字符串。
  • insert(): 在指定位置插入指定字符或字符串。
  • erase(): 删除指定位置的字符或一段字符。
  • replace(): 用指定的字符串替换指定位置的字符或一段字符。
  • find(): 查找指定字符串第一次出现的位置,返回该位置的下标,如果没找到则返回string::npos。
  • rfind(): 查找指定字符串最后一次出现的位置,返回该位置的下标,如果没找到则返回string::npos。
  • substr(): 返回从指定位置开始,指定长度的子字符串。

​ 除此之外,string类还支持与C风格字符串的转换,可以使用c_str()函数将string类转换为C风格字符串,也可以使用string的构造函数将C风格字符串转换为string类,例如:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
#include <string>

using namespace std;

int main() {
    string str1 = "Hello, ";
    string str2 = "world!";
    string str3 = str1 + str2;

    cout << str1 << endl;
    cout << str2 << endl;
    cout << str3 << endl;

    if (str1.empty()) {
        cout << "str1 is empty" << endl;
    } else {
        cout << "str1 is not empty" << endl;
    }

    cout << "The length of str1 is " << str1.length() << endl;
    cout << "The length of str2 is " << str2.length() << endl;
    cout << "The length of str3 is " << str3.length() << endl;

    str1.clear();
    cout << "After clear, str1 is " << str1 << endl;

    str1 = "hello";
    str1.insert(3, ",");
    cout << "After insert, str1 is " << str1 << endl;

    str1.erase(3, 1);
    cout << "After erase, str1 is " << str1 << endl;

    str1.replace(0, 2, "hi");
    cout << "After replace, str1 is " << str1 << endl;

    int pos = str3.find("world");
    if (pos != string::npos) {
        cout << "The first occurrence of 'world' is at position " << pos << endl;
    } else {
        cout << "'world' is not found" << endl;
    }

    string sub = str3.substr(7, 5);
    cout << "The substring is " << sub << endl;

    return 0;
}

输出结果如下:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Hello, 
world!
Hello, world!
str1 is not empty
The length of str1 is 7
The length of str2 is 6
The length of str3 is 13
After clear, str1 is 
After insert, str1 is hel,lo
After erase, str1 is hello
After replace, str1 is hi,llo
The first occurrence of 'world' is at position 5
The substring is world

2.9 函数与递归

2.9.1 函数定义与调用,形参与实参

​ 在 C++ 中,函数是一个独立的代码块,它执行特定的任务并可以返回一个值。函数的定义分为两个部分:函数原型和函数体。函数原型定义了函数的名称、返回类型以及参数类型和数量。函数体则包含了实际的代码,执行特定的任务并返回一个值。

​ 函数的调用就是在程序中使用函数。当调用一个函数时,程序会跳转到函数的定义并执行它,然后返回到调用点并继续执行。

​ 函数有两种类型的参数:形参和实参。形参是函数定义中列出的参数,它们的类型和名称定义了在函数中要使用的数据类型和变量名。实参则是在调用函数时提供的实际参数,它们的值传递给函数并用于执行函数的任务。

​ 函数调用的一般形式如下:

C++
1
return_value = function_name(arg1, arg2, ...);

其中,return_value 是函数返回的值,function_name 是函数的名称,arg1, arg2, ... 是实参,它们传递给函数执行任务。

下面是一个简单的函数定义和调用的例子:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

using namespace std;

// 函数原型
int add(int x, int y);

int main() {
    int a = 5, b = 10, c;

    // 函数调用
    c = add(a, b);

    cout << "The sum of " << a << " and " << b << " is " << c << endl;

    return 0;
}

// 函数定义
int add(int x, int y) {
    return x + y;
}

​ 在上面的例子中,add 函数接受两个整数参数,并返回它们的和。在 main 函数中,我们调用 add 函数并将返回值存储在 c 变量中,然后输出结果。

2.9.2 传值参数与传引用参数

​ 在 C++ 中,函数的参数传递有两种方式:传值(pass by value)和传引用(pass by reference)。

​ 传值参数即将实参的值复制到形参中,在函数中对形参的修改不会影响实参的值。这是因为形参和实参占用的是不同的内存空间。

​ 传引用参数将实参的地址传递给形参,在函数中对形参的修改会影响实参的值。这是因为形参和实参指向的是同一块内存空间。

下面是一个传值参数和传引用参数的例子,用于交换两个整数的值:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;

void swapByValue(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

void swapByReference(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 1, y = 2;
    cout << "Before swap: x = " << x << ", y = " << y << endl;
    swapByValue(x, y);
    cout << "After swap by value: x = " << x << ", y = " << y << endl;
    swapByReference(x, y);
    cout << "After swap by reference: x = " << x << ", y = " << y << endl;
    return 0;
}

输出结果为:

C++
1
2
3
Before swap: x = 1, y = 2
After swap by value: x = 1, y = 2
After swap by reference: x = 2, y = 1

​ 可以看出,当使用传值参数的 swapByValue 函数交换 x 和 y 的值时,实参 x 和 y 的值并没有改变,而当使用传引用参数的 swapByReference 函数交换 x 和 y 的值时,实参 x 和 y 的值发生了改变。

2.9.3 常量与变量的作用范围

​ 常量和变量的作用范围(即生存周期)指的是它们在程序中的可访问性和生命周期。

​ 对于常量而言,其生存周期在程序整个运行期间内都是有效的,即从程序启动到结束。常量的作用范围与定义的位置有关,通常分为全局常量和局部常量。全局常量定义在函数外部,作用范围为整个程序;局部常量定义在函数内部,作用范围为该函数内部。

​ 对于变量而言,其生存周期取决于其作用范围。局部变量定义在函数内部,作用范围仅限于该函数内部;而全局变量定义在函数外部,作用范围为整个程序。在函数内部,可以定义与全局变量同名的局部变量,此时局部变量将覆盖同名的全局变量,但只在该函数内部有效。

​ 需要注意的是,变量的作用范围和生命周期是两个不同的概念。变量的生命周期指的是其存储空间从分配到释放的时间段,一般情况下,变量的生命周期与其作用范围相同,但也有一些特殊情况,例如静态变量和动态内存分配,其生命周期与作用范围是不同的。

2.9.4 递归函数的概念、定义与调用

​ 递归函数是指在函数定义中,调用函数自身的函数。递归函数可以用于解决一些特定的问题,比如递归计算阶乘、斐波那契数列等。

​ 递归函数的定义和普通函数的定义相似,不同之处在于递归函数的函数体中包含了对自身的调用。以下是一个计算阶乘的递归函数的示例:

C++
1
2
3
4
5
6
7
int factorial(int n) {
    if (n == 0) {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}

​ 这个函数计算给定整数 n 的阶乘。如果 n 为0,则返回1。否则,返回 n 乘以递归调用 factorial(n - 1) 的结果。

​ 在调用递归函数时,需要注意函数是否会一直递归下去,导致栈溢出等问题。递归函数需要在某个条件下停止递归调用,否则会导致死循环。这个条件称为“基准情形”,在上面的示例中,当 n 等于0时,就是基准情形。

​ 递归函数的形参和普通函数一样,定义时需要指定形参的类型和个数。调用递归函数时,需要传递实参给函数。

2.10 结构体的定义及应用

​ 结构体是一种自定义的数据类型,它允许将不同类型的数据组合在一起,形成一个新的数据类型。结构体中的数据成员可以是内置类型(如int、double等),也可以是其他结构体类型,甚至可以是指向函数的指针等复杂类型。通过定义结构体,我们可以将相关联的数据组织在一起,便于管理和使用。

在C++中,结构体的定义使用struct关键字。结构体定义的一般形式如下:

C++
1
2
3
4
5
struct 结构体名 {
    数据类型 成员变量名1;
    数据类型 成员变量名2;
    //...
};

下面是一个简单的例子,定义了一个Person结构体,它包含两个数据成员nameage

C++
1
2
3
4
struct Person {
    string name;   //姓名
    int age;            //年龄
};

定义结构体后,我们就可以用它来创建结构体类型的变量,例如:

C++
1
2
3
Person p1;         //创建一个Person类型的变量p1
p1.name = "Tom";   //给p1的name成员赋值
p1.age = 18;       //给p1的age成员赋值

​ 结构体变量的成员可以使用.运算符来访问,例如,p1.name表示变量p1name成员。

​ 除了使用结构体变量来存储数据外,我们还可以将结构体作为参数传递给函数,或者定义一个结构体类型的数组等。结构体在程序设计中非常有用,它可以用来表示各种复杂的数据结构,例如链表、树等。

计算一个结构体占用的空间需要考虑以下几个方面:

  1. 结构体中所有成员变量所占用的空间。
  2. 空间对齐(padding),使结构体各个成员变量的地址按一定规则排列。
  3. 对齐填充(pack),使整个结构体的大小是字节数的整数倍。

​ 结构体中各个成员变量所占用的空间取决于变量的类型,例如 int 类型通常占用 4 个字节,char 类型通常占用 1 个字节,等等。

​ 空间对齐的规则通常是:每个成员变量的地址必须是该变量大小的整数倍,如果不是,编译器会自动加上 padding 使地址满足要求。例如,如果一个 int 类型的变量的地址是 0x00000001,那么编译器会在它前面加上 3 个字节的 padding,使它的地址变成 0x00000004。

​ 对齐填充是指为了使整个结构体的大小是字节数的整数倍而在结构体最后添加一些 padding。对于大多数结构体,对齐填充的字节数通常是结构体中最大的成员变量的字节数。

举个例子,假设有以下结构体定义:

C++
1
2
3
4
5
struct Person {
    char name[20];
    int age;
    double salary;
};

​ 根据上面的规则,name 数组占用 20 个字节,age 变量占用 4 个字节,salary 变量占用 8 个字节,所以这个结构体占用的空间为:

C++
1
20 + 4 + 8 = 32

​ 因为 salary 是最大的成员变量,所以对齐填充的字节数也是 8。所以整个结构体占用 40 个字节的空间。

​ 需要注意的是,不同编译器对于空间对齐和对齐填充的规则可能不同,因此具体占用空间可能会有所不同。

2.11 指针类型

2.11.1 指针的概念及调用

​ 在 C++ 中,指针是一个存储变量内存地址的变量。指针变量在内存中也有自己的地址,因此可以使用指针变量的地址来修改指针变量的值。

定义指针变量时,需要指定指针变量所指向的数据类型,例如:

C++
1
int *ptr;  // 声明一个指向整型变量的指针变量

​ 在上面的示例中,* 表示指针类型的声明符,int 表示指针所指向的数据类型,ptr 是指针变量的名称。

​ 要获取变量的地址,可以使用取地址符 &,例如:

C++
1
2
int num = 10;
int *ptr = &num;  // 指向 num 变量的指针

在上面的示例中,& 取地址符返回 num 变量的内存地址,ptr 指向了该地址。

要获取指针变量所指向的变量的值,可以使用解引用符 *,例如:

C++
1
2
3
int num = 10;
int *ptr = &num;  // 指向 num 变量的指针
cout << *ptr << endl;  // 输出 10

在上面的示例中,*ptr 表示指针所指向的变量的值,即 num 变量的值。

另外,指针还可以用于动态内存分配。使用 new 运算符可以在运行时分配指定大小的内存,并返回指向该内存的指针。例如:

C++
1
2
int *ptr = new int;  // 分配一个整型变量的内存空间
*ptr = 10;  // 将指针所指向的内存空间设置为 10

​ 在上面的示例中,new 运算符分配了一个整型变量的内存空间,并返回指向该内存空间的指针。然后使用解引用符 * 设置该内存空间的值为 10。

要释放动态分配的内存空间,可以使用 delete 运算符,例如:

C++
1
2
3
int *ptr = new int;
*ptr = 10;
delete ptr;  // 释放指针所指向的内存空间

在上面的示例中,delete 运算符释放了 ptr 指向的内存空间。

2.11.2 指针与数组

​ 指针和数组在 C++ 中有密不可分的关系,因为数组名其实就是一个指向数组首元素的指针。

可以通过定义指针来访问数组中的元素,比如:

C++
1
2
3
4
5
6
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;  // 指向数组首元素的指针

// 访问数组中的元素
cout << ptr[0] << endl;  // 输出 1
cout << *(ptr+1) << endl;  // 输出 2

​ 这里,定义了一个包含 5 个元素的数组 arr,并初始化了它。同时,定义了一个指向数组首元素的指针 ptr,这里可以将数组名 arr 看成一个指针常量。可以通过指针来访问数组中的元素,例如 ptr[0] 访问第一个元素,*(ptr+1) 访问第二个元素。

当然,指针也可以通过数组名进行访问,例如:

C++
1
2
3
4
5
int arr[5] = {1, 2, 3, 4, 5};

// 通过数组名访问数组中的元素
cout << arr[0] << endl;  // 输出 1
cout << *(arr+1) << endl;  // 输出 2

这里,通过数组名 arr 访问数组中的元素,和上面使用指针访问的方式是等价的。

2.11.3 字符指针与string类

字符指针和string类都可以用来表示字符串,它们在不同情况下有各自的优缺点。

字符指针是一个指向字符数组的指针,可以用来操作字符数组,例如:

C++
1
2
char str1[] = "Hello";
char *str2 = "World";

​ 这里,str1是一个字符数组,长度为6,包括一个空字符,存储在栈内存中;str2是一个指针,指向一个字符串常量,在内存中以只读的形式存储。通过字符指针可以修改字符串常量的内容,但这是不被允许的,会导致程序出错。

string类是C++标准库中的一个类,定义在头文件<string>中,提供了更方便、安全的字符串操作方式。例如:

C++
1
2
3
4
5
#include <string>
using namespace std;

string str1 = "Hello";
string str2 = "World";

​ 这里,str1str2string类的对象,它们可以使用string类中提供的各种方法操作字符串,例如获取字符串长度、截取子串、连接两个字符串等等。string类会自动进行内存管理,不需要手动分配和释放内存,避免了内存泄漏等问题。

​ 总的来说,字符指针适用于简单的字符串操作,如读取和输出字符串等,而string类更适用于复杂的字符串处理,如字符串连接、分割、查找等。

2.11.4 指向结构体的指针

​ 指向结构体的指针可以用于方便访问结构体中的成员变量。可以通过指向结构体的指针来访问结构体中的成员变量,也可以通过指向结构体的指针来传递结构体作为函数参数,或者动态分配结构体的内存空间。

以下是指向结构体的指针的示例代码:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <string>
using namespace std;

struct Student {
    string name;
    int age;
    double score;
};

int main() {
    Student stu = {"Tom", 18, 98.5};
    Student *p = &stu; // 定义指向结构体的指针,并将其初始化为结构体变量 stu 的地址

    cout << "Name: " << p->name << endl;
    cout << "Age: " << p->age << endl;
    cout << "Score: " << p->score << endl;

    return 0;
}

​ 上述示例代码中,定义了一个结构体 Student,包含三个成员变量:nameagescore,然后创建一个结构体变量 stu,并初始化其中的成员变量。接着定义一个指向结构体 Student 的指针 p,并将其初始化为结构体变量 stu 的地址,即 &stu。然后可以通过指针 p 来访问结构体中的成员变量,使用 -> 运算符访问,例如 p->name

​ 注意,在定义指向结构体的指针时,需要使用结构体的类型名作为指针变量的类型,例如 Student *p。指向结构体的指针也可以作为函数的参数传递,以及用于动态分配结构体的内存空间。

2.12 文件及基本读写

2.12.1 文件的基本概念,文本文件的基本操作

​ 在计算机中,文件是数据存储的一种方式。文件通常包含数据,它们可以是程序的源代码、文本文档、图像、音频、视频等等。在 C++ 中,文件是通过文件流进行读写的,文件流是 C++ 标准库中的一种 I/O 流。

​ C++ 的文件处理涉及到两个重要类:ofstream(输出文件流)和 ifstream(输入文件流)。这两个类都是从 fstream 类继承而来的。ofstream 类用于创建文件并向文件写入信息,而 ifstream 类用于从文件中读取信息。

文本文件的基本操作:

​ C++ 中的文本文件是以 ASCII 码形式存储的文件,使用 ifstream 和 ofstream 对文本文件进行读写操作非常简单。下面是一些常用的文本文件操作函数。

1.打开文件

​ 要对文件进行读写操作,必须先打开文件。可以使用 ifstream 和 ofstream 类中的 open() 函数打开文件。该函数有两个参数:文件名和打开模式。打开模式可以是 ios::in(打开文件用于读取),ios::out(打开文件用于写入)或者两者结合。

例如,要打开一个文件,以进行写入操作,可以使用以下代码:

C++
1
2
ofstream myFile;
myFile.open("example.txt", ios::out);

2.写入数据

要将数据写入文件,可以使用 ofstream 类中的 << 运算符。下面是一个将文本写入文件的示例:

C++
1
2
3
4
5
ofstream myFile;
myFile.open("example.txt", ios::out);
myFile << "Hello, World!\n";
myFile << "This is a text file.\n";
myFile.close();

​ 在上面的示例中,<< 运算符用于将字符串写入文件。注意,在每个字符串后面加上换行符(\n),这样每个字符串就单独占据一行。

3.读取数据

要从文件中读取数据,可以使用 ifstream 类中的 >> 运算符。下面是一个从文本文件中读取数据的示例:

C++
1
2
3
4
5
6
7
ifstream myFile;
myFile.open("example.txt", ios::in);
string line;
while (getline(myFile, line)) {
    cout << line << endl;
}
myFile.close();

​ 在上面的示例中,while 循环用于读取文件中的每一行数据。getline() 函数用于读取每一行数据,并将其存储在字符串变量 line 中。然后,将 line 输出到屏幕上。

4.关闭文件

完成文件读写操作后,必须关闭文件。可以使用 ifstream 和 ofstream 类中的 close() 函数来关闭文件。

C++
1
2
3
4
ofstream myFile;
myFile.open("example.txt", ios::out);
myFile << "Hello, World!\n";
myFile.close();

在上面的示例中,close() 函数用于关闭文件。

2.12.2 文本文件类型与二进制文件类型

​ 在计算机中,可以将文件分为文本文件和二进制文件两类。它们之间的最大区别在于它们的存储方式和读取方式不同。

​ 文本文件是一种纯文本的文件格式,它的内容是由可见字符和控制字符组成的,比如 ASCII 码字符集中的字符。文本文件通常以文本编辑器打开和编辑,也可以用代码读取和写入。文本文件的每行末尾通常以换行符结束(\n)。

​ 二进制文件是以二进制代码的形式存储的文件格式,其中包含了不仅仅是文本字符,还包括图像、音频、视频等数据。二进制文件通常需要使用特殊的程序或代码进行读取和写入,如图像编辑器、音频编辑器、数据库等。二进制文件不像文本文件一样有换行符或者其他规则。

​ 因为二进制文件可以存储各种类型的数据,所以它们通常更加灵活和高效。文本文件则比较适合存储简单的文本数据,且占用空间更小。在程序开发中,常常需要读取和写入文件,因此需要根据需要选择不同类型的文件。

2.12.3 文件重定向、文件读写等操作

​ 文件重定向是指将程序本来打印到屏幕或者读取键盘的输出或输入流重定向到文件上,或者将本来的标准错误输出流(stderr)重定向到文件上。这种操作可以通过操作系统提供的重定向命令,如Unix中的“>”、“<”等符号,或者在C++代码中使用freopen函数来实现。

freopen 是一个函数,可以重定向标准输入输出流,将其与文件关联起来。它的函数原型为:

C++
1
FILE *freopen(const char *filename, const char *mode, FILE *stream);

​ 其中,filename 为文件名,mode 为打开文件的模式,stream 是一个指向 FILE 结构的指针,用于表示要打开的文件。freopen 函数的返回值为一个指向 FILE 结构的指针,如果打开文件成功,则返回值与 stream 参数相同,否则返回 NULL

​ 下面是一个使用 freopen 重定向标准输入输出流的例子,假设要从文件 input.txt 读入数据,输出到文件 output.txt 中:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <cstdio>

int main() {
    FILE *fin = freopen("input.txt", "r", stdin);  // 将标准输入流与文件 input.txt 关联
    FILE *fout = freopen("output.txt", "w", stdout);  // 将标准输出流与文件 output.txt 关联

    int a, b;
    scanf("%d%d", &a, &b);
    printf("%d + %d = %d\n", a, b, a + b);

    fclose(fin);  // 关闭文件
    fclose(fout);
    return 0;
}

​ 在上面的例子中,首先调用 freopen 函数将标准输入流 stdin 与文件 input.txt 关联,将标准输出流 stdout 与文件 output.txt 关联。然后从输入文件中读入两个整数,计算它们的和并输出到输出文件中。最后关闭文件并退出程序。

​ 需要注意的是,freopen 函数只能用于重定向标准输入输出流,不能用于重定向文件指针,也不能用于关闭文件。如果要关闭文件,应该使用 fclose 函数。

​ 文件读写是指程序通过文件流(FILE类型指针)来访问文件,从而进行读取或写入操作。常见的文件读写函数包括:fopen、fclose、fread、fwrite、fscanf、fprintf、fgets、fputs等。

举个例子,如果我们想要从名为“input.txt”的文件中读取内容并输出到屏幕上,可以使用如下代码:

C++
1
2
3
4
5
6
7
8
FILE *fin;
char s[100];

fin = fopen("input.txt", "r"); // 打开文件
while (fgets(s, 100, fin) != NULL) { // 按行读取文件内容
    printf("%s", s); // 输出读取到的内容
}
fclose(fin); // 关闭文件

上述代码使用fopen函数打开名为“input.txt”的文件,然后使用fgets函数按行读取文件内容,最后使用printf函数输出读取到的内容。最后使用fclose函数关闭文件。

2.13 STL模板应用

2.13.1 Sort函数详解

​ 在 C++ STL 中,sort() 函数用于对数组、向量、字符串等进行排序。sort() 函数可以排序整数、浮点数、字符串和自定义数据类型等。

sort() 函数是一个泛型算法,因此可以接受一个比较函数作为参数,以便对各种类型的数据进行排序。默认情况下,sort() 函数使用 < 运算符对元素进行比较。

以下是 sort() 函数的基本语法:

C++
1
sort(start_pointer, end_pointer, compare_function);

​ 其中,start_pointerend_pointer 分别指向要排序的元素的起始位置和结束位置。compare_function 是一个可选参数,它是一个函数指针,用于指定自定义比较函数。

以下是一个简单的例子,用于对整数数组进行排序:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <iostream>
#include <algorithm>
using namespace std;

int main() {
    int arr[] = { 5, 2, 8, 6, 1, 3 };
    int n = sizeof(arr) / sizeof(arr[0]);

    sort(arr, arr + n);

    for (int i = 0; i < n; i++) {
        cout << arr[i] << " ";
    }

    return 0;
}

上述程序中,sort() 函数使用默认的比较函数(即 < 运算符)对整数数组进行升序排序。

下面是一个使用自定义比较函数的例子,用于对字符串进行降序排序:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;

bool my_compare(string a, string b) {
    return a > b;
}

int main() {
    string arr[] = { "apple", "orange", "banana", "grape" };
    int n = sizeof(arr) / sizeof(arr[0]);

    sort(arr, arr + n, my_compare);

    for (int i = 0; i < n; i++) {
        cout << arr[i] << " ";
    }

    return 0;
}

​ 在上述程序中,my_compare() 函数用于比较两个字符串的大小。sort() 函数使用 my_compare() 函数对字符串数组进行降序排序。

2.13.2 栈(stack)、 队列(queue)、链表(list)、向量(vector)等容器

​ 在C++中,STL容器提供了一组类来存储和操作不同类型的数据。常见的容器有:栈(stack)、队列(queue)、链表(list)、向量(vector)等。

以下是这些容器的简介:

1.栈(stack):栈是一种后进先出(Last In First Out,LIFO)的容器,只允许在容器顶部进行插入和删除操作。它的内部实现基于数组或链表,可以用于处理逆序的问题。

栈容器主要提供以下常用操作:

  • push:将元素压入栈顶;
  • pop:将栈顶元素弹出;
  • top:返回栈顶元素的引用;
  • empty:判断栈是否为空;
  • size:返回栈中元素的个数。

2.队列(queue):队列是一种先进先出(First In First Out,FIFO)的容器,只允许在容器的前端进行删除操作,在容器的尾端进行插入操作。它的内部实现基于数组或链表,可以用于处理排队的问题。

队列容器主要提供以下常用操作:

  • push:将元素加入队尾;
  • pop:将队首元素弹出;
  • front:返回队首元素的引用;
  • back:返回队尾元素的引用;
  • empty:判断队列是否为空;
  • size:返回队列中元素的个数。

3.链表(list):链表是一种线性的容器,可以在任意位置插入或删除元素,每个元素包含自身的数据和指向下一个元素的指针。链表的内部实现基于节点,每个节点包含元素数据和指向下一个节点的指针。链表不支持随机访问,因此访问其元素的时间复杂度为 O(n),但插入和删除元素的时间复杂度为 O(1)。

链表容器主要提供以下常用操作:

  • push_back:将元素加入链表尾部;
  • push_front:将元素加入链表头部;
  • pop_back:将链表尾部元素弹出;
  • pop_front:将链表头部元素弹出;
  • insert:在指定位置插入元素;
  • erase:删除指定位置的元素;
  • clear:清空链表

4.向量(vector):向量是一种可以动态增长的数组,支持随机访问和在末尾插入或删除元素。向量的内部实现基于数组,当插入或删除元素时,需要对数组进行扩容或缩容操作。因此,插入和删除元素的时间复杂度为 O(n),访问元素的时间复杂度为 O(1)。