掘根宝典之C语言字符串输入函数(gets(),fgets(),get_s())

字符串输入前的注意事项

如果想把一个字符串读入程序,首先必须预留该字符串的空间,然后用输入函数获取该字符串

 这意味着必须要为字符串分配足够的空间

不要指望计算机在读取字符串时顺便计算它的长度,然后再分配空间(计算机不会这样做,除非你编写一个处理这些任务的函数)。

假设编写了如下代码:

char *name;
scanf("%s",name);

虽然可能会通过编译(编译器很可能给出警告),但是在读入name时,name可能会擦写掉程序中的数据或代码,从而导致程序异常中止。

因为scanf()要把信息拷贝至参数指定的地址上,而此时该参数是个未初始化的指针,name可能会指向任何地方。大多数程序员都认为出现这种情况很搞笑,但仅限于评价别人的程序时。
 

最简单的方法是,在声明时显式指明数组的大小:
 

char name[81];
scanf("%s",name);


现在name是一个已分配块(81字节)的地址。还有一种方法是使用C库函数来分配内存

为字符串分配内存后,便可读入字符串。C语言提供了许多读取字符串的函数:gets(),fgets(),gets_s()函数

gets()

C语言中的gets()函数是一个标准库函数,用于从标准输入(键盘)读取一行字符并存储到指定的字符数组中。它的函数原型如下:

char *gets(char *str);

gets()函数的参数是一个字符数组,用于存储读取到的字符,返回值是读取到的字符数组的首地址。

注意:gets()函数存在一些安全性问题,因为它无法保证读取的字符数不超过指定的字符数组大小,可能导致缓冲区溢出。因此,在实际的程序开发中,最好使用更安全的替代函数fgets()来代替gets()函数。

特点

在读取字符串时,scanf()和转换说明%s只能读取一个单词。可是在程序中经常要读取一整行输入,而不仅仅是一个单词。许多年前,gets()函数就用于处理这种情况。

gets()函数简单易用,它读取整行的输入,直至遇到换行符,然后丢弃换行符,储存其余字符,并在这些字符的末尾添加一个空字符使其成为一个C字符串。

使用示例:

#include <stdio.h>
int main()
{
char words[81];gets(words) ; // 典型用法printf("s\n", words);
}

结果

//输入abcd
abcd

但是我们拿着上面这段代码去运行的时候,就会发现编译器报错或者发出警告,这是为什么呢?

缺点

问题就出现在gets唯一的参数是words,它无法检查数组是否装得下输入行。

因此gets()函数只知道数组的开始处(通过传入的数组名),但是并不知道数组中有多少个元素。

如果输入的字符串过长,会导致缓冲区溢出(buffer overflow),即多余的字符超出了指定的目标空间。

如果这些多余的字符只是占用了尚未使用的内存,就不会立即出现问题;

如果它们擦写掉程序中的其他数据,会导致程序异常中止:或者还有其他情况。

为了让输入的字符串容易溢出,把程序中的STLEN设置为5,程序的输出如下:

//输入abcd
abcd
Segmentation fault:11


“Segmentation fault”(分段错误)似乎不是个好提示,的确如此。在UNIX系统中,这条消息说明该程序试图访问未分配的内存。

C 提供解决某些编程问题的方法可能会导致陷入另一个尴尬棘手的困境。

但是,为什么要特别提到gets()函数?

因为该函数的不安全行为造成了安全隐患。

过去,有些人通过系统编程,利用gets()插入和运行一些破坏系统安全的代码。
不久,C编程社区的许多人都建议在编程时摒弃gets()。制定C99标准的委员会把这些建议放入了标准,承认了gets()的问题并建议不要再使用它。尽管如此,在标准中保留gets()也合情合理,因为现有程序中含有大量使用该函数的代码。而且,只要使用得当,它的确是一个很方便的函数。
好景不长,C11标准委员会采取了更强硬的态度,直接从标准中废除了gets()函数。然而在实际应用中,编译器为了能兼容以前的代码,大部分都继续支持gets()函数。不过,VS2022就不支持了

fgets()

过去通常用fgets()来代替gets(),fgets()函数稍微复杂些,在处理输入方面与gets()略有不同。

原型

在C语言中,fgets()函数用于从指定的输入流中读取一行字符串。它接受三个参数:输入缓冲区指针,缓冲区大小和要读取的输入流。

使用fgets()函数的语法如下:

char *fgets(char *str, int size, FILE *stream);

fgets()函数的第2个参数指明了读入字符的最大数量。

如果该参数的值是n,那么fgets()函数从输入流中读取至多n - 1个字符(因为会自动加\0),或者遇到换行符('\n')为止。

fgets()函数的第3个参数指明要读入的文件。如果读入从键盘输入的数据,则以stdin(标准输入)作为参数,该标识符定义在stdio.h中。

fgets()函数返回指向char的指针。如果一切进行顺利,该函数返回的地址与传入的第1个参数相同但是,如果函数读到文件结尾,它将返回一个特殊的指针:空指针(null pointer)。该指针保证不会指向有效的数据,所以可用于标识这种特殊情况。在代码中,可以用数字0来代替,不过在C语言中用宏NULL来代替更常见(如果在读入数据时出现某些错误,该函数也返回NULL)。

读取规则

它将读取到的字符逐个存储在字符数组中,直到达到指定的大小或者遇到换行符为止。

如果没有遇到换行符,或者输入流中没有更多字符可读,fgets()函数会在最后一个字符后面添加一个空字符('\0')(如果数组没存满,系统会自动添加\0直到装满),表示字符串的结束。

看个例子

#include<stdio.h>
int main()
{char a[10];fgets(a, 10, stdin);printf("%s", a);}

输入1234567890(超出指定大小),结果是

123456789

 输入1234,按enter(遇到换行符),结果是

1234

我们可以再看个例子啊

#include <stdio.h>
int main(void)
{char words[10];puts("Enter strings (empty line to quit):");while (fgets(words, 14, stdin) != NULL && (words[0] != '\n'))fputs(words, stdout);puts("Done.");
}

输入By the way,the gets() function, 结果是

有人就会有疑问了啊,这输入的东西不是超除了words的大小吗?那为什么还能正常打印?

实际上它确实超过了,但是这是循环!

程序中的fgets()一次读入 10 -1个字符(该例中为9个字符)。所以,一开始它只读入了“By the wa”,并储存为By the wa\0:接着fputs()打印该字符串,而且并未换行然后while循环进入下一轮迭代,fgets()继续从剩余的输入中读入数据,即读入“y,the ge”并储存为y,the ge\0接着fputs()在刚才打印字符串的这一行接着打印第2次读入的字符串。然后while 进入下一轮迭代,fgets()继续读取输入、fputs()打印字符串,这一过程循环进行,直到读入最后的“tion\n”fgets()将其储存为tion\n\0,fputs()打印该字符串,由于字符串中的\n,光标被移至下一行开始处。

保留换行符

需要注意的是,fgets()函数会保留输入流中的换行符,所以读取到的字符串可能包含换行符。如果你希望去除换行符,可以使用strtok()或者手动处理字符串

系统采用缓冲的IO,这意味着用户在按下enter键之前,输入都会被存在临时存储区(缓冲区)。

这点与gets()不同,gets()会丢弃换行符。

我们可以先借用puts()函数的特性:自动在字符串末尾加换行符

我们可以验证一下

#include <stdio.h>
int main(void)
{char words[14];puts("请输入:");fgets(words, 14, stdin);printf("见证奇迹的时刻:\n");puts(words);printf("sjajj");}

结果是: 

apple pie,比fgets()读入的整行输入短,因此,apple pie\n\0被储存在数组中(因为fgets()会自动存储换行符)。当puts()显示该字符串时又在末尾添加了换行符,调用puts()apple pie\n\0里的换行符起作用将光标移动到下一行,但是puts自动在字符串末尾添加换行符,所以光标再次移动到下一行。因此apple pie下面有一行空行。

系统使用缓冲的I/O。这意味着用户在按下Return键之前,输入都被储存在临时存储区(即,缓冲区)中。按下Enter键就在输入中增加了一个换行符,并把整行输入发送给fgets()。对于输出,fputs()把字符发送给另一个缓冲区,当发送换行符时,缓冲区中的内容被发送至屏幕上。

fgets()储存换行符有好处也有坏处。

坏处是你可能并不想把换行符储存在字符串中,这样的换行符会带来一些麻烦。

好处是对于储存的字符串而言,检查末尾是否有换行符可以判断是否读取了一整行。如果不是一整行,要妥善处理一行中剩下的字符。

处理掉换行符


首先,如何处理掉换行符?

一个方法是在已储存的字符串中查找换行符,并将其替换成空字符:

while (words[i] !='\n')// 假设\n在words中
i++;
words[i]='\0';


其次,如果仍有字符串留在输入行怎么办?

一个可行的办法是,如果目标数组装不下一整行输入,就丢弃那些多出的字符

while(getchar()!='\n')
contine;

gets_s()函数

C11标准新增的gets_s()函数也可代替gets()。该函数与gets()函数更接近,而且可以替换现有代码中的 gets()。但是,它是stdio.h.输入/输出函数系列中的可选扩展,所以支持C11的编译器也不一定支持它。

在C语言中,gets_s()函数用于读取用户输入的字符串。gets_s()函数的声明如下:

char *gets_s(char *str, rsize_t n);

其中,str是指向字符数组的指针,用于存储读取到的字符串;n表示字符数组的大小。

gets_s()函数会读取用户输入的字符串,并将其存储到指定的字符数组中,直到读取到换行符或数组大小的限制。读取到的字符串将包含换行符,且以\0字符结尾。

需要注意的是,gets_s()函数是C11中引入的安全版本的函数,主要解决了gets()函数的缓冲区溢出问题。

C11新增的gets_s()函数(可选)和fgets()类似,用一个参数限制读入的字符数。


特性

  1. gets_s()只从标准输入中读取数据,所以不需要第3个参数。
  2. 如果gets_s()读到换行符,会丢弃它而不是储存它。
  3. 如果gets_s()读到最大字符数都没有读到换行符,会执行以下几步。首先把目标数组中的首字符设置为空字符,读取并丢弃随后的输入直至读到换行符或文件结尾,然后返回空指针。接着,调用依赖实现的“处理函数”(或你选择的其他函数),可能会中止或退出程序。

第2个特性说明,只要输入行未超过最大字符数,gets_s()和gets()几乎一样,完全可以用gets_s()换gets()。第3个特性说明,要使用这个函数还需要进一步学习。

三种输入方式的选择

我们来比较一下gets()、fgets()和gets_s()的适用性。

如果目标存储区装得下输入行,3个函数都没问题。但是fgets()会保留输入末尾的换行符作为字符串的一部分,要编写额外的代码将其替换成字符。

如果输入行太长会怎样?

使用gets()不安全,它会擦写现有数据,存在安全隐患。gets_s()函数很全,但是,如果并不希望程序中止或退出,就要知道如何编写特殊的“处理函数”。另外,如果打算让程继续运行,gets_s(会丢弃该输入行的其余字符,无论你是否需要。由此可见,当输入太长,超过数组容纳的字符数时,fgets()函数最容易使用,而且可以选择不同的处理方式。如果要让程序继续使用输中超出的字符,可以参考程序清单11.8中的处理方法。
所以,当输入与预期不符时,gets_s()完全没有fgets()函数方便、灵活。也许这也是gets s二民的因之一。鉴于此,fgets()通常是处理类似情况的最佳选择。
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/269454.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

#QT(网络编程-UDP)

1.IDE&#xff1a;QTCreator 2.实验&#xff1a;UDP 不分客户端和服务端 3.记录 &#xff08;1&#xff09;做一个UI界面 &#xff08;2&#xff09;编写open按钮代码进行测试&#xff08;用网络调试助手测试&#xff09; &#xff08;3&#xff09;完善其他功能测试 4.代码 …

Git 远程仓库之Github

目前我们使用到的 Git 命令都是在本地执行&#xff0c;如果你想通过 Git 分享你的代码或者与其他开发人员合作。 你就需要将数据放到一台其他开发人员能够连接的服务器上。 目前最出名的代码托管平台是Github&#xff0c;我们将使用了 Github 作为远程仓库。 添加远程库 要添…

【Python】进阶学习:__len__()方法的使用介绍

【Python】进阶学习&#xff1a;__len__()方法的使用介绍 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希望得到您的订…

209.长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;返回 0 。 第一次写&#xff0c;越界了 in…

链式插补 (MICE):弥合不完整数据分析的差距

导 读 数据缺失可能会扭曲结果&#xff0c;降低统计功效&#xff0c;并且在某些情况下&#xff0c;导致估计有偏差&#xff0c;从而破坏从数据中得出的结论的可靠性。 处理缺失数据的传统方法&#xff08;例如剔除或均值插补&#xff09;通常会引入自己的偏差或无法充分利用数…

MySQL王国:从基础到高级的完整指南【文末送书-28】

文章目录 MySQL从入门到精通第一部分&#xff1a;MySQL基础第二部分&#xff1a;MySQL进阶第三部分&#xff1a;MySQL高级应用 MySQL从入门到精通&#xff08;第3版&#xff09;&#xff08;软件开发视频大讲堂&#xff09;【文末送书-28】 MySQL从入门到精通 MySQL是一种开源…

Linux中汇编语言的学习(加法、乘法、除法、左移、右移、按位与等多种命令操作实例以及ARM的 N、Z、C、V 标志位的解释)

汇编概述 汇编需要学习的大致框架如下&#xff1a; 汇编中的符号 1.指令&#xff1b;能够北嘁肷梢惶?2bit机器码&#xff0c;并且能够被cpui识别和执行 2.伪指令&#xff1a;本身不是指令&#xff0c;编译器可以将其替换成若干条指令 3.伪操作&#xff1a;不会生成指令…

技术指标的买入形态之均线形成多头排列

一、技术特征 1、在股价横盘整理过程中&#xff0c;其短期均线、中期均线持续纠缠在一起。 2、整理一段时间后&#xff0c;短期均线向上突破了中期均线&#xff0c;中期均线也向上突破了长期均线。 均线多头排列是股价处于上涨行情中的信号。 二、买点描述 当均线的多头排列…

tomcat nginx 动静分离

实验目的:当访问静态资源的时候&#xff0c;nginx自己处理 当访问动态资源的时候&#xff0c;转给tomcat处理 第一步 关闭防火墙 关闭防护 代理服务器操作&#xff1a; 用yum安装nginx tomcat &#xff08;centos 3&#xff09;下载 跟tomcat&#xff08;centos 4&#xff0…

3分钟开通GPT-4

AI从前年12月份到现在已经伴随我们一年多了&#xff0c;还有很多小伙伴不会开通&#xff0c;其实开通很简单&#xff0c;环境需要自己搞定&#xff0c;升级的话就需要一张visa卡&#xff0c;办理visa卡就可以直接升级chatgptPLSU 一、虚拟卡支付 这种方式的优点是操作简单&…

AI-RAN联盟在MWC24上正式启动

AI-RAN联盟在MWC24上正式启动。它的logo是这个样的&#xff1a; 2月26日&#xff0c;AI-RAN联盟&#xff08;AI-RAN Alliance&#xff09;在2024年世界移动通信大会&#xff08;MWC 2024&#xff09;上成立。创始成员包括亚马逊云科技、Arm、DeepSig、爱立信、微软、诺基亚、美…

mysql高可用架构设计

一、主从架构 主从架构一般如下所示 这里从节点一般设置成只读&#xff08;readonly&#xff09;模式。这样做&#xff0c;有以下几个考虑&#xff1a; 有时候一些运营类的查询语句会被放到备库上去查&#xff0c;设置为只读可以防止误操作&#xff1b; 防止切换逻辑有 bug&a…

Unity2023.1.19_ECS_DOTS

Unity2023.1.19_ECS_DOTS 盲学-盲目的学习&#xff1a; 懒着自己整理就看看别人整理的吧&#xff0c;整合一下逻辑通了不少&#xff1a; DOTS/data oriented technology stack-面向数据的技术栈 ECS/Entities-Component-System Unity-Entities包 Entities提供ECS架构面向数…

javaWebssh教师荣誉库管理系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

一、源码特点 java ssh在线授课辅导系统是一套完善的web设计系统&#xff08;系统采用ssh框架进行设计开发&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0…

云计算时代的运维: 职业发展方向与岗位选择

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua&#xff0c;在这里我会分享我的知识和经验。&#x…

基于Spring Boot + Vue的网上订餐系统

末尾获取源码作者介绍&#xff1a;大家好&#xff0c;我是墨韵&#xff0c;本人4年开发经验&#xff0c;专注定制项目开发 更多项目&#xff1a;CSDN主页YAML墨韵 学如逆水行舟&#xff0c;不进则退。学习如赶路&#xff0c;不能慢一步。 目录 一、项目简介 二、开发技术与环…

10kV/35kV并网分布式光伏系统-实现错峰有序用电

根据国家电网 Q/GDW1480-2015 《分布式电源接入电网技术规定》&#xff1a;分布式电源并网电压等级可根据各 并网点装机容量进行初步选择&#xff0c;推荐如下&#xff1a; • 8kW 及以下可接入 220V &#xff1b; • 8kW~400kW 可接入 380V &#xff1b; • 400kW~60…

实名制交友-智能匹配-仿二狗交友系统-TP6+uni-APP小程序H5公众号-源码交付-支持二开!

一、代码风格 通常不同的开发者具备不同的代码风格&#xff0c;但为了保证语音交友系统开发质量&#xff0c;在编码前需要进行代码风格的统一&#xff0c;通过制定一定的规则&#xff0c;约束开发者的行为。具有统一风格的代码才能更清晰、更完整、更容易理解、更方便后期维护…

springboot,druid动态数据源切换

关键字&#xff1a;springboot&#xff0c;druid数据库连接池&#xff0c;两个数据源&#xff08;可以切换成多个&#xff09;&#xff0c;事务管理 关于druid简介传送门&#xff1a;https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98 具体分为四…

利用文件实现进程间共享数据

概述 文件可以存储任何非结构化字节序列&#xff0c;这个比较简单&#xff0c;就一个写一个读&#xff1b;学习到此&#xff0c;留个记录&#xff0c;以后可以直接抄代码&#xff0c;哈哈 Demo代码 #include <fstream> #include <iostream> #include <thread&…