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

    int main()
    {
    char car[]="Tata";
    printf("car p格式 = %p, &car[0] = %p 
    ",car,&car[0]);
    printf("*car = %c, car[0] = %c 
    ",*car,car[0]);
    printf("car s格式 = %s 
    ",car);
    printf("(car+1) = %c, car[1] = %c 
    ",*(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个元素的数组(每个元素对应一个字符,还加上一个末尾的空字符''),每个元素被初始化为字符串字面量(顺序)对应的字符。通常,字符串都作为可执行文件的一部分存储在数据段中。当把程序载入内存时,也载入了程序中的字符串。字符串存储在静态存储区(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" //定义一个宏字符串
    
    /* 本程序演示了数组字符串、指针字符串、宏字符串及字符串本身的地址,选自CPremer第六版
       ch11-3,并稍微改动*/
    int main(void)
    {
        char ar[] = MSG;
        const char* pt = MSG;
        printf("address of   "I am special": %p 
    ", "I am special");//输出字符串地址
        printf("                 address ar: %p 
    ", ar);             //输出数组字符串地址
        printf("                 address pt: %p 
    ", pt);             //输出指针字符串地址
        printf("             address of MSG: %p 
    ", MSG);            //数组宏字符串地址
        printf("address of   "I am special": %p 
    
    ", "I am special"); //输出字符串地址
    
        int num = 200;
        printf("address of num = %p 
    ", num);  //对于整数num,输出的是其值的16进制数
        printf("address of num = %p 
    
    ", &num);  //对于整数num,&num格式才能正确输出地址
    
        char msgA[]="I am special,too.";
        printf("address of "I am special,too.": %p 
    ", "I am special,too.");
        printf("                  address msgA: %p 
    ", msgA);
        char *msgP2=msgA;
        printf("                 address msgP2: %p  msgp2=%s 
    ", msgP2,msgP2);  //由于msgP2=msgA,所以msgP2的地址与数组msgA相同。
        char *msgP="I am special,too.";
        printf("                  address msgP: %p 
    ", msgP);
        msgA[0]='i';
        printf("数组msgA=%s 
    ",msgA);   //查看数组msgA的值是否发生改变。
        printf("msgP=%s
    
    ",msgP);  //查看数组的值发生改变后,是否影响到放在数据区的字符串
    
    /* 上面这一小段程序验证了我对书上内容的理解:字符串字面量(或者叫字符串常量)无论是以数组
       形式还是以指针形式声明,都会放在数据段(静态存储区)。区别是,如果以数组形式声明,那么,
       当程序运行起来以后,编译器会给数组分配内存(数组所占内存应该是在栈上),并将保存在静态
       存储区的字符串拷贝至数组。
       且由于针对字符串数组的操作是在栈上它的副本进行的,所以,可以更改字符串数组元素的值。但
       是这种更改不会影响到在数据区存放的字符串正本。同时由于字符串数组存放在栈上,当其所在的
       函数运行完之后,字符串数组所在空间自动释放。因此,返回字符串数组地址将是无效的。但是存
       放在数据区的字符串正本不会被释放,它的地址可以返回到调用它的函数。 */
    
        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 
    ", astr);
    
        /* 无论是以数组形式还是以指针形式声明的字符串,字符串的名字既可以按照
        指针的方式来操作,也可以按照数组的方式来操作。详见下面2行程序语句 */
    
        astr[0] = 'i';    //若字符串以数组形式声明,则可以更改字符串的元素值    
        *(astr + 2) = 'A';//若字符串以数组形式声明,则可以更改字符串的元素值
        printf("给字符串个别元素重新赋值后,astr=%s 
    ", astr);
        printf("将数组的地址+1,以输出第2个元素,++astr=%c 
    ", ++astr);
        //编译上面的语句时,编译器报错并提示:“表达式必须是可修改的左值”,这
        //显然与CPrimer书说法一致:以数组形式声明的字符串,其名字不可以当左值。
        //即:以数组形式声明的字符串名,其表示的地址不可以被更改。
        return astr;
    }
    void test01()
    {
        char* a = NULL;
        a = arrayString();
        printf("arrayString函数退出后,astr=%s 
    
    ", a);
    }
    
    char* pointString()
    {
        char* pstr = "I am a *pstr !";
        //pstr[0] = 'i';       //执行这2条语句时,系统报错,显然:若字符串
        //*pstr='i';           //以指针形式声明,则不可以更改字符串的元素值    
        printf("调用pointString函数时,pstr=%s 
    ", pstr);
        printf("将数组的地址+2,以输出第3个元素,*(++pstr+1)=%c 
    ", *(++pstr+1));
        //看上行语句:若字符串以指针形式声明,则字符串名可以当左值,即:
        //字符串名表示的地址可以被更改。
        --pstr;  //将pstr指向的地址恢复。
        return pstr; 
    }
    void test02()
    {
        char* p = NULL;
        p = pointString();
        printf("pointString函数退出后,pstr=%s, *p=%c, p[0]=%c 
    ", p,*p,p[0]);  
      //当指针指向字符串常量时,如果以'%s'格式printf,则输出字符串常量。
    }     //如果以'%p'格式printf,则输出字符串地址
    int main()
    {
        test01();
        test02();
        system("pause");
        return 0;
    }
  • 相关阅读:
    package.json中 npm依赖包版本前的符号的意义
    移动端1px的border
    react下将输入的汉字转化为拼音
    h5打开App的方法。
    图片在缩放截取后以固定尺寸上传到第三方
    图片裁切,上传,自动匹配颜色。
    ReactNative学习一
    MySQL数据库8(二)MySQL基本介绍
    MySQL数据库8(一)SQL简介

  • 原文地址:https://www.cnblogs.com/GoldCrop/p/10913000.html
Copyright © 2011-2022 走看看