5.5 指针与字符串之间的关系
在C语言中可以采用两种方式来定义和访问一个字符串,一种是数组,另外一种是指针。其中采用数组的方式定义和访问字符串在第4章中已经介绍过了,接下来了解一下通过指针定义字符串的特点。
include<stdio.h>
void main()
{
char*str="Hello Bigloomy!";
printf("%s\n",str);
return;
}
运行结果:
Hello Bigloomy!
前面在介绍数组与字符串时特别强调过,使用printf()函数时字符串的后面必须有结束符‘\0’,否则就会出错。上面的代码运行时正确打印出字符串说明通过指针定义字符串时会自动在字符串后面添加一个结束符‘\0’,我们可以通过下面的代码来验证。
include<stdio.h>
void main()
{
char*str="Hello Bigloomy";
while(*str!='\0')
printf("%c",*str++);
printf("\n");
return;
}
运行结果:
Hello Bigloomy!
我们可以通过图5-10来分析上面的代码,没有采用%s格式一次性打印出字符串,而是按照单个字符的方式打印出其中的字符串。需要注意的是,通过指针定义字符串时,字符存储在一片连续的内存单元中,一开始,str指针指向的是字符串的首地址,通过*str++来逐一取出字符同时将指针移到下一个位置,通过判断字符后面是否有结束符‘\0’来确定是否结束打印。
接下来通过一段代码了解通过数组初始化的字符串和通过字符指针初始化的字符串有什么区别,先来看通过数组初始化的字符串。
include<stdio.h>
void main()
{
char str[20]="Hello World!";
str[6]='B',str[7]='i',str[8]='g',str[9]='l';
str[10]='o',str[11]='o',str[12]='m',str[13]='y';
printf("%s\n",str);
return;
}
运行结果:
Hello Bigloomy
图 5-10 char型指针对字符串中字符的引用
在上面的代码中,先通过数组来定义字符串,在随后的代码中可以通过数组元素来引用和修改字符串中的值。但是如果使用的是指针定义的字符串,能否进行修改呢?看看下面的代码。
include<stdio.h>
void main()
{
charstr="Hello World!";(str+6)='B';
printf("%s\n",str);
return;
}
这段代码在编译时不会出现任何问题,但是在运行的时候就直接崩溃了,这是为什么呢?这是因为通过指针定义的字符串在内存中具有只读属性,不能在其后的代码中进行任何修改,只可以引用。如果指针指向的字符串不具有只读属性,那么可以对其进行修改。如:
include<stdio.h>
void main()
{
char str[20]="Hello World!";
char*ptr;
ptr=str;
ptr[6]='B',ptr[7]='i',ptr[8]='g',ptr[9]='l';
ptr[10]='o',ptr[11]='o',ptr[12]='m',ptr[13]='y';
printf("%s\n",ptr);
return;
}
运行结果:
Hello Bigloomy
在上面的代码中,由于字符串是采用数组的方式定义的,不具备只读属性,因此可以对其进行修改。定义一个字符指针ptr,然后将字符数组元素的首地址传递给ptr,接下来就可以通过字符指针来对其所指向的内存单元进行相应的修改了,ptr[i]表示的是取出距离ptr地址的偏移量为i的内存单元的值,这与采用*(ptr+i)的效果一样。
之前讲解指针的时候曾指出,不能够直接对未经初始化的指针进行赋值操作,所以赋值之前需要使指针指向一个可用的地址空间,否则指针的指向是不确定的,虽然编译的时候不会出现错误,但是运行程序时就会直接崩溃并退出,如:
include<stdio.h>
void main()
{
int*a;
*a=9;
return;
}
以上程序在编译时不会发生任何错误,但运行时程序直接崩溃退出,解决这个问题的办法是先使指针a指向一个确定的地址,通常的做法是,让a指向某个已经分配了的内存地址或者为a在内存中分配一个存储空间。在采用指针定义字符串时没有显示地为指针分配内存,而编译器在编译的过程中会在内存中为字符串分配一个内存区域,同时将分配的内存区域的首地址传递给指针变量,因此在通过指针定义字符串时不必为其分配内存空间,编译器在编译的过程中会自动完成对字符串内存的分配,这与之前讲解的一般数值指针不同。