【C Primer Plus第六版 学习笔记】第十一章 字符串和字符串函数

有基础,进阶用,个人查漏补缺

  1. puts()只显示字符串,而且自动在末尾加上换行符

  2. 字符串定义(字符串有字符串常量、char类型数组、指向char的指针)

    1. 字符串常量:

      用双括号括起来,双引号中的字符和编译器自动加入末尾的\0字符,都作为字符串储存在内存中。

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

      用双括号括起来的内容被视为指向该字符串储存位置的指针。

      printf("%s, %p, %c\n", "We", "are", *"space farers");
      //输出:We, 0x100000f61, s
      //%s代表字符串型格式符,%p用于以十六进制的形式打印地址(指针地址),%c代表字符型格式符
      //%x、%X和%p的相同点都是16进制,但是%p会按编译器位数长短(32位/64位)输出地址,不够的补零
      //printf()根据%s打印We
      //根据%p打印地址(如果“are”代表一个地址,printf()将会打印该字符串的首字符地址)
      //*"space farers"表示该字符串所指向地址上储存的值,为该字符串首字符
      
      //字符串常量之间没有间隔或者用空格分割,C语言会将其视为串连起来的字符串常量
      char a[50] = "hello, and"" how are" " you"" today!";
      //等价于
      char a[50] = "hello, and how are you today!";
      
    2. char类型数组:

      在指定数组大小时,要确保数组元素个数至少比字符串长度多1(为了容纳\0),所有未被使用的元素会被自动初始化为0(指的是char形式的空字符\0,不是数字字符0)

      如果忽略数组初始化声明中的数组大小,编译器会自动根据初始化内容进行计算

      const char b1[] = "ni hao";//char数组初始化 
      const char b2[7] = {'n', 'i', ' ', 'h', 'a', 'o', '\0'};//标准的数组初始化,复杂
      //如果没有最后的\0,则是数组;有\0,则是字符串
      
    3. 指向char的指针

      //这两个声明形式几乎一致
      //初始化数组把静态存储区的字符串拷贝到数组中
      const char arl[] = "something is pointing at me.";//28个字符+\0=29个
      //初始化指针只把字符串的地址拷贝给指针
      const char * pt1 = "something is pointing at me.";
      
      1. 数组形式中,arl为该数组首元素地址的别名(&arl[0]),所以arl是地址常量。不能更改arl,如果改变,则意味着改变了数组的储存位置(即地址)。可进行arl+1的操作,但是不能++arl。递增运算符不能用于常量。

      2. 指针形式中,编译器会为指针变量pt1留一个储存位置,把字符串的地址储存在指针变量中。该变量最初指向该字符的首字符,但是它的值可以改变。因此,可以使用递增运算符,++pt1指向第二个字符。

        由于pt1指向const数据,所以应该把pt1声明为指向const数据的指针,这意味着不能用pt1改变它所指向的数据,但是仍然可以改变pt1的值(即pt1指向的位置)。

      3. 如果不修改字符串,就不要用指针指向字符串常量,即建议在把指针初始化为字符串变量时使用const限定符

        //下面语句都引用了字符串“klingon”的内存位置
        char * p1 = "klingon";
        p1[0] = 'f';      //由于是非const,允许修改为f,所以该地址对应的值变成了f
        printf("klingon");//printf()使用的是内存里的值,故"klingon"对应地址里,k变成了f
        printf(": beware the %s!\n", "klingon");//同上
        //输出:
        //flingon: beware the flingon!
        //当要打印"klingon"时,都会打印"flingon"
        
        //建议在把指针初始化为字符串变量时使用const限定符
        const char * p1 = "klingon";
        
  3. 数组和指针的区别

    1. 两者最主要的区别是:数组名ar是常量,指针名pt是变量

      char ar[] = "I love Tim.";       //数组的元素是变量,但数组名不是变量
      const char *pt = "I love Sam."; 
      
    2. 实际使用上:

      1. 都可以使用数组表示法:

        //均输出I love
        for(i=0; i<6; i++)
        {
        	putchar(ar[i]);
        }
        
        for(i=0; i<6; i++)
        {
        	putchar(pt[i]);
        }
        
      2. 都可以进行指针加法操作

        //均输出I love
        for(i=0; i<6; i++)
        {
        	putchar( *(ar + i) );
        }
        
        for(i=0; i<6; i++)
        {
        	putchar( *(pt + i) );
        }
        
      3. 只有指针法可以进行递增操作

        while( *(pt) != '\0' )  //在字符串末尾处停止
        	putchar( *(pt++) );   //打印字符,指针指向下一个位置 
        
        //输出I love Sam.
        
      4. 如果想让数组和指针统一,则

        pt = ar;//pt现在指向数组ar的首元素
        //不能这么做
        ar = pt;//非法构造,赋值运算符左侧必须可以修改(数组元素是变量,但数组名不是变量)
        //但是这么做不会导致pt指向的字符串消失,只是改变了储存在pt中的地址
        
      5. 可以改变a数组中的元素的信息

        ar[7] = 'm';
        *(ar + 7) = 'm';
        
  4. 字符串数组:

    const char *pt[5] = {"one", "tow", "three", "four", "five"};
    char ar[5][40] = {"ONE", "TOW", "THREE", "FOUR", "FIVE"};
    
    1. pt和ar都表示5个字符串
    2. 使用一个下标时都分别表示一个字符串,如pt[0]和ar[0]
    3. 使用两个下标时都分别表示一个字符,如pt[1][2]表示pt数组中第2个指针所指向的字符串的第3个字符,ar[1][2]表示ar数组的第2个字符串的第3个字符
    4. 两者初始化方式一样

    1. pt是一个内含5个指针的数组,在系统中共占40字节

    2. ar是一个内含5个数组的数组,每个数组内含40个char类型的数据,共占用200个字节

    3. 虽然pt[0]和ar[0]都分别表示一个字符串,但是pt和ar的类型并不同

      1. pt中的指针指向初始化时所用的字符串常量的位置,这些常量被储存在静态内存中
      2. ar中的数组则被储存着字符串常量的副本,所以每个字符串都被储存了两次
    4. pt可看做一个不规则数组,每行长度不一样(pt数组的指针元素所指向的字符串不必储存在连续的内存中);ar可看作一个矩形二维数组,每行长度都是40字节

      【C Primer Plus第六版 学习笔记】第十一章 字符串和字符串函数_第1张图片


    1. 综上所述,如果要用数组表示一系列待显示的字符串,就使用指针数组,效率更高,但是指针指向的字符串常量不能修改;

      而字符串数组可以修改,如果要改变字符串或为字符串输入预留空间,不要使用指向字符串常量的指针

  5. 字符串输入

    要把一个字符串读入程序,要先预留储存该字符串的空间,然后用输入函数获取该字符串

    1. 分配空间

      如果未分配空间,如

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

      会通过编译,但是在读入的过程中,name可能会擦写掉程序中的数据或代码。因为scanf()要把信息拷贝到参数指定的地址上,但此时该参数是一个未初始化的指针,name可能会指向任何地方。

      解决方法:在声明时显式指明数组大小,或使用C库函数分配内存(12章介绍)

      char name[81];
      
    2. gets()(切勿使用)

      1. 读取整行输入,直至遇到换行符,然后丢弃换行符,储存其余字符,并在末尾添加\0使其成为C字符串。
      2. 常和puts()配对使用,该函数用于显示字符串,并在末尾添加换行符。即gets丢弃,puts添加。
      3. gets()只知道数组开始处,无法检查数组是否装得下输入行,如果输入过长,则会导致缓冲区溢出,即多余字符超出了指定的目标空间。
        1. 如果这些多余的字符只是占用了尚未使用的内存,就不会立刻出现问题
        2. 如果它们擦写掉程序中的其他数据,会导致程序异常终止
      4. 不建议使用gets()
    3. gets()代替品:

      1. C11不一定支持,专门用于处理文件输入

      2. fgets()

        1. char *fgets(char *str, int n, FILE *stream)

          • str – 这是指向一个字符数组的指针,该数组存储了要读取的字符串。

          • n – 这是要读取的最大字符数(包括最后的空字符),所以将读入n-1个字符或者读到第一个换行符为止。通常是使用以 str 传递的数组长度。

          • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。即指明要读入的文件。如果读入从键盘输入的数据,则以stdin作为数据,该标识符定义在stdio.h。

          • 如果成功,该函数返回char指针,与传入的第一个参数str相同。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针(NULL)。如果发生错误,返回一个空指针。

          • fgets会读入换行符,puts则不会添加换行符。与gets和puts相反。

          • 举例代码:

            #include 
            #define STLEN 14
            int main(void)
            {
            	char words[STLEN];
            	//该函数用于显示字符串,并在末尾添加\n
            	puts("Enter a string.");
            	//输入apple pie,apple pie\n\0会被存入
            	fgets(words, STLEN, stdin);
            	puts(words);   //会丢弃换行符
            	fputs(words, stdout);
            	return 0;
            }
            
            输出:
            Enter a string.————puts()输出,末尾会添加\n
            **apple pie**      ————fgets()输入,会把\n存入,即apple pie\n\0
            ****apple pie      ————puts()输出,末尾会添加\n
            
            apple pie      ————fputs()输出,末尾不会添加\n
            
        2. 举例代码:虽然STLEN被设置为10,但是该程序在处理过长的输入时完全没有问题。

          1. 程序中的fgets()一次读入STLEN-1 = 9个字符,故一开始只读入了”By the wa”(10个字符),并储存为By the wa\0
          2. 接着fputs()打印该字符,末尾不会添加\n(本此迭代没有),不会换行
          3. while循环进入下一轮迭代,读入”y, the ge” ,并储存为y, the ge\0
          4. 直至读入”tion\n”,由于字符串中的\n被输出,光标被移至下一行开始处
          5. 在按下enter之前,输入都被储存在缓冲区,按下后输入了一个\n,并把整行输入发送给fgets()。fputs()把字符发送给另一个缓冲区,当发送换行符的时候,缓冲区的内容被发送到屏幕。
          #include 
          #define STLEN 10
          int main(void)
          {
          	char words[STLEN];
          	puts("Enter a string.");//该函数会在末尾添加\n
          
          	//当读到文件末尾或者读到空字符,或者读入了一行中的第一个字符为\n
          	while(fgets(words, STLEN, stdin) != NULL && words[0] != '\n')
          		fputs(words, stdout);
          
          	puts("Done.");//该函数会在末尾添加\n
          	return 0;
          }
          输出:
          Enter a string.                 ————puts()输出,末尾会添加\n
          **By the way, the gets() function** ————fgets()输入,会把\n存入
          By the way, the gets() function ————fputs()输出,末尾会丢弃\n
          **Also enter another string**       ————输入
          Also enter another string ****      ————输出
                                          ————输入前,光标就在该行,然后输入\n
          Done.														    则该行第一个字符为\n,结束
          
          
        3. 如何处理换行符?解决方法:

          #include 
          #define STLEN 10
          int main(void)
          {
          	char words[STLEN];
          	int i;
          	puts("Enter a string.");//该函数会在末尾添加\n
          
          	//当读到文件末尾或者读到空字符,或者读入了一行中的第一个字符为\n
          	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部分便丢弃输入行的剩余字符
          		else
          			while(getchar() != '\n')//读到\0就会结束
          				continue;
          	}
          	puts("Done.");//该函数会在末尾添加\n
          	return 0;
          }
          /*
          输出:
          Enter a string.
          **This                                 ————输入,存为**This\0
          This
          **program seems                        ————输入,存为**program s\0
          program s
          **unwilling to accept long line        ————输入,存为**unwilling\0
          unwilling
          
          done
          
        1. char *gets_s( char *str, rsize_t n )
    4. 和fgets()类似,区别如下:

      1. gets_s()只从标准输入中读取数据,故不需要第3个参数
      2. 如果gets_s()读到换行符,会丢弃它而不是储存它
      3. 如果gets_s()读到最大字符数都没有读到换行符,会执行:
        1. 首先把目标数组中的首字符设置为空字符
        2. 读取并丢弃随后的输入,直至读到换行符或文件结尾
        3. 最后返回空指针
    5. char *gets_s( char *str, rsize_t n );

      1. 读stdin入指向的字符数组,str直到找到换行符或发生文件结束。在读入数组的最后一个字符后立即写入空字符。换行符被丢弃,但不存储在缓冲区中。
      2. 读取字符,stdin直到找到换行符或发生文件结束。只将大部分n-1字符写入指向的数组中str,并始终写入终止空字符(除非str是空指针)。换行符(如果找到)将被丢弃,并且不计入写入缓冲区的字符数。在运行时检测到以下错误并调用当前安装的约束处理函数:
      • n是零
      • n大于RSIZE_MAX
      • str 是一个空指针
      • 将n-1字符存储到缓冲区后,不会遇到endline或eof 。

      无论如何,在调用约束处理程序之前,gets_s首先完成读取和放弃字符,stdin直到换行符,文件结束条件或读取错误。

      返回值

      成功时str,失败NULL。

      1. gets()、fgets()、gets_s()的适用性比较
        1. 当输入行太长,gets()会擦写现有数据,gets_s()会丢弃多余字符,故fgets()最好
        2. 故首选fgets()
    6. scanf()

      1. 若fgets()、gets()函数像获取字符串函数,则scanf()是获取单词函数

      2. scanf()如果使用%s转换说明,以下一个空白字符(空行、空格、制表符、换行符)作为字符串结束(字符串不包括空白字符)

      3. 字符宽度和scanf():口表示空格

        输入语句 原输入序列 name中的内容 剩余输入序列
        scanf(’%s’, name); fleebert口hup fleebert 口hup
        scanf(’%5s’, name); fleebert口hup fleeb ert口hup
        scanf(’%5s’, name); ann口ular ann 口ular
      4. 返回值为成功读取的项数或者EOF(读到文件结尾)

      5. 举例代码

      #include 
      int main(void)
      {
      	char name1[11], name2[11];
      	int count;
      	printf("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;
      }
      /*输出
      1.两个名字的字符个数都没有超过字段宽度,第1个单词刚好能被完全存入缓冲区
      Enter 2 names.
      **jesse jukes**
      I read the 2 names jesse and jukes.
      2.前一个单词字符个数没有超过限制5,故**遇到空格就算结束**,第2个名字只读入了前10个字符
      Enter 2 names.
      **liza applebottham**
      I read the 2 names liza and applebotth.
      **3.portensia的后4个被存入name2中,
      因为第2次调用scanf()时从上一次调用结束的地方继续读取数据**
      Enter 2 names.
      **portensia callowit**
      I read the 2 names porte and nsia.
      
  6. 字符串输出:puts()、fputs()、pinrtf()——337页最后一段话有误,应该写puts()

    1. puts()

      1. 传入字符串地址作为参数即可,遇到空字符结束,会自动换行

        #include 
        int main(void)
        { //用双引号括起来的是字符串常量
        	//并且会被视为该字符串的地址
        	char str1[80] = "I love you.";
        	const char * str2 = "You love me.";
        
        	puts("Please say.");
        	puts(str1);
        	puts(str2);
        	puts(&str1[5]);
        	puts(str2 + 4);
        	return 0;
        }
        

        输出:

        Please say.

        I love you.

        You love me.

        e you.

        ——&str1[5]是str1数组的第6个元素(e),从该元素开始输出

        love me.

        ——str2 + 4指向l,从该元素开始输出

    2. fputs()

      int fputs(const char *str, FILE *stream)

      1. 是puts()针对文件定制的版本,区别如下:
        1. fputs()第2个参数指明要写入数据的文件,如果要打印在显示器上,可以用定义在stdio.h中的stdout
        2. 不会自动换行
          函数 特点 函数 特点
          gets()(不使用) 丢弃\n puts() 添加\n,会自动换行
          fgets() 保留\n fputs() 不添加\n,不会自动换行
    3. printf()

      1. 不会自动换行
      2. 和puts相比,计算机执行时间更长
  7. 字符串函数:strlen()、strcat()、strcmp()、strncmp()、strcpy()、strncpy()

    函数 完整函数声明 作用 返回值 备注
    strlen() size_t strlen(const char *str) 用于统计字符串长度 返回字符串的长度
    strcat() char *strcat(char *dest, const char *src) 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。 返回一个指向最终的目标字符串 dest 的指针(地址) 无法检查第一个数组是否能容纳第2个数组,可能导致溢出问题,和gets()类似
    strncat() char *strncat (char *dest, const char *src, size_t n) 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止 返回一个指向最终的目标字符串 dest 的指针(地址) 解决上述strcat()问题
    strcmp() int strcmp(const char *str1, const char *str2) 把 str1 所指向的字符串和 str2 所指向的字符串进行比较 str1str2,则返回值>0;str1=str2,则返回值=0 比较的是字符串内容,而不是地址,也不是整个数组,因为数组大小和字符串大小不一定相同。当字符串长度不一致时,多的部分比较的是空字符
    strncmp() int strncmp(const char *str1, const char *str2, size_t n) 把 str1 和 str2 进行比较,最多比较前 n 个字符。 str1str2,则返回值>0;str1=str2,则返回值=0
    strcpy() char *strcpy(char *dest, const char *src) 把 src(源字符串) 所指向的字符串复制到 dest(目标字符串) 返回一个指向最终的目标字符串 dest 的指针(地址) 第一个指针应指向一个数据对象,如数组,并且该对象有足够的空间。无法检查第一个数组是否能容纳第2个数组,可能导致溢出问题,和gets()类似
    strncpy() char *strncpy(char *dest, const char *src, size_t n) 把 src 所指向的字符串复制到 dest,最多复制 n 个字符。 返回最终复制的字符串 当 src 的长度小于 n 时,dest 的剩余部分将用空字节填充。

    strcpy()、strncpy()注意:

    1. 声明数组将分配储存数据的空间,而声明指针只分配储存一个地址的空间

    2. 目标字符串必须先初始化

      char * str;
      strcpy(str, "hello");//错误,str未被初始化,该字符串会被拷贝到任何地方!!!
      
    3. 第一个参数不必指向数组开始

      const char * orig = "beast";
      char copy[40] = "be the best that you can be.";
      char * ps;
      puts(orig);//输出beast
      puts(copy);//输出be the best that you can be.
      ps = strcpy(copy + 7, orig);
      puts(copy);//输出be the beast,因为copy+7指向best中的b,从此处开始粘贴orig
      puts(ps);//输出beast,strcpy()返回一个指向最终的目标字符串 dest 的指针(地址)
      
    4. strncpy(dest, src, n)中,如果拷贝到第n个字符时还未拷贝完整个源字符串,就不会拷贝空字符,所以拷贝的副本中不一定有空字符。

      鉴于此,可以把n设置为比目标数组大小小1,然后把数组最后一个元素设置为空字符:

      #define TARGSIZE 7
      strncpy(dest, str, TARGSIZE-1);
      dest[TARGSIZE-1] = '\0';
      
  8. sprintf()

    int sprintf(char *str, const char *format, …)

    • 和printf()类似,这个是把数据写入字符串中,而不是打印在显示器,即发送格式化输出到 str 所指向的字符串

    • str – 这是指向一个字符数组的指针,该数组存储了 C 字符串。

    • format – 这是字符串,包含了要被写入到字符串 str 的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。format 标签属性是 %[flags][width][.precision][length]specifier

    • 如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。如果失败,则返回一个负数。

    • 例子:

      #include 
      int main()
      {
         char str[80];
         sprintf(str, "Pi 的值 = %f", 3.1415);
         puts(str);
         return(0);
      }
      /*输出:
      Pi 的值 = 3.1415
      

你可能感兴趣的:(C语言,c语言,学习,笔记)