11.7 ctype.h字符函数和字符串
ctype.h系列与字符串相关的函数。虽然这些函数不能处理整个字符串,但是可以处理字符串中的字符。例如,ToUpper()函数,利用toupper()函数处理字符串中的每个字符,把整个字符串转换成大写;定义的PunctCount()函数,利用ispunct()统计字符串中的标点符号个数。另外,该程序使用strchr()处理fgets()读入字符串的换行符(如果有的话。)。
// mod_str.c --修改字符串
#include<stdio.h>
#include<string.h>
#include<ctype.h>
#define LIMIT 81
void ToUpper(char *);
int PutctCount(const char *str);
int main(void)
{
char line[LIMIT];
char *find;
puts("Please enter a line:");
fgets(line,LIMIT,stdin);
find = strchr(line,'\n'); //查找换行符
if(find)
*find = '\0';
ToUpper(line);
puts(line);
printf("That line has %d punctuation characters.\n",PutctCount(line));
return 0;
}
void ToUpper(char *str)
{
while(*str)
{
*str = toupper(*str);
str++;
}
}
int PutctCount(const char *str)
{
int ct = 0;
while(*str)
{
if(ispunct(*str))
ct++;
str++;
}
return ct;
}

while(*str)循环处理str指向的每个字符,直至遇到空字符。此时*str的值为0(空字符的编码值为0),即循环条件为假,循环结束。
ToUpper()函数利用toupper()处理字符串的每个字符(由于C区分大小写,所以这是两个不同的函数名)
根据ANSI C中的定义,toupper()函数只改变小写字符。但是一些很久的C实现不会自动检查大小写,所以以前的代码通常会写成:
if(islower(*str))
*str = toupper(*str);
顺带一提,ctype.h中的函数通常作为宏(macro)来实现。这些C预处理器宏的作用很像函数,但是连着有一些重要的区别。
该程序使用fgets()和strchr()组合,读取一行输入并且把换行符替换成空字符。这种方法与使用s_gets()的区别是:s_gets()会处理输入行剩余字符(如果有的话),为下一次输入做好准备。
11.8 命令行参数
命令行(command line)是命令环境中,用户为运行程序输入命令的行。假设一个文件中有一个名为fuss 的程序。在UNIX环境中运行该程序的命令行是:
($) fuss
或者在Windows命令提示模式下是:
C> fuss
命令行参数(command-line argument)是同一行的附加项。:
($) fuss -r Ginger
一个C程序可以读取并使用这些附加项。



由此可见该程序为何命名为repeat。
C编译器允许main()没有参数或者有两个参数(一些实现main()有更多参数,属于对标准的扩展)。main()有两个参数时,第1个参数是命令行中的字符串数量。这个int类型的参数被称为arg(表示参数计数(argument count))。系统用空格表示一个字符串的结束和下一个字符串的开始。
因此上面的repeat示例中包括命令名共有4个字符串,其中后面3个供repeat使用。该程序把命令行字符串存储在内存中,并把每个字符串的地址储存在指针数组中。而该数组的地址则被储存在main()的第2个参数中。按照管理,这个指向指针的指针称为argc(表示参数[argument value])。如果系统允许,就把程序本省的名称赋给argv[0],然后把随后的第1个字符串赋给argv[1]。
argv[0]指向repeat(对大部分系统而言)
argv[1]指向Resistance
argv[2]指向is
argv[3]指向futile
上述程序清单通过一个for循环依次打印每个字符串。printf()中的%s转换说明表示,要提供一个字符串的地址作为参数,而指针数组中的每个元素(argv[0]/argc[1])都是这样的地址。
main()中的形参形式为其他形参的函数相同,许多程序员用不同的形式声明argv:
int main(int argc,char **argc)
char **argc与char *argc[]等价。也就是说,argv是一个指向指针的指针,它所指的指针指向char。因此,即使在原始定义中,argv也是指向指针的指针。两种形式都可以使用,但是我们仍为第1中形式更清楚的表明了argv表示一些列字符串。
许多环境(UNIX,和DOS)都允许用双引号把多个单词连起来形成一个参数。
例如:
repeat "I am hungry"now
运行命令行把字符串“I am hungry”赋给argv[1],把“now”赋给argv[2].
11.8.1 集成环境中的命令行参数

11.9 把字符串转换为数字
数字既能以字符串形式存储,也能以数值形式存储。把数字储存为字符串就是储存数字字符。例如:数字213以'2','1','3','\0'的形式被存储在字符串数组中。以数值形式存储213,存储的是int类型的值。
C要求用数值形式进行数值运算(如,加法和比较)。但是在屏幕上显示数字则要求字符串形式,因为屏幕显示的是字符。printf()和sprintf()函数,通过%d和其他转换说明,把数字从数值形式转换为字符串形式,scanf()可以把输入字符串转换为数值形式。C还有一些函数专门用于把字符串形式转换为数值形式。
假设你编写的程序需要使用数值命令形参,但是命令形参数被读取为字符串。因此,要使用数值必须先把字符串转换成为数字。如果需要整数,可以使用atio()函数(用于把字母数字转换成整数),该函数接受一个字符串作为参数,返回相应的整数值。


\$是UNIX和Linux的提示符(一些UNIX系统使用%)。命令行参数3被存储为字符串3\0.atoi()函数把该字符串转换成为整数值3,然后该值被赋给times。该值确定了执行for循环的次数。
如果运行该程序时没有提供命令行参数,那么argc<2为真,程序给出一条提醒信息后结束。如果times为0或负数,情况也是如此。C语言逻辑运算符的求值顺序保证了arc <2 就不会对atoi(argv[1])求值。
如果字符串仅以整数开头,atoi()函数也能处理,他只把开头的整数转换为字符串。例如,atoi("42regular")将返回整数42.如果在命令行输入hello what会怎么样子?
在我们所使用的C语言中如果命令行参数不是数字,atio()返回0.然而C规定了,这种情况下的行为是未定义的。因此,使用有错误检测功能的strtol()函数会更安全。
该程序中包含了stdlib.h头文件,因为从ANSI C开始,该文件总包含了atoi()函数的原型。初次之外,还包含了atof()和atol()函数的原型。stof()函数把字符串转换成double类型的值,stol()函数把字符串转换成long类型的值。atof()和atol()的工作原理和atoi()类似,因此它们分别返回double类型和long类型的值。
ANSI C还提供一套更智能的函数:strtol()把字符串转换成long类型的值,strtoul()把字符串转换成unsigned long 类型的值,strtod()把字符串转换成double类型的值。这些函数的智能之处在于识别和报告字符串中的首字符是否是数字。而且,strtol()和strtoul()还可以指定数字的进制。
下面的程序示例中涉及到strtol()函数,其原型如下:
long strtol(const char *restrict nptr,char **restrict endptr,int base);
这里,ndptr是指向带转换字符串的指针,endptr是一个指针的地址,该指针被设置为标识输入数字结束字符的地址,base标识以什么进制写入数字。
//strcnvt.c --使用strtol()
#include<stdio.h>
#include<stdlib.h>
#define LIM 30
char *s_gets(char *string,int n);
int main(void)
{
char number[LIM];
char *end;
long value;
puts("Enter a number(empty line to quit):");
while(s_gets(number,LIM) && number[0]!='\n')
{
value = strtol(number,&end,10); //10进制
printf("base 10 input,base 10 output: %ld,stopped at %s (%d)\n",value,end,*end);
value = strtol(number,&end,16); //10进制
printf("base 16 input,base 10 output: %ld,stopped at %s (%d)\n",value,end,*end);
puts("Next number:");
}
puts("Bye!");
return 0;
}
char *s_gets(char *string,int n)
{
char *ret_val;
int i = 0;
ret_val = fgets(string,n,stdin);
if(ret_val)
{
while(string[i]!='\n'&&string[i]!='\0')
i++;
if(string[i] = '\n')
string[i] = '\0';
else
while(getchar()!='\n')
continue;
}
return ret_val;
}

首先注意,当base分别为10和16时,字符串“10”分别被转换成了10和16.还要注意,如果end指向一个字符,*end就是一个字符。因此,第一次转换在读到空字符时结束,此时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()函数只以十进制转换,因此它需要两个参数。
许多实现使用itoa()和ftoa()函数分别把整数和浮点数转换成字符串。但是这两个函数并不是C标准库成员,可以用sprintf()函数代替它们,因为sprintf()的兼容性更好
11.10 关键概念
许多程序都要处理文本数据。一个程序可能要求用户输入姓名、公司列表、地址、一种蕨类植物的学名、音乐剧的演员等。毕竟,我们用语言与现实世界互动,使用文本的例子不计弃数。C程序通过字符串的方式来处理它们。
字符串,无论是由字符数组、指针还是字符串常量标识,都存储为包含字符编码的一系列字节,并以空字符串结尾。C库提供函数处理字符串,查找字符串并分析它们,尤其要牢记,应该使用strcmp()来代替关系运算符,当比较字符串时,应该使用strcpy()或者strncpy()代替赋值运算符把字符串赋给字符串数组。
11.11 本章小结
C字符串是一系列char类型的字符,以空字符('\0')结尾。字符串可以存储在字符数组中。字符串还可以用字符串常量来表示,里面都是字符,括在双引号中(空字符除外)。编译器提供空字符。因此,“joy”被存储为4个字符j、o、y和\0。strlen()函数可以统计字符串的长度,空字符不计算在内。
字符串常量也叫做字符串——字面量。可用于初始化字符数组。为了容纳末尾的空字符,数组大小应该至少比容纳的数组长度多1.也可以用字符串常量初始化指向char的指针。
函数使用指向字符串首字符的指针来表示待处理的字符串,通常,对应的实际参数是数组名、指针变量、或用双引号括起来的字符串。无论是哪种情况,传递的都是首字符的地址。一般而言没必要传递字符串的长度,因为函数可以通过末尾的空字符确定字符串的结束。
fgets()函数获取一行输入,puts()和fputs()函数显示一行输出。它们都是stdio.h头文件中的函数,用于代替已经被废弃的gets().
C库中有多个字符串处理函数。在ANSIC中这些函数都声明在string.h文件中。C库中还有许多字符处理函数,声明在ctype.h中。
给main()函数提供两个合适的参数,可以让程序访问命令行参数。第一个参数通常是int类型的argc,其值是命令行的单词数量。第2分参数通常是一个指向数组的指针argv,数组内含指向char的指针,每个指向char的指针都指向一个命令行参数字符串,argv[0]指向命令名称,argv[1]指向第1个命令行参数,以此类推。
atoi(),atol()和atof()函数把字符串形式的数字分别转换成int、long和double类型的数字。strtol()\strtoul()和strtod()函数把字符串形式的数字分别转换成long、unsigned long 和double类型的数字。
叨叨几句... NOTHING