本人要开学了,之后的更新会慢一些。希望您能理解。
上章入口:【C 语言学习 第六章 数组和指针】
-
字符串(character string)是以空字符(\0)结尾的 char 数组。在正式开始介绍前,您必须了解一下字符串常量。
字符串常量(string constant),又称字符串文字(string literal),是指位于一对双引号中的任何字符,双引号里的字符加上编译器自动提供的结束标志 \0 字符,作为一个字符串被存储在内存里。
同时,字符串常量属于静态存储(static storage)类。静态存储是指如果在一个函数中使用字符串常量,即使是多次调用了这个函数,该字符串在程序的整个运行过程中只存储一份。
数组和指针都可以用来使用字符串常量但是它们的用法存在区别。但我们需要谨记的是,声明一个数组将为数据分配存储空间;而声明一个指针只为一个地址分配存储空间。具体的会在下面出现的示例中。 -
1. 字符串的 I/O
-
1.1 字符串输入
-
创建存储空间
- 要做的第一件事是建立一个空间以存放读入的字符串。正如前面提过的,这意味着需要分配足够大的存储区来存放希望读入的字符串。不要指望计算机读的时候会先计算字符串的长度,然后为字符串分配空间。计算机是不会这么做的(除非您写了一个函数命令它这么做)。如果您写下下面语句:
这可能会通过编译器,但是在读入 name 的时候,name 会覆盖程序中的数据和代码,并可能导致程序异常终止。这是因为 scanf()把信息复制到由参数给定的地址中,而在这种情况下,参数是个未初始化的指针:name 可能指向任何地方。绝大多数程序员认为这很搞笑,但仅限于这出现在别人的程序中时。char *name; scanf("%s",name);
最简单的方法就是在声明中明确指出数组大小:
这是一个存储 31 个字符的数组,但它只能用来存储 30 个字符,因为它需要留一个字符来存储 \0 。如果不这么做的话,那么它可能会出现您意想不到的结果。例如程序 1。char name[31];
- 程序 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 函数
-
gets() 的一个不足是它不检查预留存储区是否能够容纳实际输入的数据。多出来的字符简单地溢出到相邻的内存区。fgets() 函数改进了这个问题,它让您指定最大读入字符数。由于 fgets () 是为文件 I/O 而设计的,在处理键盘输入时就不如 gets() 那么方便。fgets() 和 gets() 有三方面不同:char * fgets ( char * str, int num, FILE * stream );
• 它需要第二个参数来说明最大读入字符数。如果这个参数值 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 函数
-
fputs() 函数是 gets() 的面向文件版本。两者之间的主要区别是:int fputs ( const char * str, FILE * stream );
• fputs() 需要第二个参数来说明要写的文件。可以使用 stdout(代表 standard output)作为参数来进行输出显示,stdout 在 stdio.h 中定义。
• 与 puts() 不同,fputs ()并不为输出自动添加换行符。
注意,gets() 丢掉输入里的换行符,但是 puts() 为输出添加换行符。另一方面,fgets() 存储输入中的换行符,而 fputs() 也不为输出添加换行符。假定写一个循环,读取一行并把它回显在下一行,可以这么写:
回忆一下,如果遇到文件结尾,gets() 就返回空指针。空指针的值为 0(也即假),这样就结束了循环。或者也可以这么做:char line [81] ; while (gets(line)) puts (line);
在第一个循环中,line 数组中的字符串被显示在单独的一行上,这是由于 puts() 为它添加了一个换行符。第二个循环,line 数组中的字符串同样被显示在单独的一行上,这是由于 fgets() 存储了一个换行符。注意,如果把 fgets() 输入和 puts() 输出结合使用,每个字符串后就会显示两个换行符。关键在于 puts() 是为和 gets() 一起使用而设计的,而fputs() 是为和 fgets() 一起使用而设计的。char line [81] ; while (fgets (line, 81, stdin)) fputs (line, stdout);
-
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 beastbeast
(好好想想为什么 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++ 语言编程》 周纯杰 何顶新 周凯波 彭刚 张惕远 编著