字符串的数组形式和指针形式声明及其区别(摘自《C Primer Plus 中文版第六版》第11章及黑马程序员2018C语言提高深入浅出ch1-5 )

时间:2021-06-12 16:22:59
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;
}