C-字符串、字符和字节(上)

字符串是一种重要的数据类型,但是C语言并没有显式的字符串数据类型,因为字符串以字符串常量的形式出现或者存储于字符数组中。字符串常量适用于那些程序不会进行修改的字符串。所有其他字符串必须存储于字符数组或动态分配的内存中。本文描述处理字符串和字符的库函数,以及一些相关的,具有类似能力的函数。

01

字符串基础

首先,我们了解下字符串的基础知识。字符串就是一串零个或多个字符,并且以一个位模式为全0的NUL字节结尾。因此,字符串包含的字符内部不能出现NUL字节。这个限制很少会引起问题,因为NUL字节并不存在与它相关联的可打印字符,这也是它被选为终止符的原因。NUL字节是字符串的终止符,但它本身并不是字符串的一部分,所以字符串的长度并不包括NUL字节。

02

字符串的长度

字符串的长度就是它所包含的字符个数。我们很容易通过对字符进行计数来计算字符串的长度,下面程序就是这样做的。

#include <stddef.h>size_t strlen(char const *string){    int length;    for (length = 0; *string++ != '\0'; )        length += 1;    return length;}

这种实现方法说明了处理字符串所使用的的处理过程的类型。但是,事实上你极少需要编写字符串函数,因为标准库所提供的函数通常能完成这些任务。不过,如果你还是希望自己编写一个字符串函数,请注意标准保留了所有以str开头的函数名,用于标准库将来的拓展。

03

不受限制的字符串函数

最常用的字符串函数都是“不受限制”的,就是说它们只是通过寻找字符串参数结尾的NUL字节来判断它的长度。这些函数一般都指定一块内存用于存放结果字符串。在使用这些函数时,程序员必须保证结果字符串不会溢出这块内存。这节将做详细讨论。

复制字符串

    用于复制字符串的函数是strcpy,它的原型如下所示:


char *strcpy(char *dst, char const *src);

这个函数把参数src字符串复制到dst参数,如果src和dst在内存中出现重叠,其结果是未定义的。由于dst参数将进行修改,所以它必须是个字符数组或者是一个指向动态分配内存的指针,不能使用字符串常量。

目标参数的的以前内容将被覆盖并丢失。即使新的字符串比dst原先的内存更短,由于新字符串是以NUL字节结尾的,所以老字符串最后剩余的几个字符也会被有效地删除。

char messgae[] = "Original messgae";...strcpy(message, "Different");

顺利执行strcpy之后,数组将包含下面内容:

|'D'|'i'|'f'|'e'|'r'|'e'|'n'|'t'|0|'e'|'s'|'s'|'a'|'g'|'e'|0|

第一个NUL字节后面的几个字符无法被字符串函数访问,因此从实际角度来看,它们已经是丢失的了。

警告:

你必须保证目标字符数组的空间足以容纳需要复制的字符串,如果字符串比数组长,多余的字符仍被复制,它们将覆盖原先存储于数组后面的内存空间的值,strcpy无法解决这个问题,因为它无法判断目标字符数组的长度。例如:

char message[] = "Original messgae";...strcpy(message, "A different message");

第二个字符串太长了,无法容纳于message数组中。因此,strcpy函数将侵占数组后面的部分内存空间,改写原先恰好存储在那里的变量。如果你在使用这个函数前确保目标函数足以容纳源字符串,就可以避免大量调试工作。

2.连接字符串

要想把一个字符串添加(连接)到另一个字符串的后面,你可以使用strcat函数。它的原型如下:

char *strcat(char *dst, char const *src);

strcat要求dst参数原先已经包含了一个字符串(可以是空字符串),它找到这个字符串的末尾,并把src字符串的一份拷贝添加到这个位置。如果src和dst的位置发生重叠,其结果是未定义的。

下面是它的常见用法,

char name[] = "Jim";strcpy(message, "Hello ");strcat(message, name);strcat(message, ", how are you?");

每个strcat函数的字符串参数都被添加到原先存于message数组的字符串后面,其结果是下面这个字符串:

Hello Jim, how are you?

3.函数的返回值

strcpy和strcat都返回它们第一个参数的一份拷贝,就是一个指向目标字符数组的指针,由于它们都是返回这种类型的值,所以你可以嵌套地调用这些函数,如下所示:

strcat(strcpy(dst, a), b);

strcpy首先执行。它把字符串从a复制到dst并返回dst。然后这个返回值称为strcat函数的第一个参数,strcat把b添加到dst后面。

这种嵌套调用的风格较之下面这种可读性更佳的风格在功能上并无优势。

strcpy(dst, a);strcat(dst, b);

事实上,这些函数的的绝大多数调用中,它们的返回值只是被简单地忽略。

4.字符串比较

比较两个字符串涉及对两个字符串对应的字符逐个比较,直到发现不匹配为止。那个最先不匹配的字符中较“小”(在字符集中的序数较小)的那个字符所在的字符串被认为小于另外一个字符串。如果其中一个字符串是另一个字符串的前面一部分,那么它也被认为小于另外一个字符串,因为它的NUL结尾字节出现得更早。这种比较被称为“词典比较”,对于只包含大写字母或只包含小写字母的字符比较,这种比较过程所给出的结果总是和我们日常所用的字母顺序的比较相同。

库函数strcmp用于比较两个字符串,它的原型如下:

int strcmp(char const *s1, char const *s2);

如果s1小于s2,函数返回一个小于零的值,如果s1大于s2,函数返回一个大于零的值。如果两个字符串相等,函数就返回零。

04

长度受限的字符串函数

标准库还包含了一些函数,它们以一种不同的方式处理字符串。这些函数接受一个显式的长度参数,用于限定进行复制或比较的字符数。这些函数提供了一种方便的机制,可以防止难以预料的长字符从它们的目标数组溢出。

这些函数的原型如下,和它们不受限制的版本一样,如果源参数与目标参数发生重叠,strncpy和strncat的结果就是未定义的。

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);

和strcpy一样,strncpy把源字符串的字符复制到目标数组。然而,它总是正好向dst写入len个字符。如果strlen(src)的值小于len,dst就用额外的NUL字符填充到len长度。如果strlen(src)的值大于等于len,那么只有len个字符被复制到dst中。注意!它的结果将不会以NUL字节结尾。

警告:

strncpy调用的结果可能不是一个字符串,因为字符串必须以NUL字节结尾。如果在一个需要字符串的地方(如strlen函数的参数)使用了一个不是以NUL字节结尾的字符序列,会发生什么情况呢?strlen函数将无法知道NUL字节是没有的,所以它会继续查找,直到它发现一个NUL字节为止。或许找了几百个字符才找到,而strlen函数的这个返回值从本质上说是一个随机数。或者,如果函数试图访问系统分配给这个程序以外的内存范围,程序就会崩溃。

尽管strncat也是一个长度受限的函数,但它和strncpy存在不同之处。它从src中最多复制len个字符到目标数组后面。但是,strncat总是在结果字符串后面添加一个NUL字节,且它不会像strncpy那样对目标数组用NUL字节进行填充。注意目标数组中原先的字符串并没有算在strncat的长度中。strncat最多向目标数组复制len个字符(再加一个结尾的NUL字节),它不会管目标参数除去原先字符串后剩下的空间够不够。

最后,strncmp也用于比较两个字符串,但它最多比较len个字节。如果两个字符串在第len个字符之前存在不相等的字符,这个函数就像strcmp一样停止比较,返回结果。如果两个字符串的前len个字符相等,函数就返回零。

05

字符串查找

标准库中存在许多函数,它们用各种不同的方法查找字符串。这样各种各样的工具给了码手很大的灵活性。

1.查找一个字符

在一个字符串中查找一个特定字符最容易的方法是使用strchr和strrchr函数,它们的原型如下:

char *strchr(char const *str, int ch);char *strrchr(char const *str, int ch);

注意它们的第2个参数是一个整型值。但是,它包含了一个字符值。strchr在字符串str中查找字符ch第1次出现的位置,找到后函数返回一个指向该位置的指针。如果该字符串中不存在该字符,函数就返回一个NULL指针。strrchr的功能和strchr基本一致,只是它返回的是一个指向该字符串该字符最后一次出现的位置。

2.查找任何几个字符

strpbrk是个更为常见的函数。它并不是查找某个特定的字符,而是查找任何一组字符第一次在字符串中出现的位置。它的原型如下:

char *strpbrk(char const *str, char const *group);

这个函数返回一个指向str中第一个匹配group中任何一个字符的字符位置。如果未找到匹配,函数返回一个NULL指针。

如下代码:

char string[20] = "Hello there, honey.";char *ans;ans = strpbrk(string, "aeiou");

ans指向的位置是string+1,因为这个位置是第二个参数中的字符第一次出现的位置。和前面一样,这个函数也是区分大小写的。

3.查找一个子串

为了在字符串中查找一个子串,可以使用strstr函数,它的原型如下:

char *strstr(char const *s1, char const *s2);

这个函数s1中查找整个s2第一次出现的起始位置,并返回一个指向该位置的指针。如果s2并没有完整地出现在s1的任何地方,函数将返回一个NULL指针。如果第二个参数是一个空字符串,函数就返回s1。

标准库中并不存在strrstr和strrpbrk。不过,如果你需要它们,它们是很容易实现的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容