11.2字符串输入


二、字符串输入

2.1 分配空间

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

  • 分配空间有两种方法:第一种是在声明时显示指明数组的大小,

    如,char name[81]; ,现在 name 是一个已分配块(81字节)的地址。

  • 第二种方法是使用 C 库函数来分配内存。

2.2 不幸的 gets() 函数

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

  • gets() 函数经常和 puts() 函数配对使用,用于显示字符串,并在末尾添加换行符。

    // 使用 gets() 和 puts()                输出:
    #include <stdio.h>                      //Enter a string, please.
    #define STLEN 81                        //I want to learn about string theory!(输入)
    int main(void)                          //Your string twice:
    {                                       //I want to learn about string theory!   
        char words[STLEN];                  //I want to learn about string theory!
        puts("Enter a string, please.");    //Done
        gets(words); // 典型用法 
        
        printf("Your string twice:\n"); 
        printf("%s\n", words); 
        puts(words); 
        puts("Done."); 
        
        return 0; 
    } 
    //整行输入(除了换行符)都被储存在 words 中,puts(words)和 printf("%s\n, words")的效果相同。
    
  • gets() 函数只知道数组的开始处,并不知道数组中有多少个元素。

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

  • 如果这些多余的字符只是占用了尚未使用 的内存,就不会立即出现问题;如果它们擦写掉程序中的其他数据,会导致程序异常中止;或者还有其他情况。

2.3 gets() 的替代品

2.3.1 fgets() 函数(和 fputs())

  • fgets() 函数通过第 2 个参数限制读入的字符数来解决溢出的问题。
  • fgets() 和 gets() 的区别:
    • fgets() 函数的第2个参数指明了读入字符的最大数量。如果该参数的值是 n,那么 fgets() 将读入 n-1 个字符,或者读到遇到的第一个换行符为止。
    • 如果 fgets() 读到一个换行符,会把它储存在字符串中,gets() 会丢弃换行符。
    • fgets() 函数的第 3 个参数指明要读入的文件。如果读入从键盘输入的数据,则以 stdin(标准输入)作为参数,该标识符定义在stdio.h 中。
  • 因为 fgets() 函数把换行符放在字符串的末尾(假设输入行不溢出),通常要与 fputs() 函数(和 puts() 类似)配对使用,除非该函数不在字符串末尾添加换行符。
//使用 fgets() 和 fputs()            输出:Enter a string, please.
#include <stdio.h>                    // apple pie(输入)
#define SELEN 14                      // Your string twice (puts(), then fputs()):
int main(void)                        // apple pie
{                                     //
    char words[SELEN];                // apple pie
    puts("Enter a string, please.");  // Enter another string, please.
    fgets(words, SELEN, stdin);       // strawberry shortcake(输入)
    printf("Your string twice (puts(), then fputs()):\n");
    puts(words);                      // Your string twice (puts(), then fputs()):
    fputs(words, stdout);             // strawberry sh
                                      // strawberry shDone.
    puts("Enter another string, please.");   
    fgets(words, SELEN, stdin);             
    printf("Your string twice (puts(), then fputs()):\n");
    puts(words);
    fputs(words, stdout);
    puts("Done.");
 
    return 0;
}
//第 1 次输入 apple pie,比 fgets() 读入的整行输入短,因此 apple pie\n\0 被储存在数组中。当 puts() 显示该字符串时又在末尾添加了换行符,apple pie 后面有一行空行。因为fputs()不在字符串末尾添加换行符,所以并未打印出空行。
//第 2 行输入 strawberry shortcake,超过了指定的大小,所以 fgets() 只读入了 13 个字符,并把 strawberry sh\0 储存在数组中。
  • fputs() 函数的第 2 个参数指明它要写入的文件。如果要显示在计算机显示器上,应使用 stdout(标准输出)作为该参数。
  • fputs() 函数返回指向 char 的指针。
  • 如果一切进行顺利,该函数返回的地址与传入的第 1 个参数相同。
  • 如果函数读到文件结尾,它将返回一个特殊的指针:空指针,该指针保证不会指向有效的数据。
  • 在代码中,可以用数字 0 来代替,不过在 C 语言中用宏 NULL 来代替更常见(如果在读入数据时出现某些错误,该函数也返回 NULL)。
// fgets3.c -- 使用 fgets() 
#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 //遇到空字符,丢弃输入行的剩余字符
		while (getchar() != '\n') 
			continue; 
	puts(words); 
	}
    puts("done"); 
    return 0; 
}

2.3.2 空字符和空指针

  • 空字符(或'\0')是用于标记 C 字符串末尾的字符,其对应字符编码是 0。由于其他字符的编码不可能是 0,所以不可能是字符串的一部分。
  • 空指针(或 NULL)有一个值,该值不会与任何数据的有效地址对应。通常,函数使用它返回一个有效地址表示某些特殊情况发生,例如遇到文件结尾或未能按预期执行。
  • 空字符是整数类型,而空指针是指针类型。
  • 空字符和空指针都可以用数值 0 来表示,但是两者是不同类型的 0。
  • 空字符是一个字符,占 1 字节;而空指针是一个地址,通常占 4 字节。

2.3.3 gets_s() 函数

  • gets_s() 只从标准输入中读取数据,所以不需要第 3 个参数。

  • gets_s() 会丢弃读到的换行符。

  • 如果gets_s()读到最大字符数都没有读到换行符,会执行以下几步:

    • 首先把目标数组中的首字符设置为空字符,读取并丢弃随后的输入直至读到换行符或文件结尾,然后返回空指针。
    • 接着,调用依赖实现的“处理函数”(或选择其他函数),可能会中止或退出程序。
  • 如果输入行太长,使用 gets() 会擦写现有数据,存在安全隐患。

  • gets_s() 函数很安全,但是,如果不希望程序中止或退出,就要知道如何编写特殊的“处理函数”。

  • 如果打算让程序继续运行,gets_s() 会丢弃该输入行的其余字符,无论你是否需要。

  • 当输入与预期不符时,gets_s() 完全没有 fgets() 函数方便、灵活。鉴于此,fgets() 通常是处理类似情况的最佳选择。

2.3.4 s_gets()函数

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

  • 下方为我们自己创建的处理这种情 况的标准函数:

    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 ret_val; 
    }
    //们设计的 s_gets()函数并不完美,它最严重的缺陷是遇到不合适的输入时毫无反应。丢弃多余的字符时,既不通知程序也不告知用户。
    //丢弃过长输入行中的余下字符原因:输入行中多出来的字符会被留在缓冲区中,成为下一次读取语句的输入。
    

2.4 scanf() 函数

  • scanf()和gets()或fgets()的区别在于它们如何确定字符串的末尾:
    • scanf()更像是“获取单词”函数,而不是“获取字符串”函数;
    • 如果预留的存储区装得下输入行,gets()和fgets()会读取第1个换行符之前所有的字符。
    • scanf()函数有两种方法确定输入结束。无论哪种方法,都从第1个非空白字符作为字符串的开始。如果使用%s转换说明,以下一个空白字符(空行、空格、制表符或换行符)作为字符串的结束(字符串不包括空白字符)
    • 根据输入数据的性质,用 fgets() 读取从键盘输入的数据更合适。因为,scanf()无法完整读取书名或歌曲名,除非这些名称是一个单词。


作业

11.13.1.& 11.13.2

#include <stdio.h>
#define LEN 10
char * getnchar(char * str, int n);
int main(void)
{
    char input[LEN];//存储字符串的数组
    char *check;

    printf("Enter string:\n"); 
    check = getnchar(input, LEN - 1);

    if (check == NULL)  
 	puts("Input failed.");
    else
 	puts(input);
    puts("Done.\n");
 
    return 0;
}
char * getnchar(char * str, int n) 
{     
    int i;     
    char ch; 
    
    for (i = 0; i < n; i++)//读取9个字符,超过跳出。     
    {         
	ch = getchar();         
	if (ch != EOF && ch != ' ' && ch != '\n' && ch != '\t')            
	    str[i] = ch;         
	else        //读取到文件结尾,空白,换行符,制表符结束      
	    break;     
	}     
	if (ch == EOF)       
	    return NULL;     
	else     
	{         
	    str[i] = '\0';    
	    return str;     //返回指针名 
	} 
}

posted @   fidelity_233  阅读(94)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示