从头学C(47)字符指针与函数

0

第五章 指针与数组 >> 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 中 */

是进栈和出栈的标准用法

Leave A Reply