前面我们学习了 C++ 的基本数据类型,比如 int、double、char 等,这些类型直接对应计算机硬件的基本功能。
但在实际编程中,我们经常需要处理更复杂的数据,比如一串字符或一组数据。为此,C++ 标准库为我们提供了更高级的类型:string(字符串)和 vector(向量)。
string 可以用来存储和操作任意长度的文本,非常适合处理字符串相关的任务;vector 则可以动态地存放任意数量的同类型数据,非常灵活。 除此之外,还有一种叫做“迭代器”的工具,可以帮助我们方便地访问和操作 string 或 vector 里的每一个元素。
这节课我们就要系统学习这三种类型:数组(array)、字符串(string)和向量(vector)。你会发现,虽然数组是最基础的,但 string 和 vector 用起来更安全、更高效。

在C++中,string类型用来表示一串字符,也就是我们常说的“字符串”。它的长度可以变化,非常适合处理日常编程中遇到的文本数据。要使用string,你需要在程序开头加上#include <string>,并且通常会用using std::string;让代码更简洁。
你可以用多种方式创建和初始化字符串。比如:
|string emptyStr; // 创建一个空字符串 string name = "小明"; // 用字符串字面量初始化 string copyName = name; // 用另一个字符串初始化 string repeatedChar(5, '*'); // 创建一个包含5个*的字符串
如果你什么都不写,得到的是一个空字符串。你也可以直接用等号赋值,或者用括号传递参数。比如你想要一个由10个“#”组成的字符串,可以写string marks(10, '#');。
C++支持多种初始化方式。比如:
|string a = "hello"; // 拷贝初始化 string b("world"); // 直接初始化
这两种写法在大多数情况下效果一样,但如果你要用多个参数(比如重复字符),只能用括号的方式。
你还可以先用括号创建一个临时字符串,再用等号赋值:
|string s = string(3, 'A'); // 得到"AAA"
不过这种写法没什么优势,直接用括号更直观。
C++ 的 string 类型支持很多常用操作,下面用一些生活化的例子来说明:
<< 把字符串输出到屏幕,比如:
|string greeting = "你好,世界!"; cout << greeting << endl; // 输出:你好,世界!
>> 可以从键盘读取一个单词(遇到空格就停):
|string food; cin >> food; // 输入 pizza pasta,food 只会得到 pizza
getline 可以读取一整行,包括空格:
|string sentence; getline(cin, sentence); // 输入 I like C++,sentence 得到 I like C++
empty() 判断字符串是否为空:
|string s;
在实际编程中,我们经常需要对字符串里的每个字符进行操作。比如,你可能想检查字符串里有没有空格,把所有字母变成小写,或者查找某个特定字符是否出现过。这类需求在文本处理、数据清洗、用户输入校验等场景中非常常见。
C++11 之后,最推荐的做法是用“范围 for 循环”(range-based for),它可以让你非常直观地访问字符串中的每一个字符。例如:
|string nickname = "Coder123"; for (auto ch : nickname) { cout << ch << endl; // 每个字符单独输出一行 }
这段代码会把 nickname 里的每个字符都打印出来,每行一个。auto 让编译器自动推断 ch 的类型(这里是 char),你不用自己写类型。
假如你想统计一个字符串里有多少个数字字符,可以这样写:
|string phone = "Call me at 123-4567!"; int digitCount = 0; for (auto c : phone) { if (isdigit(c)) // 如果是数字字符 ++digitCount; } cout << "数字个数:" << digitCount << endl;
这里用到了 <cctype> 头文件里的 isdigit 函数,它可以判断一个字符是不是数字。C++ 标准库里有很多类似的字符处理函数,比如 isspace(判断空白)、isalpha(判断字母)、ispunct(判断标点)等。
这些字符处理函数都定义在 <cctype> 头文件里。需要注意的是,C++ 推荐你用 #include <cctype>,而不是 C 语言的 #include <ctype.h>。这样所有的函数都在 std 命名空间下,风格更统一。
这些函数都需要
#include <cctype>头文件,并且参数通常是char类型。
在C++中,vector类型用来表示一组同类型的数据,也就是我们常说的“数组”。它的长度可以变化,非常适合处理日常编程中遇到的动态数据。要使用vector,你需要在程序开头加上#include <vector>,并且通常会用using std::vector;让代码更简洁。
你可以用多种方式创建和初始化向量。比如:
|vector<int> emptyVec; // 创建一个空向量 vector<int> nums = {1, 2, 3}; // 用初始化列表初始化 vector<int> copyNums = nums; // 用另一个向量初始化 vector<int> repeatedInt(5, 10); // 创建一个包含5个10的向量
如果你什么都不写,得到的是一个空向量。你也可以直接用等号赋值,或者用括号传递参数。比如你想要一个由10个“#”组成的向量,可以写vector<int> marks(10, '#');。
C++支持多种初始化方式。比如:
|vector<int> a = {1, 2, 3}; // 拷贝初始化 vector<int> b({1, 2, 3}); // 直接初始化
这两种写法在大多数情况下效果一样,但如果你要用多个参数(比如重复元素),只能用括号的方式。
以下是你可以使用初始化 vector 的多种方式:
C++ 的 vector 类型非常灵活,支持很多常用操作。你可以像操作数组一样用下标访问元素,也可以动态添加、删除元素,还能直接获取长度、判断是否为空等。
比如,你可以用 push_back 方法在末尾添加新元素:
|vector<int> scores; scores.push_back(90); scores.push_back(85); scores.push_back(100); cout << "共有" << scores.size() << "个分数" << endl; // 此时的 scores 为 {90, 85, 100}
这样,scores 里就有了 3 个分数。你可以用 size() 得到当前元素个数。
如果你想访问或修改某个位置的元素,可以用下标:
|cout << "第一个分数是:" << scores[0] << endl; scores[1] = 88; // 把第二个分数改成88 // 此时的 scores 为 {90, 85, 100}
注意,下标从0开始。你也可以用 at() 方法访问元素,它会检查下标是否合法,如果越界会抛出异常:
|try { cout << scores.at(10) << endl; // 越界会报错 } catch (out_of_range& e) { cout << "下标越界!" << endl; }
如果你想删除最后一个元素,可以用 pop_back():
|scores.pop_back(); // 删除最后一个分数 // 此时的 scores 为 {90, 85}
判断向量是否为空,可以用 empty():
|if (scores.empty()) { cout << "还没有分数" << endl; }
你还可以用范围 for 循环遍历所有元素:
|for (int s : scores) { cout << s << endl; }
如果你想清空所有元素,可以用 clear():
|scores.clear(); cout << "现在分数个数:" << scores.size() << endl;
vector 还支持直接用 = 赋值、用 {} 初始化、用 == 比较内容是否完全一样等。
你还可以用 insert 和 erase 方法在指定位置插入或删除元素。
比如,假如你想在第二个位置插入一个分数 99,可以这样写:
|scores.insert(scores.begin() + 1, 99); // 在下标1的位置插入99 // 此时 scores 为 {90, 99, 85, 100}
如果你想删除第二个分数,可以用:
|scores.erase(scores.begin() + 1); // 删除下标1的元素 // 此时 scores 为 {90, 85, 100}
你还可以一次性删除一段区间,比如只保留第一个分数:
|scores.erase(scores.begin() + 1, scores.end()); // 删除下标1及之后的所有元素 // 此时 scores 为 {90}
insert 和 erase 都需要用到迭代器(可以简单理解为指向 vector 某个位置的“指针”),最常用的是 begin()(第一个元素)和 end()(最后一个元素的下一个位置)。
通过这些操作,你可以灵活地在 vector 里插入、删除任意位置的元素,非常适合实现动态列表、队列等数据结构。
下面的工具帮你可视化 vector 的插入和删除操作。
在C++中,迭代器(iterator)是用来遍历容器(比如vector、string、list等)里元素的一种工具。你可以把迭代器想象成“指向容器中某个元素的指针”,它能帮你灵活地访问、修改、插入和删除容器里的数据。
假如你有一个vector:
|vector<string> fruits = {"apple", "banana", "cherry"};
你可以用下标访问元素,比如fruits[0]是"apple"。但如果你想写一个通用的遍历代码,或者想用insert/erase等方法操作vector(像我们之前用到的那样),迭代器就非常有用。
vector有两个最常用的方法:begin()和end()。
begin()返回指向第一个元素的迭代器。end()返回指向“最后一个元素的下一个位置”的迭代器(注意:end()本身不是最后一个元素)。比如:
|vector<string> fruits = {"apple", "banana", "cherry"}; auto it = fruits.begin(); // it指向"apple" cout << *it << endl; // 输出 apple
你可以用++让迭代器指向下一个元素,用--指向上一个元素:
|++it; // it现在指向"banana" cout << *it << endl; // 输出 banana
你还可以用it + n让迭代器向后移动n个位置(只对vector、string等支持随机访问的容器有效)。
最常见的遍历方式是用while或for循环:
|for (auto it = fruits.begin(); it != fruits.end(); ++it) { cout << *it << endl; }
上面这段代码会依次输出所有水果的名字。
vector<T>::iterator:普通迭代器,可以修改元素。vector<T>::const_iterator:只读迭代器,只能访问,不能修改。比如:
|vector<int> nums = {1, 2, 3}; for (vector<int>::const_iterator it = nums.begin(); it != nums.end(); ++it) { cout << *it << endl; // 只能读,不能写 *it = 10; 会报错 }
有些操作(比如插入、删除元素)会让原来的迭代器失效。失效的迭代器不能再用,否则会出错。比如:
|auto it = fruits.begin(); fruits.insert(fruits.begin(), "pear"); // 现在 it 可能已经失效,不能再用 *it
对于vector、string这类支持“随机访问”的容器,迭代器其实就像指针一样,可以做加减、比较、求距离等操作。
比如,你可以让迭代器向后移动n个位置:
|auto it = fruits.begin(); it = it + 2; // 现在 it 指向"cherry" cout << *it << endl; // 输出 cherry
你也可以用减法计算两个迭代器之间的距离(即元素个数):
|int dist = fruits.end() - fruits.begin(); cout << "fruits 里有" << dist << "个元素" << endl;
还可以用比较运算符判断迭代器的位置关系:
|auto first = fruits.begin(); auto last = fruits.end() - 1; if (first < last) { cout << *first << "在" << *last << "前面" << endl; }
这些算术操作让你可以灵活地定位、跳跃、分段遍历vector,非常适合实现区间处理、二分查找等算法。
需要注意的是,只有vector、string、deque等“随机访问容器”的迭代器才能做加减法,list、set等容器的迭代器只能用++/--移动,不能直接加减。、
在C++中,数组(array)是一种用来存放一组同类型数据的基础结构。虽然现代C++更推荐用vector等容器,但理解内置数组依然很重要,尤其是在和老代码或底层接口打交道时。
定义数组时,你需要指定元素类型和元素个数。比如:
|int scores[5]; // 定义一个能存5个int的数组
这样就创建了一个名为scores的数组,可以存5个整数。你也可以在定义时直接初始化:
|int ages[3] = {18, 20, 22}; // 依次存入3个年龄
如果你只写部分初值,剩下的会自动补0:
|int nums[4] = {1, 2}; // 等价于 {1, 2, 0, 0}
还可以让编译器自动推断数组长度:
|double prices[] = {9.9, 19.9, 29.9}; // 长度自动是3
数组的长度一旦确定就不能再变。
数组的每个元素都可以通过下标访问,下标从0开始。例如:
|cout << ages[0] << endl; // 输出18 ages[2] = 25; // 把第三个年龄改成25
你可以用循环遍历数组:
|for (int i = 0; i < 3; ++i) { cout << ages[i] << endl; }
要注意,访问越界(比如访问ages[3])会导致未定义行为,可能程序崩溃或输出垃圾值。
数组的下标从0开始,这一点需要特别注意。大部分编程语言都是这样。
在C++中,数组名在大多数情况下会自动转换为指向第一个元素的指针。这意味着你可以用指针操作数组:
|int data[4] = {10, 20, 30, 40}; int *p = data; // p指向data[0] cout << *p << endl; // 输出10 cout << *(p + 2) << endl; // 输出30
你也可以用指针遍历数组:
|for (int *q = data; q != data + 4; ++q) { cout << *q << endl; }
数组和指针的这种关系让你可以用很多指针相关的算法处理数组,但也要小心指针越界。
C++的string类型很方便,但在和老代码或C库打交道时,经常会遇到C风格字符串。C风格字符串本质上就是一个以\0结尾的字符数组:
|char name[10] = "Tom"; // 实际内容是{'T','o','m','\0',...}
你可以像操作普通数组一样操作C字符串:
|cout << name[0] << endl; // 输出T name[1] = 'a'; cout << name << endl; // 输出Tam
C字符串的结尾必须有一个\0,否则很多字符串函数(如strlen、strcpy等)会出错。
在C++中,虽然我们更推荐用string类型,但在和C库或老代码打交道时,经常会用到C风格字符串(char数组)。C标准库为C字符串提供了一些常用函数,这些函数都在<cstring>头文件里。
strlen用来计算C字符串的实际长度(不包括结尾的\0):
|#include <cstring> char word[] = "hello"; cout << strlen(word) << endl; // 输出5
strcmp用来比较两个C字符串的内容。如果完全一样,返回0;如果第一个比第二个小,返回负数;大则返回正数。
|char a[] = "apple"; char b[] = "banana"; if (strcmp(a, b) < 0) { cout << a << "在" << b << "前面" << endl; }
strcat可以把一个C字符串拼接到另一个C字符串的后面。注意,目标数组要有足够空间,否则会出错。
|char s1[20] = "Hello, "; char s2[] = "world!"; strcat(s1, s2); cout << s1 << endl; // 输出 Hello, world!
strcpy可以把一个C字符串的内容复制到另一个数组里:
|char src[] = "C++"; char dest[10]; strcpy(dest, src); cout << dest << endl; // 输出 C++
这些函数都不会自动检查目标数组的大小,使用时要确保不会越界。
C++的string类型和C风格字符串(char数组)虽然都能表示文本,但它们是不同的类型。你不能直接用==比较string和char[],但可以用string的成员函数或转换来实现比较。
比如:
|string s = "hello"; char cstr[] = "hello"; if (s == cstr) { cout << "内容相同" << endl; }
这里C++会自动把char[]转换成string再比较。
如果你想用C风格的比较,也可以用strcmp:
|string s = "hi"; char cstr[] = "hi"; if (strcmp(s.c_str(), cstr) == 0) { cout << "内容一样" << endl; }
一般来说,推荐用string的==、!=等操作符,只有在和C接口交互时才用C风格的函数。
在C++中,多维数组其实就是“数组中的数组”。为什么我们需要多维数组呢?因为很多现实世界的数据都是多维的,比如一个表格、棋盘、像素矩阵等。假设我们有一个3行4列的二维数组:
|int grid[3][4];
如果你想给每个格子赋一个唯一的编号,可以用嵌套循环:
|int count = 0; for (int i = 0; i < 3; ++i) { for (int j = 0; j < 4; ++j) { grid[i][j] = count; ++count; } }
这样 grid[0][0] 是0,grid[2][3] 是11。
C++11 之后,你可以用“范围for”让代码更简洁:
|int count = 0; for (auto &row : grid) { for (auto &cell : row) { cell = count; ++count; } }
这里 row 是一行(int[4]),cell 是每个格子(int&)。如果你只想读元素,不想改,可以用 const auto&。
为什么外层要用引用?
如果你写成 for (auto row : grid),row 其实是一个指向行首元素的指针(int*),而不是一行数组本身。这样内层循环就会出错。所以遍历多维数组时,外层循环变量要用引用。
多维数组的本质是“数组的数组”,所以它的名字会自动转换成“指向一行的指针”。比如:
|int table[2][3] = {{1,2,3},{4,5,6}}; int (*p)[3] = table; // p指向一行(有3个int的数组) p = &table[1]; // p现在指向第二行
注意括号的位置很重要:int *p[3]是“3个int指针的数组”,而int (*p)[3]是“指向3个int的数组的指针”。
你可以用指针遍历多维数组:
|for (int (*row)[3] = table; row != table + 2; ++row) { for (int *col = *row; col != *row + 3; ++col) { cout << *col << ' '; } cout <<
这里 row 每次指向一行,col 遍历这一行的每个元素。
C++标准库的 begin 和 end 函数可以让你不用写具体类型:
|for (auto row = begin(table); row != end(table); ++row) { for (auto col = begin(*row); col != end(*row); ++col) { cout << *col << ' '; } cout << endl; }
这样写更安全,也更容易维护。
多维数组的指针类型很难写清楚。你可以用类型别名让代码更直观:
|using Row = int[3]; for (Row *row = table; row != table + 2; ++row) { for (int *col = *row; col != *row + 3; ++col) { cout << *col << ' ';
这里 Row 就是“有3个int的数组”,row 是指向一行的指针。
多维数组是C++中处理表格、矩阵等结构的基础。你可以用嵌套循环或范围for遍历元素,用指针灵活操作行和列,用类型别名让代码更清晰。理解这些技巧后,你就能轻松应对各种多维数据的处理需求。
现在让我们通过一些简单的练习题来巩固本章学到的知识。每道题都涵盖了数组、指针和字符串的核心概念,请先尝试独立完成,然后再查看答案。
编写一个程序,定义一个长度为5的int数组,依次赋值为1、2、3、4、5,然后用循环输出所有元素。
|#include <iostream> using namespace std; int main() { int arr[5] = {1, 2, 3, 4, 5}; // 定义并初始化数组 // 用循环输出所有元素 for (int i = 0; i < 5; i++) { cout
编写一个程序,定义一个char数组存储字符串"Hello",然后用strcat函数拼接上" World",最后输出完整的字符串。
|#include <iostream> #include <cstring> // 需要包含这个头文件来使用字符串函数 using namespace std; int main() { char str[20] = "Hello"; // 定义足够大的数组,初始化为"Hello" strcat(str, " World"); // 拼接字符串 cout << str << endl; // 输出:Hello World return 0; }
编写一个程序,定义一个int数组,然后用指针的方式遍历数组,将每个元素的值都加1,最后输出所有元素。
|#include <iostream> using namespace std; int main() { int arr[5] = {1, 2, 3, 4, 5}; int *p = arr; // 指针指向数组的第一个元素 // 用指针遍历数组并修改元素 for (int i = 0; i <
编写一个程序,定义一个2行3列的int二维数组,用嵌套循环给每个元素赋值为它的行号加列号,然后输出所有元素。
|#include <iostream> using namespace std; int main() { int arr[2][3]; // 定义2行3列的二维数组 // 用嵌套循环给每个元素赋值 for (int i = 0; i < 2; i++) { for (int j = 0; j < 3; j++
编写一个程序,读取用户输入的一个单词(C风格字符串),然后统计这个单词中有多少个大写字母,并输出统计结果。
|#include <iostream> #include <cstring> #include <cctype> // 用于 isupper() 函数 using namespace std; int main() { char word[100]; cout << "请输入一个单词:"; cin >> word; int count = 0; // 统计大写字母的个数 int len = strlen(word); // 获取字符串长度
size() 得到字符串长度:
|string name = "小明"; cout << name.size(); // 输出 2(中文每个字也是一个字符)
s[n] 访问第 n 个字符(下标从 0 开始):
|string code = "abc"; cout << code[1]; // 输出 b
+ 拼接字符串:
|string first = "Hello", second = "World"; string result = first + ", " + second + "!"; cout << result; // 输出 Hello, World!
== 或 != 比较字符串内容是否相同:
|string pwd = "1234"; if (pwd == "1234") cout << "密码正确";
<、> 等比较字符串的字典序:
|string a = "apple", b = "banana"; if (a < b) cout << a << "在" << b << "前面";
这道题展示了数组的定义、初始化和遍历。数组的下标从0开始,所以循环从0到4。我们也可以用范围for循环来简化代码:for (int x : arr) { cout << x << " "; }
使用C风格字符串时,需要确保目标数组有足够的空间来存储拼接后的字符串。strcat 函数会将第二个字符串追加到第一个字符串的末尾。
数组名可以当作指向第一个元素的指针使用。*(p + i) 等价于 arr[i],也等价于 p[i]。通过指针修改数组元素时,实际上修改的是原数组的内容。
二维数组需要两个下标来访问元素,第一个下标表示行,第二个下标表示列。使用嵌套循环可以方便地遍历和操作二维数组的所有元素。
这道题综合运用了C风格字符串、数组遍历和字符判断。isupper() 函数用于判断一个字符是否为大写字母,需要包含 <cctype> 头文件。我们也可以用 word[i] >= 'A' && word[i] <= 'Z' 来判断。