数组

数组声明

1
2
// type arrayName [arraySize];
double balance[10]; // 声明一个元素为double类型的,大小为10的数组balance

数组初始化

1
2
3
4
// 逐个初始化
double balance[5] = {1.0, 2.0, 3.4, 5.0, 9.0}; // 大括号内的值的书目不能大于数组大小,此处数组大小为5
// 自动识别数组大小
double balance[] = {1000.0, 2.0, 3.4, 7.0}; // 因为大括号里面有4个元素,数组balance的大小被自动识别为4

数组访问

数组元素通过数据名加索引进行访问。元素索引放在方括号内,跟在数组名称后面。如:

1
2
3
4
// 声明一个数组,并定义数组有5个值分别如下
double balance[5] = {1.0, 2.0, 3.4, 5.0, 9.0};
// 访问数组第三个值,并将其赋值给变量num3
double num3 = balance[2] // 访问第三个值,由于从0开始计算,所以索引为2的值是第三个值

数组传递及函数返回

方式一: 形参是已经定义大小的数组

1
2
3
void myFunction(int param[10]) {  // 已经定义大小为10 (不会报错,但是不推荐使用,容易让人误以为数组大小固定,实际上形参这里不影响实际数组大小)
code_block
}

方式二: 形参是没有定义大小的数组

1
2
3
void myFunction(int param[]) {  // 未定义大小
code_block
}

方式三: 形参是指针(不在该处详细介绍)

1
2
3
void myFunction(int *param) {
code_block
}

实例:
传递数组到函数:

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
#include <iostream>
using namespace std;

// 函数声明
double getAverage(int arr[], int size);

int main()
{
// 带有 5 个元素的整型数组
int balance[5] = { 1000, 2, 3, 17, 50 };
double avg;

// 传递一个指向数组的指针作为参数
avg = getAverage(balance, 5);

// 输出返回值
cout << "平均值是:" << avg << endl;

return 0;
}

// 求平均值函数,形参分别为数组,以及数组大小
double getAverage(int arr[], int size)
{
int i, sum = 0;
double avg;

for (i = 0; i < size; ++i)
{
sum += arr[i];
}

avg = double(sum) / size;

return avg;
}

从函数返回数组
不在此处详解,需要先学习指针

数组扩展

多维数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 声明N维数组
// type name[size1][size2]....[sizeN];

// 声明一个3*3的二维数组(二维数组可以理解成是一个二维表格)
int a[3][3] = {
{0, 1, 2},
{3, 4, 5},
{6, 7, 8}
};
// 内嵌括号是可选的,下面初始化方式也是可以的(不推荐,可读性差)
int a[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8};

// 多维数组访问
// type val = array[index1][index2]...[indexN];

// 二维数组访问
int val = a[0][2]; // 访问第0行第2列的数组

同理,多维数组也可以进行函数传递及返回。

字符串

C风格字符串(c++也能用)

字符串初始化

1
2
3
4
5
6
7
8
9
10
11
// 初始化C风格字符串(c++也支持)
char mystring[6] = {'h', 'e', 'l', 'l', 'o', '\0'};
// 为什么会有个'\0'? 字符串实际上是使用 null 字符 \0 终止的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符。
// 注意,如果不写'\0',数组大小也需要所字符个数+1的大小,比如hello有5个字符,需要申请的空间为5+1=6,如果申请空间为5,则'\0'没有位置,会导致随机输出。
// 如下:
char mystring[6] = {'h', 'e', 'l', 'l', 'o'}; // 正常输出
char mystring[5] = {'h', 'e', 'l', 'l', 'o'}; // 异常输出

// 根据数组初始化规则,上面正确的写法等价于下面
char mystring[] = "hello";
// 其实不需要把 null 字符显式放在字符串常量的末尾。C++ 编译器会在初始化数组时,自动把 \0 放在字符串的末尾。

C++中操作null结尾的字符串(C风格)的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 复制字符从s2 到字符串s1
strcpy(s1, s2) // 不安全,使用strcpy_s(s1, s2)替代
strcpy_s(s1, s2);

// 连接字符串 s2 到字符串 s1 的末尾。
strcat(s1, s2); // 不安全,使用strcpy_s(s1, s2)替代
strcat_s(s1, s2); // 需要注意,s1的申请的大小需要大于连接后的字符串大小,且不能为null

// 返回字符串s1的长度
// strlen(s1);

// 比较两个字符串,如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回值小于 0;如果 s1>s2 则返回值大于 0。
// 正常返回值返回的是ASCII的差值,但是strcmp可能被库进行了优化,只返回1、0、-1, 所以通过大于0,小于0,等于0判断更准确
strcmp(s1, s2)

// 查找某个字符,返回一个指向该字符的指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
strchr(s1, 'a'); // 查找字符a,找到则返回一个指向该字符的指针,找不到则返回空指针

// 查找某个字符,返回一个指向该子字符起始位置的指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。
strstr(s1, s2); // 查找字符串s2,找到则返回一个指向该子字符起始位置的指针,找不到则返回空指针

C++中的高级字符串

C++ 标准库提供了 string 类类型,支持上述所有的操作,另外还增加了其他更多的功能。
使用相关操作需要引入标准库
#include <string>
上述方法在c++中的对应方法

C 语言函数 C++ std::string 等价写法
strcpy_s(dest, size, src); std::string str = src; 或 str = src;
strcat_s(dest, size, src); str += src; 或 str.append(src);
strlen(str); str.length(); 或 str.size();
strcmp(str1, str2); str1.compare(str2);
strchr(str, ch); str.find(ch);(返回索引)或 str.find_first_of(ch); 找不到返回std::string::npos
strstr(str, substr); str.find(substr);(返回索引)找不到返回std::string::npos 或 str.contains(substr);(C++23支持)

除了对应方法之外,还有更多强大的操作,可以查看该文档
对新手友好的总结,可以查看这篇文章

引用

引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。

1
2
int a = 10;
int &ref = a; // ref 是 a 的引用

引用和指针对比(可以学习完指针后再过来进行对比)

不存在空引用,引用必须连接到一块合法的内存。
一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
引用必须在创建时被初始化。指针可以在任何时间被初始化。
引用的对象必须是一个变量,而指针必须是一个地址。

更详细的对比,参考文章

引用作为传参(经典交换两数问题):

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
#include <iostream>
using namespace std;

// 函数声明
void swap(int& x, int& y);

int main()
{
// 局部变量声明
int a = 100;
int b = 200;

cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;

/* 调用函数来交换值 */
swap(a, b);

cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;

return 0;
}

// 函数定义
void swap(int& x, int& y)
{
int temp;
temp = x; /* 保存地址 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */

return;
}

引用作为函数返回值

此处不做更多介绍,学习完指针后遇到再学习。感兴趣的可以提前自行查阅文章

基本输入输出

常用输入输出

标准输入流
引入标准库
#include <iostream>
该文件定义了如下常用对象,对应关系如下:

流对象 作用 缓冲 示例
cin 标准输入流(从键盘读取数据) 有缓冲 std::cin >> x;
cout 标准输出流(向控制台打印) 有缓冲 std::cout << “Hello”;
cerr 标准错误流(输出错误信息) 无缓冲(实时输出) std::cerr << “Error!”;
clog 标准日志流(输出日志信息) 有缓冲(不会实时输出) std::clog << “Logging…”;

有缓冲(Buffered) 和 无缓冲(Unbuffered) 的主要区别在于 数据何时被写入目标(如屏幕或文件)。

  1. 有缓冲(Buffered)
    数据会先存入缓冲区,等到缓冲区满了或者遇到刷新操作时,才真正输出到目标设备(如屏幕、文件等)。
    提高性能,减少 I/O 操作的次数,提高程序效率。
    适用于一般输出,如 std::cout 和 std::clog。
  2. 无缓冲(Unbuffered)
    数据直接输出到目标设备,不会存入缓冲区,每次调用都立刻生效。
    适用于紧急信息输出,如 std::cerr(用于错误信息)。
    可能会 降低性能,因为每次调用 cerr 都会触发 I/O 操作。

流状态检查:
可以检查输入输出流状态,以确定操作是否成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

int main() {
int num; // 期望输入一个整数
std::cout << "Enter a number: ";
std::cin >> num;

// 检查输入操作是否成功(是否是整数)
if (std::cin.fail()) {
std::cerr << "Invalid input!" << std::endl;
}
else {
std::cout << "You entered: " << num << std::endl;
}

return 0;
}

格式化输入输出

引入标注库:
#include <iomanip>

常用:
使用std::getline函数可以读取包含空格的整行输入。

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <string>

int main() {
std::string fullName;
std::cout << "Enter your full name: ";
std::getline(std::cin, fullName);
std::cout << "Hello, " << fullName << "!" << std::endl;

return 0;
}

使用std::setprecision设置输出精度
使用std::setw设置输出宽度
使用std::left/std::right设置对其方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <iomanip>

int main() {
double pi = 3.14159;

// 设置输出精度
std::cout << std::setprecision(3) << pi << std::endl;

// 设置输出宽度和对齐方式
std::cout << std::setw(10) << std::left << pi << std::endl; // 左对齐
std::cout << std::setw(10) << std::right << pi << std::endl; // 右对齐

return 0;
}

其他扩展,用到现学,不在此处介绍,感兴趣自行学习,参考文档

文件输入输出

引入标注库:
#include <fstream>

基本代码结构如下:

1
2
3
4
5
6
7
8
9
#include <fstream>

int main() {
std::fstream file; // 创建fstream对象
file.open("filename", mode); // 打开文件, mode为打开模式,详情见下方表格
// 进行文件操作
file.close(); // 关闭文件
return 0;
}
mode 描述
std::ios::in 以输入模式打开文件。(读取文件)
std::ios::out 以输出模式打开文件。(写入文件)
std::ios::app 以追加模式打开文件。(追加写入文件)
std::ios::ate 打开文件并定位到文件末尾。
std::ios::trunc 打开文件并截断文件,即清空文件内容。

创建文件

实例:
在当前目录下创建一个名为example.txt的文件,文件内容为: Hello, World!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <fstream>
#include <iostream>

int main() {
std::fstream file;
file.open("example.txt", std::ios::out); // 以输出模式打开文件

if (!file) {
std::cerr << "Unable to open file!" << std::endl;
return 1; // 文件打开失败
}

file << "Hello, World!" << std::endl; // 写入文本
file.close(); // 关闭文件

return 0;
}

读取文件

实例:

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

int main() {
std::fstream file;
file.open("example.txt", std::ios::in); // 以输入模式打开文件

if (!file) {
std::cerr << "Unable to open file!" << std::endl;
return 1; // 文件打开失败
}

std::string line;
while (getline(file, line)) { // 逐行读取
std::cout << line << std::endl;
}

file.close(); // 关闭文件

return 0;
}

追加文本内容

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <fstream>
#include <iostream>

int main() {
std::fstream file;
file.open("example.txt", std::ios::app); // 以追加模式打开文件

if (!file) {
std::cerr << "Unable to open file!" << std::endl;
return 1; // 文件打开失败
}

file << "我是追加的文本." << std::endl; // 追加文本
file.close(); // 关闭文件

return 0;
}

vector容器

引入标准库
#include <vector>

描述:
P.S.:可以简单理解为,差不多类似python的列表,c/c++数组的升级版
C++ 中的 vector 是一种序列容器,它允许你在运行时动态地插入和删除元素。
vector 是基于数组的数据结构,但它可以自动管理内存,这意味着你不需要手动分配和释放内存。
与 C++ 数组相比,vector 具有更多的灵活性和功能,使其成为 C++ 中常用的数据结构之一。
vector 是 C++ 标准模板库(STL)的一部分,提供了灵活的接口和高效的操作。

基本特性:
动态大小:vector 的大小可以根据需要自动增长和缩小。
连续存储:vector 中的元素在内存中是连续存储的,这使得访问元素非常快速。
可迭代:vector 可以被迭代,你可以使用循环(如 for 循环)来访问它的元素。
元素类型:vector 可以存储任何类型的元素,包括内置类型、对象、指针等。

使用场景:
当你需要一个可以动态增长和缩小的数组时。
当你需要频繁地在序列的末尾添加或移除元素时。
当你需要一个可以高效随机访问元素的容器时。

vector相关操作

创建:

1
2
3
4
5
6
7
8
9
std::vector<int> myVector; // 创建一个存储整数的空的vector  

vector<vector<int>> outer; // 二维/多维创建

std::vector<int> myVector(5); // 创建一个包含 5 个整数的 vector,每个值都为默认值(0)

std::vector<int> myVector(5, 10); // 创建一个包含 5 个整数的 vector,每个值都为 10

std::vector<int> vec2 = {1, 2, 3, 4}; // 初始化一个包含元素的 vector

操作/访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
myVector.push_back(7); // 将整数 7 添加到 vector 的末尾

// 有以下两种方式获取vector中的元素,不同之处见后面比较
int x = myVector[0]; // 获取第一个元素
int y = myVector.at(1); // 获取第二个元素

int size = myVector.size(); // 获取 vector 中的元素数量

// vector中元素遍历方式,有下面两种
// 第一种,迭代器
for (auto it = myVector.begin(); it != myVector.end(); ++it) {
std::cout << *it << " ";
}
//第二种,范围循环
for (int element : myVector) {
std::cout << element << " ";
}

myVector.erase(myVector.begin() + 2); // 删除第三个元素

myVector.clear(); // 清空 vector

使用下标访问myVector[0];和使用at访问myVector.at(0);的比较:

  1. operator[](下标访问)
    直接访问 vector 的元素,不进行边界检查。
    如果索引超出范围,程序不一定报错行为随机(Undefined Behavior, UB),可能导致程序崩溃或访问无效内存。
    性能稍快,因为没有额外的边界检查。
  2. at() 方法
    带边界检查,如果索引超出范围,会抛出 std::out_of_range 异常,防止访问非法内存。
    更安全,但会略微降低性能,因为有额外的检查开销。

at搭配try-catch进行异常处理的实例:

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

int main() {
std::vector<int> myVector = { 10, 20, 30 };

int x = myVector.at(0); // 安全访问
std::cout << "x = " << x << std::endl;

try {
int y = myVector.at(5); // 越界访问,会抛出异常
std::cout << "y = " << y << std::endl;
}
catch (const std::out_of_range& e) {
std::cout << "异常捕获: " << e.what() << std::endl;
}

return 0;
}

P.S.异常捕获-补充知识

异常捕获:
引入标准库
#include <exception>

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 <vector>
#include <exception>

int main() {
std::vector<int> myVector = { 10, 20, 30 };

int x = myVector.at(0); // 安全访问
std::cout << "x = " << x << std::endl;

try {
int y = myVector.at(5); // 越界访问,会抛出异常
std::cout << "y = " << y << std::endl;
}
catch (const std::out_of_range& e) {
std::cout << "越界异常捕获: " << e.what() << std::endl;
}
catch (const std::exception& e) {
std::cout << "标准异常:" << e.what() << std::endl;
}
catch (...) {
// 无法获取异常的具体信息,没有提供 what() 这样的异常描述方法。
std::cout << "未知异常,无e实例" << std::endl;
}

return 0;
}
方式 捕获类型 是否能获取异常信息 适用场景
catch (const std::exception& e) 标准异常(如 std::runtime_error) ✅ e.what() 获取详细信息 处理标准库异常
catch (…) 所有异常(包括非标准) ❌ 无法获取信息 兜底异常处理,防止程序崩溃
推荐做法 先 catch (std::exception&),再 catch (…) ✅ 既能获取信息,又能兜底 最佳实践