一、回顾6.3节字符数组中字符串的概念:
1、字符串的定义和初始化
在C语言中,没有字符串这个数据类型,字符串是以字符数组的形式存在的,并且字符串的末尾会有结束符‘\0’标志字符串的结束。所以定义以及初始化字符串时,可以使用下列方式:
char str[]={"hello world"}; char str[]="hello world";
问题:按照上图方式定义字符数组str,那么str的长度为?
答案是12,注意不要忽略末尾的结束符,验证方法:sizeof()输出长度
2、字符串的输入与输出
字符数组的输入输出有两种方式:一种是使用%c格式符输入或输出一个字符,另一种是使用%s格式符一次输入与输出整个字符串。
问题:回忆并使用两种方式编程输出上图中str字符数组。
答案:答案不唯一,只要输出字符串即可,下面代码只是其中的三种方法,结果会输出三次hello world。
1 #include <stdio.h> 2 3 int main(int argc, char const *argv[]){ 4 int i; 5 char a[]="hello world"; 6 printf("sizeof a=%lu\n",sizeof(a)); 7 8 i=0; 9 while(a[i]!=‘\0‘){ 10 printf("%c",a[i]); 11 i++; 12 } 13 printf("\n"); 14 for(i=0;i<sizeof(a);i++){ 15 printf("%c",a[i]); 16 } 17 18 printf("\na[]=%s\n",a); 19 20 return 0; 21 }
二、字符串和指针
1、字符串的指针形式定义
通过数组和指针的学习,我们知道数组可通过指针进行访问(读写),且指针和数组具有天然的联系,可以以指针的形式去访问或者遍历一个数组,也可以以数组的形式去访问指针所代表的那一大片连续的地址空间。数组和指针有着这种联系,字符串在内存里的表达形式是数组,那么定义或访问字符串时可以使用指针也可以使用数组。因此字符串的定义可以使用下述两种方式:
char *str = "hello"; //指针形式 char str[] = "hello"; //数组形式
上述两种方式均可使用格式符%s和格式符%c输出字符串str的内容。
问题:使用指针形式定义字符串,且使用%s和%c输出字符串str。
答案:答案有多种形式,下面的代码可供参考,编译运行后输出两次hello wolrd。另外思考:在下述代码中若使用%c输出,再使用%s输出,也就是将printf ("%s\n",str); 放在第10行,结果是否不变?如果变化为什么?(这个思考题其实属于指针和数组内容)
1 #include <stdio.h> 2 3 int main(void){ 4 char *str="hello world"; 5 6 printf ("%s\n",str); 7 8 while ((*str)!=‘\0‘) 9 printf ("%c",*str++); 10 11 return 0; 12 }
若将printf ("%s\n",str)放在第10行再次编译时,只能输出一次hello,world。为什么哪?因为在使用格式符%c输出字符串时,str指针执行了++运算,那么当输出字符串后,str指向了最后一个字符”\0“,可通过%p输出str的值验证,具体代码如下:
1 #include <stdio.h> 2 3 int main(void){ 4 char *str="hello world"; 5 6 printf ("%p\n",str); 7 8 while ((*str)!=‘\0‘) 9 printf ("%c",*str++); 10 11 printf ("\n%p\n",str); 12 printf ("%s\n",str); 13 return 0; 14 }
2、数组形式和指针形式定义字符串的区别
既然可以使用指针形式定义字符串,并且和数组形式定义字符串形式一样可使用格式符%s和%c输出字符串,那么两种定义方式是完全等价的吗?并不是,两种形式的不同体现在给字符串赋值时,如下述代码:
1 #include <stdio.h> 2 3 int main(void){ 4 char *s={"hello,world"}; 5 s[0]=‘B‘; 6 7 printf("here!s[0]=%c\n",s[0]); 8 }
上述代码使用指针形式定义字符串,编译运行时会出现错误,但是当将*s改为s[]时,也就是使用数组形式定义字符串,编译运行没有错误,正常输出结果如下:
here!s[0]=B -------------------------------- Process exited after 0.0329 seconds with return value 0 请按任意键继续. . .
所以指针形式定义的字符串是不可修改的,若定义一个可修改的字符串则需要使用数组的形式定义字符串。那么为什么哪?为什么字符指针的形式不可修改,而数组的形式可修改。
3、指针形式定义的字符串为什么不可修改
为什么指针形式定义字符串,字符串不可修改,我们通过下面的这段代码来分析原因:
1 #include <stdio.h> 2 3 int main(void){ 4 int i; 5 char *s = "hello,world"; 6 //s[0]=‘B‘; 7 char *s2 = "hello,world"; 8 printf("&i=%p\n",&i); 9 printf("&s=%p\n",&s); 10 printf("&s2=%p\n",&s2); 11 12 printf("s =%p\n",s); 13 printf("s2=%p\n",s2); 14 printf("s[0]=%c\n",s[0]); 15 }
新建一个整数变量i、字符串s2,给s和s2同样的初值,输出变量i、s和s2的地址:编译可能还是会有警告,先不管警告,运行后发现结果如下:
&i=000000000062FE4C
&s=000000000062FE40
&s2=000000000062FE38
s =0000000000404000
s2=0000000000404000
s[0]=h
--------------------------------
Process exited after 0.01247 seconds with return value 0
请按任意键继续. . .
从结果可以看出,作为变量i、s、s2地址是相邻的,且先定义的变量的地址大,后定义的变量的地址小。而s和s2的结果是一样的,s和&s结果不一致,变量i、s、s2地址是一个很大的数,而s和s2的值比较小,主要是因为“hello,world”在程序的代码段,地址一般比较小且所在区域一般不允许修改,如果程序修改该区域,那么操作系统会有个保护机制,会让你的代码运行错误,说你在做坏事不让你写,如果该操作系统允许你写,那么这个操作系统不够安全。
写好程序编译的时候,编译器会对编译时就已经有值(hello,world)的东西,将该值放在一个只读不能写的位置(也就是代码区),然后让你的指针指向这个值的位置,如果有两个指针的值都是这个值,那么就需要将两个指针都指向这个值的位置,也就因为允许多个指针指向这个位置,所以才不允许修改。实际上,s是一个指针,初始化为指向一个字符串常量,由于这个常量在只读不可写的位置,所以实际上s是const char *s,但是由于历史的原因,编译器接收不带const的写法,或者是省略const的写法,但是试图对这个字符串常量做写入会导致严重的后果。
4、数组形式定义的字符串为什么可修改
数组形式定义的字符串为什么可修改?在上述代码的基础上再添加数组形式定义字符串s3:
1 #include <stdio.h> 2 3 int main(void){ 4 int i; 5 char *s = "hello,world"; 6 char *s2 = "hello,world"; 7 char s3[] = "hello,world"; 8 printf("&i=%p\n",&i); 9 printf("&s=%p\n",&s); 10 printf("&s2=%p\n",&s2); 11 printf("&s3=%p\n",&s3); 12 printf("s3=%p\n",s3); 13 14 printf("s =%p\n",s); 15 printf("s2=%p\n",s2); 16 17 s3[0]=‘B‘; 18 printf("s3[0]=%c\n",s3[0]); 19 }
编译运行结果如下,发现&s3和s3是同一个位置,和i、s、s2都在同一个位置:
&i=000000000062FE4C
&s=000000000062FE40
&s2=000000000062FE38
&s3=000000000062FE20
s3=000000000062FE20
s =0000000000404000
s2=0000000000404000
s3[0]=B
--------------------------------
Process exited after 0.03045 seconds with
return
value 0
请按任意键继续. . .
使用数组形式定义字符串,那么字符串就在当前位置,也就是字符串不会在地址比较小的代码区,而是就在定义当前变量的位置。所以数组形式定义的字符串是可修改的。
综上所述:当程序需要一个字符串的时候,那么我们是将字符串写成数组的形式还是指针的形式呢?如果是作为数组,那么表示这个字符串就在这里,作为本地变量空间自动被释放。而如果作为指针,那么这个字符串不知道在哪里,所以通常我们使用指针定义字符串完成下列事情:
1)如果需要一个只读不写的字符串;
2)如果该字符串需要作为函数参数时,在学指针和数组时我们知道,将数组作为函数参数时,传递到函数内的就是指针;
3)如果需要动态分配空间,那就只能使用指针了。
所以,选择指针和数组的基本原则:如果要构造一个字符串,使用数组形式定义字符串,如果要处理一个字符串,使用指针形式定义字符串。
三、字符指针作为函数参数
字符串存在形式是字符数组,而在上一节说过数组作为函数参数时,传递到函数内部的就是指针,那么练习分别使用字符指针和字符数组的形式传递两个字符串from和to,并且将字符串to的内容复制为字符串from的内容,在赋值的时候可选用指针(*运算符)和数组形式([]运算符)完成。详见课本P259,8.4.2 字符指针作函数参数。