11.1 表示字符串和字符串I/O
字符串是以空字符(\0)结尾的char类型数据。 strings1.c 演示在程序中表示字符串的几种方式
#include <stdio.h>
#define MSG "I am a symbolic string constant."
#define MAXLENGTH 81
int main(void)
{
char words[MAXLENGTH] = "I am a string in an array.";
const char* pt1 = "Something is pointing at me.";
puts("Here are some strings:");
puts(MSG);
puts(words);
puts(pt1);
words[8] = 'p';
puts(words);
printf("%s, %p, %c\n", "We", "are", *"space farers");
return 0;
}
在程序中定义字符串
(1)字符串字面量(字符串常量) 用双引号括起来的内容称为字符串字面量。双引号中的字符和编译器自动加入末尾的\0字符,都作为字符串储存在内存中。 字符串常量属于静态存储类别(static storage class),这说明在函数中使用字符串常量,该字符串只会被储存一次,在整个程序的生命期内存在,即使函数被调用多次。 用双引号括起来的内容被释为指向该字符串储存位置的指针。 (2)字符串数组和初始化
const char m1[40] = "Limit yourself to one line's worth.";
const char m2[] = "Limit yourself to one line's worth.";
让编译器计算数组的大小只能在初始化数组时。 const char * pt1= “Limit yourself to one line’s worth.”;
注意:数组和指针的区别 数组名m2是常量,指针名pt1是变量。如果打算修改字符串,就不要用指针指向字符串字面量。 初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针。
#include <stdio.h>
#define MSG "I'm special"
int main(void)
{
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");
return 0;
}
(4)字符串数组 arrchar.c–指向字符串的指针数据和char类型数组的数组
#include <stdio.h>
#define SLEN 40
#define LIM 5
int main(void)
{
const char* mytalents[LIM] = {
"Adding number swiftly",
"Multiplying accurately", "Stashing data",
"Following instrcutions to the letter",
"Understanding the C language"
};
char yourtalents[LIM][SLEN] = {
"Walking in a straight line",
"Sleeping", "Watching televisions",
"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));
return 0;
}
综上所述,如果要用数组表示一系列待显示的字符串,请使用指针数组,因为它比二维字符数组的效率高。但是,指针数组也有自身的缺点。mytalents 中的指针指向的字符串字面量不能更改;而yourtalentsde 中的内容可以更改。所以,如果要改变字符串或为字符串输入预留空间,不要使用指向字符串字面量的指针。
11.2 字符串输入
如果想把一个字符串读入程序,首先必须预留储存该字符串的空间,然后用输入函数获取该字符串。
gets函数
C11标准委员会采取了更强硬的态度,直接从标准中废除了gets()函数。 问题出在 gets()唯一的参数是 words,它无法检查数组是否装得下输入行。上一章介绍过,数组名会被转换成该数组首元素的地址,因此,gets()函数只知道数组的开始处,并不知道数组中有多少个元素。 “Segmentation fault”(分段错误)似乎不是个好提示,的确如此。在UNIX系统中,这条消息说明该程序试图访问未分配的内存。
#include <stdio.h>
#include <string.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;
}
fgets()和fputs()函数用法
fgets()函数的第2个参数指明了读入字符的最大数量。如果该参数的值是n,那么fgets()将读入n-1个字符,或者读到遇到的第一个换行符为止。 如果fgets()读到一个换行符,会把它储存在字符串中。这点与gets()不同,gets()会丢弃换行符。 fgets()函数的第3 个参数指明要读入的文件。如果读入从键盘输入的数据,则以stdin(标准输入)作为参数,该标识符定义在stdio.h中
#include <stdio.h>
#include <string.h>
#define STLEN 14
int main(void)
{
char words[STLEN];
puts("Enter a string, please.");
fgets(words, STLEN, stdin);
printf("Your string twice(puts(), then fputs()):\n");
puts(words);
fputs(words, stdout);
puts("Enter another string, please.");
fgets(words, STLEN, stdin);
printf("Your string twice(puts(), then fputs()):\n");
puts(words);
fputs(words, stdout);
puts("Done.");
return 0;
}
fgets()函数返回指向 char的指针。如果一切进行顺利,该函数返回的地址与传入的第 1 个参数相同。但是,如果函数读到文件结尾,它将返回一个特殊的指针:空指针(null pointer)。
下面程序演示了一个简单的循环,读入并显示用户输入的内容,直到fgets()读到文件结尾或空行(即,首字符是换行符)。
#include <stdio.h>
#include <string.h>
#define STLEN 10
int main(void)
{
char words[STLEN];
puts("Enter strings (empty line to quit):");
while (fgets(words, STLEN, stdin) != NULL && words[0] != '\n') {
fputs(words, stdout);
}
puts("Done.");
return 0;
}
第一次读 By the wa\0 第二次读 y, the ge\0 最后一次读 tion\n\0 fgets()储存换行符有好处也有坏处。坏处是你可能并不想把换行符储存在字符串中,这样的换行符会带来一些麻烦。好处是对于储存的字符串而言,检查末尾是否有换行符可以判断是否读取了一整行。如果不是一整行,要妥善处理一行中剩下的字符。 首先,如何处理掉换行符?一个方法是在已储存的字符串中查找换行符,并将其替换成空字符:
while(words[i] != '\n') {
i++;
}
words[i] = '\0';
其次,如果仍有字符串留在输入行怎么办?一个可行的办法是,如果目标数组装不下一整行输入,就丢弃那些多出的字符:
while(getchar() != '\n') {
continue;
}
程序清单11.9在程序清单11.8的基础上添加了一部分测试代码。该程序读取输入行,删除储存在字符串中的换行符,如果没有换行符,则丢弃数组装不下的字符。
#include <stdio.h>
#include <string.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 {
while (getchar() != '\n') {
continue;
}
}
puts(words);
}
puts("Done.");
return 0;
}
s_gets()函数
如果 fgets()返回 NULL,说明读到文件结尾或出现读取错误,s_gets()函数跳过了这个过程。 如果字符串中出现换行符,就用空字符替换它; 如果字符串中出现空字符,就丢弃该输入行的其余字符,然后返回与fgets()相同的值。
char* s_gets(char* st, int n)
{
char* ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (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;
}
也许读者想了解为什么要丢弃过长输入行中的余下字符。这是因为,输入行中多出来的字符会被留在缓冲区中,成为下一次读取语句的输入。例如,如果下一条读取语句要读取的是 double 类型的值,就可能导致程序崩溃。丢弃输入行余下的字符保证了读取语句与键盘输入同步。
scanf()函数
scanf()函数有两种方法确定输入结束。无论哪种方法,都从第1个非空白字符作为字符串的开始。如果使用%s转换说明,以下一个空白字符(空行、空格、制表符或换行符)作为字符串的结束(字符串不包括空白字符)。如果指定了字段宽度,如%10s,那么scanf()将读取10 个字符或读到第1个空白字符停止(先满足的条件即是结束输入的条件)。 scanf()函数返回一个整数值,该值等于scanf()成功读取的项数或EOF(读到文件结尾时返回EOF)。 演示了在scanf()函数中指定字段宽度的用法:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define STLEN 10
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;
}
scanf()的典型用法是读取并转换混合数据类型为某种标准形式。例如,如果输入行包含一种工具名、库存量和单价,就可以使用scanf()。否则可能要自己拼凑一个函数处理一些输入检查。如果一次只输入一个单词,用scanf()也没问题。 scanf()和gets()类似,也存在一些潜在的缺点。如果输入行的内容过长,scanf()也会导致数据溢出。不过,在%s转换说明中使用字段宽度可防止溢出。
11.3 字符串输出
puts()函数
puts()函数很容易使用,只需把字符串的地址作为参数传递给它即可。 puts()在显示字符串时会自动在其末尾添加一个换行符。 puts()如何知道在何处停止?该函数在遇到空字符时就停止输出,所以必须确保有空字符。
fputs()函数
fputs()函数是puts()针对文件定制的版本。它们的区别如下。 fputs()函数的第 2 个参数指明要写入数据的文件。如果要打印在显示器上,可以用定义在stdio.h中的stdout(标准输出)作为该参数。 与puts()不同,fputs()不会在输出的末尾添加换行符。 注意,gets()丢弃输入中的换行符,但是puts()在输出中添加换行符。另一方面,fgets()保留输入中的换行符,fputs()不在输出中添加换行符。
printf()函数
与puts()不同的是,printf()不会自动在每个字符串末尾加上一个换行符。因此,必须在参数中指明应该在哪里使用换行符。
自定义输入\输出函数
假设你需要一个类似puts()但是不会自动添加换行符的函数。
void put1(const char* string)
{
while (*string != '\0') {
putchar(*string++);
}
}
假设要设计一个类似puts()的函数,而且该函数还给出待打印字符的个数。
int put1(const char* string)
{
int count = 0;
int i = 0;
while (string[i] != '\0') {
putchar(string[i++]);
count++;
}
putchar('\n');
return count;
}
程序清单11.16使用一个简单的驱动程序测试put1()和put2(),并演示了嵌套函数的调用。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
void put1(const char* string);
int put2(const char* string);
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;
}
void put1(const char* string)
{
while (*string != '\0') {
putchar(*string++);
}
}
int put2(const char* string)
{
int count = 0;
while (*string != '\0') {
putchar(*string++);
count++;
}
putchar('\n');
return count;
}
字符串函数
strlen()函数
strlen()函数用于统计字符串的长度。下面的函数可以缩短字符串的长度,其中用到了strlen():
void fit(char* string, unsigned int size)
{
if (strlen(string) > size) {
string[size] = '\0';
}
}
程序清单11.17中的程序测试了fit()函数。注意代码中使用了C字符串常量的串联特性。 /* test_fit.c – 使用缩短字符串长度的函数 */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
void fit(char* string, unsigned int size);
int main(void)
{
char mesg[] = "Things should be as simple as possible,"
" but not simpler.";
puts(mesg);
fit(mesg, 38);
puts(mesg);
puts("Let's look at some more of the string.");
puts(mesg + 39);
return 0;
}
void fit(char* string, unsigned int size)
{
if (strlen(string) > size) {
string[size] = '\0';
}
}
strcat()函数
strcat()(用于拼接字符串)函数接受两个字符串作为参数。该函数把第2个字符串的备份附加在第1个字符串末尾,并把拼接后形成的新字符串作为第1个字符串,第2个字符串不变。strcat()函数的类型是char (即,指向char 的指针)。strcat()函数返回第1个参数,即拼接第2个字符串后的第1个字符串的地址。 / str_cat.c – 拼接两个字符串 */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#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 encounted!");
}
puts("Bye.");
return 0;
}
char* s_gets(char* st, int n)
{
char* ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (ret_val != NULL) {
while (st[i] != '\n' && st[i] != '\0') {
i++;
}
if (st[i] == '\n') {
st[i] = '\0';
}
else {
while (getchar() != '\n') {
continue;
}
}
}
return ret_val;
}
strncat()函数
strcat()函数无法检查第1个数组是否能容纳第2个字符串。如果分配给第1个数组的空间不够大,多出来的字符溢出到相邻存储单元时就会出问题。 /* join_chk.c – 拼接两个字符串,检查第1个数组的大小 */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#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);
return 0;
}
char* s_gets(char* st, int n)
{
char* ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (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;
}
strcmp()函数
strcmp()函数比较的是字符串,不是整个数组。 ASCII标准规定,在字母表中,如果第1个字符串在第2个字符串前面,strcmp()返回一个负数;如果两个字符串相同,strcmp()返回0;如果第1个字符串在第2个字符串后面,strcmp()返回正数。然而,返回的具体值取决于实现。 如果两个字符串开始的几个字符都相同会怎样?一般而言,strcmp()会依次比较每个字符,直到发现第 1 对不同的字符为止。然后,返回相应的值。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define ANSWER "Grant"
#define SIZE 40
char* s_gets(char* st, int n);
int main(void)
{
char try[SIZE];
puts("Who is buried in Grant's tomb?");
s_gets(try, SIZE);
while (strcmp(try, ANSWER)) {
puts("No, that's wrong. Try again.");
s_gets(try, SIZE);
}
puts("That's right!");
return 0;
}
char* s_gets(char* st, int n)
{
char* ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (ret_val != NULL) {
while (st[i] != '\n' && st[i] != '\0') {
i++;
}
if (st[i] == '\n') {
st[i] = '\0';
}
else {
while (getchar() != '\n') {
continue;
}
}
}
return ret_val;
}
用strcmp()函数检查程序是否要停止读取输入。 该程序在读到EOF字符(这种情况下s_gets()返回NULL)、用户输入quit或输入项达到LIM时退出。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define STOP "quit"
#define SIZE 80
#define LIM 10
char* s_gets(char* st, int n);
int main(void)
{
char input[LIM][SIZE];
int ct = 0;
printf("Enter up to %d lines (type line 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 != NULL) {
while (st[i] != '\n' && st[i] != '\0') {
i++;
}
if (st[i] == '\n') {
st[i] = '\0';
}
else {
while (getchar() != '\n') {
continue;
}
}
}
return ret_val;
}
strncmp()函数
strcmp()函数比较字符串中的字符,直到发现不同的字符为止,这一过程可能会持续到字符串的末尾。而strncmp()函数在比较两个字符串时,可以比较到字符不同的地方,也可以只比较第3个参数指定的字符数。例如,要查找以"astro"开头的字符串,可以限定函数只查找这5 个字符。程序清单11.24 演示了该函数的用法。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define STOP "quit"
#define LISTSIZE 6
char* s_gets(char* st, int n);
int main(void)
{
const char* list[LISTSIZE] = {
"astronomy", "astounding",
"astrophsics", "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;
}
char* s_gets(char* st, int n)
{
char* ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (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;
}
strcpy()和strncpy()函数
strcpy()参数的顺序,即第1个是目标字符串,第2个是源字符串。 程序员有责任确保目标数组有足够的空间容纳源字符串的副本。 strcpy()接受两个字符串指针作为参数,可以把指向源字符串的第2个指针声明为指针、数组名或字符串常量;而指向源字符串副本的第1个指针应指向一个数据对象(如,数组),且该对象有足够的空间储存源字符串的副本。记住,声明数组将分配储存数据的空间,而声明指针只分配储存一个地址的空间。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define LIM 5
#define SIZE 40
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) != NULL) {
if (temp[0] != 'q') {
printf("%s doesn't begin with q!\n", temp);
}
else {
strcpy(qwords[i], temp);
i++;
}
}
puts("Here are the words accepted:");
for (i = 0; i < LIM; i++) {
puts(qwords[i]);
}
return 0;
}
char* s_gets(char* st, int n)
{
char* ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (ret_val != NULL) {
while (st[i] != '\n' && st[i] != '\0') {
i++;
}
if (st[i] == '\n') {
st[i] = '\0';
}
else {
while (getchar() != '\n') {
continue;
}
}
}
return ret_val;
}
strcpy()函数还有两个有用的属性。第一,strcpy()的返回类型是 char *,该函数返回的是第 1个参数的值,即一个字符的地址。第二,第 1 个参数不必指向数组的开始。这个属性可用于拷贝数组的一部分。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define WORDS "beast"
#define SIZE 40
char* s_gets(char* st, int n);
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;
}
char* s_gets(char* st, int n)
{
char* ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (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;
}
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';
这样做确保储存的是一个字符串。如果目标空间能容纳源字符串的副本,那么从源字符串拷贝的空字符便是该副本的结尾;如果目标空间装不下副本,则把副本最后一个元素设置为空字符。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define LIM 5
#define SIZE 40
#define TARGSIZE 7
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) != NULL) {
if (temp[0] != 'q') {
printf("%s doesn't begin with q!\n", temp);
}
else {
strncpy(qwords[i], temp, TARGSIZE - 1);
qwords[i][TARGSIZE - 1] = '\0';
i++;
}
}
puts("Here are the words accepted:");
for (i = 0; i < LIM; i++) {
puts(qwords[i]);
}
return 0;
}
char* s_gets(char* st, int n)
{
char* ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (ret_val != NULL) {
while (st[i] != '\n' && st[i] != '\0') {
i++;
}
if (st[i] == '\n') {
st[i] = '\0';
}
else {
while (getchar() != '\n') {
continue;
}
}
}
return ret_val;
}
sprintf()函数
该函数和printf()类似,但是它是把数据写入字符串,而不是打印在显示器上。sprintf()的第1个参数是目标字符串的地 址。其余参数和printf()相同,即格式字符串和待写入项的列表。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#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);
return 0;
}
char* s_gets(char* st, int n)
{
char* ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (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;
}
字符串示例:字符串排序
该程序的巧妙之处在于排序的是指向字符串的指针,而不是字符串本身。 我们采用选择排序算法(selection sort algorithm)来排序指针。具体做法是,利用for循环依次把每个元素与首元素比较。如果待比较的元素在当前首元素的前面,则交换两者。循环结束时,首元素包含的指针指向机器排序序列最靠前的字符串。然后外层for循环重复这一过程,这次从input的第2个元素开始。当内层循环执行完毕时,ptrst中的第2个元素指向排在第2的字符串。这一过程持续到所有元素都已排序完毕。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#define SIZE 81
#define LIM 20
char* s_gets(char* st, int n);
void stsrt(char* strings[], int num);
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]);
}
return 0;
}
void stsrt(char* strings[], int n)
{
int i, j;
char* temp;
for (i = 0; i < n - 1; i++) {
for (j = i + 1; j < n; j++) {
if (strcmp(strings[i], strings[j]) > 0) {
temp = strings[i];
strings[i] = strings[j];
strings[j] = temp;
}
}
}
}
char* s_gets(char* st, int n)
{
char* ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (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;
}
ctype.h字符函数和字符串
第7章中介绍了ctype.h系列与字符相关的函数。虽然这些函数不能处理整个字符串,但是可以处理字符串中的字符。例如,程序清单11.30中定义的ToUpper()函数,利用toupper()函数处理字符串中的每个字符,把整个字符串转换成大写;定义的 PunctCount()函数,利用 ispunct()统计字符串中的标点符号个数。另外,该程序使用strchr()处理fgets()读入字符串的换行符(如果有的话)。 /* mod_str.c – 修改字符串 */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define LIMIT 81
char* s_gets(char* st, int n);
void ToUpper(char* str);
int PunctCount(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", 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;
}
把字符串转换为数字
数字既能以字符串形式储存,也能以数值形式储存。把数字储存为字符串就是储存数字字符。例如,数字213以’2’、‘1’、‘3’、'\0’的形式被储存在字符串数组中。以数值形式储存213,储存的是int类型的值。 C要求用数值形式进行数值运算(如,加法和比较)。但是在屏幕上显示数字则要求字符串形式,因为屏幕显示的是字符。printf()和 sprintf()函数,通过%d 和其他转换说明,把数字从数值形式转换为字符串形式,scanf()可以把输入字符串转换为数值形式。C 还有一些函数专门用于把字符串形式转换成数值形式。 假设你编写的程序需要使用数值命令形参,但是命令形参数被读取为字符串。因此,要使用数值必须先把字符串转换为数字。如果需要整数,可以使用atoi()函数(用于把字母数字转换成整数),该函数接受一个字符串作为参数,返回相应的整数值。程序清单11.32中的程序示例演示了该函数的用法。 /* hello.c – 把命令行参数转换为数字 */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <ctype.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("Hello, good looking!");
}
}
return 0;
}
如果字符串仅以整数开头,atio()函数也能处理,它只把开头的整数转换为字符。例如, atoi(“42regular”)将返回整数42。atof()函数把字符串转换成 double 类型的值, atol()函数把字符串转换成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); 这里,nptr是指向待转换字符串的指针,endptr是一个指针的地址,该指针被设置为标识输入数字结束字符的地址,base表示以什么进制写入数字。程序清单11.33演示了该函数的用法。 /* strcnvt.c – 使用 strtol() */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define LIM 30
char* s_gets(char* st, int n);
int main(int argc, char *argv[])
{
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!");
return 0;
}
char* s_gets(char* st, int n)
{
char* ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (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;
}
复习题1.下面字符串的声明有什么问题?
int main(void) { char name[] = {‘F’, ‘e’, ‘s’, ‘s’ }; … } 2.下面的程序会打印什么? #include <stdio.h> int main(void) { char note[] = “See you at the snack bar.”; char *ptr; ptr = note; puts(ptr); puts(++ptr); note[7] = ‘\0’; puts(note); 847 puts(++ptr); return 0; } 3.下面的程序会打印什么? #include <stdio.h> #include <string.h> int main(void) { char food [] = “Yummy”; char *ptr; ptr = food + strlen(food); while (–ptr >= food) puts(ptr); return 0; } 4.下面的程序会打印什么? #include <stdio.h> #include <string.h> int main(void) 848 { char goldwyn[40] = "art of it all "; char samuel[40] = “I read p”; const char * quote = “the way through.”; strcat(goldwyn, quote); strcat(samuel, goldwyn); puts(samuel); return 0; } 5.下面的练习涉及字符串、循环、指针和递增指针。首先,假设定义了下面的函数: #include <stdio.h> char *pr(char *str) { char *pc; pc = str; while (pc) putchar(pc++); do { 849 putchar(–pc); } while (pc - str); return (pc); } 考虑下面的函数调用: x = pr(“Ho Ho Ho!”); a.将打印什么? b.x是什么类型? c.x的值是什么? d.表达式–pc是什么意思?与–pc有何不同? e.如果用–pc替换–*pc,会打印什么? f.两个while循环用来测试什么? g.如果pr()函数的参数是空字符串,会怎样? h.必须在主调函数中做什么,才能让pr()函数正常运行? 6.假设有如下声明:
char sign = '$';
sign占用多少字节的内存?'
′
占
用
多
少
字
节
的
内
存
?
"
'占用多少字节的内存?"
′占用多少字节的内存?""占用多少字节的内存? 7.下面的程序会打印出什么?
8.下面的程序会打印出什么?
9.本章定义的s_gets()函数,用指针表示法代替数组表示法便可减少一个变量i。请改写该函数。
10.strlen()函数接受一个指向字符串的指针作为参数,并返回该字符串的长度。请编写一个这样的函数。
11.本章定义的s_gets()函数,可以用strchr()函数代替其中的while循环来查找换行符。请改写该函数。
12.设计一个函数,接受一个指向字符串的指针,返回指向该字符串第1个空格字符的指针,或如果未找到空格字符,则返回空指针。
13.重写程序清单11.21,使用ctype.h头文件中的函数,以便无论用户选择大写还是小写,该程序都能正确识别答案。
编程练习
1.设计并测试一个函数,从输入中获取下n个字符(包括空白、制表符、换行符),把结果储存在一个数组里,它的地址被传递作为一个参数。 2.修改并编程练习1的函数,在n个字符后停止,或在读到第1个空白、制表符或换行符时停止,哪个先遇到哪个停止。不能只使用scanf()。 3.设计并测试一个函数,从一行输入中把一个单词读入一个数组中,并丢弃输入行中的其余字符。该函数应该跳过第1个非空白字符前面的所有空白。将一个单词定义为没有空白、制表符或换行符的字符序列。 4.设计并测试一个函数,它类似编程练习3的描述,只不过它接受第2个参数指明可读取的最大字符数。 5.设计并测试一个函数,搜索第1个函数形参指定的字符串,在其中查找第2个函数形参指定的字符首次出现的位置。如果成功,该函数返指向该字符的指针,如果在字符串中未找到指定字符,则返回空指针(该函数的功能与 strchr()函数相同)。在一个完整的程序中测试该函数,使用一个循环给函数提供输入值。 6.编写一个名为is_within()的函数,接受一个字符和一个指向字符串的指针作为两个函数形参。如果指定字符在字符串中,该函数返回一个非零值(即为真)。否则,返回0(即为假)。在一个完整的程序中测试该函数,使用一个循环给函数提供输入值。 7.strncpy(s1, s2, n)函数把s2中的n个字符拷贝至s1中,截断s2,或者有必要的话在末尾添加空字符。如果s2的长度是n或多于n,目标字符串不能以空字符结尾。该函数返回s1。自己编写一个这样的函数,名为mystrncpy()。在一个完整的程序中测试该函数,使用一个循环给函数提供输入值。
8.编写一个名为string_in()的函数,接受两个指向字符串的指针作为参数。如果第2个字符串中包含第1个字符串,该函数将返回第1个字符串开始的地址。例如,string_in(“hats”, “at”)将返回hats中a的地址。否则,该函数返回空指针。在一个完整的程序中测试该函数,使用一个循环给函数提供输入值。
9.编写一个函数,把字符串中的内容用其反序字符串代替。在一个完整的程序中测试该函数,使用一个循环给函数提供输入值。
10.编写一个函数接受一个字符串作为参数,并删除字符串中的空格。在一个程序中测试该函数,使用循环读取输入行,直到用户输入一行空行。该程序应该应用该函数只每个输入的字符串,并显示处理后的字符串。
11.编写一个函数,读入10个字符串或者读到EOF时停止。该程序为用户提供一个有5个选项的菜单:打印源字符串列表、以ASCII中的顺序打印字符串、按长度递增顺序打印字符串、按字符串中第1个单词的长度打印字符串、退出。菜单可以循环显示,除非用户选择退出选项。当然,该程序要能真正完成菜单中各选项的功能。
12.编写一个程序,读取输入,直至读到 EOF,报告读入的单词数、大写字母数、小写字母数、标点符号数和数字字符数。使用ctype.h头文件中的函数。
13.编写一个程序,反序显示命令行参数的单词。例如,命令行参数是see you later,该程序应打印later you see。
14.编写一个通过命令行运行的程序计算幂。第1个命令行参数是double类型的数,作为幂的底数,第2个参数是整数,作为幂的指数。
15.使用字符分类函数实现atoi()函数。如果输入的字符串不是纯数字,该函数返回0。
16.编写一个程序读取输入,直至读到文件结尾,然后把字符串打印出来。该程序识别和实现下面的命令行参数: -p 按原样打印 -u 把输入全部转换成大写 -l 把输入全部转换成小写 如果没有命令行参数,则让程序像是使用了-p参数那样运行。
|