字符串

字符数组

用来存放字符变量的数组称为字符数组。

字符串常量

最后一个字符是结束符的字符数组,称为字符串,通常是被双引号引起来的,例如:"abc",其实这个字符串共包含了 4 个字符,分别是 'a', 'b', 'c', '\0',最后一个是 ASCII 码值为 0 的字符,即结束符

这种用双引号引起来的字符串,称为字符串常量,它的内容是不允许被修改的

结束符

  • ASCII 码值为 0 的字符('\0'0),称为 结束符,表示字符串内容到此结束;
  • 注意,不是 '0''0'ASCII 码值为 48;

为什么需要结束符?

为了方便字符串的输出与遍历

如果没有结束符,我们只有像输出整数数组元素那样,用一个 for 循环从字符串的第 0 个字符开始,逐个输出,直到最后一个字符。这样很麻烦,字符串是一个频繁使用的数据类型,我们需要有一个更高效的实现方式。

通过在字符数组中最后一个字符后加上结束符,我们可以将整个字符串变量(或字符数组名)作为一个变量输出。系统会从给定的某个地址开始,逐个字符输出,直到遇到结束符停止

注意:如果用 printf 语句输出字符串,占位符需要使用 %s

例:

char ch[5] = {'a', 'b', '\0'}, s[5] = "ab";//两种赋初值的方式是一样的效果
printf("%s %s", ch, s);//或者 cout << ch << " " << s;

字符串常量

就像数值常量(如 5, 100)一样,字符串常量就是直接写出其内容的字符串,如 "hello",它用一对双引号将若干个字符引起来,其内容是不允许修改的。如果想要实现修改的效果,就需要将其内容直接赋值给一个字符数组。如:

char s[5] = "abc";

注意:双引号内会在最后自动添加一个结束符,因此 "abc" 实际占用 4 个字节。

字符串的输入

  1. 之前提到,数组名变量即为数组的首地址
  2. 输入字符串可以使用 %s,不必像普通数组那样逐个字符输入,而是直接从第一个字符开始,直至空白符结束,中间的部分全部存入字符数组中
char s[30];
scanf("%s", s);//s已经是数组的首地址了,不需要再加取地址符

空白符

空格、tab(制表符)、换行('\n'或endl),当使用 cinscanf 语句输入字符串内容时,遇到空白符就会停止。例子如下:

char s[30];
cin >> s;//在控制台输入“Bili John”,中间也可以是 tab 或 换行
cout << s;//实际输出内容:Bili

假如我们已经声明了一个字符数组变量,char s[30];

输入不含空白符的字符串

  1. scanf("%s", s);//使用 %s 输入整个字符串,后面 s 即为数组的首地址,不需要加取地址符 &

  2. cin >> s;

  3. 在有些时候,因为题目的原因,我们希望字符串也从下标 \(1\) 开始输入,可以使用如下方式:

  • scanf("%s", s+1);

  • cin >> s+1;

那么对应的输出就也应该加上 \(1\)

如何输入中间带空格或 Tab 的字符串?

  1. gets(s); 该方法已经被淘汰,因为它不安全,很多场合禁止使用

  2. fgets(s, 30, stdin); 有个缺点,如果输入字符串后带有回车,则会把换行符也存入

  3. cin.getline(s, 30); 该方法不会包含最后的换行符,是目前最好用的方法。使用中要注意,如果之前上一行已经有输入,且不是用 cin.getline() 输入的内容,那么第一次调用 cin.getline(),会读入上一行末的空换行,需要再执行一次 cin.getline() 方可。

  4. scanf("%[^\n]", s); 输入一个字符串,直到遇到 \n 时结束。其中 [^\n] 是一个正则表达式,即一个给定输入规则的表达式,这里的规则即为:遇到 ^ 后面的字符就停止输入。

#include <iostream>
using namespace std;
const int N = 107;
char s[N];
int main() {
	//使cin,cout提升10倍速度,但还是比scanf,printf略慢
	//加上这句,就绝不允许cin,cout和scanf,printf混用了
	ios::sync_with_stdio(false);
	int n;
	//一旦使用cin.getline(),最好全用cin,cout
	cin >> n;
	cin.getline(s, N);//得到50后面的回车
	cin.getline(s, N);//得到Bili John
	cout << s;
	return 0;
}
// 50
// Bili John
  1. 如果在 C++ 中使用 string s;,那么我们也可以使用 getline(cin, s); 来输入一行字符串。

如何获取字符串的长度(不包含结束符)

  • 假设输入从字符串的第0个位置开始的,scanf("%s", str);
  1. 通过遍历字符串获得
int len = 0;//输入时从第0个字符输入
while (str[len]) ++len;//当str[len]不为结束符时(即ASCII不为0),++len
  1. 通过strlen函数获得
#include <cstring>//C语言中为 <string.h>
int len = strlen(str);

由此我们可以得到,一个从 s[0] 开始录入的字符串 s最后一个字符 s[len-1];

  • 假设输入从字符串的第一个位置开始的,scanf("%s", str+1);
  1. 通过遍历字符串获得
int len = 1;//如果输入时从第一个字符输入的,则初始值为1
while (str[len]) ++len;//当str[len]不为结束符时(即ASCII不为0),++len
--len;
  1. 通过strlen函数获得
#include <cstring>//C语言中为 <string.h>
int len = strlen(str+1);

由此我们可以得到,一个从 s[1] 开始录入的字符串 s+1最后一个字符 s[len];

字符串的遍历

  • 方法一,先通过 strlen(s) 求出字符串的长度,然后使用 for 循环遍历;
int len = strlen(s);
for (int i = 0; i < len; ++i) ...
  • 方法二,利用字符串的最后一个字符就是结束符的特性,直接使用 for 循环进行遍历
for (int i = 0; s[i]; ++i) ...

这种方式效率更高一些,大部分时间我们都使用这种方式。

另外,初学者经常喜欢将 strlen(s) 放在 for 循环中使用:

for (int i = 0; i < strlen(s); ++i) ...不建议这样写

这样写效率很低,因为程序不会记住字符串的长度,每次执行 i < strlen(s) 语句时,都会重新执行 strlen(s) 将字符串再数一遍,浪费了时间。

printf 的字符串输出格式:用来输出一个串,有几种用法:

  • %s:例如:printf("%s", "CHINA") 输出 "CHINA" 字符串(不包括双引号)。
  • %ms:输出的字符串占 m 列,如字符串本身长度大于 m,则突破 m 的限制,将字符串全部输出;若串长小于 m,则左补空格。
  • %-ms:如果串长小于 m,则在 m 列范围内,字符串向左靠,右补空格。
  • %m.ns:输出占 m 列,但只取字符串中左端 n 个字符。这 n 个字符输出在 m 列的右侧,左补空格。
  • %-m.ns:其中 m、n 含义同上,n 个字符输出在 m 列范围的左侧,右补空格。如果 n>m,则自动取 n 值,即保证 n 个字符正常输出。

常见的字符及字符串的输入输出函数

  • getchar(): 从键盘获取一个字符并返回;
  • putchar(c): 输出一个字符c;
  • gets(s): (已淘汰)从键盘获取一行字符串,输入到地址 s 开始的一段空间;
  • puts(s): 从地址 s 开始的地方输出一个字符串并换行
  • sscanf(s, "%d...", &a, ...): 将 s 作为格式化输入的内容;
  • sprintf(s, "%d...", a, ...): 将输出内容存入字符串 s 中。

#include <cctype>

  • isdigit(c): 判断字符 c 是否是数字;
  • isalpha(c): 判断字符 c 是否是英文字母;
  • isalnum(c): 判断字符 c 是否是英文字母或数字;
  • islower(c): 判断字符 c 是否是小写字母;
  • isupper(c): 判断字符 c 是否是大写字母;
  • tolower(c): 返回字符 c 对应的小写字母;若 c 不是字母,返回 c 本身;
  • toupper(c): 返回字符 c 对应的大写字母;若 c 不是字母,返回 c 本身。

#include <cstring>

char s[37];

  • strlen(s):求 s 的长度;
  • strcpy(s, "hello"):将 "hello" 拷贝到 s 中;
  • strcat(s, "hello"):将 "hello" 添加到 s 的末尾;
  • strcmp(s, "hello"):比较 s 和 "hello" 的字典序,如果 s 的字典序在 "hello" 前,返回值小于 0;如果在 "hello" 后,返回值大于 0;如果 s 就是 "hello",返回值等于 0;
  • strncmp(s, "hello", n): 比较 s 和 "hello" 的前 n 个字符。
  • strcasecmp(s, "hello"):是 strcmp 的忽略大小写的版本。
  • strchr(s, ' '):找到 s 位置开始后第一次出现空格的地址。
  • strncpy(s1, s, n):将 s 开始的连续 n 个字符复制到 s1。
  • strtok(s, ","):将字符串s开头第一个","前的部分拷贝出来,并返回该子串拷贝的首地址。
  • strstr(s1, s2): 若 s2 包含于 s1 中,则返回其在 s1 中的首地址,否则返回 NULL
// 字符串子串查找的暴力方法
int search(char s[], char a[]) {
    for (int i = 0, j = 0; s[i]; ++i, j = 0) {
        // 如果两个字符串都没有结束,且对应位置相等,就继续比对下一个位置
        int k = i;
        while (s[k] && a[j] && s[k]==a[j]) ++k, ++j;
        // a字符串已经比较完毕,返回其在s中出现的首个位置
        if (!a[j]) return k - j;
    }
    return -1;
}

// 替换子串的暴力方法,将sa开始的长m的字符串替换为b
void substitute(char s[], int sa, int m, char b[]) {
    char ans[N];
    memset(ans, 0, sizeof ans);
    int j = 0;
    for (int i = 0; i < sa; ++i) ans[j++] = s[i];
    for (int i = 0; b[i]; ++i) ans[j++] = b[i];
    for (int i = sa+m; s[i]; ++i) ans[j++] = s[i];
    memcpy(s, ans, sizeof ans);
}

二维字符数组逐字符输入的方法

for (int i = 1; i <= n; ++i)
	for (int j = 1; j <= m; ++j)
		scanf(" %c", s[i]+j); //在 %c 前加一个空格,该方法可以主动屏蔽掉换行及空格

string 类型

C++ 的 STL(标准模版库) 中自带的一种字符串类型

string a; 可以声明一个字符串变量 a,它可以直接被赋值为一个字符串,例如:a = "hello";

  1. 输入和输出

    由于它是C++中的一个类,所以C语言的scanfprintf 语句都不能识别它,只能使用 cincout 进行输入输出。

  2. 成员函数

    它不是基础数据类型,而是一个类,这是C++面向对象的设计方法,所谓一个对象,就是不仅拥有自己的数据值,还自己能“做事”的一种数据类型,它们通过自带的成员函数来完成一些和自己相关的工作,例如:“修改自己的值”,“查找自己的值”,“返回自己某一段”等等。
    我们通过在变量后面加点的方式来调用变量自己的成员函数,例如:s.size() 就是返回 string 变量 s 中字符的个数(即长度)。

#include <string>

using namespace std;

string s;

注意:

  1. s 的长度根据所存字符个数自动确定,可以对字符串整体引用

  2. stringC++ 的内容,只能通过 cingetline() 输入,需要 #include <iostream>

cin >> s; //读入一个字符串,遇到空白符结束。
getline(cin, s); // 读入一整行,遇到换行符时结束
string a[100]; // 定义了一个可以存放 100 个字符串的字符串数组
  • s.size():求 s 的长度;也可以写作 s.length()
  • s = "hello":将 "hello" 拷贝到 s 中;
  • s += "hello":将 "hello" 添加到 s 的末尾;
  • s < "hello":比较 s 和 "hello" 的字典序,s 的字典序是否在 "hello" 前;
  • s > "hello":比较 s 和 "hello" 的字典序,s 的字典序是否在 "hello" 后;
  • s == "hello": 比较 s 和 "hello" 的字典序,s 是否与 "hello" 相等;
  • s.find(t):在字符串 s 中查找子串 t,如果子串 t 出现,则返回 t 第一次出现的位置(从 0 开始),否则返回 -1;(需要注意的是:返回的变量是 unsigned 类型,需要转成 int 才能得到 -1)
  • s.find(t, x):在 s 的第 x 位及之后查找 t,返回值同 s.find(t)
  • s.erase(x, y):删除 s 的第 x 位及之后的 y 个字符;
  • s.substr(x, len):生成字符串 s 中从下标 x 开始的长为 len 的子串。
  • s.replace(x, len, t):将 s 的第 x 位及之后的 len 位替换为字符串 t,下标从 0 开始;注意:x ~ x+len-1 一定要是合法的下标!
  • s.insert(x, t):在 s 的第 x 位上插入字符串 t,下标从 0 开始;注意 x 一定要在合法范围内!
  • s.append(t):在字符串 s 后面加上一个字符串 t;
  • s.begin():返回 s 的首地址,即 &s[0] 或 s;
  • s.end():返回 s 最后结束符的地址;
  • stoi(s),stol(s),stoll(s): 返回 s 构成带符号整数
  • stoul(s),stoull(s): 返回 s 构成的无符号整数
  • stof(s), stod(s),stold(s): 返回 s 构成的浮点数
  • to_string(a): 将整数或浮点数转为 string 类型

C++输入原理

  1. 关于输入缓冲区

程序的输入都有一个缓冲区,即输入缓冲区。一次输入过程是这样的,当一次键盘输入结束时会将输入的数据存入输入缓冲区,而 cin 对象直接从输入缓冲区中取数据。正因为 cin 对象是直接从缓冲区取数据的,所以有时候当缓冲区中有残留数据时,cin 对象会直接取得这些残留数据而不会请求键盘输入;当 cin>> 从缓冲区中读取数据时,若缓冲区中第一个字符是空格、tab 或换行这些分隔符时,cin>> 会将其忽略并清除,继续读取下一个字符,若缓冲区为空,则继续等待。但是如果 cin 读取成功,字符后面的分隔符是残留在缓冲区的,cin>> 不做处理。

当我们从键盘输入字符串的时候需要敲一下回车键才能够将这个字符串送入到缓冲区中,那么敲入的这个回车键(\r)会被转换为一个换行符(\n),这个换行符也会被存储在 cin 的缓冲区中并且被当成一个字符来计算。比如我们在键盘上敲下了 123456 这个字符串,然后敲一下回车键(\r)将这个字符串送入了缓冲区中,那么此时缓冲区中的字节个数是 7 ,而不是 6。

  1. 比较几个输入函数

(下面所说的分割符由 enterspacetab 键入;换行符指由 enter 键入,在缓冲区中为 '\n'。)

(1)cin

对输入缓冲区中的第一个换行符忽略清除,继续阻塞等待缓冲区有效数据的到来,遇到分割符时结束读取。读取成功后,字符后的分隔符残留在缓冲区。

(2)getline()

从输入缓冲区读取字符串时不忽略分隔符,直接读取,默认遇到换行符时终止,送入目标 string 后(换行符一并读入 string),再将字符串的换行符替换为空字符 '\0'。换行符不残留在输入缓冲区中,不会影响下面的输入处理。 因此,进行从键盘读取一行字符时,建议使用 getline,较为安全。

(3)cin.get()

从输入缓冲区读取单个字符时不忽略分隔符,直接读取,默认遇到换行符时终止。不处理换行符,换行符仍然残留在输入缓冲区。 效果同 getchar()

(4)getchar()

键盘输入的字符都存到缓冲区内,一旦键入回车,getchar 就进入缓冲区读取字符,一次只返回第一个字符作为 getchar 函数的值,如果有循环或足够多的 getchar 语句,就会依次读出缓冲区内的所有字符直到 '\n''\n'也将被读出)。

之所以你输入的一系列字符被依次读出来,是因为循环的作用使得反复利用 getchar 在缓冲区里读取字符,而不是 getchar 可以读取多个字符,事实上 getchar 每次只能读取一个字符。如果需要取消 '\n' 的影响,可以用 getchar 来清除,即从缓冲区读走一个字符。

  1. ios::sync_with_stdio(false) 的作用

作用是取消缓冲区同步,例如 printf()/scanf() 是 C 函数,而 cin/cout 是 C++ 函数,这些函数需要用到各自的缓冲区,为了防止各自的缓冲区错位,C++ 默认将 C 函数和 C++ 函数的缓冲区同步。 它的原理是使本该同步的输入输出流分开,就是让 C 的输入输出流和 C++ 的输入输出流分开。

posted @ 2024-03-30 16:09  飞花阁  阅读(934)  评论(0)    收藏  举报
//雪花飘落效果