前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C语言字符串I\O

C语言字符串I\O

作者头像
CtrlX
发布2022-11-14 15:05:30
4.5K0
发布2022-11-14 15:05:30
举报
文章被收录于专栏:C++核心编程C++核心编程

引入

详见CPrimerPlus P329

分析常用的处理字符串输入和输出的函数,以及如何结合这几个函数进行优化和设计一些新的处理字符串输入输出的函数。

字符串输入

分配空间

要做的第一件事是分配空间,以存储稍后要读入的字符串。

这意味这要未字符串分配足够的空间,注意计算机在读取字符串时不会计算它的长度。

常见的一个错误写法:scanf要把信息拷贝到参数指定的地址上,而此时的参数是一个未初始话的指针,那么可以指向程中的任意的地址,可能会擦写掉程序中的数据和到吗,导致程序中断。

代码语言:javascript
复制
char *name;
scanf("%s",name);

分配内存的方法

1.声明时显式指明数组的大小。

代码语言:javascript
复制
char name[80];

2.使用C语言的库函数来分配内存。动态内存分配(malloc等)

代码语言:javascript
复制
int name = (int*)malloc(sizeof(int)*n)

字符串输入

gets()函数

简介

在读取字符串时,scanf()函数和转换符%s只能读取一个单词,但是在程序中经常要读取一整行输入,因此gets()可以处理这种情况。

使用

gets函数读取整行输入,直到遇到换行符,然后丢弃换行符(与fgets函数区分),存储其他的字符,并在这些字符后面加上一个空字符使其成为一个C字符串。

问题

问题在于gets的唯一参数就是数组名,它无法检查数组是否装得下输入行,数组名会被转换成该数组的首元素地址,因此gets函数只知道数组的开始处,如果输入的字符串过长,会导致缓冲区溢出,及多余的字符超出了指定的内存空间,如果这些多余的字符只是占据了尚未使用的内存时,并不会立刻出现问题,但是如果它们占擦写掉了程序中的其他数据,会导致程序中止。

因此C11委员会在标准中废除了gets()函数。

gets()函数的替代品

过去常使用fgets函数来代替gets函数,在输入处理方面与gets函数略有不同。

C11标准中新增的gets_s函数也可以代替gets函数,该函数与gets函数很接近,而且可以替换现有代码中的gets函数。

fgets()函数

fgets函数和gets函数的区别

  • fgets函数的第二个参数指明了读入字符的最大数量。如果参数是n则读取n-1个字符,或读到遇到的第一个换行符为止。
  • 如果fgets函数读到一个换行符,会把它储存在字符串中,这点与gets函数不同,gets函数会丢弃换行符。
  • fgets函数的第三个参数指明要读入的文件。如果读入从键盘输入的数据,则以stdin作为参数,改标识符定义在stdio,h中。

案例一

代码语言:javascript
复制
#include<stdio.h>
#define STLEN 14
int main()
{
    char words[STLEN];
    
    puts("Enter a string,please.");//自动添加换行符,无需添加’\n‘
    fgets(words,STLEN,stdin);
    printf("Your string twice (puts(),then fputs()):\n");//手动添加’\n‘
    puts(words);
    fputs(words,stdout);
    
    puts("Enter another string,please.");
    fgets(words,STLEN,stdin);
    printf("Your string twice (puts(),then fputs()):\n");
    puts(words);
    fputs(words,stdout);
    puts("Done.");
    
    return 0;
        
}

输出:

代码语言:javascript
复制
Enter a string,please.
apple pie
Your string twice (puts(),then fputs()):
apple pie

apple pie
Enter another string,please.
strawberry shortcake
Your string twice (puts(),then fputs()):
strawberry sh
strawberry shDone.

分析:

gets函数抛弃\n,puts函数添加\n,fgets函数保留\n,fputs函数不添加\n

第一行输入中:apple pie比fgets函数读入的整行输入短,因此apple pie\n\0被存储在了数组中,不过puts函数在显示改字符串时又在末尾添加了换行符,因此apple pie后面又一行空行。fputs函数不在字符串,末尾添加换行符所以未打印空行。

第二行输入中:strawberry shortcake超过了大小限制,所以fgets函数只读取了13个字符,把strawberry sh\0存储到了数组中。

案例二:简单的循环,输入并显示用户输入的内容,直到fgets读取到文件结尾或空行。

fgets函数返回指向char的指针,如果一切顺利,该函数返回的地址与传入的第一个参数的相同,但是如果函数读到文件结尾,它将返回一个特殊的指针:空指针。

代码语言:javascript
复制
#include<stdio.h>
#define STLEN 10
int main ()
{
	char words[STLEN];
    
    puts("Enter strings (empty line to quit):");
    while(fgets(words,STLEN,stdin) != NULL && words[0] != '\n')//当读取到文件结尾或出现了换行符时停止循环
        fputs(words,stdout);
    puts("Done.");
    
    return 0;
}

输出:

代码语言:javascript
复制
Enter strings (empty line to quit):
by the way,the gets function
by the way,the gets function

分析:利用fputs函数不在字符串末尾不添加\n这一特性处理超过了规定的数组限制的输入情况,并且输出完整的字符串。

程序中的fgets函数一次读入STLEN-1个字符(本案例中为9个字符),所以它第一次只读取了by the wa,并存储为by the wa\0,接着fputs函数打印该字符串,并且为换行。然后while循环进入了下一轮迭代,fgets函数继续从输入的剩余中读入数据,一直循环,直到读到tion\n,fgets函数将其存储为tion\n\0,fputs函数打印该字符,并且由于\n进行换行。

fgets()函数存储’\n’的好处与坏处

好处:是对于存储的字符串而言,检查末尾的换行符可以判断是否成功读取了一整行,如果不是一整行则妥善处理一行中剩下的字符。(如上面的案例)

坏处:是有时候我们并不想把换行符存储在字符串中,这样的换行符会带来一些麻烦。

如何处理掉换行符?

代码语言:javascript
复制
while(words[i] != '\n')
    i++;
words[i] = '\0';
//将\0替换为\n  

如何丢弃掉仍存在输入行中的字符串?

如果目标数组装不下一整行输入,就丢弃多出的字符:

代码语言:javascript
复制
while(getchar() != '\n')//读取但是不存储输入,包括\n
    continue;

案例三

程序读取输入行,删除储存在字符串中的换行符,如果没有换行符,则丢弃数组装不下的字符。

代码语言:javascript
复制
#include<stdio.h>
#define STLEN 10
int main ()
{
    char words[STRLEN];
    int i;
    
    puts("Enter strings(empty line to quit):");
    while(fgets(words,STRLEN,stdin) != NULL && word[0] != '\0'){
        i = 0;
        while(words[i] != '\n' && wprds[i] != '\0')
            i++;
        if(words[i] == '\n')//将存储在字符串中的换行符替换成’\0‘
            word[i] = '\0';
        else //如果没有换行符则丢弃字符串剩余的部分
            while(getchar() != '\n')
                continue;
        puts(words);
    }
    puts("done");
    return 0;
}

分析:遍历字符串,直至遇到换行符或则和空字符。如果先遇到换行符,下面的if语句就将其替换成,空字符;如果先遇到空字符,else部分便丢弃输入行的剩余字符。

PS:注意区分空字符和空指针(详见CPrimerPlus P335)

空字符是整数类型,但是空指针是指针类型。 两者容易混淆的原因是:它们都可以用数值0表示,但是,从概念上看,两者是不同类型的0。 空字符是一个字符,占1个字节;而空指针是一个地址,通常占4字节。

gets_s()函数

C11新增的gets_s函数和fgets函数类型,用一个参数限制读入的字符数。

gets_s函数和fgets函数的区别是:

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

gets_s(),fgets(),gets()之间的选择与对比分析

如果目标存储区装得下输入行,三个函数都没有问题。注意fgets函数会保留出入末尾的换行符作为字符串的一部分,要编写额外的代码将其替换成空字符,但是只要输入行未超过最大字符数,gets_s和gets函数几乎一样,完全可以用gets_s函数替换gets。 如果输入太长,使用gets()不安全,它会擦写现有的程序,存在安全隐患,但是使用gets_s()函数很安全,但是由上面可知超出限定后gets_s()函数可能会中止或者退出程序,所以要知道如何编写特殊的“处理函数”。 由此可见如果输入太长,超过数组可容纳的字符数时,fgets函数最容易使用,而且可以选择不同的处理方法,如果想让程序急促使用输入行中超出的字符可以参考案例二中的处理方法,如果想丢弃初入行的超出字符,可以参考案例三中的处理方法。

鉴于此,fgets()通常是处理特殊情况的最佳选择。

s_gets()函数

上面案例演示了fgets()函数的一种使用方法:读取整行输入并用空字符替换换行符,或者读取一部分输入,并丢弃其余部分。

既然没有处理这种情况的函数,我们可以创建一个。

代码语言:javascript
复制
char * s_gets(char * st,int n)
{
    char * ret_val;
    int i = 0;
    
    ret_val = fgets(st,n,stdin);
    if(ret_val)//即ret_val != NULL
    {
        while(st[i] != '\n' && st[i] != '\0')
            I++;
        if(st[i] == '\n')
            st[i] = '\0';
        else 
            while(getchar() != '\n')
                continue;
    }
    return 0; 
}

分析:如果fgets函数返回了NULL说明读到文件结尾或出现了错误,s_gets()函数跳过了这个过程。

如果字符串中出现了换行符,那就用空字符替换它,如果字符串中出现了空字符,那就丢弃该输入行中的其余字符,然后返回与fgets()相同的值。

为什么要丢弃过长输入中的余下字符?这是因为输入行中多出来的字符会被留在缓冲区中,成为下一次读取语句的输入,

我们设计的s_gets()函数并不完美,缺陷:遇到不合适的输入时毫无反应,它丢弃多余的字符时既不通知程序也不告诉用户。

scanf()函数

使用scanf()和%s转换说明读取字符串。scanf()和gets()或者fgets()的区别在于它们如何缺点字符串的末尾。

  • 如果使用%s转换说明,以下一个空白字符(空格、空行、制表符、换行符)作为字符串的结束(字符串不包括空白字符)。
  • scanf()和gets()类似,也存在一些潜在的缺点:如果输入行的内容过长,scanf()也会导致数据溢出,不过可以在%s转换说明中使用字段宽度可以防止溢出。

案例:在scanf函数中指定字符宽度的用法

代码语言:javascript
复制
#include<stdio.h>
int main()
{
	char name1[11],name2[11];
    int count;
    
    printf("Please enter 2 names.\n");
    count = scanf("%5s %10s",name1,name2);
    printf("I read the %d names %s and %s.\n",count.name1,name2);
    
    return 0;
}

结果

代码语言:javascript
复制
Please enter 2 names.
Portensia Callowit
I read the 2 names Porte and nsia.

分析:scanf第二次读取数据时从上一次调用结束的地方继续读取数据。

PS

区分空白字符(空格、空行、制表符、换行符)和空字符(’\0‘)

字符串输出

puts()函数

使用方法:只需把字符串的地址作为参数传递给它即可。

该函数在遇到空字符时就停止输出,所以必须确保字符串中有空字符(’\0’),注意不是空白字符,区分两者。

案例

代码语言:javascript
复制
#include<stdio.h>
int main()
{
    char side_a[] = "Side A";
    char dont[] = {'W','O','W','!'};
    char side_b[] = "Side B";
    
    puts(dont);
    return 0;
}

输出:

代码语言:javascript
复制
WOW!Side A

分析:由于dont缺少了一个表示结束的空字符,所以它不是一个字符串,因此puts()不知道在哪里停止,它会一直打印dont后面内存中的内容,知道发现一个空字符为止。

fputs()函数

printf()函数

自定义输入输出函数

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-11-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引入
  • 字符串输入
    • 分配空间
    • 字符串输入
      • gets()函数
        • gets()函数的替代品
          • fgets()函数
          • gets_s()函数
          • s_gets()函数
        • scanf()函数
        • 字符串输出
          • puts()函数
            • fputs()函数
              • printf()函数
              • 自定义输入输出函数
              相关产品与服务
              对象存储
              对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档