前面我们学习了 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 只会得到 pizzagetline 可以读取一整行,包括空格:
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; // 输出TamC字符串的结尾必须有一个\0,否则很多字符串函数(如strlen、strcpy等)会出错。
在C++中,虽然我们更推荐用string类型,但在和C库或老代码打交道时,经常会用到C风格字符串(char数组)。C标准库为C字符串提供了一些常用函数,这些函数都在<cstring>头文件里。
strlen用来计算C字符串的实际长度(不包括结尾的\0):
#include <cstring>
char word[] = "hello";
cout << strlen(word) << endl; // 输出5strcmp用来比较两个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' 来判断。