第五章 指针与数组 >> 5.5 字符指针与函数
1. 字符串常量
字符串常量是一个字符数组,例如:”I am a string”,以空字符’\0’结尾,所以字符串常量占据的存储单元数比双引号内的字符数要多 1 个。
字符串常量最常见的用法是作为函数参数,例如:
printf("hello, world\n");
实际上 printf 函数接受的是一个指向字符数组第一个字符的指针,通过字符指针来访问该字符串。也就是,字符串常量可以通过一个指向其第一个元素的指针访问。
字符串常量还用来初始化指针变量。例如我们声明一个指针:
char *pmessage;
那么语句:
pmessage = "now is the time";
将把该字符串常量第一个字符的地址赋给 pmessage,也就是说 pmessage 将指向该字符串常量。要注意的是:该过程并没进行字符串的复制,而仅仅是指针指向特定对象的操作。
C语言没有提供将整个字符串作为一个整体进行处理的运算符!
下面两个定义有很大的差别:
char amessage[] = "now is the time"; /* 定义了一个数组 */ char *pmessage = "now is the time"; /* 定义了一个指针 */
amessage 是一个只能存放初始化字符串以及空字符’\0’的一维数组。数组中单个字符可以进行修改,但 amessage 始终指向同一个存储位置。
pmessage 是一个指针,其初值指向一个字符串常量,后续可以被修改以指向其他地址,但如果试图修改字符串的内容,结果是没有定义的。
通过一个最简单的例子来看字符指针与字符数组的差异:
#include <stdio.h> int main() { char *pa = "hello"; char pb[] = "world"; pa[0] = '1'; /* 非法操作 */ pa[1] = '1'; /* 非法操作 */ pb[0] = '2'; /* 合法操作 */ pb[1] = '2'; /* 合法操作 */ printf("pa = %s\n", pa); printf("pb = %s\n", pb); return 0; }
该程序编译可以通过,但运行时会报“Segmentation fault”。把第 8、 9 行注释掉,程序就运行正常了。
2. 字符串拷贝函数 – strcpy
通过几个不同版本的 strcpy 函数(把指针 t 所指向的字符串复制到指针 s 所指向的存储单元。)来看下字符指针和字符数组的使用方法。
注意:如果使用语句 s = t 来实现,其实只是把 s 指向 t 所指的同一个存储位置而已,并没有进行字符的复制。
第一个版本,通过字符数组的方法实现:
/* strcpy: copy t to s; array subscript version */ void strcpy(char *s, char *t) { int i; i = 0; while((s[i]= t[i]) != '\0') i++; }
为了便于比较,第二个版本通过字符指针的方式实现:
/* strcpy: copy t to s; pointer version */ void strcpy(char *s, char *t) { while((*s = *t) != '\0') { s++; t++; } }
两种方法都可以实现同样的功能,因为参数是通过值传递的,所以在 strcpy 函数中可以以任何方式使用参数 s 和 t。
然而,经验丰富的程序员会用更精炼的语句写出 strcpy 函数:
/* strcpy: copy t to s; pointer version 2 */ void strcpy(char *s, char *t) { while((*s++ = *t++) != '\0') ; }
表达式 *t++ 是执行自增运算之前 t 所指向的字符,在读取该字符之后,自增运算符才会使 t 指向下一个字符。
表达式*s++ 也是同样的过程,在执行自增运算之前,t 所指向的字符被拷贝到 s 所指向的存储单元,然后 s 才指向想一个存储单元。另外,当前拷贝到 s 所指向单元的字符会进行一个判断,是否为字符串结束符’\0’。
我们应该注意到,上例的字符串结束符’\0’判断是多余的,因为我们还只需判断表达式 (*s++ = *t++) 是否为 0即可,所以,更精炼的程序是:
/* strcpy: copy t to s; pointer version 3 */ void strcpy(char *s, char *t) { while(*s++ = *t++) ; }
备注:标准库 <string.h> 中提供的 strcpy 函数把目标字符串作为函数值返回。
3. 字符串比较函数 – strcmp
字符串比较函数 strcmp(s, t) 比较字符串 s 和 t,根据 s 按照字典顺序小于、等于或大于 t 的结果分别返回负值、0、正值。返回值取 s 和 t 由前向后逐字符比较时遇到的第一个不相等字符处的字符差值。
第一个版本,通过字符数组实现:
/* strcmp: retrun <0 if s<t, 0 if s==t, >0 if s>t*/ int strcmp(char *s, char *t) { int i; for(i = 0; s[i]== t[]; i++) if(s[i]== '\0') return 0; return s[i]- t[i]; }
下面是指针方式实现的:
/* strcmp: retrun <0 if s<t, 0 if s==t, >0 if s>t*/ int strcmp(char *s, char *t) { for( ; *s == *t; s++, t++) if(*s == '\0') return 0; return *s - *t; }
++ 和 — 既可作为前缀运算符,也可作为后缀运算符,所以可以和 * 按照其他方式组合使用。比如表达式
*--p
是在读取指针 p 所指的字符之前,先对 p 执行自减运算,即读取的是 p 前一个存储位置的字符。
事实上,下面的两个表达式:
*p++ = val; /* 将 val 压入栈 */ val = *--p; /* 将栈顶元素弹出到 val 中 */
是进栈和出栈的标准用法。