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

  • 字符串(character string)是以空字符(\0)结尾的 char 数组。在正式开始介绍前,您必须了解一下字符串常量。
    字符串常量(stringconstant),又称字符串文字(stringliteral),是指位于一对双引号中的任何字符,双引号里的字符加上编译器自动提供的结束标志 \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:
      #includeint main(void){char name[4]={'1','2','3','4'};printf("%s",name);return 0;}

      结果:
      1234��
      (我用的是 vscode 如果用 vs 您更可能见到 “烫” 这个字)

    • gets 函数

    • char * gets ( char * str );

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

    • 程序 2:

      #include#define MAX 81int main(void){char name1[MAX],*name2;printf("Hi, what's your name" />

      结果:
      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(代表standardinput)作为该参数,这个标识符在stdio.h中定义。
      程序 3 使用 fgets() 代替程序 2 中的 gets() 。

    • 程序 3:
      #include#define MAX 81int 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() 更基于获取单词(getword)而不是获取字符串(getstring);而gets() 函数,正如您所看到的,会读取所有的字符,直到遇到第一个换行符为止。scanf() 使用两种方法决定输入结束。无论哪种方法,字符串都是以遇到的第一个非空白字符开始。如果使用 %s 格式,字符串读到(但不包括)下一个空白字符(比如空格、制表符或换行符)。如果指定了字段宽度,比如 %10s ,scanf() 就会读入 10 个字符或直到遇到第一个空白字符,由二者中最先满足的那个终止输入。
      回忆一下,scanf() 函数返回一个整数值,这个值是成功读取的项目数;或者遇到文件结束时返回一个 EOF 。程序 4 是 scanf() 的使用示例。

    • 程序 4:

      #includeint 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 #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() 也不为输出添加换行符。假定写一个循环,读取一行并把它回显在下一行,可以这么写:

      charline [81] ;while (gets(line))puts(line);

      回忆一下,如果遇到文件结尾,gets() 就返回空指针。空指针的值为 0(也即假),这样就结束了循环。或者也可以这么做:

      charline [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:
      #includevoid my_put(const char*string)/*不会改变这个字符串*/{while(*string!='\0')putchar(*string++);}
  • 2. 字符串常用函数

    • C库提供了许多处理字符串的函数:ANSIC用头文件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 #includeint 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(代表stringconcatenation)函数接受两个字符串参数。它将第二个字符串的一份拷贝添加到第一个字符串的结尾,从而使第一个字符串成为一个新的组合字符串,第一个字符串并没有改变。strcat() 函数是char*(指向char的指针)类型。这个函数返回它的第一个参数的值,即其后添加了第二个字符串的那个字符串中第一个字符的地址。
      strncat从字符串中追加字符将第二个字符串的前num个字符附加到目标,以及终止 null 字符。如果source中 C 字符串的长度小于num,则仅复制直到终止 null 字符的内容。

    • 程序 7:

      #include #include#define SIZE 31#define BUGSIZE 13int 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 #include#define LISTSIZE 5int 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 #include#define WORDS "beast"#define SIZE 41int 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 #include#define MAX 20int 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带有三个命令行参数,其命令行是:

    testprogl.cprog2.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 的运行集成环境下,可通过菜单进行设置。如在BorlandCt+3.1fordos 或TurboC++3.0tordos 的菜单RUN的argumnet 子菜单下可设置命令行参数。
    程序如何访问这些参数?请看下面的程序 11。

  • 程序 11:

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

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