【C / C++】fgets, gets, getchar,和 scanf 的对比及使用建议

1.scanf

上来就是咋们的老朋友 scanf 了,作为 C 语言中最基础的输入函数,相信大家并不陌生,但是你或许并不清楚它的一些特殊用法

输入前必须要确认输入内容的格式,在 VS 上使用还报警告,没法读取带空格的内容这或许是大家在使用 scanf 这个函数时最常见的诟病了,也难怪 C++ 引入了 cin 作为替代,

1.1 为什么输入前必须要确认输入内容的格式

先来看看大家对输入前必须要确认输入内容的格式做一个解释scanf和cin同样作为控制输入内容的函数,为什么scanf它一定要先明确指明输入对象的格式,而cin不用呢?

1.C 和 C++ 的设计原则导致差异(核心原因)

C语言是过程式弱类型的语言。scanf是一个标准的库函数,它的设计遵循C语言的简单、直接原则。

C++是面向对象强类型的语言,并且支持运算符重载cin是 std::istream 类的一个全局对象

大家可能觉得完全没有办法理解上面的话,其实简单来说就是因为 C 语言在编写的时候编程技术不如 C++ ,导致 C++ 编译器在编译时就能做的事在 C 语言中需要手动操作,简而言之,C++ 的 cin 功能

工作原理scanf的第一个参数是一个格式字符串(如 "%d %f"),它告诉函数:

  1. 接下来要解析几个数据?(通过%的个数)。

  2. 每个数据是什么类型?%d表示整数,%f表示浮点数等)。

  3. 输入流中的这些数据是如何分隔的?(空格、换行符、逗号等,由格式字符串中的普通字符指定)

  • 当你写下 cin >> a; 时,C++编译器在编译期就会做以下事情:

    1. 查看变量 a 的类型(假设是 int)。

    2. 寻找一个能接受 std::istream& 和 int& 作为参数的 operator>> 函数。

    3. 找到并调用那个专门为 int 设计的版本。

很轻易的就能发现,cin 就明显会比 scanf 更加的智能。它无需我们去输入什么数据类型,它会根据你输入的变量名自动的去查找你在哪个地方定义了这个变量,它属于什么类型?

1.2 为什么在 VS 上使用报警告

scanf 在读取数据时,并不关心你给的空间有多大,颇有一种我只管读,代码是自己写的,bug你自己处理的意思,所以 VS 为了避免这个问题,便通过报警告来提醒用户

就像这样,在只能存储5个字符的 arr 数组里读取了超过5个字符的内容,会出现这样的情况。

大家可以设想一下这个场景,就是我写了上千行代码,然后我可能在某一个地方因为不注意细节,就出现了类似于这样的代码,然后我在我运行的时候,它忽然就报了一个这样的错误,那我肯定会特别的崩溃,因为我没有办法去寻找究竟是哪个地方出的错误。如果要调试的话,几千行的代码调试起来是特别麻烦的。

那有什么办法可以规避这样的错误?其实也是有的

只需要我们在使用 %s 的时候,在 % 和 s 之间加入一个整数,它代表的意思是读取字符串长度的最大值。若字符串超过了这个长度,那么多余的就会被丢弃。

#include<cstdio>
int main()
{
	char arr[10];
	scanf("%9s", arr);  //liuqipeizjb
	printf("%s", arr); //liuqipeiz
	return 0;
}

大家需要注意一个点,就是我这行代码。所能输入的整数最大就是9,因为你要首先要明白 scanf 在编译时所做的一个行为,就是他在读取完你字符串之后,他会在你字符串的末尾自动加上一个 \0。 而 \0 身为一个字符,它本身也是要占据数组中一个字节的空间的

大家可以试着回想一下,是不是每次用 scanf 读取完,然后马上用 printf 输出,它也并不会出现因为没有 \0 而读取到很多奇奇怪怪数据的情况。

VS 提供了更加安全的版本: scanf_s,但是大家需要注意,如果你调用这个函数,就意味着你的代码失去了跨平台性,这个函数只可以在 VS 上使用,并且它的使用方式和 scanf 略有差异,如果大家要使用的话,要先了解清楚它的功能。

1.3 如何让 scanf 读取带有空格的内容

其实大家应该可以猜到,scanf 肯定是有能够读取带有空格的字符的方法,毕竟在那个只有 c 语言的年代,人们同样需要一个的函数去读取带空格的字符串

具体方法有是以 \n 作为 scanf 读取数据的结束标志,只需要在确认格式的时候输入 "%[^\n]s" 即可

#include<cstdio>
int main()
{
	char arr[15];
	scanf("%[^\n]s",arr);  //输入liu qi pei
	printf("%s", arr);     //输出liu qi pei
	return 0;
}

不知道大家是不会觉得我在输入字符串的时候。还需要特意的去输入一个 \n 作为我的结束标志,就会有点麻烦,但是如果你有这样的认识,你就错了,因为我们在最后输入完字符串敲的那个回车,在代码编译时就会被认定是一个换行符

2. fgets (最推荐)

调用这个函数的时候需要传入三个参数,第一个是将要被读取的数据需要被存储的地址,第二个是读取多少个字节大小的空间,最后一个表示的其实是读取数据源头的地址,

如果你是一个初学者,最令你疑惑的一定是第三个。但其实你不太需要注意这最后一个的定义,这其实是一个功能特别强大的。函数它可以从你电脑里某个文件里去读取数据。当然也可以从你的键盘上读取。

#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
	char arr[20];
	fgets(arr, 15, stdin);  //输入 liu qi pei zjb 
	cout << arr << endl;    //输出 liu qi pei zjb 
	return 0;
}

那么 fgets 作为一个控制输入的函数,相信需要包含什么头文件大家也能猜得到。<cstdio> 虽然你包含 iostream 这个头文件,它同样能够正常的编译,但是我建议大家为了自己代码风格的养成,最好还是标注它最直接的头文件。

如果是你第一次见这个函数,最令你感到疑惑的应该是这个 stdin。那他具体代表什么意思?他为什么又能充当这个文件指针的位置?这其实是一个很标准的写法,std 代表 standard,in 代表输入。合起来它的意思其实是标准输入流。指的是从你键盘上读取数据

gets 这个函数其实还有一个特点,就是如果你让它读取 num 个数据,并且你也输入了超过num - 1个数据,那它至多会读取 num - 1 个数据,然后在末尾加上 \0 , 如果你输入的数据小于等于 num - 2,那它甚至会读取你在字符串结尾所输入的回车键,也就是你在输出的时候都不用给它加换行符,便会自动换行

3.gets 

这个函数在 c++ 11及之后的标准中被移除了,因为它不符合现代编程的安全规范

安全问题(核心原因)gets函数不检查输入的长度,如果用户输入的字符数超过目标缓冲区的大小,会直接导致缓冲区溢出—— 这会破坏程序内存,引发崩溃,甚至被黑客利用进行代码注入攻击。

gets 是从第⼀个字符开始读取,⼀直读取到 \n 停⽌,但是不会读取 \n ,也就是读取到的内容中没有包含 \n ,但是会在读取到的内容后⾃动加上 \0 。

由于我的编译器并不支持这个函数,而且我也并不建议大家使用这个函数,所以便不作代码展示了

4.getchar 

get 得到,char 表示 character, 读取字符

这个函数的逻辑其实是:每调用它一次,它就读取一个字符。如果想利用它读取含空格的字符串的时候,那我们就要多次的调用它,一般是借助循环实现的

#include<iostream>
using namespace std;
int main()
{
	char arr[20];
	int i = 0;
	int ch = 0;
	while((ch = getchar()) != '\n')
	{
		arr[i++] = ch;  //liu qi pei zjb
	}
	cout << arr << endl; //liu qi pei zjb
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值