c的输入输出

c的输入输出

​ C I / O发生在流中,它们是字节序列。如果字节从键盘,磁盘驱动器或网络连接等设备流向主存储器,则称为输入操作,如果字节从主存储器流向显示屏幕,打印机,磁盘驱动器,或网络连接等,这称为输出操作

I / O库头文件为<stdio.h>

一、c的输入

在C语言中,有五个函数可以从键盘获得用户输入。

  • getchar:输入单个字符,保存到字符变量中。
  • gets:输入字符串,但是不安全,会发生内存溢出。
  • gets_s:输入字符串。
  • fgets:输入字符串,一般用于文件的输入
  • scanf:格式化输入函数,一次可以输入多个数据,保存到多个变量中。

1 单字符输入:getchar()

int getchar(void) 函数从屏幕读取下一个可用的字符,并把它返回为一个整数。这个函数在同一个时间内只会读取一个单一的字符。您可以在循环内使用这个方法,以便从屏幕上读取多个字符。

#include <stdio.h>
 
int main( )
{
   int c;
 
   printf( "Enter a value :");
   c = getchar( );
 
   printf( "\nYou entered: ");
   putchar( c );
   printf( "\n");
   return 0;
}

当上面的代码被编译和执行时,它会等待您输入一些文本,当您输入一个文本并按下回车键时,程序会继续并只会读取一个单一的字符,显示如下::

Enter a value :runoob

You entered: r

2 字符串输入:gets()

char *gets(char *s) 函数从 stdin 读取一行到 s 所指向的缓冲区,直到一个终止符或 EOF。

示例:

#include <stdio.h>
 
int main( )
{
   char str[100];
 
   printf( "Enter a value :");
   gets( str );
 
   printf( "\nYou entered: ");
   puts( str );
   return 0;
}

当上面的代码被编译和执行时,它会等待您输入一些文本,当您输入一个文本并按下回车键时,程序会继续并读取一整行直到该行结束,显示如下:

Enter a value :runoob

You entered: runoob

2.1 gets()的不安全性

​ 如果输入的字符串过长,会导致缓冲区溢出(内存泄漏),即多余的字符超出了指定的目标空间。如果这些多余的字符只是占用了尚未使用的内存,就不会立即出问题;如果它们擦写掉程序中的其他数据,会导致程序异常中止。如以下情况:

#include <stdio.h>
#define STLEN 5
int main(void)
{
    char words[STLEN];
     
    puts("Enter a string, please.");
    gets(words);  // typical use
    printf("Your string :\n");
    printf("%s\n", words);
    puts("Done.");
    
    return 0;
}

以上程序定义的字符数组空间是5个字符,但输入超过5个字符字符串的时候,便会发现段错误,表示该程序试图访问未分配的内存。

Enter a string, please.
i think i am ok!
Your string :
i think i am ok!
Done.
段错误 (核心已转储)

另外有些编译器在编译上面的代码时,会提示gets()函数是不安全的:

#gcc -o test test.c
test.c: 在函数‘main’中:
test.c:9:5: 警告:implicit declaration of function ‘gets’; did you mean ‘fgets’? [-Wimplicit-function-declaration]
     gets(words);  // typical use
     ^~~~
     fgets
/tmp/ccR82LEd.o:在函数‘main’中:
test.c:(.text+0x1f): 警告:the `gets' function is dangerous and should not be used.

因此,在C11标准中,直接废弃了gets()函数。但是在实际应用中,编译器为了能兼容以前的到吗,大部分都继续支持gets()函数。

2.2 gets()函数的替代品:fgets()和gets_s()

​ 通常都使用fgets()来代替gets(),但是也可以使用C11新增的gets_s()函数。该函数与gets()函数更接近,而且可以替换现有代码中的gets()。

fgets(char* _Buffer,int _MaxCount, FILE* _Stream)

(1)fgets()函数(和fputs())

​ fgets(char* _Buffer,int _MaxCount, FILE* _Stream)函数通过第2个参数限制读入的字符数来解决溢出的问题。该函数专门设计用于处理文件输入,所以一般情况下可能不太好用。fgets()和gets()的区别如下。

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

​ 因为fgets()函数把换行符放在字符串的末尾(假设输入行不溢出),通常要与fputs()函数(和puts()类似)配对使用,除非该函数不在字符串末尾添加换行符。fputs()函数的第2个参数指明它要写入的文件。如果要显示在计算机显示器上,应使用stdout(标准输出)作为该参数。下列程序演示了fgets()和fputs()函数的用法。

#include <stdio.h>
#define STLEN 14
int main(void)
{
    char words[STLEN];
    
    puts("Enter a string, please.");
    fgets(words, STLEN, stdin);
    printf("Your string twice (puts(), then fputs()):\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;
}

输出:

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.

​ 第1行输入,apple pie,比fgets()读入的整行输入短,此时因为fgets()读到一个换行符,会把它储存在字符串中,因此,applepie\n\0被储存在数组中。所以当puts()显示该字符串时又在末尾添加了换行符,因此applepie后面有一行空行。因为fputs()不在字符串末尾添加换行符,所以并未打印出空行。
​ 第2行输入,strawberry shortcake,超过了大小的限制,所以fgets()只读入了13个字符,并把strawberry sh\0储存在数组中。再次提醒,puts()函数会在待输出字符串末尾添加一个换行符,而fputs()不会这样做。

fgets()函数的返回值

​ fgets(char* _Buffer,rsize_t _Size)函数返回指向char的指针。如果一切进行顺利,该函数返回的地址与传入的第1个参数相同。但是,如果函数读到文件结尾,它将返回一个特殊的指针:空指针(null pointer)。该指针保证不会指向有效的数据,所以可用于标识这种特殊情况。在代码中,可以用数字0来代替,不过在C语言中用宏NULL来代替更常见(如果在读入数据时出现某些错误,该函数也返回NULL)。下面程序演示了一个简单的循环,读入并显示用户输入的内容,直到fgets()读到文件结尾或空行(即,首字符是换行符)。

程序1:

#include <stdio.h>
#define STLEN 10
int main(void)
{
    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;
}

输出:

Enter strings (empty line to quit):
By the way, the gets() function
By the way, the gets() function

Done.

​ 有意思,虽然STLEN被设置为10,但是该程序似乎在处理过过长的输入时完全没问题。程序中的fgets()一次读入STLEN一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()存储换行符有好处也有坏处。坏处是你可能并不想把换行符存储在字符串中,这样的换行符会带来一些麻烦。好处是对于存储的字符串而言,检查末尾是否有换行符可以判断是否读取了一整行。

首先,如何处理掉换行符?一个方法是在已存储的字符串中查找换行符,并将其替换成空字符:

while (words[i] != '\n' && words[i] != '\0')
  i++;
if (words[i] == '\n')
  words[i] = '\0';

其次,如果仍有字符串留在输入行怎么办?一个可行的办法是,如果目标数组装不下一整行输入,就丢弃多余的字符:

while (getchar() != '\n') //读取但不存储输入,包括\n
		continue;

(2)gets_s()

#include <stdio.h>
#define STLEN 10
int main(void)
{
    char words[STLEN];
    int i;
    
    puts("Enter strings (empty line to quit):");
    while (fgets(words, STLEN, stdin) != NULL
                          && words[0] != '\n')
    {
        i = 0;
        while (words[i] != '\n' && words[i] != '\0')
            i++;
        if (words[i] == '\n')
            words[i] = '\0';
        else // must have words[i] == '\0'
            while (getchar() != '\n')
                continue;
        puts(words);        
    }
    puts("done");
    return 0;
}

​ C11新增的gets_s(char* _Buffer, rsize_t _Size)函数(可选)和fgets()类似,用一个参数限制读入的字符数。假设把以上程序中的fgets()换成gets_s(),其他内容不变,那么下面的代码将把一行输入中的前9个字符读入words数组中,假设末尾有换行符:
gets_s (words, STLEN);

gets_s()与fgets()的区别如下。

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

第2个特性说明,只要输入行未超过最大字符数,gets_s()和gets()几乎一样,完全可以用gets_s()替换gets()。

第3个特性说明,要使用这个函数还需要进一步学习。

2.3 gets()、gets_s()、fgets()的适用性

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

​ 如果输入行太长会怎样?使用gets()不安全,它会擦写现有数据,存在安全隐患。gets_s()函数很安全,但是,如果并不希望程序中止或退出,就要知道如何编写特殊的“处理函数”。另外,如果打算让程序继续运行,gets_s()会丢弃该输入行的其余字符,无论你是否需要。

​ 由此可见,当输入太长,超过数组可容纳的字符数时,fgets()函数最容易使用,而且可以选择不同的处理方式。如果要让程序继续使用输入行中超出的字符,可以参考程序1中的处理方法。如果想丢弃输入行的超出字符,可以参考程序2中的处理方法。

​ 所以,当输入与预期不符时,gets_s()完全没有fgets()函数方便、灵活。也许这也是gets_s()只作为C库的可选扩展的原因之一。鉴于此,fgets()通常是处理类似情况的最佳选择。

2.4 自己编写函数

我们可以自己编写函数,用于读取整行输入并用空字符代替换行符,或者读取一部分输入,并丢弃其余部分。
程序2

char * s_gets(char * st, int n)
{
    char * ret_val;
    int i = 0;
    
    ret_val = fgets(st, n, stdin);
    if (ret_val)
    {
        while (st[i] != '\n' && st[i] != '\0')
            i++;
        if (st[i] == '\n')
            st[i] = '\0';
        else // 如果words[i] == '\0'则执行这部分代码
            while (getchar() != '\n')
                continue;
    }
    return ret_val;
}

3 标准输入流 stdin输入:scanf()

int scanf(const char *format, ...) 函数从标准输入流 stdin 读取输入,并根据提供的 format 来浏览输入。

format 可以是一个简单的常量字符串,但是您可以分别指定 %s、%d、%c、%f 等来输出或读取字符串、整数、字符或浮点数。还有许多其他可用的格式选项,可以根据需要使用。现在让我们通过下面这个简单的实例来加深理解:

#include <stdio.h>
int main(void)
{
    int age;             // variable
    float assets;        // variable
    char pet[30];        // string
    
    printf("Enter your age, assets, and favorite pet.\n");
    scanf("%d %f", &age, &assets); // 这里要使用&
    scanf("%s", pet);              // 字符数组不使用&
    printf("%d $%.2f %s\n", age, assets, pet);
    
    return 0;
}

当上面的代码被编译和执行时,它会等待您输入一些文本,当您输入一个文本并按下回车键时,程序会继续并读取输入,显示如下:

Enter your age, assets, and favorite pet.
38
92360.88 lla
38 $92360.88 lla 

scanf()函数使用空白(空格、制表符或换行符)把输入分成多个字段。在依次把转换说明和字段匹配时跳过空白符。注意,上面示例的输入项分成了两行。只要在每个输入项之间输入至少一个空白符即可,可以在一行或多行输入:

Enter your age, assets, and favorite pet.
  42
		2121.45

	guppy
42 $2121.45 guppy

唯一例外的是%c转换说明。根据%c,scanf()会读取每个字符,包括空白符。

注意事项

(1)scanf函数的变量列表,除了字符串,其它的变量前面都要加&,表示取地址,如果不加,程序编译不会报错,但运行时会出现意想不到的后果(1-赋值不成功;2-段错误Core dump)

(2)scanf()和gets()类似,也存在一些潜在的缺点。如果输入行的内容太长,scanf()也会导致数据溢出。不过,在”%s“转换说明中使用字段宽度可防止溢出。

备注:空格、制表符或换行符都是指空白符。

scanf()的转换说明修饰符

​ scanf()函数所用的转换说明与printf函数几乎一致。主要区别是,对于float和double类型,printf()都使用%f、%e、%E、%g和%G转换说明。而scanf()只把它们用于float类型,对于double类型要使用l修饰符。下表列出了常用的转换说明:

image-20210823091640708

可以在上表所列的转换说明中(百分号和转换字符之间)使用修饰符。如果要使用多个修饰符,必须要按下表所列的顺序书写。

image-20210823091657505

image-20210823091730319

scanf()的返回值

scanf()函数返回成功读取的项数:

  • 如果没有读取任何项,且需要读取一个数字而用户输入一个非数值字符串,则返回0;
  • 当检测到“文件结尾时”,返回EOF。

scanf()的运行逻辑

​ 空白字符(制表符、空格和换行符)在 scanf()处理输入时起着至关重要的作用。除了%c模式(读取下一个字符), scanf()在读取输入时会跳过非空白字符前的所有空白字符,然后一直读取字符,直至遇到空白字符或与正在读取字符不匹配的字符。

​ 考虑一下,如果 scanf()根据不同的转换说明读取相同的输入行,会发生什么情况。假设有如下输入行:

-13.45e12#0

​ 如果其对应的转换说明是%d, scanf()会读取3个字符(-13)并停在小数点处,小数点将被留在输入中作为下一次输入的首字符。

​ 如果其对应的转换说明是%f, scanf()会读取-13.45e12,并停在#符号处,而#将被留在输入中作为下一次输入的首字符;然后, scanf()把读取的字符序列-13.45e12转换成相应的浮点值,并储存在foa类型的目标变量中。

​ 如果其对应的转换说明是%s, scanf()会读取-13.45e12#,并停在空格处,空格将被留在输入中作为下一次输入的首字符;然后, scanf()把这10个字符的字符码储存在目标字符数组中,并在末尾加上一个空字符。

​ 如果其对应的转换说明是%c, scanf()只会读取并储存第1个字符,该例中是一个空格。

二、c的输出

在C语言中,有三个函数可以把数据输出到屏幕。

  • putchar:输出单个字符。
  • puts:输出字符串。
  • fputs:输出字符串,一般用于文件的输出。
  • printf:格式化输出函数,可输出常量、变量等。

1 单字符输出:putchar()

int putchar(int _Character) 函数把字符输出到屏幕上,并返回相同的字符。这个函数在同一个时间内只会输出一个单一的字符。您可以在循环内使用这个方法,以便在屏幕上输出多个字符。

#include <stdio.h>
 
int main( )
{
   int c;
 
   printf( "Enter a value :");
   c = getchar( );
 
   printf( "\nYou entered: ");
   putchar( c );
   printf( "\n");
   return 0;
}

当上面的代码被编译和执行时,它会等待您输入一些文本,当您输入一个文本并按下回车键时,程序会继续并只会读取一个单一的字符,显示如下::

Enter a value :runoob

You entered: r

2 字符串输出:puts()

int puts(char const _Buffer)* 函数把字符串 _Buffer和一个尾随的换行符写入到 stdout

示例:

#include <stdio.h>
 
int main( )
{
   char str[100];
 
   printf( "Enter a value :");
   gets( str );
 
   printf( "\nYou entered: ");
   puts( str );
   return 0;
}

当上面的代码被编译和执行时,它会等待您输入一些文本,当您输入一个文本并按下回车键时,程序会继续并读取一整行直到该行结束,显示如下:

Enter a value :runoob

You entered: runoob

3 标准输出流 **stdout输入:printf()

int printf(const char *format, ...) 函数把输出写入到标准输出流 stdout ,并根据提供的格式产生输出。

format 可以是一个简单的常量字符串,但是您可以分别指定 %s、%d、%c、%f 等来输出或读取字符串、整数、字符或浮点数。还有许多其他可用的格式选项,可以根据需要使用。

printf()函数的格式:

printf(格式字符串,待打印项1,待打印项2,....),待打印项1,待打印项2等都是要打印的项。它们可以是变量,常量,甚至是打印之前先要计算的表达式。

示例:

#include <stdio.h>
int main( ) {
 
   char str[100];
   int i;
 
   printf( "Enter a value :");
   scanf("%s %d", str, &i);
 
   printf( "\nYou entered: %s %d ", str, i);
   printf("\n");
   return 0;
}

当上面的代码被编译和执行时,它会等待您输入一些文本,当您输入一个文本并按下回车键时,程序会继续并读取输入,显示如下:

Enter a value :runoob 123

You entered: runoob 123 

注意事项

(1)printf函数的变量列表,变量名前都不需要加&。

printf()的转换说明修饰符

printf()的转换说明

image-20210823091814398

printf()的修饰符

image-20210823091837777

printf的标记

image-20210823091850490

使用修饰符和标记的示例:

/* width.c -- field widths */
#include <stdio.h>
#define PAGES 959
int main(void)
{
    printf("*%d*\n", PAGES);
    printf("*%2d*\n", PAGES);
    printf("*%10d*\n", PAGES);
    printf("*%-10d*\n", PAGES);
    
    return 0;
}


#输出
*959*
*959*
*       959*
*959       *

printf()的返回值

printf()函数返回打印字符的个数,输出错误,则返回负值。

#include <stdio.h>
int main(void)
{
    int bph2o = 212;
    int rv;
    
    rv = printf("%d F is water's boiling point.\n", bph2o);
    printf("The printf() function printed %d characters.\n",
           rv);
    return 0;
}

输出如下:

212 F is water's boiling point.
The printf() function printed 32 characters.

注意,printf()函数的返回值计算针对所有字符数,包括空格和不可见的换行符。

三、总结

​ gets()函数输入一行数据,直至遇到换行符,然后丢弃换行符,存储到字符串变量中,并在末尾添加一个空字符使其成为一个字符串。它经常与puts()函数配对使用,该函数用于显示字符串,并在末尾添加换行符,另外,当输入一行数据,输入的字符超过存储空间,gets()函数也不会丢弃超出的字符,因此会导致内存溢出。

​ fgets()函数输入一行数据,直至遇到换行符,存储换行符到字符串变量中,并在末尾添加一个空字符使其成为一个字符串,通常与fputs()函数配对使用,除非fgets()函数不在字符串末尾添加换行符。fputs()函数与puts()函数不同,不会在末尾添加换行符。fgets()与fputs()一般用于文件的输入输出;另外,输入一行数据,读到最大字符数都未遇到换行符,fgets()函数不会丢弃超出的字符。

​ printf()和scanf()函数对输入和输出提供多种支持。两个函数都使用格式字符串,其中包含的转换说明表明待读取或待打印数据项的数量和类型。另外,可以使用转换说明控制输出的外观:字段宽度、小数位和字段内的布局。

函数 参数 功能 返回值 指定输入的字符数 是否会在末尾存储换行符
getchar() void 输入单个字符,保存到字符变量中 如果读到文件结尾,返回EOF 不会
gets() char* _Buffer 1.输入一行数据,直至遇到换行符,然后丢弃换行符,存储到字符串变量中,并在末尾添加一个空字符使其成为一个字符串。
2.输入一行数据,输入的字符超过存储空间,也不会丢弃超出的字符,导致内存溢出
如果读到文件结尾,返回NULL(空指针) 不会
gets_s() char* _Buffer,rsize_t _Size 1.输入一行数据,直至遇到换行符,然后丢弃换行符,存储到字符串变量中,并在末尾添加一个空字符使其成为一个字符串。
2.输入一行数据,读到最大字符数都未遇到换行符,则丢弃超出的字符
正常返回指向char的指针;
如果读到文件结尾,返回NULL
可以 不会
fgets() char* _Buffer,int _MaxCount, FILE* _Stream 1.输入一行数据,直至遇到换行符,存储换行符到字符串变量中,并在末尾添加一个空字符使其成为一个字符串。
2.输入一行数据,读到最大字符数都未遇到换行符,则不会丢弃超出的字符
正常返回指向char的指针;
如果读到文件结尾,返回NULL
可以
scanf() const char *format, ... 格式化输入函数,一次可以输入多个数据,保存到多个变量中。 输入成功返回成功读取的项数;
如果读到文件结尾,返回EOF;
如果没有读取任何项,且需要读取一个数字而用户输入一个非数值字符串,则返回0
-- 不会
putchar() int _Character 输出单个字符 -- 不会
puts() char const* _Buffer 输出字符串,并在末尾添加换行符 --
fputs() char* _Buffer,FILE* _Stream 输出字符串,不末尾添加换行符 -- 不会
printf() const char *format, ... 格式化输出函数,可输出常量、变量等。 返回打印字符的个数;
输出错误,则返回负值
-- 不会

posted on 2021-08-23 10:14  xufat  阅读(627)  评论(0)    收藏  举报

导航

/* 返回顶部代码 */ TOP