Wangyt

导航

字符串与字符串函数 - 字符串 & 字符串输入

一、表示字符串与字符串I/O

  【字符串 - 以空字符(\0)结尾的 char 类型数组】

  1、字符串字面量(字符串常量)

  用双引号括起来的内容称为字符串常量(string literal),也叫做字符串常量(string constant)。双引号中的字符和编译器自动加入末尾的 \0 字符,都作为字符串存在内存中。

  ANSI C 标准规定,如果字符串常量之间没有间隔,或者用空白字符分隔,C 会将其视为串联起来的字符串常量。

char chr1[50] = "Hello, and""how are" " you"
                     " today!";
char chr2[50] = "Hello, and how are you today!";    //chr1 与chr2 中存储的内容一致

  【如果要在字符串内部使用双引号,必须在双引号前面加上一个反斜杠(\)。】

  字符串常量属于静态存储类别(static storage class),这说明如果在函数中使用字符串常量,该字符串只会被储存一次,在整个程序的生命周期内存在,即使函数被调用多次。

  2、字符串数组的初始化

  定义字符串数组时,必须让编译器知道需要多少空间。

  可以在初始化数组的时候顺便填充数组,也可以先初始化,之后再填充。不管如何,初始化的时候,数组的大小对于编译器来说,是必须要确定的。

  3、数组与指针的区别

  初始化数组:把静态存储区的字符串拷贝到数组中;初始化指针:只把字符串的地址拷贝给指针。

#define MSG "I'm special!"
#include <stdio.h>

int main(void) {
    char ar[] = MSG;
    const char * pt = MSG;
    printf("address of \"I'm special!\": %p \n", "I'm special!");
    printf("        address ar: %p \n", ar);
    printf("        address pt: %p \n", pt);
    printf("        address of MSG: %p \n", MSG);
    printf("address of \"I'm special!\": %p \n", "I'm special!");
    return 0;
}

 

  

 

 

 

  通过上述的结果我们知道:

  第一,pt 和 MSG 的地址一样,而 ar 的地址不同;

  第二,虽然字符串自变量“I‘m special” 在程序的两个 printf() 函数中出现了两次,但是编译器只使用了一个存储位置,而且与 MAG 的地址相同,编译器可以把多次使用的相同字面量储存在一处或多处。也有的编译器会在不同的位置存储三个“I'm special”;

  第三,静态数据使用的内存与 ar 使用的动态内存不同,不仅值不同,特定编译器甚至使用不同的位数来表示两种内存。

char heart[] = "Hello!";
const char * head = "Hello!";

  首先,heart 是常量名,head 是变量。其两者之间的区别如下:

  第一,两者都可以使用数组表示法【heart[i] 、head[i]】;

  第二、两者都能进行指针加法操作【*(heart + 1) 、*(head + 1)】;

  第三、只有指针可以进行递增操作;

  第四、可以通过 heart 改变数组中元素的信息。

 

二、字符串输入

  1、gets() 函数

#define STLEN 50
...
char words[STLEN];
...
gets(words);    //典型用法
...
puts(words);
...

  函数功能:读取整行输入直至遇到换行符,然后丢弃换行符,储存其余字符,并在这些字符的末尾添加一个空字符使其成为一个 C 字符串。经常和 puts() 函数配对使用【puts() 函数会在待输出字符串末尾添加一个换行符】。

  问题:gets() 函数只知道数组的开始处,并不知道数组中具体有多少个元素。如果输入的字符串过长,会导致缓冲区溢出(buffer overflow),即有多余的字符超出了指定的目标空间。如果这些多余的字符只是占用了那些尚未使用的内存,就不会立即出现问题;如果擦写程序中的其他数据,会导致程序异常终止;或者还有其他的情况。在 UNIX 系统中,“Segmentation fault”说明当前程序视图访问未分配的内存。

  【C99 、C11 标准不建议使用该函数】

  2、fgets() 函数

#define STLEN 14
...
char words[STLEN];
...
fgets(words, STLEN, stdin);
...
fputs(words, stdout);
...

  fgets() 函数与 gets() 函数之间的区别如下:

  第一,fgets() 函数的第 2 个参数指明了读入字符的最大数量,如果该参数是 n ,那么 fgets() 函数将读入 n - 1 个字符,或者读到遇到第一个换行符为止;

  第二,如果 fgets() 函数读到一个换行符,会把它储存在字符串中,gets() 函数会丢弃换行符;

  第三,fgets() 函数的第3个参数指明要读入的文件,如果读入的是从键盘输入的数据,则以 stdin (标准输入)作为参数,该标识符定义在 stdio.h 中;与 fputs() 函数配套使用【fputs() 函数的第 2 个参数指明其需要写入的文件,如果要在显示器上进行显示,应使用 stdout(标准输出)作为参数进行传递】【fputs() 函数不会在待输出字符串末尾添加一个换行符】。

#define STLEN 10
#include <stdio.h>

int main(void) {
    char words[STLEN];
    
    puts("Enter string (empty line to quit):");
    while (fgets(words, STLEN, stdin) != NULL && words[0] != '\n') 
        fputs(words, stdout);
    puts("Done.");

    return 0;
}

  在上述的例子中,虽然字符串的长度被限制在了10以内,但是从结果上来看,长字符串的输入并没有受到明显的影响。

   【在程序中,fgets() 函数一次读入 STLEN - 1 个字符,即第一次只读入了“By the wa”,存储在 words 数组中的内容为‘By the wa\0’,接着 fputs() 函数打印该字符串,没有换行然后 while 循环进入下一轮迭代, fgets() 函数继续从剩余的输入中读取数据,即读入“y, the ge”,并存储为 y, the ge\0,接着 fputs() 函数在刚才打印字符串的这一行接着打印第2次读入的字符串;继续循环,直到读入最后的“tion\n”,fgets() 将其存储为 tion\n\0,fputs()函数将其输出,由于字符串中的 \n,光标被移至下一行开始处。】

  【系统使用缓存 I/O,在用户键入 ENTER 键之前,输入的内容都会被存储在临时存储区(即,缓存区)中。键入 ENTER 键之后,在输入缓存区中增加一个换行符,并把整行输入发送给 fgets() 函数。fputs() 函数把字符发送给另一个缓冲区,当发送换行符时,缓冲区中的内容被发送至屏幕上。】

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

  3、gets_s() 函数

  gets_s() 函数与 fgets() 函数类似,用一个参数限制读入的字符数。

gets_s(words, STRLEN);

  gets_s() 与 fgets() 的区别:1 - gets_s() 函数只从标准输入中读取数据,所以不需要第 3 个参数;2 - 如果 gets_s() 函数读取到换行符,将丢弃换行符,而不是继续存储换行符;3 - 如果 gets_s() 函数读到最大字符数的时候都没有读取到换行符,则进行以下操作【1,把目标数组中的首字符设置为空字符,读取并丢弃随后的输入直到读到换行符或文件结尾;2,返回空指针;3,调用依赖实现的“处理函数”(或自己选择的其他函数),可能会中止或退出程序。】。

  【总结:1 - 如果输入行未超过最大字符数,gets_s()、gets()、 fgets() 三函数没有太大区别,只不过 fgets() 函数会保留输入末尾的换行符作为字符串的一部分;2 - 如果输入行太长,gets() 函数并不安全,会擦写现有数据,存在安全隐患,gets_s() 函数虽然较为安全,但是并不希望程序的中止或退出。 】

  4、sacnf() 函数

  scanf() 函数更像是“获取单词”的函数,而不是“获取字符串”的函数。

  scanf() 函数的典型用法是读取并转换混合数据类型为某种标准格式。【输入行过长,可能也会导致 scanf() 函数读取溢出,但是,在 %s 转换说明中指定字段宽度可在一定程度上解决该问题。】

posted on 2019-07-07 15:52  Wangyt  阅读(688)  评论(0编辑  收藏  举报