C_Primer第11章 字符串和字符器函数

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

第4章介绍过,字符串是以空字符(\0)结尾的char类型数组。因此,可以把上一章学到的数组和指针的知识应用于字符串。不过,由于字符串十分常用,所以C提供了许多专门用于处理字符串的函数。

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

//strings1.cabs
#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;
	
}

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

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

11.1.1 在程序定义字符串

程序清单11.1中使用了多种方法(即字符串常量、char类型数组、指向char的指针)定义字符串。程序应该确保有足够的空间储存字符串。

1.字符串字面量(字符串常量)

用双引用号括起来的内容称为字符串字面量(string literal),也叫做字符串常量(string constant)。

如果要在字符串内部使用双引号,必须在双引号前面上加上一个反斜杠(\):

printf("\"Run,Spot,run!\" exclaimed Dick.\n");

字符串常量属于静态存储类别(static storage class),这说明如果在函数中使用字符串常量,该字符串只会被储存一次,在整个程序的生命期内存在,即使函数被调用多次。用双引号括起来的内容被视为指向该字符串储存位置的指针。害类似于把数组名作为指向该数组位置的指针。

程序清单11.2 strptr.c程序

// strptr.c --把字符串看作指针
#include <stdio.h>
int main(void)
{
	printf("%s,%p,%c\n","We","are",*"space farers");
	return 0;
}

输出

We,00405064,s

 

2. 字符串数组和初始化

定义字符串数组时,必须让编译器知道需要多少空间。一种方法是足够空间的数组储存字符串。在下面的声明中,用指定的字符串初始化数组m1:

const char m1[40] = "Limit yourself to one line's wowrth.";

const 表明不会更改这个字符串 .

这种形式的初始化比标准的数组初始化形式简单得多:

:

注意最后的空字符。没有这个空字符,这就不是一个字符串,而是一个字符数组。

通常,让编译器确定数组的大小很方便。回忆一下,省略数组初始化声明中的大小,编译器会自动计算数组的大小"

const char m2[] = "If you can't think of anything , fake it.";

让编译器确定初始化字符数组的大小很合理。因为处理字符串的函数通常都不知道数组的大小,这些函数通过查找字符串末尾控字符确定字符串在何处结束。

指针表示法创建字符串。例如,程序清单11.1中使用了下面的声明:

const char * pt1 = "Something is pointing at me.";

该声明和下面的声明几乎相同:

const char ar1 = "Something is pointing at me.";

以上两个声明表明,pt1和ar1都是该字符串的陡。在这两种情况下,带双引号的字符串本身决定了预留给字符串的存储空间。尽管如此,这两种形式并不完全相同。

3. 数组和指针

数组形式和指针形式有何不同?以上面的声明为例,数组形式(ar1[])在计算机的内存分配为一个内含29个元素的数组(每个元素对应一个字符,还加上一个末尾的空字符‘\0’),每个元素初始化为字符串字面量对应的字符。通常,字符串都作为可执行文件的一部分储存在数据段中。当把程序载入内存时,也载入了程序中的字符串。字符串储存在静态存储区(static memory)中。但是,程序在开始运行时才会为该数组分配内存。此时,才将字符串拷贝到数组中。注意,此时字符串有两个副本。一个是在静态内存中的字符串字面量,另一个是储存在ar1数组中的字符串。

此后,编译器便把数组名ar1识别为该数组元素地址(&ar1[0])的别名。这里关键要理解,在数组形式中,ar1是地址常量。不能更改ar1,如果改变了ar1,同意味着改变了数组的存储位置(即地址) 。可以进行类似ar+1这样的操作,标识数组的下一下元素。但是不允许进行++ar1这样的操作。递增运算符只能用于变量名前(或概括地说,只能用于可修改的左值),不能用于常量。

指针形式(*pt1)也便得编译器为字符串在静态存储区预留29个元素的空间。另外,一旦开始执行程序,它会为指针 变量pt1留出一个储存位置,并把字符串的地址储存在指针变量中。该变量最初指向该字符串的首字符,但是它的值 可以改变。因此,可以使用递增运算符。例如,++pt1将指向第2个字符(o)。

字符串字面量被视为const数据。由于pt1指向这个const数据,所以应该把pt1声明为指向const数据的指针。这意味着不能用pt1改变它所指向的数据,但是仍然可以改变pt1的值(即,pt1指向的位置)。如果把一个字符串字面量拷贝给一个数组,就可以随意改变数据,除非把数组声明为const。

总这,初始化数组把静态储存区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针。程序清单 11.3演示了这一点。

// addressses.c --字符串的地址
#define MSG "I'm special"
#include <stdio.h>
int main()
{
	char ar[] = MSG;
	const char *pt = MSG;
	printf("address of \"I'm special\": %p \n","I'm special");
	printf("              address ar: %p\n",ar);
	printf("              address pt: %p\n",pt);
	printf("             address MSG: %p\n",MSG);
	printf("address of \"I'm special\": %p \n","I'm special");
	return 0;
}

 

程序输出

address of "I'm special": 00405064
              address ar: 0028FF30
              address pt: 00405064
             address MSG: 00405064
address of "I'm special": 00405064

该程序的输出说明了什么?第一,pt和MSG的地址相同,而ar的地址不同,这与我们前面讨论的内容一致。第二,虽然字符串字面量“I'm special”在程序的两个printf()函数中出现了两次,但是编译器只使用了一个储存位置,而且与MSG的地址相同。编译顺可以把多次使用的相同字面量储存在一处或多处。另一个编译器可能在不同的位置储存3个"I'm special"。第三,静态数据使用的内存与ar使用的动态内存不同。不仅值 不同,特定编译器甚至使用不同的位数表示两种内存。

数组和指针表示字符串的区别是否很重要?通常不太重要,但是这取决于想用程序做什么。

4. 数组和指针的区别

#include <stdio.h>
#include <string.h>
int main(){
	/**
	初始化字符数组储存字符串,初始化指针指向字符串有何区别
	数组名heart是常量,head是变量
	
	*/
	char heart[] = "I love Tillie!";
	const char *head = "I love Millie!";

	//实际使用
	
	//数组表示法都可以
	for(int i = 0; i < 6 ; i++)
		putchar(heart[i]);
	putchar('\n');
	for(int i = 0; i < 6 ; i++)
		putchar(head[i]);
	putchar('\n');
	
	//指针加法操作
	for(int i = 0; i < 6 ; i++)
		putchar(*(heart + i));
	putchar('\n');
	for(int i = 0; i < 6 ; i++)
		putchar(*(head +i));
	putchar('\n');
	
	//只有指针表示法可以进行递增操作
	while(*(head) != '\0')
		putchar(*(head++));
	putchar('\n');
	
	head = heart;   /* head 现在指向数组 heart*/
	
	/**非法构造,不能这样写 编译错误:
	error: assignment to expression with array type*/
	//heart = head ;
	heart[7] = 'M';
	for(int i = 0; i < 10 ; i++)
		putchar(heart[i]);
	putchar('\n');
	
	const char * line = "---------------------------\n";
	while(*(line) != '\0'){
		putchar(*(line++) );
	}
	
	char * word = "frame";
	//word[1] = 'l';  //编译器允许,但运行程序报错。
	
	/** 不能运行,可以编译。
	//编译器使用内存中的一个副本来表示所有完全相同的字符串字面量
	char * p1 = "Klingon";
	p1[0] = 'F';
	printf("Klingon");
	printf(": Beware the %ss!\n","Klingon");
	*/
	const char * p1 = "Klingon";  //推荐用法 
	p1[0] = 'F';  //编译错误,提示error: assignment of read-only location '*p1'
	
	//const 数组初始化字符中字面量,数组获得提原始字符串的副本。
}

如果不修改字符串,不要用指针指向字符串字面量

I love
I love
I love
I love
I love Millie!
I love Mil
---------------------------

5.字符串数组

如果创建一个字符数组会很方便,可以通过数组下标访问多个不同的字符串。程序清单11.4演示了两种方法:指向字符串的指针数组和char类型数组的数组。

// arrchar.c --指针数组,字符串数组
#include <stdio.h>
#define SLEN 40
#define LIM 5
int main(void)
{
	const char *mytalents[LIM]={
		"Adding numbers swiftly",
		"Multiplying accurately","Stashing data",
		"Following instructins to the letter",
		"Understanding the C language"
	};
	char yourtalents[LIM][SLEN]={
		"Walking in a straight line",
		"Sleeping","Watching television",
		"Mailing letters","Reading email"
	};
	int i;
	puts("Let's compare talents.");
	printf("%-36s %-25s\n","My Talents","Your Talents");
	for (i = 0; i < LIM; i++)
		printf("%-36s %-25s\n",mytalents[i],yourtalents[i]);
	printf("\nsizeof mytalents:%d,sizeof yourtalents:%d\n",
				sizeof(mytalents),sizeof(yourtalents));
	return 0;	
}

程序输出:

Let's compare talents.
My Talents                           Your Talents
Adding numbers swiftly               Walking in a straight line
Multiplying accurately               Sleeping
Stashing data                        Watching television
Following instructins to the letter  Mailing letters
Understanding the C language         Reading email

sizeof mytalents:20,sizeof yourtalents:200

从某些方面来看,mytalents和yourtalents非常相似。两者都代表5个字符串。使用一个下标时都分别表示一个字符串,如mytalents[0]和yourtalents[0];使用两个下标时都分别表示一个字符,例如mytalenets[1][2]表示mytalents数组中第2个指针所指向的字符串的第3个字符“l”,yourtalents[1][2]表示yourtalents数组的第2个字符串的第3个字符'e'。而且,两者的初始化方式也相同。

但是,它们也有区别。mytalentes数组是一个内含5个指针的数组,在我们的系统中共占用20字节。而yourtalents是一个内含5个数组的数组,每个数组内含40个char 类型的值,共占用200字节。所以,虽然mytalents[0]和yourtalents[0]都分别表示一个字符串,但mytalents和yourtalents的类型并不相同。mytalents中的指针指向初如化时所用的字符串字面量的位置,这些字符串字面量被储存在静态内存中;而yourtalenets中的数组则储存着字符串字面的副本,所以每个字符串都被储存了两次。此外,为字符串数组分配内存的使用率较低。yourtalents中的每个元素的大小必须相同,而且必须是能储存最长字符串的大小。

如果要用数组表示一系列待显示的字符串,请使用指针数组,因为它比二维字符数组的效率高。但是,指针数组也有自身的缺点.

mytalents中的指针指向的字符中字面量不能更改;而yourtalenets中的内容可以更改。氢,如果要改变字符串或为字符串输入预留空间,不要使用指向字符串字面量的指针。

11.1.2 指针和字符串

字符串的绝大多数操作都是通过指针完成的。

/* p_and_s.c --指针和字符串*/
#include <stdio.h>
int main(void)
{
	const char * mesg = "Don't be a fool!";
	const char * copy;
	
	copy = mesg;
	printf("%s\n",copy);
	printf("mesg = %s; &mesg = %p; value = %p\n",mesg,&mesg,mesg);
	printf("copy = %s; &copy = %p; value = %p\n",copy,&copy,copy);
	
	return 0;
	
}

程序输出:

Don't be a fool!
mesg = Don't be a fool!; &mesg = 0028FF3C; value = 00405064
copy = Don't be a fool!; &copy = 0028FF38; value = 00405064

第1项:输出两个字符串

第2荐,打印两个指针的地址,不同,因为声明 了两个不同的指针。

第3项:显示两个指针的值,指针存在的地址。两者相同。

 

11.2 字符串输入

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

11.2.1 分配空间

要做的第1件事是分配空间,以储存稍后读入的字符串。

C库提供了许多读取字符串的函数:scanf()、gets()和fgets()。

11.2.2 不幸的gets()函数

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

// getsputs.c --使用gets() 和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

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

C99保留gets()

C11废除gets()

11.2.3 gets()的替代品

1.fgets()函数(fputs())

// fgets1.c --使用fgets() 和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,pleae.");
	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,pleae.
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\n储存在数组中。再次提醒读者注意,puts()函数会在待输出字符末尾添加一个换行符,而fputs()不会这样做。

 

fgets()和fputs(),返回指向char指针。当函数读到文件结尾,它将返回一个特殊的指针。空指针(null pointer).在C中,用宏NULL代替。

程序清单11.8

// fgets2.c --使用fgets() 和fputs()
#include <stdio.h>
#define STLEN 10
int main(void)
{	
	char words[STLEN];
	while (fgets(words,STLEN,stdin) != NULL && words[0] != '\n')
	fputs(words,stdout);
	puts("Done.");
	return 0;
}

程序输出

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.

有意思,虽然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。这意味着用户在按下Entern键之前,输入都被储存在临时存储区(即,缓冲区)中。按下Entern键就在输入中增加了一个换行符,并把整行输入发送给fgets()。对于输出,fputs)_把字符发送给另一个缓冲区,当发送换行符时,缓存区中的内容被发送到屏幕上。

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

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

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

// fgets3.c --使用fgets() 和fputs()
#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 //如果word[i] == '0' 则执行这部分代码
			while (getchar() != '\n') //读取但不储存输入,包括\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 ling lines.
unwilling
But is doesn't get stuck on long
But is do
lines either.
lines eit

done

2 gets_s()函数

3.s_gets()函数

/*s_gets()函数 功能 读取整行输入并用空字符代替换行符,或者读取一部分输入
*/
char * s_gets(char * st, int n)
{
	char * ret_val;
	int i = 0;
	
	ret_val = fgets(st,n,stdin);
	if (ret_val) //即,ret_val != NULL
	{
		while (st[i] != '\n' && st[i] != '\0')
			i ++;
		if (st[i] == '\n')
			st[i] = '\0';
		else 
			while (getchar() != '\n')
				continue;
	}
	return ret_val;
}

11.2.4 scanf()函数

scanf()函数返回一人整数值,该值等于scanf()成功读取的荐数或EOF(读到文件结尾时返回EOF)。

程序清单11.11演示了在scanf()函数中指定字段宽度的用法。

// scan_str.c --使用scanf()
#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;
}

程序的3个输出示例

Please enter 2 names.
Jesse Jukes
I read the 2 names Jesse and Jukes.

Please enter 2 names.
Liza Applebotthan
I read the 2 names Liza and Applebotth.

Please enter 2 names.
Portensia Callowit
I read the 2 names Porte and nsia.

scanf()适合一次只输入一个单词。

11.3 字符串输出

11.3.1 puts()函数

// put_out.c --使用puts()
#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 argumetn to puts()");
	puts(DEF);
	puts(str1);
	puts(str2);
	puts(&str1[5]);
	puts(str2 + 4);
	
	return 0;
}

程序输出

I'm an argumetn 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

如上所示,每个字符串占一行,因为puts()在显示字符串是会自动在其末尾添加一个换行符。

该程序示例再次说明,用双引号括起的内容是字符串常量,且被视为该字符串的地址。另外,储存字符串的数组名也被看伤脑筋是地址。

puts()如何知道在何处停止?该函数在遇到空字符时就停止输出,所以必须确保有空字符。不要模仿程序清单11.13中的程序!

// nono.c --千万不要模仿!!
#include <stdio.h>
#define DEF "I am a #defined string."
int main(void)
{
	char side_a[] = "Side A";
	char dont[] = {'W','O','W','!'};
	char side_b[] = "Side B";
	puts(dont);	//dont 不是一个字符串
	
	return 0;
}

11.3.2 fputs()函数

gets()与puts()

fgets()与fputs()

11.3.3 printf()函数 

格式化不同的数据类型

11.4 自定义输入/输出函数

/**put_put.c --打印字符串,不添加\n*/
#include <stdio.h>
void put1(const char *);
int put2(const char *);
int main(void)
{
	put1("If I'd as much money");
	put1( "as I could spend,\n");
	printf("I count %d characters.\n",
		put2("I never would cry old chairs to mend."));
	return 0;
}

// put1.c --打印字符串,不添加\n
void put1(const char * string) /*不会改变字符串*/
{
	while (*string != '\0') //可用while (*string)代替
		putchar(*string++);
}

//put2打印一个字符,并统计打印字符数
int put2(const char * string)
{
	int count = 0;
	while (*string) /*常规用法*/
	{
		putchar(*string++);
		count++;
	}
		
		putchar('\n');//不统计换行符
		
		return count;
		
}

程序中使用printf()打印put2的值,但是为了获得put2()返回值,计算机必须先执行put2(),因此在打印字符数之前先打印了传递给该函数的字符串。下在是该程序的输出:

If I'd as much moneyas I could spend,
I never would cry old chairs to mend.
I count 37 characters.

11.5字符串函数

C库中提供了多个处理字符串的函数。ANSI C把这些函数的原型放在string.h头文件中。其中最常用的函数有strlen()、strcat()、strcmp()、strncmp()、strcpy()和strncpy()。

11.5.1 strlen()函数

strlen()函数用于统计字符串的长度。下面的函数可以缩短字符串的长度,其中用到了strlen():

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

该函数要改变字符串,所以函数头在声明形式参数string时没有使用const限定符。

程序清单11.17 中的程序测试了fit()函数。注意代码中使用了C字符串常量的串联特性。

程序清单11.17 test_fit.c程序 

//test_fit.c --使用缩短字符串长度的函数
#include <stdio.h>
#include <string.h> /* 内含字符串函数原型*/
void fit(char *,unsigned int);

int main(void)
{
	//使用了C字符串常量的串联特性
	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 stirng.");
	puts(mesg + 39);
	
	return 0;
}

 //缩短字符串长度函数fit。
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 stirng.
 but not simpler.

fit()函数把mesg[38]处的逗号替换成‘\0’字符。puts()函数在空字符处停止输出,并忽略其余字符。然而,这些字符还在缓冲区中,睛面的函数调用把这些字符打印了出来:

puts (mesg + 39);

表达式mesg + 39 是mesg[39]的地址,该地址上储存的是空格字符。所以 puts()显示该字符并继续输出直至遇到原来的字符串中的空字符。

11.5.2 strcat()函数

strcat()(用于拼接字符串)函数接受两个字符串作为参数。该函数把第2个字符串的备份附加在第1个字符串末尾,并把拼接后形成的新字符串作为第1个字符串,第2个字符串不变。strcat()函数的类型是char *(即,指向char的指针)。strcat()函数返回第1个参数,即拼接第2个字符串后的第1个字符串的地址。

程序清单11.18.c程序

/*str_cat.c  --拼接两个字符串*/
#include <stdio.h>
#include <string.h> /*strcat()函数的原型在该头文件中*/
#define SIZE 80
char * s_gets(char *st,int n);
int main(void)
{
	char flower[SIZE];
	char addon [] = "s smell like old shone.";
	
	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;
	
}


/*s_gets()函数 功能 读取整行输入并用空字符代替换行符,或者读取一部分输入
*/
char * s_gets(char * st, int n)
{
	char * ret_val;
	int i = 0;
	
	ret_val = fgets(st,n,stdin);
	if (ret_val) //即,ret_val != NULL
	{
		while (st[i] != '\n' && st[i] != '\0')
			i ++;
		if (st[i] == '\n')
			st[i] = '\0';
		else 
			while (getchar() != '\n')
				continue;
	}
	return ret_val;
}

程序输出

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

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

11.5.3 strncat()函数

strcat()函数无法检查第1个数组是否能容纳第2个字符串。如果分配给第1个数组的空间不够大,多出来的字符溢出到相邻存储单元时就会出问题。当然,可以像程序清单11.17那样,用strlen()查看第1个数组的长度。注意,要给拼接后的字符串长度加1才够空间存放末尾的空字符。或者,用strncat(),该函数的第3个参数指定了最大添加字符数。例如,strnct(bugs,adddon,13)将把addon字符串的内容附加经bugs, 在加到第13个字符或遇到空字符时停止。因此,算上空字符(无论哪种情况都要添加空字符),bugs数组应该足够大,以容纳原始字符串(不包含空子符)、添加原始字符串在后面的13个字符和末尾的空字符。程序清单11.19使用这种方法,计算avaiable变量的值,用于表示允许添加的最大字符数。

程序清单11.19 join_chk.c程序

/* join_chk.c --拼接两个字符串,检查第1个数组的大小*/
#include <stdio.h>
#include <string.h>
#define SIZE 30
#define BUGSIZE 13
char * s_gets(char *st,int n);
int main()
{
	char flower[SIZE];
	char addon [] = "s smell like old shone.";
	char bug[BUGSIZE];
	int available;
	
	puts("What is your favorite flower?");
	s_gets(flower,SIZE);
	if((strlen(addon) + strlen(flower) + 1 ) <= SIZE)
		strcat(flower,addon);
	puts(flower);
	puts("What is your favorite bug?");
	s_gets(bug,BUGSIZE);
	available = BUGSIZE - strlen(bug) -1;
	strncat(bug,addon,available);
	puts(bug);
	return 0;
}

/*s_gets()函数 功能 读取整行输入并用空字符代替换行符,或者读取一部分输入
*/
char * s_gets(char * st, int n)
{
	char * ret_val;
	int i = 0;
	
	ret_val = fgets(st,n,stdin);
	if (ret_val) //即,ret_val != NULL
	{
		while (st[i] != '\n' && st[i] != '\0')
			i ++;
		if (st[i] == '\n')
			st[i] = '\0';
		else 
			while (getchar() != '\n')
				continue;
	}
	return ret_val;
}

程序输出

What is your favorite flower?
Rose
Roses smell like old shone.
What is your favorite bug?
Aphid
Aphids smell

available = BUGSIZE - strlen(bug) -1;

输入Aphid,available的值为:13-5-1=7,所以只会在Aphid 接接上7个字符 s smell(包括空格)

strcat和gets()类似,也会导致缓冲区溢出。为什么C11标准不废弃strcat(),只留下strncat?为何对gets()那么残忍?这也许是因为gets()造成的安全隐患来自于使用该程序的人,而strcat()暴露的问题是那些粗心的程序员造成的。无法控制用户会进行什么操作,但是,可以控制你的程序做什么。C语言相信程序员,因此程序员有责任确保strcat()的使用安全。

11.5.4 strcmp()函数

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

程序清单11.20 nogo.c程序

/* nogo.c -- 该程序是否能正常运行*/
#include <stdio.h>
#define ANSWER "GRANT"
#define SIZE 40
char * s_gets(char *st,int n);
int main()
{
	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;
}

/*s_gets()函数 功能 读取整行输入并用空字符代替换行符,或者读取一部分输入
*/
char * s_gets(char * st, int n)
{
	char * ret_val;
	int i = 0;
	
	ret_val = fgets(st,n,stdin);
	if (ret_val) //即,ret_val != NULL
	{
		while (st[i] != '\n' && st[i] != '\0')
			i ++;
		if (st[i] == '\n')
			st[i] = '\0';
		else 
			while (getchar() != '\n')
				continue;
	}
	return ret_val;
}

这个程序看上云没问题,但是运行后却不对劲。ANSWER和try都是指针,所以try != ANSWER检查的不是两个字符串是否相等,而是这一个人个字符串地址是否相同。因为ANSWER和try储存在不同的位置,所以这两个地址不可能相同。

程序清单 11.21 compare.c程序

strcmp()函数,如果两个字符串参数相同,该函数就返回0,否则返回非零值。

/* compare.c -- 该程序是正常运行*/
#include <stdio.h>
#include <string.h> //strcmp()函数的原型在该头文件中
#define ANSWER "grant"
#define SIZE 40
char * s_gets(char *st,int n);
char * s_getstos(char * st, int n);
int main(void)
{
	char try[SIZE];
	puts("Who is buried in Grant's tomb?");
	s_getstos(try,SIZE);
	while (strcmp(try,ANSWER) != 0) //由于非零值都为“真”使用while (strcmp(try,ANSWER))代替
	{
		puts("No,that's wrong,Try again.");
		s_getstos(try,SIZE);
	}
	puts("That's right!");
	//s_getstos(try,SIZE);
	//puts(try);
	
	return 0;
}

/*s_gets()函数 功能 读取整行输入并用空字符代替换行符,或者读取一部分输入
*/
char * s_gets(char * st, int n)
{
	char * ret_val;
	int i = 0;
	
	ret_val = fgets(st,n,stdin);
	if (ret_val) //即,ret_val != NULL
	{
		while (st[i] != '\n' && st[i] != '\0')
			i ++;
		if (st[i] == '\n')
			st[i] = '\0';
		else 
			while (getchar() != '\n')
				continue;
	}
	return ret_val;
}

/*s_getstos()函数 功能 将大写字母转为小写字母
*/
char * s_getstos(char * st, int n)
{
	char * ret_val;
	int i = 0;
	
	ret_val = fgets(st,n,stdin);
	if (ret_val) //即,ret_val != NULL
	{
		while (st[i] != '\n' && st[i] != '\0')
		{
			if(st[i]>=65 && st[i]<=90)
				st[i]= st[i]+ 32;
			i++;
		}
			
		if (st[i] == '\n')
			st[i] = '\0';
		else 
			while (getchar() != '\n')
				continue;
	}
	return ret_val;
}

1. strcmp()的返回值

如果strcmp()比较的字符串不同,它会返回什么值?

程序清单11.22 compback.c程序

/*compback.c -- strcmp()的返回值*/
#include <stdio.h>
#include <string.h>
int main(void)
{
	printf("strcmp(\"A\",\"A\") is ");
	printf("%d\n",strcmp("A","A"));
	
	printf("strcmp(\"A\",\"B\") is ");
	printf("%d\n",strcmp("A","B"));
	
	printf("strcmp(\"B\",\"A\") is ");
	printf("%d\n",strcmp("B","A"));
	
	printf("strcmp(\"C\",\"A\") is ");
	printf("%d\n",strcmp("C","A"));
	
	printf("strcmp(\"Z\",\"a\") is ");
	printf("%d\n",strcmp("Z","a"));
	
	printf("strcmp(\"apples\",\"apple\") is ");
	printf("%d\n",strcmp("apples","apple"));
	return 0;
}

程序输出

strcmp("A","A") is 0
strcmp("A","B") is -1
strcmp("B","A") is 1
strcmp("C","A") is 1
strcmp("Z","a") is -1
strcmp("apples","apple") is 1

strcmp()比较“A”和本身,返回0;比较“A”和"B",返回-1;比较“B”和"A",返回1。这说明,如果在字母表中第1个字符串位于第2个字符串前面,strcmp()中就返回负数;反之,strcmp()则返回正数。所以strcmp()比较“C”和"A",返回1。其他系统可能返回2,即一两者的ASCII码之差。

程序清单11.23用strcmp()函数检查程序是否要停止读取输入

程序清单11.23 quit_chk.c程序

/* quit_chk.c --某程序的开始部分*/
#include <stdio.h>
#include <string.h>
#define SIZE 80
#define LIM 10
#define STOP "quit"
char * s_gets(char *st,int n);
int main()
{
	char input[LIM][SIZE];
	int ct = 0;
	printf("Enter up to %d lines (type quit to quit):\n",LIM);
	while(ct < LIM && s_gets(input[ct],SIZE) != NULL &&
				strcmp(input[ct],STOP) !=0)
	{
		ct++;
	}
	printf("%d strings entered\n",ct);
	return 0;
	
}
char * s_gets(char * st, int n)
{
	char * ret_val;
	int i = 0;
	
	ret_val = fgets(st,n,stdin);
	if (ret_val) //即,ret_val != NULL
	{
		while (st[i] != '\n' && st[i] != '\0')
			i ++;
		if (st[i] == '\n')
			st[i] = '\0';
		else 
			while (getchar() != '\n')
				continue;
	}
	return ret_val;
}

该程序在读到EOF字符(这种情况下s_gets()返回NULL)、用户输入quit或输入项达到LIM时退出。

顺带一提,有时输入空行(即,只按下Enter键或Return)表示结束输入更方便。为实现这一功能,只需要修改一下while循环的条件即可:

while(ct < LIM && s_gets(input[ct],SIZE) != NULL && input[ct][0] != '\0')									//按下Enter键退出

这里,input[ct]是输入的字符串,input[ct][0]是该字符串的第1个字符。如果用户输入空行,s_gets()便会把该行第1个字符(换行符)替换成空字符。

2. strncmp()函数

strcmp()函数比较字符串中的字符,直到发现不同的字符为止,这一过程可能会持续到字符串的末尾。

而strncmp()函数在比较两个字符串时,可以比较到字符不同的地方,也可以只比较第3个参数指定的字符数。

程序清单11.24 starsrch.c程序

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

程序输出

Found: astronomy
Found: astrophysics
Found: astrophobia
The list contained 3 words beginning with astro.

11.5.5 strcpy() 和strncpy()函数

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

pts1 = pts1;

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

函数声明

char *strcpy(char *dest, const char *src)

程序清单11.25 copy1.c程序

/* copy1.c --演示strcpy()*/
#include <stdio.h>
#include <string.h>
#define SIZE 40
#define LIM 5
char * s_gets(char *st,int n);
int main()
{
	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') //判断temp第一个字符是否是以q开头,不以q开头,提示后重新输入。只有以q开头的字符串,才会执行strcpy,i递增。
		if(strncmp(temp,"q",1) != 0){ //temp字符串和“q”的每1个元素是否相等。不相等,strncmp返回非0,提示扣重新输入。相等,strncmp返回0,才会执行strcpy,i递增
			printf("%s doesn't begin with q!\n",temp);
		}
		else{
			strcpy(qwords[i],temp);
			i++;
		}
	}
	printf("Here are the words accetped:\n");
	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) //即,ret_val != NULL
	{
		while (st[i] != '\n' && st[i] != '\0')
			i ++;
		if (st[i] == '\n')
			st[i] = '\0';
		else 
			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 accetped:
quackery
quasar
quilt
quotient
quiz

 

 函数原型

char *strcpy(char *str1,const char *2 );
char *strcpy(char *strDestination,const char *strSource );

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

1.strcpy()的其他属性

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

程序清单 11.26 copy2.c程序

/* copy2.c --使用strcpy()*/
#include <stdio.h>
#include <string.h>
#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的第1上t(见图11.5)。注意,由于第1个参数是copy + 7,所以ps指向copy中的第8个元素(下标为7),因此puts(ps)从该处开始打印字符串。

 

2,更谨慎的选择:strncpy()

该函数的第3个参数指明可拷贝的最大字符数。程序清单11.27用strncpy()代替程序清单11.25中的strcpy()。为了演示目标空间装不下源字符串的副本会发生什么情况,该程序使用了一个相当小的目标字符串(共7个元素,包含6个字符)。

程序清单11.27 copy3.c程序

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

strncpy(qwords[i],temp,TARGSIZE-1);
qwords[i][TARGSIZE-1] = '\0';

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

11.5.6 sprintf()函数

sprintf()函数声明在stdio.h中,而不是在string.h中。该函数和printf()类似,但是它是把数据写入字符串,而不是打印在显示器上。因此,该函数可以把多个元素组成一个字符串。sprintf()的第1个参数是目标字符串的地址。其余参数即和printf()相同,即格式字符串和待写入项的列表。

程序清单  11.28中的程序用printf()把3个项(两个字符串和一个数字)组合成一个字符串。濂sprintf()的用法和printf()相同,只不过sprintf()把组合的字符串储存在数组formal中而不是显示在屏幕上。

11.6 字符串示例:字符串排序

11.6.1  排序指针而非字符串

11.7  ctype.h字符函数和字符串

ctype.h系列字符相关的函数。虽然这些函数不能处理整个字符串,但是可以处理字符串中的字符。例如,程序清单11.30中定义的ToUpper()函数,利用toupper()函数处理字符中的每个字符,把整个字符串转换成大写;定义的PunctCount()函数,利用ispunct()统计字符中的标点符号个数。另外,该程序使用strchr()处理fgets()读入字符串换行符(如果有的话)。

程序清单 11.30 mod_str.c程序

/* mod_str.c --修改字符串*/
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define LIMIT 81
void ToUpper(char *);
int PunctCount(const char *);

/*strchr
 C 标准库 - <string.h>
描述
C 库函数 char *strchr(const char *str, int c) 在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。

声明
下面是 strchr() 函数的声明。
char *strchr(const char *str, int c)

参数
str -- 要被检索的 C 字符串。
c -- 在 str 中要搜索的字符。

返回值
该函数返回在字符串 str 中第一次出现字符 c 的位置,如果未找到该字符则返回 NULL。
*/

/*
toupper
C 库函数 - toupper()
C 标准库 - <ctype.h> C 标准库 - <ctype.h>

描述
C 库函数 int toupper(int c) 把小写字母转换为大写字母。

声明
下面是 toupper() 函数的声明。
int toupper(int c);

参数
c -- 这是要被转换为大写的字母。
返回值
如果 c 有相对应的大写字母,则该函数返回 c 的大写字母,否则 c 保持不变。返回值是一个可被隐式转换为 char 类型的 int 值。
*/

/*
ispunct
C 标准库 - <ctype.h> C 标准库 - <ctype.h>

描述
C 库函数 int ispunct(int c) 检查所传的字符是否是标点符号字符。标点符号字符可以是非字母数字(正如 isalnum 中的一样)的任意图形字符(正如 isgraph 中的一样)。

声明
下面是 ispunct() 函数的声明。

int ispunct(int c);
参数
c -- 这是要检查的字符。
返回值
如果 c 是一个标点符号字符,则该函数返回非零值(true),否则返回 0(false)。
*/
int main(void)
{
	char line[LIMIT];
	char * find;
	
	puts("Please enter a line:");
	fgets(line,LIMIT,stdin);
	find = strchr(line,'\n'); //查找换行符
	if (find)           //如果地址不是NULL,
		*find = '\0'; //用空字符替换
	ToUpper(line);
	puts(line);
	printf("That line has %d punctuation characters,\n",PunctCount(line));
	
	return 0;
}
void ToUpper(char * str)
{
	while (*str)
	{
		*str = toupper(*str);
		str++;
	}
}
int PunctCount(const char * str)
{
	int ct = 0;
	while (*str)
	{
		if (ispunct(*str))
			ct++;
		str++;
	}
	return ct;
}

while(*str)循环处理str指向的字符串中的每个字符,直至遇到空字符。此时*str的值为0(空字符的编码值为0),即循环条件为假,循环结束。下面是该程序的运行示例:

Please enter a line:
We? You talkin' to me? Get outta here!
WE? YOU TALKIN' TO ME? GET OUTTA HERE!
That line has 4 punctuation characters,

11.8 命令行参数

在图形界面普通之前,都使用命令行界面。DOS和UNIX就是例子。Linux终端提供类UNIX命令行环境。命令行(command line)是在命令行环境中,用户为运行程序输入命令的行。假设一个文件中有一个名为fuss的程序。在UNIX环境中运行该程序的命令行是:

$ fuss

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

C:> fuss

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

$ fuss -r Ginger

一个C程序可以读取并使用这些附加项(见下图)

程序清单11.27 是一个典型的例子,该程序通过main()的参数读取这些附加项目。

/* repeat.c --带参数的main()*/
#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。下面是通过命令行运行该程序后的输出:

D:\myprogram\c\c_plus\ch11>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],然后把随后的第1个字符串赋给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表示一系列字符串。

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

repeat "I am hungry"  now

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

11.9  把字符串转换为数字

数字既能以字符串形式储存,也能以数值形式储存。把数字储存为字符串就是储存数字字符。例如,数字213以‘2’、‘1’、‘3’、‘、0’的形式被储存在字符串数组中。以数值形式储存213,储存的是int类型的值。

C要求用数值形式进行数值运算(如,加法和比较)。但是在屏幕上显示数字则要求字符串形式,因为屏幕显示的是字符。printf()和sprintf()函数。通过%d和其他转说明,把数字从数值形式转换为字符串形式。scanf()可以把输入字符串转换为数值形式。C还有一些函数专门用于把字符串形式转换成数值形式。

假设你编写的程序需要使用数值命令形参,但是命令行参数被读取为字符串。因此,要使用数值必须先把字符串转换为数字。如果需要整数,可以使用atoi()函数(用于把字母数字转换成整数),该函数接受一个字符串作为参数,返回相应的整数值。程序清单11.32中的程序示例演示了该函数的用法。

程序清单 11.32 hello.c程序

/*hello.c --把命令行参数转换为数字*/
#include <stdio.h>
#include <stdlib.h>

int main(int argc,char *argv [])
{
	int i,times;
	
	if(argc < 2 || (times = atoi(argv[1])) < 1)
		printf("Usage: %s positive-number\n",argv[0]);
	else
		for(i = 0; i < times; i++)
			puts("Helo,good looking");
}

程序输出

D:\>hello 3
Helo,good looking
Helo,good looking
Helo,good looking

命令行参数3被储存为字符串3\0。atoi()函数把该字符串转换为整数值3,然后该值被赋给times。该值确定了执行for循环的次数。

如果运行该程序时没有提供命令行参数,那么  argc < 2 为真,程序给出一条提示后结束。如果times 为9 或负数,情况也是如此。C语言逻辑运算符求值顺序保证了如果 argc < 2,就不会对atoi(argv[1]) 求值。

如果字符串仅以整数开头,atio()函数也能处理,它只把开对的整数转换为字符。例如 ,atoi("42regular")将返回42.如果在命令行输入hello what 会怎样?在我们所用的C实现 中,如果命令行参数不是数字,atoi()函数返回0。然而C标准规定,这种情况下的行为是未定义的。因此,使用有错误检测功能的strtol()函数会更安全。

stdlib.h 中包含atof().atol(),atoi()。

ANSI C还提供了一套更智能的函数:strtol()把字符串转换成long类型值,strtoul()把字符串转换成unsigned long类型的值,strtod()把字符串转换成double类型的值。这些函数的智能之处在于识别和报告字符串中的首字符是否是数字。而且 ,strtol()和strtoul()还可以指定数字的进制。

strtol()函数原型:

long strtol (const char * restrict nptr, char ** restrict endptr,int base );

这里,nptr是指向转换字符串的指针,endptr是一个指针的地址,该指针被设置为标识输入数字结束字符的地址。base表示以什么进制写入数字。程序清单11.33 演示了该函数的用法。

程序清单 11.33 strcnvt.c程序

/*strcnvt.c --使用strtol()*/
#include <stdio.h>
#include <stdlib.h>
#define LIM 30
char * s_gets(char * st,int n);

int main()
{
	char number[LIM];
	char * end;
	long value;
	
	puts("Enter a number (empty line to quit):");
	while(s_gets(number,LIM) && number[0] != '\0')
	{
		value = strtol (number,&end,10);
		printf("base 10 input,base 10 output:%ld,stopped at %s (%d)\n",
		        value,end,*end);
		value = strtol (number,&end,16);
		printf("base 10 input,base 10 output:%ld,stopped at %s (%d)\n",
		        value,end,*end);
		puts("Next number:");		
	}
	puts("Bye!\n");
}

/*s_gets()函数 功能 读取整行输入并用空字符代替换行符,或者读取一部分输入
*/
char * s_gets(char * st, int n)
{
	char * ret_val;
	int i = 0;
	
	ret_val = fgets(st,n,stdin);
	if (ret_val) //即,ret_val != NULL
	{
		while (st[i] != '\n' && st[i] != '\0')
			i ++;
		if (st[i] == '\n')
			st[i] = '\0';
		else 
			while (getchar() != '\n')
				continue;
	}
	return ret_val;
}

程序输出

Enter a number (empty line to quit):
10
base 10 input,base 10 output:10,stopped at  (0)
base 10 input,base 10 output:16,stopped at  (0)
Next number:
10atom
base 10 input,base 10 output:10,stopped at atom (97)
base 10 input,base 10 output:266,stopped at tom (116)
Next number:

Bye!

首先注意,当base分别为10和16时,字符串“10”分别被转换为数字10和16.还要注意,如果end指向一个字符,*end就是一个字符。因此,第1次转换在读到空字符时结束,此时end指向空字符。打印end会显示一个空字符串,以$d转换说明软件开发手册*end显示的是空字符的ASCII码。

对于第2个输入的字符串,当base为10时,end的值是‘a’字符的地址。所以打印end显示的是字符串“atom”,打印*end显示的是‘a’字符的ASCII码。然而,当base为16时,'a'字符被识别为一人有效的十六进制数,strtol()函数氢十六进制数10a转换成十进制数266.

strtol()函数最多可以转换三十六进制,'a' ~ 'z' 字符都可以用作数字。strtoul()函数与该函数类似,但是它把字符串转换成无符号值。strtod()函数只以十进制转换,因此它只需要两个参数。

11.10 关键概念

许多程序都要处理文本数据。一个程序可以标示 用户输入姓名,公司列表、地址、一种蕨类植物的学名、音乐剧的演员。毕竟,我们用的语言与现实世界互动,使用文本的例子不其数。C程序通过字符串的方式来处理它们。

字符串,无论是由字符数组、指针还是字符串常量标识,都储存为包含字符编码的一系列字节,并以空字符串结尾。C提供库函数处理字符串,查找字符串并分析它们。尤其要牢记,应该使用strcmp()来代替关系运算符,当比较字符串时,应该使用strcpy()或strncpy()代替赋值运算符把字符串赋给字符数组。

11.11 本单小结

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值