11 字符串和字符串函数

1 表示字符串和字符串I/O

字符串是以空字符’\0’结尾的char类型数组。不过,由于字符串十分常用,所以C提供了许多专门用于处理字符串的函数。

程序11.1 演示了在程序中表示字符串的几种方式:

// 程序 11.1 strings1.c
#include <stdio.h>
#define MSG "I am a symbolic string constant."
#define MAXLENGTH 81

int main(void){
 	char words[MAXLENGTH] = "I am a string in an array.";
 	const char * pt1 = "Something is pointing at me.";
 	puts("Here are some strings:");
 	puts(MSG);
 	puts(words);
 	puts(pt1);
 	words[8] = 'p';
 	puts(words);
 	return 0;
} 

和pirntf函数一样,puts函数也属于stdio.h系列的输入/输出函数。但是,puts只显示字符串,而且自动在显示的字符串末尾加上换行符。下面是程序11.1的输出:

Here are some strings:
I am an old-fashioned symbolic string constant.
I am a string in an array.
Something is pointing at me.
I am a spring in an array. 

在后面的内容中,我们首先分析一下该程序中定义字符串的几种方法,然后再讲解把字符串读入程序涉及的一些操作,最后学习如何输出字符串。

1.1 在程序中定义字符串
1.1.1 字符串字面量(字符串常量)

用双引号括起来的内容称为字符串字面量(string literal),也叫做字符串常量(string constant)。双引号中的字符会和编译器自动加入末尾的\0字符一起作为字符串储存在内存中。例如,"I am a string
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.1.2 字符串数组和初始化

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.1.3 数组和指针

待补充

2 字符串输入

如果想把一个字符串读入程序,首先必须预留储存该字符串的空间,然后用输入函数获取该字符串。

2.1 分配空间

要做的第1件事是分配空间,以储存稍后读入的字符串。前面提到过,这意味着必须要为字符串分配足够的空间。不要指望计算机在读取字符串时顺便计算它的长度,然后再分配空间(计算机不会这样做,除非你编写-一个处理这些任务的函数)。假设编写了如下代码:

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

虽然可能会通过编译(编译器很可能给出警告),但是在读入name时,name可能会擦写掉程序中的数据或代码,从而导致程序异常中止。因为scanf()要把信息拷贝至参数指定的地址上,而此时该参数是个未初始化的指针,name可能会指向任何地方。

最简单的方法是,在声明时显式指明数组的大小:

char name[81] ;

现在name是一个已分配块(81字节)的地址。还有一种方法是使用C库函数来分配内存,第十二节将详细介绍。

为字符串分配内存后,便可读入字符串。C库提供了许多读取字符串的函数: scanf()、 gets()和fgets()。我们先讨论最常用gets ()函数。

2.2 不安全的gets函数

在读取字符串时,scanf()和转换说明符%s只能读取一个单词。可是在程序中经常要读取一整行输入,而不仅仅是一个单词。许多年前,get函数就用于处理这种情况。get函数简单易用,它读取整行输入,直至遇到换行符,然后丢弃换行符,储存其余字符,并在这些字符的末尾添加一个空字符使其成为一个C字符串。它经常和puts函数配对使用,该函数用于显示字符串,并在末尾添加换行符。程序11.6演示了这两个函数的用法:

程序11.6

/* getsputs.c -- using gets() and puts() */
#include <stdio.h>
#define STLEN 81

int main(void){
 	char words[STLEN];
 	
 	puts("Enter a string, please.");
 	gets(words);
 	printf("Your string twice:\n");
 	printf("%s\n", words);
 	puts(words);
 	puts("Done.");
 	
 	return 0;
}

下面是该程序在某些编译器(至少是旧式编译器)中的运行示例:

Enter a string, please.
I want to learn about string theory!
Your string twice:
I want to learn about string theory!
I want to learn about string theory!
Done. 

整行输入(除了换行符) 都被储存在words中,puts(words)和printf(“%s\n”,words)的效果相同。

下面是该程序在另一个编译器中的输出示例:

Enter a string, please.
warning: this program uses gets(), which is unsafe.
Oh, no!
Your string twice:
Oh, no!
Oh, no!
Done. 

编译器在输出中插入了一行警告信息。每次运行这个程序,都会显示这行消息。

上面这种使用方式的问题处在gets唯一的参数是words,它无法检查数组是否装得下输出行。数组名最终会被转换成该数组首元素的地址,因此,gets函数只知道数组的开始处,并不知道数组中有多少个元素。

如果输入的字符串过长,会导致缓冲区溢出(buffer overflow),即多余的字符超出了指定的目标空间。如果这些多余的字符只是占用了尚未使用的内存,就不会立即出现问题:如果它们擦写掉程序中的其他数据,会导致程序异常中止:或者还有其他情况。为了让输入的字符串容易溢出,把程序中的STLEN设置为5,程序的输出如下:

Enter a string, please.
warning: this program uses gets(), which is unsafe.
I think I'll be just fine.
Your string twice:
I think I'll be just fine.
I think I'll be just fine.
Done.
Segmentation fault: 11 

在UNIX系统中,“Segmentation fault”说明程序试图访问未分配的内存。

gets函数的不安全行为造成了安全隐患,过去,有些人通过系统编程,利用gets插入和运行一些破坏系统安全的代码。

不久,C编程社区的许多人都建议在编程时摒弃gtes。制定C99标准的委员会也把这些建议放入了标准,承认了gets的问题并建议不要再使用它。尽管如此,在标准中保留gets也合情合理,因为现有程序中含有大量使用该函数的代码。而且,只要使用得当,它的确是一个很方便的函数。

但是好景不长,C11标准委员会采取了更强硬的态度,直接从标准中废除了gets函数。但在实际应用中,编译器为了兼容以前的代码,大部分都继续支持gets函数。

2.3 gets的替代品

过去通常使用fgets来代替gets,fgets函数稍微复杂些,在处理输入方面与gets略有不同。C11标准新增的gets_s()函数也可以代替gets。该函数与gets函数更接近,而且可以替换现有代码中的gets。但是,它是stdio.h输入/输出函数系列的可选拓展,所以支持C11的编译器也不一定支持它。

2.3.1 fgets函数(和fputs)

fgets()函数通过第2个参数限制读入的字符数来解决溢出的问题。该函数专门设计用于处理文件输入,所以一般情况下可能不太好用。fgets ()和gets ()的区别如下。

  • fgets()函数的第2个参数指明了读入字符的最大数量。如果该参数的值是n,那么fgets()将.读入n-1个字符,或者读到遇到的第-一个换行符为止。
  • 如果fgets()读到一个换行符,会把它储存在字符串中。这点与gets()不同,gets() 会丢弃换行符。.
  • fgets()函数的第3个参数指明要读入的文件。如果读入从键盘输入的数据,则以stdin (标准输入)作为参数,该标识符定义在stdio.h中。

因为fgets ()函数把换行符放在字符串的末尾(假设输入行不溢出),通常要与fputs()函数 (和puts ()类似)配对使用,除非该函数不在字符串末尾添加换行符。fputs ()函数的第2个参数指明它要写入的文件。如果要显示在计算机显示器上,应使用stdout (标准输出)作为该参数。程序清单11.7演示了fgets()和fputs ()函数的用法。

程序11.7 fgets1.c

/* fgets1.c -- using fgets() and fputs() */
#include <stdio.h>
#define STLEN 14

int main(void){
 	char words[STLEN];
 	
 	puts("Enter a string, please.");
 	fgets(words, STLEN, stdin);
 	printf("Your string twice (puts(), then fputs()):\n");
 	puts(words);
 	fputs(words, stdout);
 	puts("Enter another string, please.");
 	fgets(words, STLEN, stdin);
 	printf("Your string twice (puts(), then fputs()):\n");
 	puts(words);
 	fputs(words, stdout);
 	puts("Done.");
 	return 0;
} 

程序的输出示例如下:

Enter a string, please.
apple pie
Your string twice (puts(), then fputs()):
apple pie

apple pie
Enter another string, please.
strawberry shortcake
Your string twice (puts(), then fputs()):
strawberry sh
strawberry shDone. 

第1行输入,apple pie,比fgets()读入的整行输入短,因此,apple pie\n\0被储存在数组中。所以当puts ()显示该字符串时又在末尾添加了换行符,因此apple pie后面有一行空行。 因为fputs()不在字符串末尾添加换行符,所以并未打印出空行。.

第2行输入,strawberry shortcake,超过了大小的限制,所以fgets()只读入了13个字符,并把strawberry sh\0 储存在数组中。再次提醒读者注意,puts ()函数会在待输出字符串末尾添加一个换行符,而fputs()不会这样做。

fputs()函数返回指向char的指针。如果—切进行顺利,该函数返回的地址与传入的第1个参数相同。但是,如果函数读到文件结尾,它将返回一个特殊的指针:空指针(null pointer)。该指针保证不会指向有效的数据,所以可用于标识这种特殊情况。在代码中,可以用数字0来代替,不过在C语言中用宏NULL来代替更常见(如果在读入数据时出现某些错误,该函数也返回NULL)。

程序清单11.8演示了一个简单的循环,读入并显示用户输入的内容,直到fgets()读到文件结尾或空行(即,首字符是换行符)。

程序11.8

/* fgets2.c -- using fgets() and fputs() */
#include <stdio.h>
#define STLEN 10

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

下面是该程序的输出示例:

Enter strings (empty line to quit):
By the way, the gets() function
By the way, the gets() function
also returns a null pointer if it
also returns a null pointer if it
encounters end-of-file.
encounters end-of-file.

Done. 

虽然STLEN被设置为10,但是该程序似乎在处理过长的输入时完全没问题。程序中的fgets()一次读入STLEN - 1个字符(该例中为9个字符)。所以,一开始它只读入了“By the wa”,并储存为By the wa\0;接着fputs()打印该字符串,而且并未换行。然后while循环进入下一轮迭代,fgets()继续从剩余的输入中读入数据,即读入“y, the ge” 并储存为y, the ge\0; 接着fputs ()在刚才打印字符串的这一行接着打印第2次读入的字符串。然后while进入下一轮迭代,fgets ()继续读取输入、fputs()打印字符串,这一过程循环进行,直到读入最后的tion\n。 fgets() 将其储存为tion\n\0,fputs ()打印该字符串,由于字符串中的\n,光标被移至下一行开始处。

系统使用缓冲的I/O。这意味着用户在按下Return键之前,输入都被储存在临时存储区(即,缓冲区)中。按下Return键就在输入中增加了一个换行符,并把整行输入发送给fgets()。 对于输出,fputs ()把字符发送给另一个缓冲区,当发送换行符时,缓冲区中的内容被发送至屏幕上。

fgets ()储存换行符有好处也有坏处。坏处是你可能并不想把换行符储存在字符串中,这样的换行符会带来一些麻烦。好处是对于储存的字符串而言,检查末尾是否有换行符可以判断是否读取了一整行。如果不是一整行,要妥善处理一行中剩下的字符。

首先,如何处理掉换行符? 一个方法是在已储存的字符串中查找换行符,并将其替换成空字符:

while (words[i] != '\n') // assuming \n in words
 	i++;
words[i] = '\0'; 

其次,如果仍有字符串留在输入行怎么办?一个可行的办法是,如果目标数组装不下一整行输入,就丢弃那些多出的字符:

while (getchar() != '\n') // 读取但不储存输入,包括\n
	continue;

程序清单11.9 在程序清单11.8 的基础上添加了一部分测试代码。该程序读取输入行,删除储存在字符串中的换行符,如果没有换行符,则丢弃数组装不下的字符。

程序11.9

/* fgets3.c -- using fgets() */
#include <stdio.h>
#define STLEN 10

int main(void){
 	char words[STLEN];
 	int i;
 	puts("Enter strings (empty line to quit):");
 	
 	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 // must have words[i] == '\0'
 			while (getchar() != '\n')
 				continue;
 		puts(words);
 	}
 	puts("done");
 	return 0;
} 

循环

while (words[i] != '\n' && words[i] != '\0')
	i++;

遍历字符串,直至遇到换行符或空字符。如果先遇到换行符,下面的if语句就将其替换成空字符:如果先遇到空字符,else部分便丢弃输入行的剩余字符。下面是该程序的输出示例:

Enter strings (empty line to quit):
This
This
program seems
program s
unwilling to accept long lines.
unwilling
But it doesn't get stuck on long
But it do
lines either.
lines eit

done

在这里插入图片描述

2.3.2 gets_s()函数

CI1新增的gets_ s()函数(可选)和fgets()类似,用一“个参数限制读入的字符数。假设把程序清单11.9中的fgets()换成gets_ s(), 其他内容不变,那么下面的代码将把一行输入中的前9个字符读入words数组中,假设末尾有换行符:

gets_s(words, STLEN) ;

gets_ s() 与fgets()的区别如下。

  • gets_ s()只从标准输入中读取数据,所以不需要第3个参数。
  • 如果gets_ s() 读到换行符,会丢弃它而不是储存它。
  • 如果gets_ s() 读到最大字符数都没有读到换行符,会执行以下几步。首先把目标数组中的首字符设置为空字符,读取并丢弃随后的输入直至读到换行符或文件结尾,然后返回空指针。接着,调用依赖实现的“处理函数”(或你选择的其他函数),可能会中止或退出程序。

第2个特性说明,只要输入行未超过最大字符数,gets_ s()和gets()几乎- -样,完全可以用gets_ s()替换gets()。第3个特性说明,要使用这个函数还需要进一步学习。

我们来比较-一下gets()、fgets()和gets_ s() 的适用性。如果目标存储区装得下输入行,3个函数都没问题。但是fgets ()会保留输入末尾的换行符作为字符串的一部分, 要编写额外的代码将其替换成空字符。.

在这里插入图片描述

2.3.3 gets_s()函数

在这里插入图片描述
程序11.10

char * s_gets(char * st, int n){
 	char * ret_val;
 	int i = 0;
 	ret_val = fgets(st, n, stdin);
 	if (ret_val){ // i.e., ret_val != NULL
 		while (st[i] != '\n' && st[i] != '\0')
 			i++;
 		if (st[i] == '\n')
 			st[i] = '\0';
 		else // must have words[i] == '\0'
 			while (getchar() != '\n')
 				continue;
 	}
 	return ret_val;
} 

在这里插入图片描述

5 字符串函数

在这里插入图片描述

5.1 strlen函数

在这里插入图片描述
程序11.17 test_fit.c

/* test_fit.c -- try the string-shrinking function */
#include <stdio.h>
#include <string.h> /* contains string function prototypes */

void fit(char *, unsigned int);

int main(void){
 	char mesg[] = "Things should be as simple as possible,"
 " but not simpler.";
 	puts(mesg);
 	fit(mesg,38);
 	puts(mesg);
 	puts("Let's look at some more of the string.");
 	puts(mesg + 39);
 	return 0;
}

void fit(char *string, unsigned int size){
 	if (strlen(string) > size)
 		string[size] = '\0';
} 

程序输出如下:

Things should be as simple as possible, but not simpler.
Things should be as simple as possible
Let's look at some more of the string.
 but not simpler. 

在这里插入图片描述

图11.4以一个更简单的例子演示了上面的过程。在11.17中,fit函数把第39个元素的逗号替换成’\0’字符。puts函数在这个空字符停止了输出,并忽略了其他字符,但是实际上这些字符还在缓冲区中。在图11.4的例子中,调用puts(mesg+8)可以将剩余字符打印出来。因为mesg+8mesg[8]的地址,该地址上存储了t。所以puts显示该字符并继续输出直到遇到原来字符串中的空字符。

5.2 strcat函数

在这里插入图片描述
程序 11.18 str_cat.c

/* str_cat.c -- joins two strings */
#include <stdio.h>
#include <string.h> /* declares the strcat() function */
#define SIZE 80

char * s_gets(char * st, int n);

int main(void){
 	char flower[SIZE];
 	char addon[] = "s smell like old shoes.";
 	puts("What is your favorite flower?");
 	if (s_gets(flower, SIZE)){
 		strcat(flower, addon);
 		puts(flower);
 		puts(addon);
 	}
 	else
 		puts("End of file encountered!");
 	puts("bye");
 	return 0;
}

char * s_gets(char * st, int n){
 	char * ret_val;
 	int i = 0;
 	ret_val = fgets(st, n, stdin);
 	if (ret_val){
		while (st[i] != '\n' && st[i] != '\0')
 			i++;
 		if (st[i] == '\n')
 			st[i] = '\0';
 		else // must have words[i] == '\0'
 			while (getchar() != '\n')
 				continue;
 	}
 	return ret_val;
} 

该程序的输出示例如下:

What is your favorite flower?
wonderflower
wonderflowers smell like old shoes.
s smell like old shoes.
bye 

从以上输出可以看出,flower改变了,而addon保持不变。

5.4 strcmp函数

假设要把用户的响应与已储存的字符串作比较,如程序11.20所示。

程序11.20

#include <stdio.h>
#define ANSWER "Grant"
#define SIZE 40

char * s_gets(char * st, int n);

int main(void){
 	char try[SIZE];
 	puts("Who is buried in Grant's tomb?");
 	s_gets(try, SIZE);
 	
 	while (try != ANSWER){
 		puts("No, that's wrong. Try again.");
 		s_gets(try, SIZE);
 	}
 	puts("That's right!");
 	return 0;
}
 
char * s_gets(char * st, int n){
 	char * ret_val;
 	int i = 0;
 	ret_val = fgets(st, n, stdin);
 	if (ret_val){
 		while (st[i] != '\n' && st[i] != '\0')
 			i++;
 		if (st[i] == '\n')
 			st[i] = '\0';
 		else // must have words[i] == '\0'
 			while (getchar() != '\n')
 				continue;
 	}
 	return ret_val;
} 

在这里插入图片描述
程序11.21

/* compare.c -- this will work */
#include <stdio.h>
#include <string.h> // declares strcmp()
#define ANSWER "Grant"
#define SIZE 40

char * s_gets(char * st, int n);

int main(void){
 	char try[SIZE];
 	puts("Who is buried in Grant's tomb?");
 	s_gets(try, SIZE);
 	while (strcmp(try,ANSWER) != 0){
 		puts("No, that's wrong. Try again.");
 		s_gets(try, SIZE);
 	}
 	puts("That's right!");
 	return 0;
}

char * s_gets(char * st, int n){
 	char * ret_val;
 	int i = 0;
 	ret_val = fgets(st, n, stdin);
 	if (ret_val){
 		while (st[i] != '\n' && st[i] != '\0')
 			i++;
 		if (st[i] == '\n')
 			st[i] = '\0';
 		else // must have words[i] == '\0'
 			while (getchar() != '\n')
 				continue;
 	}
 	return ret_val;
} 

在这里插入图片描述

5.4.1 strcmp()的返回值

在这里插入图片描述
待补充 368

5.5 strcpy和strncpy函数

如果pts1和pts2都是指向字符串的指针,那么下面语句拷贝的是字符串的地址而不是字符串本身:

pts2=pts1;

如果希望拷贝整个字符串,要使用strcpy函数。程序11.25要求用户输入以q开头的单词。该程序把输入拷贝至一个临时数组中,如果第1个字母是1,程序调用strcpy把整个字符串从临时数组拷贝至目标数组中。strcpy函数相当于字符串赋值运算符。

程序11.25 copy1.c

/* copy1.c -- strcpy() demo */
#include <stdio.h>
#include <string.h> // declares strcpy()
#define SIZE 40
#define LIM 5

char * s_gets(char * st, int n);

int main(void){
 	char qwords[LIM][SIZE];
 	char temp[SIZE];
 	int i = 0;
 	printf("Enter %d words beginning with q:\n", LIM);
 	while (i < LIM && s_gets(temp, SIZE)){
 		if (temp[0] != 'q')
 			printf("%s doesn't begin with q!\n", temp);
 		else{
 			strcpy(qwords[i], temp);
 			i++;
 		}
 	}
 	puts("Here are the words accepted:");
 	for (i = 0; i < LIM; i++)
 	puts(qwords[i]);
 	return 0;
}
 
char * s_gets(char * st, int n){
	char * ret_val;
 	int i = 0;
 	ret_val = fgets(st, n, stdin);
 	if (ret_val){
 		while (st[i] != '\n' && st[i] != '\0')
 			i++;
 		if (st[i] == '\n')
 			st[i] = '\0';
 		else // must have words[i] == '\0'
 			while (getchar() != '\n')
 				continue;
 	}
 	return ret_val;
} 

下面是该程序的运行示例:

Enter 5 words beginning with q:
quackery
quasar
quilt
quotient
no more
no more doesn't begin with q!
quiz
Here are the words accepted:
quackery
quasar
quilt
quotient
quiz 

注意,只有在输入以q开头的单词后才会递增计数器i。

strcpy函数将第二个参数(temp)指向的字符串拷贝至第一个参数(qword[i])指向的数组中。拷贝出来的字符串被称为目标字符串,最初的字符串被称为源字符串,即第一个参数是目标字符串,第二个参数是源字符串。

如果以整型赋值的方式给字符串赋值,那么会报错:

char target[20];
int x;
x = 50; /* assignment for numbers */
strcpy(target, "Hi ho!"); /* assignment for strings */
target = "So long"; /* syntax error */ 

程序员有责任明确目标数组有足够的空间容纳源字符串的副本,下面的代码存在问题:

char * str;
strcpy(str, "The C of Tranquility"); // a problem

strcy函数把"The C of Tranquility"拷贝到str指向的地址上,但是str未被初始化,所以该字符串有可能被拷贝到任意的地方。

总之,strcpy接受两个字符串指针作为参数,可以把指向源字符串的第二个指针声明为指针、数组名或字符串常量;而指向源字符串副本的的第一个指针应指向一个数据对象(如,数组),且该对象有足够的空间存储源字符串的副本。记住,声明数组将分配储存数据的空间,而声明指针只分配储存一个地址的空间。

5.5.1 strcpy的其他属性

strcpy函数还有两个有用的属性。第一,strcpy函数的返回类型是char *,该函数返回的是第一个参数的值,即一个字符的地址。第二,第一个参数不必指向数组的开始。这个属性可用于拷贝数组的一部分。程序12.26演示了该函数的这两个属性:

程序12.26 copy2.c

/* copy2.c -- strcpy() demo */
#include <stdio.h>
#include <string.h> // declares strcpy()
#define WORDS "beast"
#define SIZE 40

int main(void){
 	const 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);
 	puts(ps);
 	return 0;
} 

程序输出如下:

beast
Be the best that you can be.
Be the beast
beast 

注意,strcpy函数把源字符串中的空字符也拷贝在内。在该例中,空字符覆盖了copy数组中that的第一个t,如图11.5所示。注意,由于第一个参数是copy+7,所以ps指向copy中的第八个元素(下标为7)。因此puts(ps)从该处开始打印字符串。

在这里插入图片描述

5.5.2 更谨慎的选择:strncpy

在这里插入图片描述
程序 11.27 copy3.c

/* copy3.c -- strncpy() demo */
#include <stdio.h>
#include <string.h> /* declares strncpy() */
#define SIZE 40
#define TARGSIZE 7
#define LIM 5

char * s_gets(char * st, int n);

int main(void){
 	char qwords[LIM][TARGSIZE];
 	char temp[SIZE];
 	int i = 0;
 	printf("Enter %d words beginning with q:\n", LIM);
 	while (i < LIM && s_gets(temp, SIZE)){
 		if (temp[0] != 'q')
 			printf("%s doesn't begin with q!\n", temp);
 		else{
 			strncpy(qwords[i], temp, TARGSIZE - 1);
 			qwords[i][TARGSIZE - 1] = '\0';
 			i++;
 		}
 	}
 	puts("Here are the words accepted:");
 	for (i = 0; i < LIM; i++)
 	puts(qwords[i]);
 	return 0;
}
 
char * s_gets(char * st, int n){
 	char * ret_val;
 	int i = 0;
 	ret_val = fgets(st, n, stdin);
 	if (ret_val){
 		while (st[i] != '\n' && st[i] != '\0')
 			i++;
 		if (st[i] == '\n')
 			st[i] = '\0';
 		else // must have words[i] == '\0'
	 		while (getchar() != '\n')
 				continue;
 	}
 	return ret_val;
}

下面是该程序的运行示例:

Enter 5 words beginning with q:
quack
quadratic
quisling
quota
quagga
Here are the words accepted:
quack
quadra
quisli
quota
quagga 

strncpy(target,source,n)把source中的n个字符或空字符之前的字符拷贝到target中。因此,如果source中的字符数小于n,则拷贝整个字符串,包括空字符。但是,strncpy拷贝字符串的长度不会超过n,如果拷贝到第n个字符时还未拷贝完整个源字符串,就不会拷贝空字符。所以,拷贝的副本中不一定有空字符。鉴于此,该程序把n设置为比目标数组大小小1,然后把数组最后一个元素设置为空字符。

这样做确保储存的是一个字符串。如果目标空间能容纳源字符串的副本,那么从源字符串拷贝的空字符串便是该副本的结尾;如果目标空间装不下副本,则把副本最后一个元素设置为空字符。

课后习题与答案

3

在这里插入图片描述
在这里插入图片描述

5

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

8 命令行参数: argc 和 argv

命令行(command line)是在命令行环境中,用户为运行程序输入命令的行。假设一个文件中有一个名为fuss的程序。在UNIX环境中运行该程序的命令行是:

$ fuss

或者在Windows命令提示模式下是:

C> fuss

命令行参数(command-line argument)是同一行的附加项。 如下例:

$ fuss -r Ginger

一个C程序可以读取并使用这些附加项(见图11.7)。
在这里插入图片描述
程序 11.31

/* repeat.c -- main() with arguments */
#include <stdio.h>

int main(int argc, char *argv[]){
 	int count;
 	printf("The command line has %d arguments:\n", argc - 1);
 	for (count = 1; count < argc; count++)
 		printf("%d: %s\n", count, argv[count]);
 	printf("\n");
 	return 0;
 }

把该程序编译为可执行文件repeat。下面是通过命令行运行该程序后的输出:

C> repeat Resistance is futile
The command line has 3 arguments:
1: Resistance
2: is
3: futile 

下面解释一下程序的运行原理。

C编译器允许main()没有参数或者有两个参数(一些实现允许main()有更多参数,属于对标准的扩展)。main()有两个参数时,第1个参数是命令行中的字符串数量。过去,这个int类型的参数被称为argc(表示参数计数(argument count))。系统用空格表示一个字符串的结束和下一个字符串的开始。因此,上面的repeat示例中包括命令名共有4个字符串,其中后3个供repeat使用。该程序把命令行字符串储存在内存中,并把每个字符串的地址储存在指针数组中。而该数组的地址则被储存在main()的第2个参数中。按照惯例,这个指向指针的指针称为argv (表示参数值[argument value])。 如果系统允许(一些操作系统不允许这样),就把程序本身的名称赋给argv[0],然后把随后的第I个字符串赋给argv[1],以此类推。在我们的例子中,有下面的关系:

  • argv[0]指向repeat (对大部分 系统而言)
  • argv[1]指向"Resistance"
  • argv[2]指向"is"
  • argv[3]指向"futile"

程序清单11.31的程序通过一个for循环依次打印每个字符串。printf()中的%s转换说明表明,要提供一个字符串的地址作为参数,而指针数组中的每个元素(argv[0]、 argv[1]等)都是这样的地址。

main()中的形参形式与其他带形参的函数相同。许多程序员用不同的形式声明argv:

int main(int argc, char **argv)

char **argv 与char *argv[] 等价。也就是说,argv是一个指向指针的指针,它所指向的指针指向char。因此,即使在原始定义中,argv也是指向指针(该指针指向char)的指针。两种形式都可以使用,但我们认为第1种形式更清楚地表明argv表示一系列字符串。

顺带一提, 许多环境(包括UNIX和DOS)都允许用双引号把多个单词括起来形成一个参数。例如:

repeat "I am hungry" now

这行命令把字符串"I am hungry"赋给argv[1],把"now"赋给argv[2]。

待补充 385

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值