目录
一、表示字符串和字符串I/O
1.1 在程序中定义字符串
1.字符串字面量(字符串常量)
2.字符串数组和初始化
3.数组和指针
4.数组和指针的区别
5.字符串数组
1.2 指针和字符串
二、字符串输入
2.1 分配空间
2.2 不幸的gets()函数
2.3 gets()的替代品
1.fgets()函数(和fputs())
2.gets_s()函数
3.s_gets()函数
2.4 scanf()函数
三、字符串输出
3.1 puts()函数
3.2 fputs()函数
3.3 printf()函数
四、自定义输入/输出函数
五、字符串函数
5.1 strlen()函数
5.2 strcat()函数
5.3 strncat()函数
5.4 strcmp()函数
1.strcmp()的返回值
2.strncmp()函数
5.5 strcpy()和strncpy()函数
1.strcpy()的其他属性
2.更谨慎的选择:strncpy()
5.6 sprintf()函数
5.7 其他字符串函数
1、char *strcpy(char * restrict s1, const char * restrict s2);
2、char *strncpy(char * restrict s1, const char * restrict s2, size_t n);
3、char *strcat(char * restrict s1, const char * restrict s2);
4、char *strncat(char * restrict s1, const char * restrict s2, size_t n);
5、int strcmp(const char * s1, const char * s2);
6、int strncmp(const char * s1, const char * s2, size_t n);
7、char *strchr(const char * s, int c);
8、char *strpbrk(const char * s1, const char * s2);
9、char *strrchr(const char * s, int c);
10、char *strstr(const char * s1, const char * s2);
11、size_t strlen(const char * s);
六、字符串示例:字符串排序
七、ctype.h字符函数和字符串
八、命令行参数
九、把字符串转换为数字
#include
#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);
getchar();
return 0;
}
运行结果:
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.
和printf()函数一样,puts()函数也属于stdio.h系列的输入/输出函数。但是,puts()函数只显示字符串,而且自动在显示的字符串末尾加上换行符。
定义字符串的方法:字符串常量、char类型数组、指向char的指针
用双引号括起来的内容称为字符串字面量(string literal),也叫作字符串常量(string constant)。双引号中的字符和编译器自动加入末尾的 \0 字符,都为字符串储存在内存中。
从ANSI C标准起,如果字符串字面量之间没有间隔,或者用空白字符分隔,C会将其视为串联起来的字符串字面量。
char greeting[50] = "Hello, and"" how are" " you"" today!";
如果要在字符串内部使用双引号,必须在双引号前面加上一个反斜杠(\)
printf("\"Run, Spot, run!\" exclaimed Dick.\n");
字符串常量属于静态存储类别(static storage class),用双引号括起来的内容被视为指向该字符串储存位置的指针。
/* strptr.c -- 把字符串看作指针 */
#include
int main(void)
{
printf("%s, %p, %c\n", "We", "are", *"space farers");
getchar();
return 0;
}
运行结果:
We, 00405064, s
定义字符串数组时,必须让编译器知道需要多少空间。一种方法是用足够空间的数组储存字符串。另一种方法是让编译器确定初始化字符数组的大小。
const char m1[40] = "Limit yourself to one line's worth.";
const char m1[40] = { 'L','i', 'm', 'i', 't', ' ', 'y', 'o', 'u', 'r', 's', 'e', 'l',
'f', ' ', 't', 'o', ' ', 'o', 'n', 'e', ' ','l', 'i', 'n', 'e',
'\", 's', ' ', 'w', 'o', 'r','t', 'h', '.', '\0'};、//标准的数组初始化形式,更复杂
注意:注意最后的空字符。没有这个空字符,这就不是一个字符串,而是一个字符数组。
所有未被使用的元素都被自动初始化为0(这里的0指的是char形式的空字符,不是数字字符0)
const char m2[] = "If you can't think of anything, fake it.";//编译器会自动计算数组的大小
让编译器计算数组的大小只能用在初始化数组时。如果创建一个稍后再填充的数组,就必须在声明时指定大小。声明数组时,数组大小必须是可求值的整数。
一些数组初始化的例子:
int n = 8;
char cookies[1]; // 有效
char cakes[2 + 5];// 有效,数组大小是整型常量表达式
char pies[2*sizeof(long double) + 1]; // 有效
char crumbs[n]; // 在C99标准之前无效,C99标准之后这种数组是变长数组
字符数组名和其他数组名一样,是该数组首元素的地址。因此:
char car[10] = "Tata";
//一下表达式都为真
car == &car[0]、*car == 'T'、*(car+1) == car[1] == 'a'。
还可以使用指针表示法创建字符串。
const char * pt1 = "Something is pointing at me.";
const char ar1[] = "Something is pointing at me.";
在这两种情况下,带双引号的字符串本身决定了预留给字符串的存储空间。尽管如此,这两种形式并不完全相同。
数组形式(ar1):
当把程序载入内存时,也载入了程序中的字符串。字符串储存在静态存储区(static memory)中。但是,程序在开始运行时才会为该数组分配内存。此时,才将字符串拷贝到数组中。此时字符串有两个副本:一个是在静态内存中的字符串字面量,另一个是储存在ar1数组中的字符串。
不能更改ar1,如果改变了ar1,则意味着改变了数组的存储位置(即地址)。可以进行类似ar1+1这样的操作,标识数组的下一个元素。但是不允许进行++ar1这样的操作。递增运算符只能用于变量名前(或概括地说,只能用于可修改的左值),不能用于常量。
指针形式(*pt1):
一旦开始执行程序,它会为指针变量pt1留出一个储存位置,并把字符串的地址储存在指针变量中。该变量最初指向该字符串的首字符,但是它的值可以改变。因此,可以使用递增运算符。
字符串字面量被视为const数据。应该把pt1声明为指向const数据的指针。不能用pt1改变它所指
向的数据,但是仍然可以改变pt1的值。如果把一个字符串字面量拷贝给一个数组,就可以随意改变数据。
总之,初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针。
// addresses.c -- 字符串的地址
#define MSG "I'm special"
#include
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 of MSG: %p\n", MSG);
printf("address of \"I'm special\": %p \n", "I'm special");
getchar();
return 0;
}
运行结果:
address of "I'm special": 00405064
address ar: 0061FF10
address pt: 00405064
address of MSG: 00405064
address of "I'm special": 00405064
char heart[] = "I love Tillie!";
const char *head = "I love Millie!";
两者主要的区别是:数组名heart是常量,而指针名head是变量。
假设想让head和heart统一,可以这样做:
head = heart; /* head现在指向数组heart */
heart = head; /* 非法构造,不能这样写 */
另外,还可以改变heart数组中元素的信息:
heart[7]= 'M';或者*(heart + 7) = 'M';
数组的元素是变量(除非数组被声明为const),但是数组名不是变量。
建议在把指针初始化为字符串字面量时使用const限定符。如果不修改字符串,不要用指针指向字符串字面量。
// arrchar.c -- 指针数组,字符串数组
#include
#define SLEN 40
#define LIM 5
int main(void)
{
const char *mytalents[LIM] = {
"Adding numbers swiftly",
"Multiplying accurately", "Stashing data",
"Following instructions 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: %zd, sizeof yourtalents: %zd\n",
sizeof(mytalents), sizeof(yourtalents));
getchar();
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 instructions to the letter Mailing letters
Understanding the C language Reading email
sizeof mytalents: 20, sizeof yourtalents: 200
mytalents中的指针指向初始化时所用的字符串字面量的位置,这些字符串字面量被储存在静态内存中;而yourtalents 中的数组则储存着字符串字面量的副本。为字符串数组分配内存的使用率较低。
(实际上,mytalents 数组的指针元素所指向的字符串不必储存在连续的内存中,图中所示只是为了强调两种数组的不同)
综上所述,如果要用数组表示一系列待显示的字符串,请使用指针数组,因为它比二维字符数组的效率高。如果要改变字符串或为字符串输入预留空间,不要使用指向字符串字面量的指针。
/* p_and_s.c -- 指针和字符串 */
#include
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);
getchar();
return 0;
}
运行结果:
Don't be a fool!
mesg = Don't be a fool!; &mesg = 0061FF1C; value = 00405064
copy = Don't be a fool!; © = 0061FF18; value = 00405064
第1项,mesg和copy都以字符串形式输出(%s转换说明)。第2项,打印两个指针的地址。最后一项,显示两个指针的值。程序并未拷贝字符串。
char *name;
scanf("%s", name);
此时该参数是个未初始化的指针,name可能会指向任何地方。最简单的方法是,在声明时显式指明数组的大小。
char name[81];
gets()函数,读取整行输入,直至遇到换行符,然后丢弃换行符,储存其余字符,并在这些字符的末尾添加一个空字符使其成为一个 C 字符串。它经常和 puts()函数配对使用,该函数用于显示字符串,并在末尾添加换行符。
/* getsputs.c -- 使用 gets() 和 puts() */
#include
#define STLEN 5
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.");
getchar();
return 0;
}
运行结果:
Enter a string, please.
no
Your string twice:
no
no
Done.
gets()唯一的参数是 words,它无法检查数组是否装得下输入行。如果输入的字符串过长,会导致缓冲区溢出(buffer overflow)。
C11标准委员会直接从标准中废除了gets()函数。
如果fgets()读到一个换行符,会把它储存在字符串中。这点与gets()不同,gets()会丢弃换行符。fgets()函数的第3 个参数指明要读入的文件。如果读入从键盘输入的数据,则以stdin(标准输入)作为参数,该标识符定义在stdio.h中。fgets()函数把换行符放在字符串的末尾(假设输入行不溢
出),通常要与 fputs()函数(和puts()类似)配对使用。
/* fgets1.c -- 使用 fgets() 和 fputs() */
#include
#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.");
system("pause");
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.
puts()函数会在待输出字符串末尾添加一个换行符,而fputs()不会这样做。
fputs()函数返回指向 char的指针。如果一切进行顺利,该函数返回的地址与传入的第 1 个参数相同。但是,如果函数读到文件结尾,它将返回一个特殊的指针:空指针(null pointer)。如果在读入数据时出现某些错误,该函数也返回NULL。
/* fgets2.c -- 使用 fgets() 和 fputs() */
#include
#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.");
system("pause");
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.
统使用缓冲的I/O。这意味着用户在按下Enter键之前,输入都被储存在临时存储区(即,缓冲区)中。按下Enter键就在输入中增加了一个换行符,并把整行输入发送给fgets()。对于输出,fputs()把字符发送给另一个缓冲区,当发送换行符时,缓冲区中的内容被发送至屏幕上
以下程序读取输入行,删除储存在字符串中的换行符,如果没有换行符,则丢弃数组装不下的字符。
/* fgets3.c -- 使用 fgets() */
#include
#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')
continue;
puts(words);
}
puts("done");
system("pause");
return 0;
}
运行结果:
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
空字符和空指针
空字符(或'\0')是用于标记C字符串末尾的字符,其对应字符编码是0。由于其他字符的编码不可能是 0,所以不可能是字符串的一部分。空指针(或NULL)有一个值,该值不会与任何数据的有效地址对应。通常,函数使用它返回一个有效地址表示某些特殊情况发生,例如遇到文件结尾或未能按预期执行。
空字符是整数类型,而空指针是指针类型。
空字符是一个字符,占1字节;而空指针是一个地址,通常占4字节。
C11新增的gets_s()函数(可选)
gets_s(words, STLEN);
gets_s()与fgets()的区别如下:
编写函数:如果字符串中出现换行符,就用空字符替换它;如果字符串中出现空字符,就丢弃该输入行的其余字符,然后返回与fgets()相同的值。
#include
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;
}
其缺陷是遇到不合适的输入时毫无反应。它丢弃多余的字符时,既不通知程序也不告知用户。
scanf()函数有两种方法确定输入结束。无论哪种方法,都从第1个非空白字符作为字符串的开始。如果使用%s转换说明,以下一个空白字符(空行、空格、制表符或换行符)作为字符串的结束(字符串不包括空白字符)。如果指定了字段宽度,如%10s,那么scanf()将读取10 个字符或读到第1个空白字符停止(先满足的条件即是结束输入的条件)
scanf()函数返回一个整数值,该值等于scanf()成功读取的项数或EOF(读到文件结尾时返回EOF)
/* scan_str.c -- 使用 scanf() */
#include
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);
system("pause");
return 0;
}
运行结果:
Please enter 2 names.
Jesse Jukes
I read the 2 names Jesse and Jukes.
或者
Please enter 2 names.
Portensia Callowit
I read the 2 names Porte and nsia.
根据输入数据的性质,用fgets()读取从键盘输入的数据更合适。例如,scanf()无法完整读取书名或歌曲名,除非这些名称是一个单词。scanf()的典型用法是读取并转换混合数据类型为某种标准形式。
scanf()和gets()类似,也存在一些潜在的缺点。如果输入行的内容过长,scanf()也会导致数据溢出。
/* put_out.c -- 使用 puts() */
#include
#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);
system("pause");
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.
puts()在显示字符串时会自动在其末尾添加一个换行符。
用双引号括起来的内容是字符串常量,且被视为该字符串的地址。储存字符串的数组名也被看作是地址。
该函数在遇到空字符时就停止输出,所以必须确保有空字符。
/* nono.c -- 千万不要模仿! */
#include
int main(void)
{
char side_a[] = "Side A";
char dont[] = { 'W', 'O', 'W', '!' };
char side_b[] = "Side B";
puts(dont); /* dont 不是一个字符串 */
system("pause");
return 0;
}
运行结果:
WOW!Side A
由于dont缺少一个表示结束的空字符,所以它不是一个字符串,因此puts()不知道在何处停止。它会一直打印dont后面内存中的内容,直到发现一个空字符为止。
fputs()函数是puts()针对文件定制的版本。它们的区别如下:
fputs()函数的第 2 个参数指明要写入数据的文件。如果要打印在显示器上,可以用定义在stdio.h中的stdout(标准输出)作为该参数。
fputs()不会在输出的末尾添加换行符。
gets()丢弃输入中的换行符,但是puts()在输出中添加换行符。另一方面,fgets()保留输入中的换行符,fputs()不在输出中添加换行符。
puts()应与gets()配对使用,fputs()应与fgets()配对使用。
和puts()一样,printf()也把字符串的地址作为参数。它可以格式化不同的数据类型。与puts()不同的是,printf()不会自动在每个字符串末尾加上一个换行符。
printf()的形式更复杂些,需要输入更多代码,而且计算机执行的时间也更长。然而,使用 printf()打印多个字符串更加简单。
在getchar()和putchar()的基础上自定义所需的函数。
一个类似puts()但是不会自动添加换行符的函数:
/* put1.c -- 打印字符串,不添加\n */
#include
void put1(const char * string)/* 不会改变字符串 */
{
while (*string != '\0')
putchar(*string++);
//数组表示法
//int i = 0;
//while (string[i]!= '\0')
或者while (*string),当string指向空字符时,*string的值是0
//putchar(string[i++]);
}
++的优先级高于*
一个类似puts()的函数,该函数还给出待打印字符的个数:
/* put2.c -- 打印一个字符串,并统计打印的字符数 */
#include
int put2(const char * string)
{
int count = 0;
while (*string) /* 常规用法 */
{
putchar(*string++);
count++;
}
putchar('\n'); /* 不统计换行符 */
return(count);
}
使用一个简单的驱动程序测试put1()和put2(),并演示嵌套函数的调用.
//put_put.c -- 用户自定义输出函数
#include
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."));
system("pause");
return 0;
}
void put1(const char * string)
{
while (*string) /* 与 *string != '\0' 相同 */
putchar(*string++);
}
int put2(const char * string)
{
int count = 0;
while (*string)
{
putchar(*string++);
count++;
}
putchar('\n');
return(count);
}
运行结果:
If I'd as much money as I could spend,
I never would cry old chairs to mend.
I count 37 characters.
为了获得 put2()的返回值,计算机必须先执行put2(),因此在打印字符数之前先打印了传递给该函数的字符串。
C库提供了多个处理字符串的函数,ANSI C把这些函数的原型放在string.h头文件中。
strlen()函数用于统计字符串的长度。
/* test_fit.c -- 使用缩短字符串长度的函数 */
#include
#include /* 内含字符串函数原型 */
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);
system("pause");
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.
fit()函数把第39个元素的逗号替换成'\0'字符。puts()函数在空字符处停止输出,并忽略其余字符。然而,这些字符还在缓冲区中。
strcat()(用于拼接字符串)函数接受两个字符串作为参数。该函数把第2个字符串的备份附加在第1个字符串末尾,并把拼接后形成的新字符串作为第1个字符串,第2个字符串不变。strcat()函数的类型是char*(即,指向char的指针)。strcat()函数返回第1个参数,即拼接第2个字符串后的第1个字符串的地址。
/* str_cat.c -- 拼接两个字符串 */
#include
#include /* strcat()函数的原型在该头文件中 */
#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");
system("pause");
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
while (getchar() != '\n')
continue;
}
return ret_val;
}
运行结果:
What is your favorite flower?
rose
roses smell like old shoes.
s smell like old shoes.
bye
flower改变了,而addon保持不变
strncat()函数的第3 个参数指定了最大添加字符数。
/* join_chk.c -- 拼接两个字符串,检查第1个数组的大小 */
#include
#include
#define SIZE 30
#define BUGSIZE 13
char * s_gets(char * st, int n);
int main(void)
{
char flower[SIZE];
char addon [] = "s smell like old shoes.";
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);
system("pause");
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
while (getchar() != '\n')
continue;
}
return ret_val;
}
运行结果:
What is your favorite flower?
rose
roses smell like old shoes.
What is your favorite bug?
nonono
nononos smel
C语言相信程序员,程序员有责任确保strcat()的使用安全。
strcmp()函数通过比较运算符来比较字符串,就像比较数字一样。如果两个字符串参数相同,该函数就返回0,否则返回非零值。
/* compare.c -- 该程序可以正常运行 */
#include
#include // 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))
{
puts("No, that's wrong. Try again.");
s_gets(try, SIZE);
}
puts("That's right!");
system("pause");
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
while (getchar() != '\n')
continue;
}
return ret_val;
}
运行结果:
Who is buried in Grant's tomb?
grant
No, that's wrong. Try again.
Grant
That's right!
strcmp()函数比较的是字符串,不是整个数组,这是非常好的功能。虽然数组try占用了40字节,而储存在其中的"Grant"只占用了6字节(还有一个用来放空字符),strcmp()函数只会比较try中第1个空字符前面的部分。所以,可以用strcmp()比较储存在不同大小数组中的字符串。
/* compback.c -- strcmp()的返回值 */
#include
#include
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"));
system("pause");
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()比较所有的字符,不只是字母。所以,与其说该函数按字母顺序进行比较,不如说是按机器排序序列(machine collating sequence)进行比较。
当两个参数为降序时,返回1,当两个参数为升序时,返回-1。
strcmp()函数比较的是字符串,不是字符,所以其参数应该是字符串(如"apples"和"A"),而不是字符(如'A')。下面语句都有效:
if (strcmp(word, "quit") == 0) // 使用strcmp()比较字符串
puts("Bye!");
if (ch == 'q') // 使用 == 比较字符
puts("Bye!");//不要使用ch或'q'作为strcmp()的参数。
/* quit_chk.c -- 某程序的开始部分 */
#include
#include
#define SIZE 80
#define LIM 10
#define STOP "quit"
char * s_gets(char * st, int n);
int main(void)
{
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);
system("pause");
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
while (getchar() != '\n')
continue;
}
return ret_val;
}
运行结果:
Enter up to 10 lines (type quit to quit):
GOGOGO
second
that's all
quit
3 strings entered
input[ct][0] != '\0'可用来检测空行
strncmp()函数在比较两个字符串时,可以比较到字符不同的地方,也可以只比较第3个参数指定的字符数。
/* starsrch.c -- 使用 strncmp() */
#include
#include
#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);
system("pause");
return 0;
}
运行结果:
Found: astronomy
Found: astrophysics
Found: astrophobia
The list contained 3 words beginning with astro.
如果希望拷贝整个字符串,要使用strcpy()函数。
/* copy1.c -- 演示 strcpy() */
#include
#include // 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]);
system("pause");
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
while (getchar() != '\n')
continue;
}
return ret_val;
}
运行结果:
Enter 5 words beginning with q:
quit
wer
wer doesn't begin with q!
quio
qjlklk
qjksjdf
qcxvdv
Here are the words accepted:
quit
quio
qjlklk
qjksjdf
qcxvdv
strcpy()第2个参数(temp)指向的字符串被拷贝至第1个参数(qword[i])指向的数组中。拷贝出来的字符串被称为目标字符串,最初的字符串被称为源字符串。
char target[20];
int x;
x = 50; /* 数字赋值*/
strcpy(target, "Hi ho!"); /* 字符串赋值*/
target = "So long"; /* 语法错误 */
strcpy()接受两个字符串指针作为参数,可以把指向源字符串的第2个指针声明为指针、数组名或字符串常量;而指向源字符串副本的第1个指针应指向一个数据对象(如,数组),且该对象有足够的空间储存源字符串的副本。记住,声明数组将分配储存数据的空间,而声明指针只分配储存一个地址的空间。
char * str;
strcpy(str, "The C of Tranquility"); // 有问题,str未初始化
strcpy()函数还有两个有用的属性。第一,strcpy()的返回类型是char *,该函数返回的是第 1个参数的值,即一个字符的地址。第二,第 1 个参数不必指向数组的开始。这个属性可用于拷贝数组的一部分。
/* copy2.c -- 使用 strcpy() */
#include
#include // 提供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);
system("pause");
return 0;
}
运行结果:beast
Be the best that you can be.
Be the beast
beast
strcpy()把源字符串中的空字符也拷贝在内。
strcpy()和 strcat()都有同样的问题,它们都不能检查目标空间是否能容纳源字符串的副本。拷贝字符串用 strncpy()更安全,该函数的第 3个参数指明可拷贝的最大字符数。
/* copy3.c -- 使用strncpy() */
#include
#include /* 提供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]);
system("pause");
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
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(TARGSIZE-1),然后把数组最后一个元素设置为空字符:
strncpy(qwords[i], temp, TARGSIZE - 1);
qwords[i][TARGSIZE - 1] = '\0';
sprintf()函数声明在stdio.h中,而不是在string.h中。该函数和printf()类似,但是它是把数据写入字符串,而不是打印在显示器上。sprintf()的第1个参数是目标字符串的地址。其余参数和printf()相同,即格式字符串和待写入项的列表。
/* format.c -- 格式化字符串 */
#include
#define MAX 20
char * s_gets(char * st, int n);
int main(void)
{
char first[MAX];
char last[MAX];
char formal[2 * MAX + 10];
double prize;
puts("Enter your first name:");
s_gets(first, MAX);
puts("Enter your last name:");
s_gets(last, MAX);
puts("Enter your prize money:");
scanf("%lf", &prize);
sprintf(formal, "%s, %-19s: $%6.2f\n", last, first, prize);
puts(formal);
system("pause");
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
while (getchar() != '\n')
continue;
}
return ret_val;
}
运行结果:
Enter your first name:
Zhang
Enter your last name:
san
Enter your prize money:
5000
san, Zhang : $5000.00
sprintf()函数获取输入,并将其格式化为标准形式,然后把格式化后的字符串储存在formal中。
该函数把s2指向的字符串(包括空字符)拷贝至s1指向的位置,返回值是s1
该函数把s2指向的字符串拷贝至s1指向的位置,拷贝的字符数不超过n,其返回值是s1。该函数不会拷贝空字符后面的字符,如果源字符串的字符少于n个,目标字符串就以拷贝的空字符结尾;如果源字符串有n个或超过n个字符,就不拷贝空字符
该函数把s2指向的字符串拷贝至s1指向的字符串末尾。s2字符串的第1个字符将覆盖s1字符串末尾的空字符。该函数返回s1
该函数把s2字符串中的n个字符拷贝至s1字符串末尾。s2字符串的第1个字符将覆盖s1字符串末尾的空字符。不会拷贝s2字符串中空字符和其后的字符,并在拷贝字符的末尾添加一个空字符。该函数返回s1
如果s1字符串在机器排序序列中位于s2字符串的后面,该函数返回一个正数;如果两个字符串相等,则返回0;如果s1字符串在机器排序序列中位于s2字符串的前面,则返回一个负数
该函数的作用和strcmp()类似,不同的是,该函数在比较n个字符后或遇到第1个空字符时停止比较
如果s字符串中包含c字符,该函数返回指向s字符串首位置的指针(末尾的空字符也是字符串的一部分,所以在查找范围内);如果在字符串s中未找到c字符,该函数则返回空指针。
如果 s1 字符中包含 s2字符串中的任意字符,该函数返回指向 s1 字符串首位置的指针;如果在s1字符串中未找到任何s2字符串中的字符,则返回空字符。
该函数返回s字符串中c字符的最后一次出现的位置(末尾的空字符也是字符串的一部分,所以在查找范围内)。如果未找到c字符,则返回空指针。
该函数返回指向s1字符串中s2字符串出现的首位置。如果在s1中没有找到s2,则返回空指针
该函数返回s字符串中的字符数,不包括末尾的空字符。
关键字restrict限制了函数参数的用法。例如,不能把字符串拷贝给本身。size_t类型是sizeof运算符返回的类型。
/* sort_str.c -- 读入字符串,并排序字符串 */
#include
#include
#define SIZE 81 /* 限制字符串长度,包括 \0 */
#define LIM 20 /* 可读入的最多行数 */
#define HALT "" /* 空字符串停止输入 */
void stsrt(char *strings [], int num); /* 字符串排序函数 */
char * s_gets(char * st, int n);
int main(void)
{
char input[LIM][SIZE]; /* 储存输入的数组 */
char *ptstr[LIM]; /* 内含指针变量的数组 */
int ct = 0; /* 输入计数 */
int k; /* 输出计数 */
printf("Input up to %d lines, and I will sort them.\n", LIM);
printf("To stop, press the Enter key at a line's start.\n");
while (ct < LIM && s_gets(input[ct], SIZE) != NULL
&& input[ct][0] != '\0')
{
ptstr[ct] = input[ct]; /* 设置指针指向字符串 */
ct++;
}
stsrt(ptstr, ct); /* 字符串排序函数 */
puts("\nHere's the sorted list:\n");
for (k = 0; k < ct; k++)
puts(ptstr[k]); /* 排序后的指针 */
system("pause");
return 0;
}
/* 字符串-指针-排序函数 */
void stsrt(char *strings [], int num)
{
char *temp;
int top, seek;
for (top = 0; top < num - 1; top++)
for (seek = top + 1; seek < num; seek++)
if (strcmp(strings[top], strings[seek]) > 0)
{
temp = strings[top];
strings[top] = strings[seek];
strings[seek] = temp;
}
}
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
while (getchar() != '\n')
continue;
}
return ret_val;
}
运行结果:
Input up to 20 lines, and I will sort them.
To stop, press the Enter key at a line's start.
O that I was where I would be,
Then would I be where I am not;
But where I am I must be,
And where I would be I can not.
Here's the sorted list:
And where I would be I can not.
But where I am I must be,
O that I was where I would be,
Then would I be where I am not;
排序的是指向字符串的指针,而不是字符串本身。排序过程把ptrst重新排列,并未改变
input。
我们采用选择排序算法(selection sort algorithm)来排序指针。
ctype.h系列与字符相关的函数虽然不能处理整个字符串,但是可以处理字符串中的字符。
/* mod_str.c -- 修改字符串 */
#include
#include
#include
#define LIMIT 81
void ToUpper(char *);
int PunctCount(const char *);
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));
system("pause");
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;
}
运行结果:
Please enter a line:
Are you OK?
ARE YOU OK?
That line has 1 punctuation characters.
ToUpper()函数,利用toupper()函数处理字符串中的每个字符,把整个字符串转换成大写;PunctCount()函数,利用ispunct()统计字符串中的标点符号个数。另外,使用strchr()处理fgets()读入字符串的换行符(如果有的话)。
while (*str)循环处理str指向的字符串中的每个字符,直至遇到空字符。
ctype.h中的函数通常作为宏(macro)来实现。
命令行(command line)是在命令行环境中,用户为运行程序输入命令的行。
命令行参数(command-line argument)是同一行的附加项。
/* repeat.c -- 带参数的 main() */
#include
int main(int argc, char *argv [])
{
int count;
printf("The command line has %d arguments:\n", argc - 1);
for (count = 0; count < argc; count++)
printf("%d: %s\n", count, argv[count]);
printf("\n");
system("pause");
return 0;
}
运行结果:
PS C:\Users\***\Desktop\VS_PJ\CPPch11> .\repeat Resistance is futile
The command line has 3 arguments:
0: C:\Users\Hao Guoqing\Desktop\VS_PJ\CPPch11\repeat.exe
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],以此类推。
char **argv与char *argv[]等价。也就是说,argv是一个指向指针的指针,它所指向的指针指向 char。
数字既能以字符串形式储存,也能以数值形式储存。
在屏幕上显示数字则要求字符串形式,因为屏幕显示的是字符。printf()和sprintf()函数,通过%d 和其他转换说明,把数字从数值形式转换为字符串形式,scanf()可以把输入字符串转换为数值形式。
/* hello.c -- 把命令行参数转换为数字 */
#include
#include
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("Hello, good looking!");
system("puase");
return 0;
}
运行结果:
PS C:\Users\***\Desktop\VS_PJ\CPPch11> .\hello 3
Hello, good looking!
Hello, good looking!
Hello, good looking!
命令行参数3被储存为字符串3\0。atoi()函数把该字符串转换为整数值3,然后该值被赋给times。该值确定了执行for循环的次数。
如果字符串仅以整数开头,atoi()函数也能处理,它只把开头的整数转换为字符。如果命令行参数不是数字,atoi()函数返回0。
从ANSI C开始,stdlib.h头文件中包含了atoi()函数的原型。除此之外,还包含了 atof()和 atol()函数的原型。atof()函数把字符串转换成 double 类型的值, atol()函数把字符串转换成long类型的值。
ANSI C还提供一套更智能的函数:strtol()把字符串转换成long类型的值,strtoul()把字符串转换成unsigned long类型的值,strtod()把字符串转换成double类型的值。这些函数的智能之处在于识别和报告字符串中的首字符是否是数字。而且,strtol()和strtoul()还可以指定数字的进制。
long strtol(const char * restrict nptr, char ** restrict endptr, int base);
nptr是指向待转换字符串的指针,endptr是一个指针的地址,该指针被设置为标识输入数字结束字符的地址,base表示以什么进制写入数字。
/* strcnvt.c -- 使用 strtol() */
#include
#include
#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 16 input, base 10 output: %ld, stopped at %s (%d)\n",
value, end, *end);
puts("Next number:");
}
puts("Bye!\n");
system("pause");
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
while (getchar() != '\n')
continue;
}
return ret_val;
}
运行结果:
10
base 10 input, base 10 output: 10, stopped at (0)
base 16 input, base 10 output: 16, stopped at (0)
Next number:
10atom
base 10 input, base 10 output: 10, stopped at atom (97)
base 16 input, base 10 output: 266, stopped at tom (116)
Next number:
Bye!
第1次转换在读到空字符时结束,此时end指向空字符。打印end会显示一个空字符串,以%d转换说明输出*end显示的是空字符的ASCII码。对于第2个输入的字符串,当base为10时,end的值是'a'字符的地址。所以打印end显示的是字符串"atom",打印*end显示的是'a'字符的ASCII码。当base为16时,'a'字符被识别为一个有效的十六进制数,strtol()函数把十六进制数10a转换成十进制数266。
许多实现使用itoa()和 ftoa()函数分别把整数和浮点数转换成字符串。但是这两个函数并不是 C标准库的成员,可以用sprintf()函数代替它们,因为sprintf()的兼容性更好。
使用 strcmp()来代替关系运算符,当比较字符串时,应该使用strcpy()或strncpy()代替赋值运算符把字符串赋给字符数组。