int main() { char car[]="Tata"; printf("car p格式 = %p, &car[0] = %p \n",car,&car[0]); printf("*car = %c, car[0] = %c \n",*car,car[0]); printf("car s格式 = %s \n",car); printf("(car+1) = %c, car[1] = %c \n",*(car+1),car[1]); return 0; }
观察上面代码,容易得出如下结论:数组
car=&car[0]=字符串首地址(car须要%p或者%u格式,固然应该是%p格式最恰当,%u格式彷佛不是特别严谨)函数
*car=car[0]=字符串首个字符(‘T’)spa
car=整个字符串内容(car须要%s格式)指针
*(car+1)=car[1]=字符串的第二个元素的值code
三、数组和指针(字符串的指针和数组形式)blog
const char *pt1="Something is pointing at me."; const char at1[ ]="Something is pointing at me.";
数组形式和指针形式有何不一样?以上面的2个声明为例:内存
数组形式(ar1[])在计算机的内存中分配为一个内含29个元素的数组(每一个元素对应一个字符,还加上一个末尾的空字符'\0'),每一个元素被初始化为字符串字面量(顺序)对应的字符。一般,字符串都做为可执行文件的一部分存储在数据段中。当把程序载入内存时,也载入了程序中的字符串。字符串存储在静态存储区(static memory)中。可是,程序在开始时才会为该数组分配内存。此时,才将字符串拷贝到数组中。注意:此时字符串有两个副本。一个是在静态内存中的字符串字面量(字符串常量),另外一个是存储在ar1数组中的字符串。此后,编译器便把数组名ar1识别为该数组首元素地址( &ar1[0] )的别名。这里关键要理解,在数组形式中,ar1是地址常量。不能更改ar1(的值,就是ar1不能作左值),若是改变了ar1,则意味着改变了数组的存储位置(即地址)。能够进行ar1+1这样的操做,来识别数组的下一个元素。可是不容许++ar1这样的操做。递增运算符只能用于变量名前(或归纳地说,只能用于可修改的左值),不能用于常量。ci
指针形式(*pt1)也使得编译器为字符串在静态存储区预留29个元素的空间。另外,一旦开始执行程序,它会为指针变量pt1留出一个存储位置,并把字符串的地址存储在指针变量中。该变量最初指向该字符串的首字符,可是它的值能够改变。所以,可使用递增运算符.例如,++pt1将指向第2个字符‘o’。字符串字面量(字符串常量)被视为const数据。因为pt1指向这个const数据,因此应该把pt1声明为指向const数据的指针。这意味着不能用pt1改变它所指向的数据,可是仍然能够改变pt1的值(即,pt1指向的位置)。若是把一个字符串字面量(字符串常量)拷贝给一个数组,就能够随意改变数据(应该是数组的元素),除非把数组声明为const。rem
总之,初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针只能把字符串的地址拷贝给指针。字符串
以上是书上的内容(括号内的除外),下面我来总结一下:
一、字符串字面量(或者叫字符串常量)不管是以数组形式仍是以指针形式声明,都会放在数据段(静态存储区)。
二、区别是,若是以数组形式声明,那么,当程序运行起来之后,编译器会给数组分配内存(数组所占内存应该是在栈上),并将保存在静态存储区的字符串拷贝至数组。所以,以数组形式声明的字符串有2个副本(有正本吗?)。指针形式声明的字符串则没有2个副本。阅读下面代码:仔细体会该程序所验证的第1条,第2条总结内容:
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MSG "I am special" //定义一个宏字符串 /* 本程序演示了数组字符串、指针字符串、宏字符串及字符串自己的地址,选自CPremer第六版 ch11-3,并稍微改动*/ int main(void) { char ar[] = MSG; const char* pt = MSG; printf("address of \"I am special\": %p \n", "I am special");//输出字符串地址 printf(" address ar: %p \n", ar); //输出数组字符串地址 printf(" address pt: %p \n", pt); //输出指针字符串地址 printf(" address of MSG: %p \n", MSG); //数组宏字符串地址 printf("address of \"I am special\": %p \n\n", "I am special"); //输出字符串地址 int num = 200; printf("address of num = %p \n", num); //对于整数num,输出的是其值的16进制数 printf("address of num = %p \n\n", &num); //对于整数num,&num格式才能正确输出地址 char msgA[]="I am special,too."; printf("address of \"I am special,too.\": %p \n", "I am special,too."); printf(" address msgA: %p \n", msgA); char *msgP2=msgA; printf(" address msgP2: %p msgp2=%s \n", msgP2,msgP2); //因为msgP2=msgA,因此msgP2的地址与数组msgA相同。 char *msgP="I am special,too."; printf(" address msgP: %p \n", msgP); msgA[0]='i'; printf("数组msgA=%s \n",msgA); //查看数组msgA的值是否发生改变。 printf("msgP=%s\n\n",msgP); //查看数组的值发生改变后,是否影响到放在数据区的字符串 /* 上面这一小段程序验证了我对书上内容的理解:字符串字面量(或者叫字符串常量)不管是以数组 形式仍是以指针形式声明,都会放在数据段(静态存储区)。区别是,若是以数组形式声明,那么, 当程序运行起来之后,编译器会给数组分配内存(数组所占内存应该是在栈上),并将保存在静态 存储区的字符串拷贝至数组。 且因为针对字符串数组的操做是在栈上它的副本进行的,因此,能够更改字符串数组元素的值。但 是这种更改不会影响到在数据区存放的字符串正本。同时因为字符串数组存放在栈上,当其所在的 函数运行完以后,字符串数组所在空间自动释放。所以,返回字符串数组地址将是无效的。可是存 放在数据区的字符串正本不会被释放,它的地址能够返回到调用它的函数。 */ system("pause"); return 0; } /* 从程序运行结果能够看出:除数组字符串之外,宏字符串地址=指针字符串地址=字符串地址 另:当数组名、指针名、宏名、字符串名配上'%p'时,printf输出的是地址 */
三、因为数组保存在栈上,因此,数组元素(字符串)能够被更改(数组元素被更改之后,存放于静态存储区的副本是否也随之变化那?不会变,上面的程序已经验证了不会变。)。且具备保存在栈上的变量的特征:当其所在的函数运行完以后,字符串数组所在空间自动释放。所以,返回字符串数组地址将是无效的。可是存 放在数据区的字符串正本不会被释放,它的地址能够返回到调用它的函数。而由于指针形式声明的字符串只存放在静态存储区,没有存放在其余地方的副本,因此,指针形式声明的字符串不能够被更改,它的地址也能够返回到调用它的函数。
四、同时,数组名所表明的地址不能够被更改,也就是数组名不能够作左值。指针名则能够。阅读下面代码,仔细体会该程序所验证的第3条,第4条总结内容:
#include <stdio.h> #include <stdlib.h> #include <string.h> char* arrayString() { char astr[] = "I am an astr !"; printf("调用arrayString函数时,astr=%s \n", astr); /* 不管是以数组形式仍是以指针形式声明的字符串,字符串的名字既能够按照 指针的方式来操做,也能够按照数组的方式来操做。详见下面2行程序语句 */ astr[0] = 'i'; //若字符串以数组形式声明,则能够更改字符串的元素值 *(astr + 2) = 'A';//若字符串以数组形式声明,则能够更改字符串的元素值 printf("给字符串个别元素从新赋值后,astr=%s \n", astr); printf("将数组的地址+1,以输出第2个元素,++astr=%c \n", ++astr); //编译上面的语句时,编译器报错并提示:“表达式必须是可修改的左值”,这 //显然与CPrimer书说法一致:以数组形式声明的字符串,其名字不能够当左值。 //即:以数组形式声明的字符串名,其表示的地址不能够被更改。 return astr; } void test01() { char* a = NULL; a = arrayString(); printf("arrayString函数退出后,astr=%s \n\n", a); } char* pointString() { char* pstr = "I am a *pstr !"; //pstr[0] = 'i'; //执行这2条语句时,系统报错,显然:若字符串 //*pstr='i'; //以指针形式声明,则不能够更改字符串的元素值 printf("调用pointString函数时,pstr=%s \n", pstr); printf("将数组的地址+2,以输出第3个元素,*(++pstr+1)=%c \n", *(++pstr+1)); //看上行语句:若字符串以指针形式声明,则字符串名能够当左值,即: //字符串名表示的地址能够被更改。 --pstr; //将pstr指向的地址恢复。 return pstr; } void test02() { char* p = NULL; p = pointString(); printf("pointString函数退出后,pstr=%s, *p=%c, p[0]=%c \n", p,*p,p[0]); //当指针指向字符串常量时,若是以'%s'格式printf,则输出字符串常量。 } //若是以'%p'格式printf,则输出字符串地址 int main() { test01(); test02(); system("pause"); return 0; }