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; © = %p; value = %p\n",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!; © = 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 本单小结