内容目录

数组

数组声明

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

数组初始化

// 逐个初始化
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

数组访问

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

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

数组传递及函数返回

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

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

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

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

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

void myFunction(int *param) {
    code_block
}

实例:
传递数组到函数:

#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;
}

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

数组扩展

多维数组:

// 声明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++也能用)

字符串初始化

// 初始化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风格)的常用方法

// 复制字符从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支持)

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

引用

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

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

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

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

更详细的对比,参考文章

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

#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 操作。

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

#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函数可以读取包含空格的整行输入。

#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设置对其方式

#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>

基本代码结构如下:

#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!

#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;
}

读取文件

实例:

#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;
}

追加文本内容

实例:

#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相关操作

创建:

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

操作/访问:

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进行异常处理的实例:

#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>

#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 (…)✅ 既能获取信息,又能兜底最佳实践