C 语言学习 第七章 字符串

 本人要开学了,之后的更新会慢一些。希望您能理解。
上章入口:【C 语言学习 第六章 数组和指针】

 

  •        字符串(character string)是以空字符(\0)结尾的 char 数组。在正式开始介绍前,您必须了解一下字符串常量。
           字符串常量(string constant),又称字符串文字(string literal),是指位于一对双引号中的任何字符,双引号里的字符加上编译器自动提供的结束标志 \0 字符,作为一个字符串被存储在内存里。
           同时,字符串常量属于静态存储(static storage)类。静态存储是指如果在一个函数中使用字符串常量,即使是多次调用了这个函数,该字符串在程序的整个运行过程中只存储一份。
           数组和指针都可以用来使用字符串常量但是它们的用法存在区别。但我们需要谨记的是,声明一个数组将为数据分配存储空间;而声明一个指针只为一个地址分配存储空间。具体的会在下面出现的示例中。

  • 1. 字符串的 I/O

    • 1.1 字符串输入

    • 创建存储空间

    •        要做的第一件事是建立一个空间以存放读入的字符串。正如前面提过的,这意味着需要分配足够大的存储区来存放希望读入的字符串。不要指望计算机读的时候会先计算字符串的长度,然后为字符串分配空间。计算机是不会这么做的(除非您写了一个函数命令它这么做)。如果您写下下面语句:
      char *name;
      scanf("%s",name);
              这可能会通过编译器,但是在读入 name 的时候,name 会覆盖程序中的数据和代码,并可能导致程序异常终止。这是因为 scanf()把信息复制到由参数给定的地址中,而在这种情况下,参数是个未初始化的指针:name 可能指向任何地方。绝大多数程序员认为这很搞笑,但仅限于这出现在别人的程序中时。
              最简单的方法就是在声明中明确指出数组大小:
      char name[31];
             这是一个存储 31 个字符的数组,但它只能用来存储 30 个字符,因为它需要留一个字符来存储 \0 。如果不这么做的话,那么它可能会出现您意想不到的结果。例如程序 1。
    • 程序 1:
      #include<stdio.h>
      int main(void)
      {
          char name[4]={'1','2','3','4'};
          printf("%s",name);
          return 0;
      }
      结果:
      1234��
       (我用的是 vscode 如果用 vs 您更可能见到 “烫” 这个字)
    • gets 函数

    • char * gets ( char * str );

             gets ()(代表 get string)函数对于交互式程序非常方便。它从系统的标准输入设备(通常是键盘)获得…个字符串。因为字符串没有预定的长度,所以 gets() 需要知道输入何时结束。解决办法是读字符串直到遇到一个换行字符(\n),按回车键可以产生这个字符。它读取换行符之前(不包括换行符)的所有字符,在这些字符后添加一个空字符(\0),然后把这个字符串交给调用它的程序。它将读取换行符并将其丢弃,这样下一次读取就会在新的一行开始。程序 2 是一个使用 gets() 的简单例子。

    • 程序 2:

      #include<stdio.h>
      #define MAX 81
      int main(void)
      {
          char name1[MAX],*name2;
          printf("Hi, what's your name?\n");
          name2=gets(name1);
          printf("%s? Hi, %s!\n",name1,name2);
          return 0;
      }

      结果:
      Hi, what's your name?
      Cunnian【Enter】
      Cunnian? Hi, Cunnian!

    •        据此,我们可以知道 gets() 函数的两种输入方式:
             •它使用一个地址把字符串赋予 name。
             • gets () 的代码使用 return 关键字返回字符串的地址,程序把这个地址分配给 ptr。注意到 ptr 是一个 char 指针,这意味着 gets() 必须返回一个指向 char 的指针值。
             ANSIC 要求 stdio.h 头文件包括 gets() 的函数原型。您不需要亲自声明这个函数,只须记住包含这个头文件即可。但是一些C的旧版本要求您提供 gets() 的函数声明。
             附带提一下,不要混请空指针和空字符。空指针是一个地址,而空字符是一个 char  类型的数据对象,其值 0 。数值上两者都可以用 0 表示,但是它们的概念不同:NULL 是一个指针,而 \0 是一个 char 类型的常量。

    • fgets 函数

    • char * fgets ( char * str, int num, FILE * stream );
              gets() 的一个不足是它不检查预留存储区是否能够容纳实际输入的数据。多出来的字符简单地溢出到相邻的内存区。fgets() 函数改进了这个问题,它让您指定最大读入字符数。由于 fgets () 是为文件 I/O 而设计的,在处理键盘输入时就不如 gets() 那么方便。fgets() 和 gets() 有三方面不同:
              • 它需要第二个参数来说明最大读入字符数。如果这个参数值 fgets() 就会读取最多 n-1 字符或者读完一个换行符为止,由这一者中最先满足的那个来结束输入。
              • 如果 fgets() 读取到换行符,就会把它存到字符串里,而不是像 gets() 那样丢弃它。
              • 它还需要第三个参数来说明读哪一个文件。从键盘上读数据时,可以使用stdin(代表 standard input)作为该参数,这个标识符在 stdio.h 中定义。
              程序 3 使用 fgets() 代替程序 2 中的 gets() 。
    • 程序 3:
      #include<stdio.h>
      #define MAX 81
      int main(void)
      {
          char name1[MAX],* name2;
          printf("Hi, what's your name?\n");
          name2 = fgets(name1,MAX,stdin);
          printf("%s? Hi, %s!\n", name1, name2);
          return 0;
      }

      结果:
      Hi, what's your name?
      Cunnian【Enter】
      Cunnian
      ? Hi, Cunnian
      !
      ( fgets() 把换行符存储到字符串里,这样每次显示字符串时就会显示换行符。本章后面“字符串其他函数”小节将会介绍如何用 strchr() 来定位和删除换行符。)

    • scanf 函数

    • int scanf ( const char * format, ... );

             前面您已经使用了带有 %s 格式的 scanf() 函数来读入一个字符串。scanf() 和 gets() 主要的差别在于它们如何决定字符串何时结束。scanf() 更基于获取单词(get word)而不是获取字符串(get string);而 gets() 函数,正如您所看到的,会读取所有的字符,直到遇到第一个换行符为止。scanf() 使用两种方法决定输入结束。无论哪种方法,字符串都是以遇到的第一个非空白字符开始。如果使用 %s 格式,字符串读到(但不包括)下一个空白字符(比如空格、制表符或换行符)。如果指定了字段宽度,比如 %10s ,scanf() 就会读入 10 个字符或直到遇到第一个空白字符,由二者中最先满足的那个终止输入。
             回忆一下,scanf() 函数返回一个整数值,这个值是成功读取的项目数;或者遇到文件结束时返回一个 EOF 。程序 4 是 scanf() 的使用示例。

    • 程序 4:

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

      结果:
      Please enter 2 names.
      Zhangsan Lisi【Enter】
      I read the 2 names Zhang and san.

    •        根据所需输入的特点,用 gets() 从键盘读取文本可能要更好,因为它更容易被使用、更快,而且更简洁。scanf() 主要用于以某种标准形式输入的混合类型数据的读取和转换。例如,如果每一个输入行都包括一种工具的名称、库存数量和单价,您就可以使用 scanf() ;否则您必须在函数中自己处理输入错误的检测。如果希望一次只输入一个单词,最好使用 scanf() 。

    • 1.2 字符串的输出

    • puts 函数

    • int puts ( const char * str );
              puts() 函数的使用很简单,只需要给出字符串参数的地址。程序 5 是使用 puts() 的示例。
    • 程序 5:
      #include <stdio.h>
      #define DEF "I am a #defined string. "
      int main (void)
      {
          char str1[80] = "An array was initialized to me.";
          const char * str2 = "A pointer was initialized to me.";
          puts ("I'm an argument to puts () .");
          puts (DEF);
          puts (str1); 
          puts (str2);
          puts (&str1 [5]);
          puts (str2+4);
          return 0;
      }

      结果:
      I'm an argument to puts () .
      I am a #defined string.
      An array was initialized to me.
      A pointer was initialized to me.
      ray was initialized to me.
      inter was initialized to me.

    • fputs 函数

    • int fputs ( const char * str, FILE * stream );
             fputs() 函数是 gets() 的面向文件版本。两者之间的主要区别是:
             • fputs() 需要第二个参数来说明要写的文件。可以使用 stdout(代表 standard  output)作为参数来进行输出显示,stdout 在 stdio.h 中定义。
             • 与 puts() 不同,fputs ()并不为输出自动添加换行符。
             注意,gets() 丢掉输入里的换行符,但是 puts() 为输出添加换行符。另一方面,fgets() 存储输入中的换行符,而 fputs() 也不为输出添加换行符。假定写一个循环,读取一行并把它回显在下一行,可以这么写:
      char line [81] ;
      while (gets(line))
      puts (line);
              回忆一下,如果遇到文件结尾,gets() 就返回空指针。空指针的值为 0(也即假),这样就结束了循环。或者也可以这么做:
      char line [81] ;
      while (fgets (line, 81, stdin))
      fputs (line, stdout);
             在第一个循环中,line 数组中的字符串被显示在单独的一行上,这是由于 puts() 为它添加了一个换行符。第二个循环,line 数组中的字符串同样被显示在单独的一行上,这是由于 fgets() 存储了一个换行符。注意,如果把 fgets() 输入和 puts() 输出结合使用,每个字符串后就会显示两个换行符。关键在于 puts() 是为和 gets() 一起使用而设计的,而fputs() 是为和 fgets() 一起使用而设计的。
    • printf 函数

    • int printf ( const char * format, ... );

             如同 puts() 一样,printf() 需要一个字符串地址作为参数。printf() 函数使用起来没有 puts() 那么方便,但是它可以格式化多种数据类型,因而更通用。
             它们的区别之一就是 printf() 并不自动在新行上输出每一个字符串。相反,您必须指明需要另起一行的地方。因此:

      printf("%s\n",string);
      //与下面语句相同
      puts(string);

             正如您所见,第一种形式需要键入更多代码,此外计算机的执行时间也更长(但您觉察不到)。不过,printf() 使在一行上输出多个字符串变得更为简单。例如,下面的语句把 Well、用户名和一个用 #define 定义的字符串统统显示在一行上:

      printf("Well,%s,%s\n",name,MSG);
    • 1.3 自定义字符串输入/输出函数

    •        不一定要使用标准 C 库的函数进行输入和输出。如果不具备或者不喜欢它们,您可以自行定义,在 getchar() 和 putchar() 的基础上建立自己的函数。假定您希望有一个类似 puts() 但并不自动添加换行符的函数。程序 6 给出了一种方法。
    • 程序 6:
      #include<stdio.h>
      void my_put(const char*string)/*不会改变这个字符串*/
      {
          while(*string!='\0')
          putchar(*string++);
      }

  • 2. 字符串常用函数

    •         C库提供了许多处理字符串的函数:ANSI C用头文件 string.h给出这些函数的原型。下面是一些最有用和最常用的函数:strlen()、strcat()、strncat()、strcmp()、strncmp()、strcpy() 和 strncpy()。
              此外我们也将研究一下头文件 stdio.h 支持的 sprintf()函数。

    • 2.1 strlen 函数

    • size_t strlen ( const char * str );

             strlen() 函数返回字符串的长度(不包括 '\0')。我们需要注意区分 sizeof 和 strlen 函数的区别。程序 6 是示范案例。

    • 程序 6:

      #include <stdio.h>
      #include<string.h>
      int main (void)
      {
          char arr[31]="what's your name?\n";
          printf("%d %d",strlen(arr),sizeof(arr));
          return 0;
      }

      结果:
      18 31

    • 2.2 strcat 函数和strncat 函数

    • char * strcat ( char * destination, const char * source );
      
      char * strncat ( char * destination, const char * source, size_t num );

             strcat(代表 string concatenation)函数接受两个字符串参数。它将第二个字符串的一份拷贝添加到第一个字符串的结尾,从而使第一个字符串成为一个新的组合字符串,第一个字符串并没有改变。strcat() 函数是 char *(指向char 的指针)类型。这个函数返回它的第一个参数的值,即其后添加了第二个字符串的那个字符串中第一个字符的地址。
             strncat 从字符串中追加字符将第二个字符串的前 num 个字符附加到目标,以及终止 null 字符。如果 source 中 C 字符串的长度小于 num,则仅复制直到终止 null 字符的内容。

    • 程序 7:

      #include <stdio.h>
      #include<string.h>
      #define SIZE 31
      #define BUGSIZE 13
      int main (void)
      {
          char flower[SIZE];
          char addon[]="s smell like old shoes.";
          char bug[BUGSIZE];
          int available;
      
          puts("What's your favorite flower?");
          gets(flower);
          if(strlen(addon)+strlen(flower)+1<=SIZE)
          strcat(flower,addon);
          puts(flower);
          puts("What's your favorite bug?");
          gets(bug);
          available=BUGSIZE-strlen(bug)-1;
          strncat(bug,addon,available);
          puts(bug);
      
          return 0;
      }

      结果:
      What's your favorite flower?
      Rose
      Roses smell like old shoes.
      What's your favorite bug?
      Aphid
      Aphids smell

    • 2.3 strcmp 函数和strncmp 函数

    • int strcmp ( const char * str1, const char * str2 );
      
      int strncmp ( const char * str1, const char * str2, size_t num );

             strcmp()比较两个字符串的字符将 C 字符串 str1 的字符数与 C 字符串 str2 的字符数进行比较。此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续执行以下对,直到字符不同,直到达到终止的空字符('\0')。 若比较过程中 str1 的字符小于 str2 ,它返回一个负数;如果两个字符串相同,它返回0;若 str1 的字符大于 str2 ,它返回一个正数。
             strncmp() 比较两个字符串的字符将 C 字符串 str1 的字符数与 C 字符串 str2 的字符数进行比较。此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续执行以下对,直到字符不同,直到达到终止的空字符,或直到两个字符串中的 num 个字符匹配,以先发生者为准。返回结果同上。

    • 程序 8:

      include <stdio.h>
      #include<string.h>
      #define LISTSIZE 5
      int main (void)
      {
          char*list[LISTSIZE]={
              "astronomy","astounding",
              "astrophysics","ostracize",
              "asterism"
          };
          int count =0,count2=0;
      
          for(int i=0;i<LISTSIZE;i++)
          {
          if(strncmp(list[i],"astro",5)==0)
          {
              printf("Found \"astro\":%s\n",list[i]);
              count++;
          }
          if(strcmp(list[i],"astronomy")==0)
          {
              printf("Found \"astronomy\":list %d\n",i+1);
              count2++;
          }
          }
          printf("The list contained %d words beginning"
          "with astro.\n",count);
          return 0;
      }

      结果:
      Found "astro":astronomy
      Found "astronomy":list 1
      Found "astro":astrophysics
      The list contained 2 words beginningwith astro.

    • 2.4 strcpy 函数和strncpy 函数

    • char * strcpy ( char * destination, const char * source );
      
      char * strncpy ( char * destination, const char * source, size_t num );
              strcpy() 从字符串中复制字符 source 复制到  desstination 。它返回的是destination的地址。其在字符串运算中相当于赋值运算符。
              strcnpy() 从字符串中复制字符 source 的前  num 个字符复制到  desstination 。如果在复制  num 个字符之前找到  source 字符串(由空字符表示)的末尾,则  destination 将填充为零,直到总共写入  num 个字符。如果  source 的长度大于  num,则不会在destination 末尾隐式追加空字符。因此,在这种情况下, destination 不应被视为以空字符结尾的字符串(这样读取它会溢出)。它返回的是destination的地址。
    • 程序 9:
      #include <stdio.h>
      #include<string.h>
      #define WORDS "beast"
      #define SIZE 41
      int main (void)
      {
          char*orig=WORDS;
          char copy[SIZE]="Be the best that you can be.";
          char *ps;
      
          puts(orig);
          puts(copy);
          ps=strcpy(copy+7,orig);
          puts(copy);
          strncpy(copy,ps,SIZE-1);
          puts(ps);
          puts(copy);
          return 0;
      }

      结果:
      beast
      Be the best that you can be.
      Be the beast

      beast

      (好好想想为什么 puts(ps) 仅仅是换行。)

    • 2.5 spirntf 函数

    • int sprintf ( char * str, const char * format, ... );

             sprintf() 函数是在 stdio.h 而不是在 string.h 里声明的。它的作用和 printf() 一样,但是它写到字符串里而不是写到输出显示。因此,它提供了把几个元素组合成一个字符串的一种途径。sprintf() 的第一个参数是目标字符串的地址,其余的参数和 printf() 一样:一个转换说明字符串,接着是要写的项目的列表。

    • 程序 10:

      #include <stdio.h>
      #include<string.h>
      #define MAX 20
      int main (void)
      {
          char first[MAX];
          char last[MAX];
          char formal[2*MAX + 10];
          double prize;
      
          puts("Enter your first name:");
          gets(first);
          puts("Enter your last name:");
          gets(last);
          puts("Enter your prize money:");
          scanf("%lf",&prize);
          sprintf(formal,"%s,%-19s:$%6.2f\n",last,first,prize);
          puts(formal);
          return 0;
      }

      结果:
      Enter your first name:
      Teddy
      Enter your last name:
      Behr
      Enter your prize money:
      2000
      Behr,Teddy              :$2000.00

  • 3. 字符串其他函数

  •        ANSI C 库里有 20 多个处理字符串的函数,但是现在写的实在是太多了。我就给链接了:string.h (这个是 C++ 官网里的,很多常用的 C 语言函数都有。请放心食用)​​​​​​​
    ​​​​​​​

  • *4. 带参数的 main 函数

  •         到目前为止,我们所接触到的 main函数都是不带参数的,事实上,main 函数是可以带参数的。
            我们把在操作系统状态下,为了执行某个程序而键人的一行字符称为命令行。命令行一般以回车【Enter】作为结束符。命令行中必须有程序的可执行文件名,此外经常带有若干参数。例如,为了复制文件须键人以下一行字符:
    copy file.txt file2.txt 【Enter】

           其中,copy 是可执行文件名,有时称它为命令名。而 filel.txt 和 file2.txt 则是命令行参数。一个命令行的命令名与各个参数之间要求用空格分隔,并且命令名和参数不能使用空格字符。那么,在操作系统下键入的命令行参数如何传递到C语言程序中呢?C 语言专门设置了接收命令行参数的方法:在程序的主函数main()中使用形式参数来接收。执行带有命令行参数的C语言程序的主函数应该是下列形式:

    int main(int argc,char *argv[])
    {
       ...
    }

            这时 main() 带有两个形式参数 argc 和 argv,这两个参数的名字可由用户任意命名,但习惯上都使用上面给定的名字。从参数说明可以看出,参数 argc 是 int 型变量,而 argv 是字符指针数组,它指向多个字符串。这些参数在程序运行时由系统对它们进行初始化。初始化的结果是:
           1) argc 的值是命令行中包括命令在内的所有参数的个数之和。
           2)指针数组 argv[ ] 的各个指针分别指向命令中命令名和各个参数的字符串。其中指针  argv[0]总是指向命令名字符串,从 argv[1]开始依次指向按先后顺序出现的命令行参数字符串。
           例如,C语言程序 test 带有三个命令行参数,其命令行是:

    test progl.c prog2.c /p 【Enter】

           在执行这个命令行时,test 程序被启动运行。则主函数 main() 的参数 argc 被初始化为4,因为命令行中命令名和参数共有四个字符串。指针数组 argv[ ] 的初始化过程是:

    argv [0]="test";
    argv [1]="progl.c";
    argv [2]="prog2.c";
    argv [3]="/p";
    argv [4]=0;//最后一个参数是编译系统为了程序处理的方便

           由此看出,argc 的值和 argv[ ] 元素的个数取决于命令行中命令名和参数的个数。argv[ ]的下标是从 0 到 argc 范围内的值。
           在程序中使用 argc 和 argv[ ] 就可以处理命令行参数的内容。从而把用户在命令行中键人的参数字符串传递到了程序内部。
           命令行的参数(不包括命令本身)但 C 的运行集成环境下,可通过菜单进行设置。如在Borland Ct+3.1 for dos 或 Turbo C++ 3.0 tor dos 的菜单 RUN 的 argumnet 子菜单下可设置命令行参数。
           程序如何访问这些参数?请看下面的程序 11。

  • 程序 11:

    #include <stdio.h>
    #include<stdlib.h>
    int main(int argc,char**argv)
    {
        //打印参数,直到遇到 NULL 指针(未使用 argc),程序跳过
        while(*++argv!=NULL)
        printf("%s\n",*argv);
        return 1;
    }
    

 参考书籍:《C Primer Plus》【美】 Stephen Prata 著
                  《程序设计教程 用 C/C++ 语言编程》 周纯杰 何顶新 周凯波 彭刚 张惕远 编著 

  • 23
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值