字符与字符串的读入,格式化输入输出

发布时间:2024年01月14日

字符串读入

参考:
gets和gets_s,fgets
getline


getchar

int getchar(void);

一次读入一个字符并作为返回值返回。因为返回的实际是个ascii码,所以只要是ascii码表里有的字符都能读。包括空格,换行 ‘\n’,Tab '\t’等分隔符

getchar不要拿来读入换行符来进行行末的判断。因为表示一行终止的符号不止有\n(换行),还有\r(回车),这是个历史遗留问题
不同的操作系统对换行字符的规定不太一样,所以循环getchar然后判断’\n’停止可能导致不停读入导致爆字符数组,然后RE(或者其他奇奇怪怪的错误)。


gets gets_s fgets

char *gets(char *str);

可以读入一行字符串,到换行符和文件末尾(EOF)为止,读入换行符,自动在字符末尾后加入\0,读入成功会返回首个字符首地址str,否则返回null

因为安全性不高,C11之后被移除,洛谷的编译器版本C++14是不支持的(提交前可以用洛谷提供的IDE运行一遍,可以排除兼容性问题)

char *gets_s(char *str,int count)

C11后用于代替gets,功能和用法与gets相同,不过到达count-1长度就会直接结束读入(补上\0正好占用count个空间)

char *fgets(char *str,int count,FILE *stdin) 

作用与gets_s相似,可以选择输入文件流,如果还是标准输入流就写stdin就行了


getline

istream& getline (char* s, streamsize n, char delim='\n')

delim可以省略,默认是’\n’(也就是默认遇到’\n’就结束读入),会读入delim字符但是不会放进字符数组里(也就是会把光标移动到delim字符后面,而不是停留在这上面,下次读入从delim下一个字符开始读取),n表示读入的最大的字符串长度,包括字符串结束标识符\0。

这个getline作为istream对象的一个成员函数,调用需要有明确的对象来调用,比如:

cin.getline(ch,100);

istream& getline (istream& is, string& str, char delim='\n')

和上面的getline差不多,delim可以省略,默认遇到’\n’就结束读入。这里是cstring里对上面getline函数进行了重载(好像也不能算重载),这里的getline作为istream的一个友元函数,不需要使用具体的对象来调用。但是参数表要求提供一个istream&类型的参数(标准输入流给cin就行了),比如:

getline(cin,s)


scanf 和 cin

scanf("%c",&ch)

和getchar差不多,一次一个字符,读入分隔符比如空格和换行,读到的字符直接赋值给字符ch。scanf本身有返回值,如果读入成功就返回读入成功的个数,失败时返回-1。

但是! 如果你往scanf的格式字符串(就是引号中的字符串)里加入空格,它就会变成一个通配符一样的东西,会“吃掉”0个或多个连续的分隔符。比如:
在这里插入图片描述
第一个scanf的格式串读入a和b中间有个空格,但是它实际上可以吃掉多个空格(tab和换行符等分隔符也是可以的),其他的字符却没有这样的效果,只会跳过单个这样的字符,而且如果实际读入过程中如果这里没有给出这个字符就会出错。如果读入单个字符时光标正好指向分隔符的话,还是会读入分隔符的。
在这里插入图片描述

scanf("%s",ch)

ch是一个字符数组的首地址,它会连续读入字符串直到读到分隔符。有几个细节:

  1. 如果一开始光标就在分隔符,它会不断后跳,直到找到第一个非分隔符的字符再开始读入
  2. 读到分隔符后,光标会停留在分隔符上,不会跳过也不会读入字符数组

另外scanf也可以利用c_str()实现对string的读入,不过十分不建议这样用。
个人猜测:string存储字符串也是使用的字符数组,string开辟一块可变长度的连续空间(类似于vector<char>),本身保存字符数组的首地址来进行访问和修改。string是一种对象,char* 是一个字符串指针,编译器不会把它们当作一种东西,所以参数表里两者不互通,不能混用。

不过string里提供了一个直接访问字符数组的办法(函数),也就是c_str(),在printf中我们可以用它来进行输出,一些需要传递char*的场合也可以通过c_str()来传递string。但是!这样是有危险的

因为传递走char*后,函数所作的所有操作都是直接针对这一块空间上的,string完全不知道发生了什么,就有可能发生一些错误,比如我们使用scanf来向string里读入,就有可能喜提CE,RE,WA全家桶。举个例子:
在这里插入图片描述
在这里插入图片描述

string对象s 一开始没有分配空间,直接读入导致数据丢失。分配了一些空间后,读入时直接从字符首地址开始覆盖,然后末尾加\0,但是string还是按照自己认为的字符串原本的长度进行输出。如果你读入的字符长度远大于string分配的空间,在触及到一些非法内存时,就会RE。因此不建议使用scanf向string读入。

cin>>ch

ch可以是一个 字符数组的首地址 或 字符,也就是char* 和char&的区别,两者的细节基本一致,即:

  1. 如果一开始光标就在分隔符,它会不断后跳,直到找到第一个非分隔符的字符再开始读入
  2. 对字符数组,读到分隔符后,光标会停留在分隔符上,不会跳过也不会读入字符数组
  3. 对单个字符,读入首个非分隔符的字符后光标后移一位

可以看到cin读入单个字符的时候和scanf是不一样的,最主要的区别是cin不会读入分隔符,而scanf会,所以cin的这个机制有时候就会比较有用

比如给了你一个01串,你就是想一个字符一个字符读入,那你就用cin,而用scanf会比较麻烦。


简单提一嘴int sscanf(const char *str, const char *format, ...) 这个。
它是从字符串中去读取,作用和scanf几乎一致,只不过是从char* str读取而不是标准输入流,ssprintf同理,是向字符数组中输出。
其实sstream里的istreamstring和ostreamstring实现的功能也和它们差不多,想了解的可以自行查阅资料,这里不多赘述。


总结:

读入一整行一般用getline,读入单个字符或者一个词块就用cin,没了。
其实我个人不太建议使用那三种gets,理由如下:

  1. geline够用了,它既有针对字符数组的成员函数也有针对string的重载
  2. 不如getline通用。有些编译器没有,而且gets和gets_s时间上也不是一直都存在的
  3. 三种gets会读入换行符并储存,但是一般实际使用时都不需要。getline会跳过换行符

因为字符数组实际上传递的都是首地址指针,所以不一定只能写数组名,还能这样:cin>>ch+1
表示从ch[1]开始存放字符串。


scanf 和 printf 的 格式说明字符串

参考:
菜鸟教程scanf
scanf(个人推荐)
菜鸟教程printf(个人推荐)


scanf(和sscanf)的格式说明字符串:

scanf格式说明符一般形式为:“%[*] [输入数据宽度] [长度] 类型”

方括号包括的选项为可选项。可以不加。

类型:

  1. 整数:
    1. d表示十进制有符号整数(decimal) u表示十进制无符号整数(unsigned)
    2. o表示八进制整数(octal)
    3. x表示十六进制整数(hexadecimal)
  2. 浮点数:
    1. f表示浮点数形式(float) e表示指数形式科学计数法 (exponent)
  3. 字符:
    1. c表示单个字符(char)
    2. s表示字符串(string)

长度:

  1. l表示long
  2. h表示short

比如:
ld表示long int
llu表示unsigned long long int(也就是unsigned long long)
lf表示double(双精度浮点型,long float的感觉)

宽度:

用十进制整数指定输入的宽度(即字符数),比如%4d就只看四个字符的宽度,输入12345678,只会读入1234。

*:

用以表示该输入项,读入后不赋予相应的变量,即跳过该输入值。


除了上面的常规用法,还有一个扫描字符集合的用法
一般格式是:“%[*] [输入数据宽度] [字符集合] \color{red}\text{[字符集合]} [字符集合]

前面两个方括号是可选项,作用和上面说的一致。后面红色标红的为字符集合(个人感觉这个东西类似于%s里的那个s)。这里的这对 方括号不能省略。

scanf 函数的格式说明字符串中 %[] 是一个扫描集合,用于匹配一组字符。当使用 %[] 时,你可以指定一个字符集合,scanf 将会读取输入直到遇到不在指定集合中的字符为止。

用法如下:

  1. 单个字符:表示这个字符集合包含这个字符,可以是空格或者换行符等特殊字符
  2. ^:反选,即包含后面的字符。这个字符必须放在开头才能表示反选作用,否则相当于单个字符,属于字符集合
  3. a-z:中间的横线(减号)表示一个范围,即包含ascii码表上从前一个字符到后一个字符的所有字符,包括两端

使用例:
在这里插入图片描述

sscanf 和 scanf 中的格式说明字符串的用法是一致的。


printf(和sprintf)的格式说明字符串:

printf格式说明符一般形式为:% [*][ flags ][ width ][ .precision ][ length ] specifier

flag标志,width宽度,precision精度,length长度,specifier类型(直译规范)。
同样的方括号包括的选项为可选项。可以不加。


类型:

  1. 整数:
    1. d表示十进制有符号整数 u表示十进制无符号整数
    2. o表示八进制整数
    3. x表示十六进制整数
  2. 浮点数:
    1. f表示浮点数形式 e表示指数形式
    2. g表示 序号1的f和e中 较短的那个(而且在舍入结束后,输出时不会输出无效的0(有精度要求也不会输出),而f和e是会用0补齐的
    3. a表示十六进制浮点数(C99后)
  3. 字符:
    1. c表示单个字符(char)
    2. s表示字符串(string)
  4. 指针:
    1. p表示输出指针内容(和用十六进制输出差不多,但是会输出前导0,一共16位十六进制位,也就是64位二进制位全都输出了)

注:
输出八进制数和十六进制数的时候是直接把整数补码输出,比如int的-1按十六进制来输出,会得到ffffffff。
有些输出格式会输出字母,如果你格式控制符使用大写,输出时就会使用大写字母。比如%X,%E,%G,%A

长度:

  1. l表示long(大小写好像没区别)
  2. h表示short

宽度:

用十进制整数指定输出的宽度(即字符数),比如%4d就输出四个字符的宽度,超长的话就不管宽度了,继续输出

精度:

一般用在浮点数中,效果如下:

  1. 用于整数,和使用width宽度效果一致
  2. 用于f或e,表示保留到小数点后几位(默认是六位,四舍五入)
  3. 用于g,表示输出几位有效数字(实际可能可能会多出一些无效位,有效数字简单来说就是从该数的第一个非零数字起,直到末尾数字止的数字,比如0.012的有效数字位数为2)
  4. 用于s,表示输出几位字符,这里和宽度不一样,这里是输出个数够了就不输出了。但是只是宽度超了的话会继续输出

使用例:
在这里插入图片描述

在这里插入图片描述
上面g舍入后会消除末尾无效的0

在精度设置方面,cout的setprecision()也可以实现差不多的功能(至少我看不出区别,但是有些b题用cout是对的,有的用printf是对的,邪门的很,尤其是01分数规划那玩意)

要用cout设置精度首先要包含 iomanip头文件(io+manipulator),里面有两个manipulator流操纵器,分别是fixed(小数形式)和scientific(科学计数法形式,指数形式)。另外还有setprecision(precision)可以控制输出位数。用法如下:

cout<<fixed<<precision(2)<<f;
cout<<scientific<<precision(2)<<f;

fixed相当于printf中的%f,scientific相当于%e,一般模式相当于%g。setprecision相当于在设置精度。(所以设置了precision后,一般模式该不输出末尾无效0仍然不会输出)。

标志:

  1. - :输出内容左对齐,一般和width搭配使用(默认是右对齐)
  2. + :输出带符号整数时强制带上符号(一般正数前面的正号会被省略)
  3. # :输出八进制和十六进制数时带上前缀(也就是0和0x)
  4. 0 :多出来的空位用0填充,一般和width搭配使用(默认是填充空格)

欢迎勘误

string

不写了,写的太多了,之后写个STL,扔那里面得了

string用法:

总结:
string读入和输出建议用cin和cout,非要用printf输出可以用s.c_str()printf("%s",s.c_str());

string的+操作就是在后面接上另一个string或字符
+=+复杂度不一样,+=是在原来的基础上添加所以快,+会创建一个中间变量来存放,慢!
string的判断操作<,>,==,!=
大小比较是按照字典序大小来的
  
string s,s1;//s是母串,四种操作,start不是地址,可以当作是数组下标
s.insert(start,dis,s1);
s.erase(start,dis);
s.clear();
s.replace(start,dis,s1);

string s,s1;		//s是母串,查找操作
s.find(s1,start);	//从start位置向后寻找s1子串
s.rfind(s1,start);	//从start位置向前寻找s1子串

s.find_first_of(s1,start)	
//从start位置向后查找s1集合包含的字符,返回找到的第一个字符的数组下标(位置)
s.find_last_of(s1,start)	
//从start位置向前查找s1集合包含的字符,返回找到的第一个字符的数组下标(位置)

s.find_first_not_of(s1,start)	
//从start位置向后查找s1集合不包含的字符,返回找到的第一个字符的数组下标(位置)
s.find_last_not_of(s1,start)	
//从start位置向前查找s1集合不包含的字符,返回找到的第一个字符的数组下标(位置)
文章来源:https://blog.csdn.net/qq_45809243/article/details/135548341
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。