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’)
car=整个字符串内容(car需要%s格式)
*(car+1)=car[1]=字符串的第二个元素的值
3、数组和指针(字符串的指针和数组形式)
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这样的操作。递增运算符只能用于变量名前(或概括地说,只能用于可修改的左值),不能用于常量。
指针形式(*pt1)也使得编译器为字符串在静态存储区预留29个元素的空间。另外,一旦开始执行程序,它会为指针变量pt1留出一个存储位置,并把字符串的地址存储在指针变量中。该变量最初指向该字符串的首字符,但是它的值可以改变。因此,可以使用递增运算符.例如,++pt1将指向第2个字符‘o’。字符串字面量(字符串常量)被视为const数据。由于pt1指向这个const数据,所以应该把pt1声明为指向const数据的指针。这意味着不能用pt1改变它所指向的数据,但是仍然可以改变pt1的值(即,pt1指向的位置)。如果把一个字符串字面量(字符串常量)拷贝给一个数组,就可以随意改变数据(应该是数组的元素),除非把数组声明为const。
总之,初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针只能把字符串的地址拷贝给指针。
以上是书上的内容(括号内的除外),下面我来总结一下:
1、字符串字面量(或者叫字符串常量)无论是以数组形式还是以指针形式声明,都会放在数据段(静态存储区)。
2、区别是,如果以数组形式声明,那么,当程序运行起来以后,编译器会给数组分配内存(数组所占内存应该是在栈上),并将保存在静态存储区的字符串拷贝至数组。因此,以数组形式声明的字符串有2个副本(有正本吗?)。指针形式声明的字符串则没有2个副本。阅读下面代码:仔细体会该程序所验证的第1条,第2条总结内容:
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MSG "I am special" //定义一个宏字符串 /* 本程序演示了数组字符串、指针字符串、宏字符串及字符串本身的地址 */ int main() { int num = 200; 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", "I am special"); //输出字符串地址 printf("address of num = %p \n", num); //对于整数num,输出的是其值的16进制数 printf("address of num = %p \n", &num); //对于整数num,&num格式才能正确输出地址 system("pause"); return 0; } /* 从程序运行结果可以看出:除数组字符串以外,宏字符串地址=指针字符串地址=字符串地址 另:当数组名、指针名、宏名、字符串名配上'%p'时,printf输出的是地址 */
3、由于数组保存在栈上,所以,数组元素(字符串)可以被更改(数组元素被更改以后,存放于静态存储区的副本是否也随之变化那?)。且具有保存在栈上的变量的特征(类似于局部变量?):在其声明的作用域内有效,在作用域以外,该地址所存内容丢失。而指针形式声明的字符串只存放在静态存储区,没有存放在其他地方的副本,所以,指针形式声明的字符串不可以被更改,且相当于全局变量。
4、同时,数组名所代表的地址不可以被更改,也就是数组名不可以做左值。指针名则可以。阅读下面代码,仔细体会该程序所验证的第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; }