C/C++字符串处理常用技巧

前言

由于在做题时,经常纠结到底该使用C还是C风格的字符串,而且对读入方式也比较纠结,所以写一篇blog来总结一下C/C中字符串处理的技巧。

C语言

与其说C语言的字符串是字符串,不如说是字符数组,C语言中对字符串的操作也大多依托于指针。

声明字符串

1
2
3
4
[...]
char str1[1001]; //声明一个字符数组
char str2[1001]="Hello, World!" //为其赋初值(此方法会自动添加结束符)
[...]

当然,由于C语言字符串本质上是字符串,也可以使用逐个字符赋值的方法,需要注意的是别忘记在行末加上结束符。

读入字符串

1. scanf

1
scanf("%s",str);

参数为想要读入的字符串的初地址,如果希望字符串下标从1开始,可以将参数改为str+1

使用该方式读入的特点:

  • 遇到空格、回车会自动停止,并将其遗留在缓冲区中
  • 自动为字符串添加结束符’\0’

scanf用于读取单个字符时需要特别注意

若想要使用循环来为字符串的各个字符赋值,由于缓冲区中不符合条件的字符会被scanf丢弃,所以不需要添加占位符,直接处理即可。但是,读取单个字符的情况并不会像读取字符串一样抛弃空格和换行,所以有如下代码的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
char str1[101];
int main()
{

for(int i=0;i<5;i++)
{
scanf("%c",&str1[i]);
}
printf("%s",str1);
}
/*input:
ab ef<回车>
output:
ab ef
*/

总而言之,当使用%s读取时,scanf会忽略回车和空格;使用%c读取时,不会忽略回车和空格,且要考虑scanf的占位符和缓冲区,所以用scanf读取单个字符时常常需要使用空格作为占位符

2.getchar

getchar的特点是不会抛弃如何字符,而是直接将缓冲区中的内容读入目标地址中。可以利用getchar的这一特点来清楚缓冲区中的换行。

3.gets

gets与scanf有如下不同的地方:

  • gets会直接读取一整行,不会丢弃空格
  • gets会将行末的回车符从缓冲区中取出并丢弃(而在结尾补上结束符),所以gets读完之后并不需要getchar来清理缓冲区。

4.fgets

函数原型:

1
char *fgets(char *s, int size, FILE *stream);

其中size是读取的最大长度(不过通常不会填满,需要留出一位给结束符),当读取长度大于size时,多出来的长度会被截取;读取长度小于size时,fegts会将缓冲区中的换行符读入字符串(与gets不同),接下来的一个位置用结束符来填充。

第三个参数是文件指针,若从键盘读入,则用使用常数stdin

输出字符串

printf

正常输出,遇到停止符则不再输出,且不会自动添加换行符,当需要换行时,需要手动添加’\n’。

puts

输出完字符串后会自动再输出一个换行符

处理字符串

1. strlen

1
size_t strlen(const char* str);

计算字符串长度,遇到第一个停止符才算结束,若字符串不是以’\0’结束则会不停找下去。

2.1 strcat

1
char* strcat(char* s1, char* s2);

strcat() 将把 arrayName2 连接到 arrayName1 后面,并删除原来 arrayName1 最后的结束标志’\0’

2.2 strncat

1
char* strncat(char* s1, char* s2,size_t count);

与上述函数类似,但限制了拼接字符的个数。

3.1 strcpy

1
char *strcpy(char* dest,char* source);

将字符串source复制到字符串dest中,并覆盖原始字符串,可以用来为字符串变量赋值。从首元素开始,遇到\0结束。

3.2 strncpy

1
char *strncpy(char* dest,char* source,size_t count)

将source字符串中前count个字符复制到dest中,并覆盖原始字符串。与strcpy不同的是,该函数只会更改前count个字符。

4.1 strcmp

按字典序比较字符串。

返回值为正数(未必为1),负数或0。

4.2 strncmp

按字典序比较前n个字符。

4.3 stricmp

按字典序(但忽略大小写)比较前n个字符。

5.1 strchr

1
char* strchr(const char* str,int _val);

返回指定字符在字符串中首次出现的地址,若想要利用此函数获取索引,则需要减去字符串的首地址,未找到则返回NULL。

5.2 strrchar

与上一个函数类似,但是从字符串尾部开始寻找。

5.3 strstr

1
char* strstr(const char* str1,const char* str2);

在字符串str1中查找字符串str2的位置,若找到,则返回str2第一个字符在str1中的位置的指针,若没找到,返回NULL

6. atoi,atof,atol

分别是字符串转整型、浮点型、长整型。

遇到正负号或数字则开始转化,遇到非数字字符则停止。

7. strtok

1
char *strtok(char *str, const char *delim);

类似python中的split方法,但是该函数将str中包含的所有delim子串转化成’\0’

8.sprintf

1
int sprintf(char *str, const char *format, ...);

将format中(类似printf格式)的数据以字符串形式存到str中,该方法转化的字符串与使用printf的结果是一样的,只是不在屏幕上输出而已。尝尝用于想要将整型变量转化成字符串的情形。

9.sscanf

1
int sscanf(const char *str, const char *format, ...);

与sprintf相同但是方向相反,是将字符串中的数据读入到后续format代表的数据中。

C++

C在某种意义上可以看成C语言的扩展,所以上文提到的C语言风格字符串处理方式,在C中也可以用。另一方面,C++在C语言的基础上增加了类、流的概念,因此有了更多的操作空间。

声明字符串

1
2
3
4
5
6
string s;//声明一个string对象,可以进行‘+’操作
string s1(); // s1 = ""
string s2("Hello"); // s2 = "Hello"
string s3(4, 'K'); // s3 = "KKKK"
string s4("12345", 1, 3); //s4 = "234",即 "12345" 的从下标 1 开始,长度为 3 的子串
s.assign(str); //形参可以是string对象或者C字符串

读入字符串

cin

c++中可以用cin来为字符数组和string对象读取数据,cin的特性与scanf十分相似,都是遇到空格、换行停止,且会将最后的换行符留在缓冲区中。

不同的是,scanf不可以用于读入string对象。

cin.get

该函数有三种用法:

1
2
3
cin.get(ch); //读入单个字符,效果和getchar类似
cin.get(str,len); //读入字符串,遇到换行符结束,并在结尾补'\0',不会将换行符从流中去掉
cin.get(str,len,ch) //读入字符串,遇到ch结束,并在结尾补'\0',不会将换行从流中去掉

cin.getline

该函数有两个原型:

1
2
cin.getline(char* str,int n);
cin.getline(char* str,int n,int char);

这两个函数与上文的cin.get看似相似,实则会从流中获得空格并丢弃。

输出字符串

这部分没什么好说的,似乎就比C多了一个cout。

处理字符串

这一部分主要介绍C++中string对象的成员函数,其中大部分成员函数的参数可以是string对象也可以是C的字符串指针。

1. length

用于获取该字符串的长度。

2. append

append方法对string对象进行原地修改。

1
2
3
4
5
string s1("123"), s2("abc");
s1.append(s2); // s1 = "123abc"
s1.append(s2, 1, 2); // s1 = "123abcbc",添加从下标1开始,长度为2的子串
s1.append(3, 'K'); // s1 = "123abcbcKKK"
s1.append("ABCDE", 2, 3); // s1 = "123abcbcKKKCDE",添加 "ABCDE" 的子串(2, 3)

3.substr

1
2
3
string s1 = "this is ok";
string s2 = s1.substr(2, 4); // s2 = "is i",截取长度为4的子串
s2 = s1.substr(2); // s2 = "is is ok",未指定第二个长度则直接截取到尾部。

4. erase

与append一样,对string对象进行原地修改。

1
2
3
string s1("Real Steel");
s1.erase(1, 3); //删除子串(1, 3),此后 s1 = "R Steel"
s1.erase(5); //删除下标5及其后面的所有字符,此后 s1 = "R Ste"

5.insert

同样是原地修改

1
2
3
4
string s1("Limitless"), s2("00");
s1.insert(2, "123"); //在下标 2 处插入字符串"123",s1 = "Li123mitless"
s1.insert(3, s2); //在下标 2 处插入 s2 , s1 = "Li10023mitless"
s1.insert(3, 5, 'X'); //在下标 3 处插入 5 个 'X',s1 = "Li1XXXXX0023mitless"

6.compare

与C语言中strcmp的效果类似。

7.c_str

返回string对象的C字符串数组指针。

8. find与rfind

1
2
s1.find(s2,pos); //查找字符串s2第一次出现的位置
s1.find(ch,pos); //查找字符ch第一次出现的位置

其中pos是起始的搜索位置,若未指定,则从头开始。

结语

C++和C语言风格的字符串有一些类似的地方,函数调用方法有很大不同,做题时特别需要注意的是读入字符串时回车是否会被取出并丢弃,否则会引发许多问题。

That’s all!


C/C++字符串处理常用技巧
http://zhouhf.top/2022/04/16/C-C-字符串处理常用技巧/
作者
周洪锋
发布于
2022年4月16日
许可协议