字符串
字符串的定义
字符串实质上和整数数组有些相似,只是数组中的每一个元素都是一个字符(实际上,都是存成对应 ASCII 码的数字),并且这个数组是从 $0$ 开始的。将这些字符存储下来,便组成了一串字符,也就是字面意义上的字符串,可以进行进一步操作。
回忆一下我们的第一节课。
cout << "Hello world!" << endl;我们在第一节课就学过的 Hello world! 就是一个字符串。在 C++ 中,使用双引号 " " 括起来的内容才可以是一个字符串。
我们可以使用 string 来定义一个字符串变量。例如:
string s;我们就成功定义了一个字符串 $s$。
与数组类似,我们同样可以使用中括号 [ ] 访问字符串中的字符。例如,我们可以使用 s[0] 访问字符串 $s$ 中下标为 $0$ 的字符。
与我们见到的其他数据类型的变量相同,可以直接使用 cin 和 cout 进行输入或输出。例如:
cin >> s;
cout << s << ' ' << s[1] << endl;我们就读入了字符串 $s$,并且输出了 $s$ 和 $s$ 中下标为 $1$ 的字符。
字符串的操作
假设我们有一个字符串 $s$,我们可以对 $s$ 进行如下操作:
- 获取长度,
s.size()或s.length():获取字符串的长度。注意这里的长度指的是字符串的字符个数,因此,最后一个字符应该是s[s.size() - 1]或s[s.length() - 1]。 寻找某字符串第一次出现的位置,
s.find(str, pos):在 $s$ 中查找字符串 $str$ 在 $pos$ 以及之后的位置中第一次出现的位置。- 其中 $pos$ 可省略,默认为 $0$。
- 如果 $s$ 中没有出现,则返回
s.npos。
截取子串,
s.substr(pos, len):返回字符串 $s$ 从 $pos$ 位置开始截取最多 $len$ 个字符组成的字符串(如果从 $pos$ 开始的后缀(即 $pos$ 开始后面的部分)长度不足 $len$ 则截取这个后缀)。- $len$ 可省略,默认返回从 $pos$ 开始的后缀。
- 需要注意的是,
substr()返回的内容为原始字符串的子字符串,不会改变原始字符串。
- 插入字符串,
s.insert(pos, str):在字符串 $s$ 的 $pos$ 处插入字符串 $str$。 - 插入字符,
s.insert(pos, count, ch):在字符串 $s$ 的 $pos$ 处连续 $count$ 次插入字符 $ch$。 删除字符串,
s.erase(pos, count):将字符串 $s$ 从 $pos$ 位置开始的 $count$ 个字符删除。- $count$ 可省略,默认删除 $pos$ 开始的后缀。
- 替换字符串,
s.replace(pos, count, str):表示将字符串 $s$ 从 $pos$ 位置开始的 $count$ 个字符替换为字符串 $str$。
C 风格字符串
如上文所述,字符串其实就是存放着字符的数组。因此,我们也可以使用 char 数组存储字符串,这被称为 C 风格字符串(C-style string)。例如,我们可以使用 char c[100] 创建一个最大长度为 99 的字符串。
同样地,我们可以直接使用 cin 和 cout 进行输入输出。
char c[100];
cin >> c;
cout << c << endl;C 风格字符串有如下常用操作:
- 获取长度,
strlen(c):获取 C 风格字符串 $c$ 的长度。注意这里的长度指的是字符串的字符个数。
当然,还有一些暂时用不到的操作。值得注意的是,C 风格字符串操作都需要引入 cstring 头文件。
当然,C 风格字符串可以转化为 string。对于一个 C 风格字符串 $c$,直接复制给 string 变量即可。例如:
char c[100];
string s;
s = c;结构体
结构体,可以看做是自定义的数据类型,是一系列称为成员元素的组合体。
定义结构体
观察下面一段代码。
struct Object {
int weight, value;
string name;
} e[array_length];
Object b, B[array_length], tmp;上例中定义了一个名为 Object 的结构体,两个 int 类型的成员元素 $value$、$weight$ 和一个 string 类型的成员元素 $name$。array_length 代表数组长度。
在 } 后,定义了数据类型为 Object 的数组 $e$、变量 $b$、数组 $B$、变量 $tmp$。这也说明,我们既可以在 } 后直接定义这个数据类型的变量,也可以像其他数据类型一样,输入数据类型名称,后面接上变量名。
对于某种已经存在的类型,都可以使用这里的方法进行定义常量、变量、指针、数组等。
访问成员元素
我们可以使用 变量名.成员元素名 进行访问。例如:
struct Object {
int weight, value;
string name;
} e[114514];
Object b, B[114514], tmp;
b.weight = 1;
e[1].name = "1919810";
cout << b.name << ' ' << e[1].name << endl;为什么需要结构体
首先,条条大路通罗马,当然可以不使用结构体达到相同的效果。但是结构体能够显式地将成员元素(在算法竞赛中通常是变量)捆绑在一起,如上面代码中的 Object 结构体,便将 $value$、$weight$、$name$ 放在了一起(定义这个结构体的实际意义是表示一件物品的重量、价值与名称)。这样的好处边是限制了成员元素的使用。
想象一下,如果不使用结构体而且有两个数组 value[]、Value[],很容易写混淆。但如果使用结构体,能够减轻出现使用变量错误的几率。
并且不同的结构体(结构体类型,如 Object 这个结构体)或者不同的结构体变量(结构体的实例,如上方的 e 数组)可以拥有相同名字的成员元素(如 tmp.value 与 b.value),同名的成员元素相互独立(拥有独自的内存,比如说修改 tmp.value 不会影响 b.value 的值)。
这样的好处是可以使用尽可能相同或者相近的变量去描述一个物品。比如说 Object 里有 value 这个成员变量;我们还可以定义一个 Car 结构体,同时也拥有 value 这个成员;如果不使用结构体,或许我们就需要定义 valueOfObject[]、valueOfCar[] 等不同名称的数组来区分。