size_t strlen( char const *string );
char *strcpy( char *dst, char const *src );
char *strcat( char *dst, char const *src );
int strcmp( char const *s1, char const *s2 );
char *strncpy( char *dst, char const *src, size_t len );
char *strncat( char *dst, char const *src, size_t len );
int strncmp( char const *s1, char const *s2, size_t len );
char *strchr( char const *str, int ch );
char *strrchr( char const *str, int ch );
char *strpbrk( char const *str, char const *group );
char *strstr( char const *s1, char const *s2 );
size_t strspn( char const *str, char const *group );
size_t strcspn( char const *str, char const *group );
char *strtok( char *str, char const *sep );
char *strerror( int error_number );
int tolower( int ch );
int toupper( int ch );
void *memcpy( void *dst, void const *src, size_t length );
void *memmove( void *dst, void const *src, size_t length );
void *memcmp( void const *a, void const *b, size_t length );
void *memchr( void const *a, int ch, size_t length );
void *memset( void *a, int ch, size_t length );
第9章 字符串、字符和字节
学习笔记
1. 库函数strlen
size_t strlen( char const *string );
注意strlen
返回一个类型为size_t
的值。这个类型是在头文件stddef.h
中定义的,它是一个无符号整数类型。在表达式中使用无符号数可能导致不可预料的结果。例如,下面两个表达式是不相等的,由于strlen
的结果是个无符号数,所以第二条语句的结果将永远是真。
if( strlen(x) >= strlen(y) ) ...
if( strlen(x) - strlen(y) >= 0 ) ...
如果把strlen
的返回值强制转换为int
,就可以消除这个问题。
2. 库函数strncpy
char *strncpy( char *dst, char const *src, size_n len );
如果strlen(src)
的值小于len
,dst
数组就用额外的NUL
字节填充到len
长度。如果strlen(src)
的值大于或等于len
,那么只有len
个字符被复制到dst
中。注意!它的结果将不会以NUL
字节结尾。
尽管strncat
也是一个长度受限的函数,但它却和strncpy
存在不同之处。它从src
中最多复制len
个字符到目标数组的后面。但是,strncat
总是在结果字符串后面添加一个NUL
字节,而且它不会像strncpy
那样对目标数组用NUL
字节进行填充。
3. 库函数strchr和strrchr
char *strchr( char const *str, int ch );
char *strrchr( char const *str, int ch );
strchr
在字符串str
中查找字符ch
第一次出现的位置,找到后函数返回一个指向该位置的指针。如果该字符并不存在于字符串中,函数就返回一个NULL
指针。strrchr
的功能和strchr
基本一致,只是它返回的是一个指向字符串中该字符最后一次出现的位置(最右边那个)。
4. 库函数strpbrk
char *strpbrk( char const *str, char const *group );
这个函数返回一个指向str
中第一个匹配group
中任何一个字符的字符位置。如果未找到匹配,函数返回一个NULL
指针。
5. 库函数strstr
char *strstr( char const *s1, char const *s2 );
这个函数在s1
中查找整个s2
第一次出现的起始位置,并返回一个指向该位置的指针。如果s2
并没有完整地出现在s1
的任何地方,函数将返回一个NULL
指针。如果第二个参数是一个空字符串,函数就返回s1
。
标准库中并不存在strrstr
或strrpbrk
函数。不过,如果你需要它们,它们是很容易实现的。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
/* 字符串中最后一个匹配group的位置 */
char *strrpbrk(char const *str, char const *group){
char *last = NULL, *current = NULL;
if(*group == '\0')
return NULL;
current = strpbrk(str, group);
while(current != NULL){
last = current;
current = strpbrk(last + 1, group);
}
return last;
}
/* 字符串中最后一个匹配子字符串的位置 */
char *strrstr(char const *s1, char const *s2){
char *current = NULL, *last = NULL;
/* 如果s2为空则返回s1 */
if(*s2 == '\0')
return s1;
current = strstr(s1, s2);
while(current != NULL){
last = current;
current = strstr(last + 1, s2);
}
return last;
}
int main(){
char *x = "1234523", *y = "", *res = NULL;
res = strrpbrk(x, y);
printf("%s\n", res);
res = strrstr(x, y);
printf("%s\n", res);
return EXIT_SUCCESS;
}
6. 库函数strspn和strcspn
size_t strspn( char const *str, char const *group );
size_t strcspn( char const *str, char const *group );
group
字符串指定一个或多个字符,strspn
返回字符串str
从第一个字符开始在字符串group
中字符的数量。strcspn
和strspn
函数的作用正好相反。
/* ptr指向buffer第一个非空白字符 */
/*
\n - 换行
\r - 回车
\f - 换页
\t - 制表符
\v - 垂直制表符
*/
ptr = buffer + strspn(buffer, "\n\r\f\t\v");
7. 库函数strtok
char *strtok( char *str, char const *sep );
sep
参数是个字符串,定义了用作分隔符的字符集合。第一个参数指定一个字符串,它包含零个或多个由sep
字符串中一个或多个分隔符分隔的标记。strtok
找到str
的下一个标记,并将其用NUL
结尾,然后返回一个指向这个标记的指针。
如果strtok
函数的第一个参数不是NULL
,函数将找到字符串的第一个标记。strtok
同时将保存它在字符串中的位置。如果strtop
函数的第一个参数是NULL
,函数就在同一个字符串中从这个被保存的位置开始像前面一样查找下一个标记。strtok
函数是不可再入的。
#include<stdio.h>
#include<string.h>
int main(){
char str[] = "my name is cc.", *token;
char const *sep = " ";
for(token = strtok(str, sep); token != NULL; token = strtok(NULL, sep))
printf("%s\n", token);
}
书后练习
问题4
如果从数组x复制50个字节到数组y,最简单的方法是什么?
memcpy(y, x, 50);
问题8
下面的表达式有何不同?
memchr( buffer, 0, SIZE ) - buffer
strlen( buffer )
如果缓冲区包含了一个字符串,memchr
将在内存中buffer
的起始位置开始查找第一个包含0的字节并返回一个指向该字节的指针。将这个指针减去buffer
获得存储在这个缓冲区中的字符串的长度。strlen
函数完成相同的任务,不过strlen
的返回值是个无符号类型的值,而指针减法的值应该是个有符号类型。
但是,如果缓冲区内的数据并不是以NULL
字节结尾,memchr
函数将返回一个NULL
指针。将这个值减去buffer
将产生一个无意义的结果。另一方面,strlen
函数在数组的后面继续查找,直到最终发现一个NUL
字节。
编程练习2
编写一个名叫my_strlen的函数。它类似于strlen函数,但它能够处理由于使用strn—函数而创建的未以NUL字节结尾的字符串。你需要向函数传递一个参数,它的值就是保存了需要进行长度测试的字符串的数组的长度。
/* my_strlen */
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
size_t my_strlen1(char const *str, size_t length){
size_t len = strlen(str);
if(len < length)
return len;
return length;
}
size_t my_strlen2(char const *str, int length){
size_t len;
for(len = 0; len < length; len++)
if(*str++ == '\0')
break;
return len;
}
int main(int argc, char*argv[]){
char *string = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
size_t length1 = 30, res;
int length2 = 30;
res = my_strlen1(string, length1);
printf("%u\n", res);
res = my_strlen2(string, length2);
printf("%u\n", res);
return EXIT_SUCCESS;
}
编程练习6
编写一个名叫my_strcpy_end的函数取代strcpy函数,它返回一个指向目标字符串末尾的指针(也就是说,指向NUL字节的指针),而不是返回一个指向目标字符串起始位置的指针。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char *my_strcpy_end(char *dst, char const *src){
char *ptr = strcpy(dst, src) + strlen(dst);
return ptr;
}
int main(int argc, char *argv[]){
char dst[10], *src = "123";
char *res = my_strcpy_end(dst, src);
printf("%s\n", res);
return EXIT_SUCCESS;
}
编程练习11
编写一个程序,对标准输入进行扫描,并对单词“the”出现的次数进行计数。进行比较时应该区分大小写,所以“The”和“THE”并不计算在内。你可以认为各单词由一个或多个空格字符分隔,而且输入行在长度上不会超过100个字符。计数结果应该写道标准输出上。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX_SIZE 100
int main(int argc, char *argv[]){
char input_string[MAX_SIZE], *token;
char const sep[] = " \n\r\f\t\v";
int count = 0;
while(gets(input_string)){
for(token = strtok(input_string, sep);
token != NULL;
token = strtok(NULL, sep))
if(strcmp(token, "the") == 0)
count++;
}
printf("There are %d 'the' in the string.\n", count);
return EXIT_SUCCESS;
}
编程练习15
标准I/O库并没有提供一种机制,在打印大整数时用逗号进行分隔。在这个练习中,你需要编写一个程序,为美元数额的打印提供这个功能。函数把一个数字字符串(代表以美分为单位的金额)转换为美元形式,如下面的例子所示:
输入 | 输出 | 输入 | 输出 |
---|---|---|---|
空 | $0.00 | 12345 | $123.45 |
1 | $0.01 | 123456 | $1,234.56 |
12 | $0.12 | 1234567 | $12,345.67 |
123 | $1.23 | 12345678 | $123,456.78 |
1234 | $12.34 | 123456789 | $1,234,567.89 |
下面是函数的原型:
void dollars( char *dest, char const *src );
src将指向需要被格式化的字符(你可以假定它们都是数字)。函数应该像上面例子所示的那样对字符进行格式化,并把结果字符串保存到dest中。你应该保证你所创建的字符串以一个NUL字节结尾。src的值不应该被修改。你应该使用指针而不是下标。
提示:首先找到第2个参数字符串的长度。这个值有助于判断逗号应插入到什么位置。同时,小数点和最后两位数字应该是唯一的需要你进行处理的特殊情况。
#include<stdio.h>
#include<string.h>
void dollars(char *dest, char const *src){
int len = strlen(src);
char *ptr = dest;
if(dest == NULL || src == NULL)
return;
/* 把$添加到dest中 */
*dest++ = '$';
/* 如果src的长度大于3 */
if(len >= 3){
for(int i = len - 2; i > 0; ){
*dest++ = *src++;
/* 判断剩下的位数是不是3的倍数 */
if(--i > 0 && i % 3 == 0)
*dest++ = ',';
}
}else
*dest++ = '0';
*dest++ = '.';
*dest++ = len < 2 ? '0' : *src++;
*dest++ = len < 1 ? '0' : *src;
*dest = '\0';
printf("%s\n", ptr);
}
int main(int argc, char *argv[]){
char dest[100] = "", *src = "123456789";
dollars(dest, src);
return 1;
}