字符串和字符串函数
本章介绍以下内容:
- 函数:
gets()、gets_s()、fgets()、puts()、fputs()、strcat()、strncat()、strcmp()、strncmp()、strcpy()、strncpy()、sprintf()、strchr()
- 创建并使用字符串
- 使用C库中的字符和字符串函数,并创建自定义的字符串函数
- 使用命令行参数
表示字符串和字符串I/O
字符串是以空字符(\0)结尾的char类型数组。
- puts()函数也属于stdio.h系列的输入/输出函数。
- puts()函数只显示字符串,而且自动在显示的字符串末尾加上换行符。
// 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);
return 0;
}
在程序中定义字符串
1.字符串字面量(字符串常量)
用双引号括起来的内容称为字符串字面量(string literal),也叫作字符串常量(string constant)。
如果要在字符串内部使用双引号,必须在双引号前面加上一个反斜杠(\)
字符串常量属于静态存储类别(static storage class),这说明如果在函数中使用字符串常量,该字符串只会被储存一次,在整个程序的生命期内存在,即使函数被调用多次。用双引号括起来的内容被视为指向该字符串储存位置的指针。这类似于把数组名作为指向该数组位置的指针。
/* strptr.c -- 把字符串看作指针 */
#include <stdio.h>
int main(void)
{
printf("%s, %p, %c\n", "We", "are", *"space farers"); // We, 00405064, s
return 0;
}
printf()根据%s 转换说明打印 We,根据%p 转换说明打印一个地址。因此,如果"are"代表一个地址,printf()将打印该字符串首字符的地址(如果使用ANSI之前的实现,可能要用%u或%lu代替%p)。最后,*"space farers"
表示该字符串所指向地址上储存的值,应该是字符串*"space farers"
的首字符。
2.字符串数组和初始化
定义字符串数组时,必须让编译器知道需要多少空间。一种方法是用足够空间的数组储存字符串。
让编译器确定数组的大小很方便。
const char m2[] = "If you can't think of anything, fake it.";
字符数组名和其他数组名一样,是该数组首元素的地址。
还可以使用指针表示法创建字符串。
const char * pt1 = "Something is pointing at me.";
- 数组和指针
// addresses.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 of 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: 0061FF10
address pt: 00405064
address of MSG: 00405064
address of "I'm special": 00405064
*/
- 数组和指针的区别
数组的元素是变量(除非数组被声明为const),但是数组名不是变 量。
编译器可以使用内存中的一个副本来表示所有完全相同的字符串字面量。
建议在把指针初始化为字符串字面量时使用const限定符
总之,如果不修改字符串,不要用指针指向字符串字面量。
- 字符串数组
指向字符串的指针数组和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 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));
return 0;
}
mytalents数组是一个内含5个指针的数组,系统中共占用40字节。而yourtalents是一个内含5个数组的数组,每个数组内含40个char类型的值,共占用200字节。
所以,虽然mytalents[0]和yourtalents[0]都分别表示一个字符串,但mytalents和yourtalents的类型并不相同。mytalents中的指针指向初始化时所用的字符串字面量的位置,这些字符串字面量被储存在静态内存中;而 yourtalents 中的数组则储存着字符串字面量的副本,所以每个字符串都被储存了两次。此外,为字符串数组分配内存的使用率较低。yourtalents 中的每个元素的大小必须相同,而且必须是能储存最长字符串的大小。
- 总结声明字符串的方式
#include <stdio.h>
int main()
{
char words[] = "I am a string in an array.";
char *pt1 = "1-Something is pointing at me.";
const char *pt2 = "2-Something is pointing at me.";
puts(words);
puts(pt1);
puts(pt2);
return 0;
}
指针和字符串
/* 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;
}
/*
mesg = Don't be a fool!; &mesg = 0061FF1C; value = 00405064
copy = Don't be a fool!; © = 0061FF18; value = 00405064 */
首先第1项,mesg和copy都以字符串形式输出(%s转换说明)。
接着第2项,打印两个指针的地址。
最后一项,显示两个指针的值。所谓指针的值就是它储存的地址。mesg 和 copy 的值都是0x0040a000,说明它们都指向的同一个位置。因此,程序并未拷贝字符串。语句copy = mesg;把mesg的值赋给copy,即让copy也指向mesg指向的字符串。
字符串函数
gets()函数
在读取字符串时,scanf()和转换说明%s只能读取一个单词。
gets()函数简单易用,它读取整行输入,直至遇到换行符,然后丢弃换行符,储存其余字符,并在这些字符的末尾添加一个空字符使其成为一个 C 字符串。
/* 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;
}
整行输入(除了换行符)都被储存在 words 中,puts(words)和printf("%s\n, words")的效果相同。
fget()和fput()
fgets()函数通过第2个参数限制读入的字符数来解决溢出的问题。
fgets()函数的第2个参数指明了读入字符的最大数量。如果该参数的值是n,那么fgets()将读入n-1个字符,或者读到遇到的第一个换行符为止。
如果fgets()读到一个换行符,会把它储存在字符串中。这点与gets()不同,gets()会丢弃换行符。
fgets()函数的第3 个参数指明要读入的文件。如果读入从键盘输入的数据,则以stdin(标准输入)作为参数,该标识符定义在stdio.h中。
因为 fgets()函数把换行符放在字符串的末尾(假设输入行不溢出),通常要与 fputs()函数(和puts()类似)配对使用,除非该函数不在字符串末尾添加换行符。fputs()函数的第2个参数指明它要写入的文件。如果要显示在 计算机显示器上,应使用stdout(标准输出)作为该参数。
/* fgets1.c -- 使用 fgets() 和 fputs() */
#include <stdio.h>
#define STLEN 14
int main(void)
{
char words[STLEN];
puts("Enter a string, please.");
// gets()不会存储换行符,会删掉换行符 houfei\0
fgets(words, STLEN, stdin); // 换行符会存储起来 houfei\n\0
printf("Your string twice (puts(), then fputs()):\n");
puts(words); // 默认会加上换行符 houfei\n\0\n
fputs(words, stdout); // houfei\n\0
puts("Enter another string, please.");
fgets(words, STLEN, stdin);
printf("Your string twice (puts(), then fputs()):\n");
puts(words);
fputs(words, stdout);
puts("\nDone.");
return 0;
}
puts()函数会在待输出字符串末尾添加一个换行符,而fputs()不会这样做
fputs()函数返回指向 char的指针。如果一切进行顺利,该函数返回的地址与传入的第 1 个参数相同。但是,如果函数读到文件结尾,它将返回一个特殊的指针:空指针(null pointer)。该指针保证不会指向有效的数据,所 以可用于标识这种特殊情况。在代码中,可以用数字0来代替,不过在C语言中用宏NULL来代替更常见(如果在读入数据时出现某些错误,该函数也返回NULL)。程序演示了一个简单的循环,读入并显示用户输入的内容,直到fgets()读到文件结尾或空行(即,首字符是换行符)。
/* fgets2.c -- 使用 fgets() 和 fputs() */
#include <stdio.h>
#define STLEN 10
int main(void)
{
char words[STLEN];
puts("Enter strings (empty line to quit):");
// 大于10个字符,会从缓存里边读,所以字符串会大于10长度
while (fgets(words, STLEN, stdin) != NULL && words[0] != '\n')
fputs(words, stdout);
puts("\nDone.");
return 0;
}
系统使用缓冲的I/O。这意味着用户在按下Return键之前,输入都被储存在临时存储区(即,缓冲区)中。按下Return键就在输入中增加了一个换行符,并把整行输入发送给fgets()。对于输出,fputs()把字符发送给另一个缓冲区,当发送换行符时,缓冲区中的内容被发送至屏幕上。
fgets()储存换行符有好处也有坏处。坏处是你可能并不想把换行符储存在字符串中,这样的换行符会带来一些麻烦。好处是对于储存的字符串而言,检查末尾是否有换行符可以判断是否读取了一整行。如果不是一整行,要妥善处理一行中剩下的字符。
该程序读取输入行,删除储存在字符串中的换行符,如果没有换行符,则丢弃数组装不下的字符。
/* fgets3.c -- 使用 fgets() */
#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')
continue;
puts(words);
}
puts("done");
return 0;
}
空字符和空指针的区别
从概念上看,两者完全不同。空字符(或'\0')是用于标记C字符串末尾的字符,其对应字符编码是0。由于其他字符的编码不可能是 0,所以不可能是字符串的一部分。
空指针(或NULL)有一个值,该值不会与任何数据的有效地址对应。通常,函数使用它返回一个有效地址表示某些特殊情况发生,例如遇到文件结尾或未能按预期执行。
空字符是整数类型,而空指针是指针类型。两者有时容易混淆的原因是:它们都可以用数值0来表示。但是,从概念上看,两者是不同类型的0。另外,空字符是一个字符,占1字节;而空指针是一个地址,通常占4字节。
gets_s()函数
C11新增的gets_s()函数(可选)和fgets()类似,用一个参数限制读入的字符数。
/* gets_s3.c -- 使用 gets_s() */
#include <stdio.h>
#define STLEN 10
int main(void)
{
char words[STLEN];
int i;
puts("Enter strings (empty line to quit):");
while (gets_s(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");
return 0;
}
gets_s()与fgets()的区别如下。
gets_s()只从标准输入中读取数据,所以不需要第3个参数。
如果gets_s()读到换行符,会丢弃它而不是储存它。
如果gets_s()读到最大字符数都没有读到换行符,会执行以下几步。首先把目标数组中的首字符设置为空字符,读取并丢弃随后的输入直至读到换行符或文件结尾,然后返回空指针。接着,调用依赖实现的“处理函数”(或你选择的其他函数),可能会中止或退出程序。
第2个特性说明,只要输入行未超过最大字符数,gets_s()和gets()几乎一样,完全可以用gets_s()替换gets()
比较 gets()、fgets()和 gets_s()
我们来比较一下 gets()、fgets()和 gets_s()的适用性。如果目标存储区装得下输入行,3 个函数都没问题。但是fgets()会保留输入末尾的换行符作为字符串的一部分,要编写额外的代码将其替换成空字符。
自定义函数是s_get()
读取整行输入并用空字符代替换行符,或者读取一部分输入,并丢弃其余部分:
#include <stdio.h>
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;
}
如果 fgets()返回 NULL,说明读到文件结尾或出现读取错误,s_gets()函数跳过了这个过程。它模仿程序清单11.9的处理方法,如果字符串中出现换行符,就用空字符替换它;如果字符串中出现空字符,就丢弃该输入行的其余字符,然后返回与fgets()相同的值。我们在后面的示例中将讨论fgets()函数。
也许读者想了解为什么要丢弃过长输入行中的余下字符。这是因为,输入行中多出来的字符会被留在缓冲区中,成为下一次读取语句的输入。例如,如果下一条读取语句要读取的是 double 类型的值,就可能导致程序崩溃。丢弃输入行余下的字符保证了读取语句与键盘输入同步。
字符串输入
用于打印字符串:put()、fputs()和printf()
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 argument to puts(). ==>");
puts(DEF);
puts(str1);
puts(str2);
puts(&str1[5]);
puts(str2 + 4);
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() 函数在遇到空字符时就停止输出
fputs()函数
fputs()函数是puts()针对文件定制的版本。它们的区别如下。
fputs()函数的第 2 个参数指明要写入数据的文件。如果要打印在显示器上,可以用定义在stdio.h中的stdout(标准输出)作为该参数。
与puts()不同,fputs()不会在输出的末尾添加换行符。
注意,gets()丢弃输入中的换行符,但是puts()在输出中添加换行符。另一方面,fgets()保留输入中的换行符,fputs()不在输出中添加换行符。
printf()函数
自定义输入/输出函数
#include <stdio.h>
void put1(const char *string) /* 不会改变字符串 */
{
while (*string != '\0')
putchar(*string++);
}
strlen()函数
strlen()函数用于统计字符串的长度。
/* test_fit.c -- 使用缩短字符串长度的函数 */
#include <stdio.h>
#include <string.h> /* 内含字符串函数原型 */
void fit(char *, unsigned int);
int main(void)
{
char mesg[] = "111111 2222 33 44 55 66 77777777,"
" 888 9-99 aaaaaaaaa.";
puts(mesg);
fit(mesg, 38);
puts(mesg);
puts("查看其他:");
puts(mesg + 39);
puts(mesg + 8);
return 0;
}
void fit(char *string, unsigned int size)
{
if (strlen(string) > size)
string[size] = '\0';
}
gets()唯一的参数是 words,它无法检查数组是否装得下输入行。
上一章介绍过,数组名会被转换成该数组首元素的地址,因此,gets()函数只知道数组的开始处,并不知道数组中有多少个元素。
strcat()
strcat()(用于拼接字符串)函数接受两个字符串作为参数。该函数把第2个字符串的备份附加在第1个字符串末尾,并把拼接后形成的新字符串作为第1个字符串,第2个字符串不变。strcat()函数的类型是char *(即,指向char的指针)。strcat()函数返回第1个参数,即拼接第2个字符串后的第1个字符串的地址。
/* 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 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");
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;
}
strncat()函数
strcat()函数无法检查第1个数组是否能容纳第2个字符串。如果分配给第1个数组的空间不够大,多出来的字符溢出到相邻存储单元时就会出问题。
用 strncat(),该函数的第3 个参数指定了最大添加字符数。
/* 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(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;
printf("还可以添加%d个字符\n", available);
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)
{
while (st[i] != '\n' && st[i] != '\0')
i++;
if (st[i] == '\n')
st[i] = '\0';
else
while (getchar() != '\n')
continue;
}
return ret_val;
}
strcmp()函数
该函数通过比较运算符来比较字符串,就像比较数字一样。如果两个字符串参数相同,该函数就返回0,否则返回非零值。
/* nogo.c -- 该程序是否能正常运行? */
#include <stdio.h>
#include <string.h> // 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) != 0)
{
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)
{
while (st[i] != '\n' && st[i] != '\0')
i++;
if (st[i] == '\n')
st[i] = '\0';
else
while (getchar() != '\n')
continue;
}
return ret_val;
}
/* nogo.c -- 该程序是否能正常运行? */
#include <stdio.h>
#include <string.h> // strcmp()函数的原型在该头文件中
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 */
这说明,如果在字母表中第1个字符串位于第2个字符串前面,strcmp()中就返回负数;反之,strcmp()则返回正数。
strncmp()函数
strcmp()函数比较字符串中的字符,直到发现不同的字符为止,这一过程可能会持续到字符串的末尾。而strncmp()函数在比较两个字符串时,可以比较到字符不同的地方,也可以只比较第3个参数指定的字符数。
strcpy()和strncpy()函数
strcpy()接受两个字符串指针作为参数,可以把指向源字符串的 第2个指针声明为指针、数组名或字符串常量;而指向源字符串副本的第1个 指针应指向一个数据对象(如,数组),且该对象有足够的空间储存源字符 串的副本。记住,声明数组将分配储存数据的空间,而声明指针只分配储存 一个地址的空间。
该程序把输入拷贝至一个临时数组中,如果第1 个字母是q,程序调用strcpy()把整个字符串从临时数组拷贝至目标数组中。strcpy()函数相当于字符串赋值运算符。
/* copy1.c -- 演示 strcpy() */
#include <stdio.h>
#include <string.h> // 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]);
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;
}
请注意,strcpy()第2个参数(temp)指向的字符串被拷贝至第1个参数(qword[i])指向的数组中。拷贝出来的字符串被称为目标字符串,最初的字符串被称为源字符串。参考赋值表达式语句,很容易记住strcpy()参数的顺序,即第1个是目标字符串,第2个是源字符串。
char target[20];
int x;
x = 50; /* 数字赋值*/
strcpy(target, "Hi ho!"); /* 字符串赋值*/
target = "So long"; /* 语法错误 */
程序员有责任确保目标数组有足够的空间容纳源字符串的副本。下面的代码有点问题:
char * str;
strcpy(str, "The C of Tranquility"); // 有问题
strcpy()把"The C of Tranquility"拷贝至str指向的地址上,但是str未被初始化,所以该字符串可能被拷贝到任意的地方!
总之,strcpy()接受两个字符串指针作为参数,可以把指向源字符串的第2个指针声明为指针、数组名或字符串常量;而指向源字符串副本的第1个指针应指向一个数据对象(如,数组),且该对象有足够的空间储存源字符串的副本。记住,声明数组将分配储存数据的空间,而声明指针只分配储存一个地址的空间。
- strcpy()的其他属性
strcpy()函数还有两个有用的属性。第一,strcpy()的返回类型是 char *,该函数返回的是第 1个参数的值,即一个字符的地址。第二,第 1 个参数不必指向数组的开始。这个属性可用于拷贝数组的一部分。程序清单11.26演示了该函数的这两个属性。
/* copy2.c -- 使用 strcpy() */
#include <stdio.h>
#include <string.h> // 提供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);
return 0;
}
注意,由于第1个参数是copy +7,所以ps指向copy中的第8个元素(下标为7)。因此puts(ps)从该处开始打印字符串。
/* copy3.c -- 使用strncpy() */
#include <stdio.h>
#include <string.h> /* 提供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]);
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;
}
strncpy(target, source, n)把source中的n个字符或空字符之前的字符(先满足哪个条件就拷贝到何处)拷贝至target中。因此,如果source中的字符数小于n,则拷贝整个字符串,包括空字符。但是,strncpy()拷贝字符串的长度不会超过n,如果拷贝到第n个字符时还未拷贝完整个源字符串,就不会拷贝空字符。所以,拷贝的副本中不一定有空字符。鉴于此,该程序把 n 设置为比目标数组大小少1(TARGSIZE-1),然后把数组最后一个元素设置为空字符。
sprintf()函数
sprintf()函数声明在stdio.h中,而不是在string.h中。该函数和printf()类似,但是它是把数据写入字符串,而不是打印在显示器上。因此,该函数可以把多个元素组合成一个字符串。sprintf()的第1个参数是目标字符串的地址。其余参数和printf()相同,即格式字符串和待写入项的列表。
字符串示例:字符串排序
/* sort_str.c -- 读入字符串,并排序字符串 */
#include <stdio.h>
#include <string.h>
#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]); /* 排序后的指针 */
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;
}
ctype.h字符函数和字符串
/* mod_str.c -- 修改字符串 */
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#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));
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;
}
命令行参数
/* 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;
}
/*
E:\github\C-CPP-basics>1 i am boy
The command line has 3 arguments:
1: i
2: am
3: boy
*/
/* hello.c -- 把命令行参数转换为数字 */
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int i, times;
// atoi()函数把该字符串转换为整数,然后该值被赋给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;
}
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 16 input, base 10 output: %ld, stopped at %s (% d)\n ", value, end, *end);
puts("Next number:");
}
puts("Bye!\n");
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;
}