zoukankan      html  css  js  c++  java
  • const

    根据ANSI C标准的赋值约束条件:

        1. 两个操作数都是指向有限定符或无限定符的相容类型的指针。

        2. 左边指针所指向的类型必须具有右边指针所指向类型的全部限定符。

    一、const char*和char*

        const char*的类型是:“指向一个具有const限定符的char类型的指针”。(不能修改其值)

        char*的类型是:“指向一个char类型的指针”。

        因此const char*和char*都是指向char类型的指针,只不过const char*指向的char类型是const的。

        因此对于代码:

    char* src; 
    const char* dest ; 
    dest = src;

        这样赋值是正确的,因为:

        * 操作数指向的都是char类型,因此是相容的

        * 左操作数具有有操作数所指向类型的全部限定符(右操作数没有限定符),同时自己有限定符(const)

        如果反过来赋值就违反了赋值的约束条件:src指向的对象的值可以修改,而dest指向的对象的值不可修改
    如果让src去指向dest所指向的那个不可修改的对象,如果合法,岂不是变得可修改了?

    src = dest; // 这样赋值,左操作数指向的类型没有右操作数指向类型的const限定符,不符合约束条件2


    2. const char** 和char**

        const char**的类型是:“指向一个有const限定符的char类型的指针的指针”。

        char**的类型是:“指向一个char类型的指针的指针”。

        对于const char** 和char**来说,二者都是没有限定符的指针类型,但是它们指向的类型不一样,前者指向char*,    而后者指向const char*,因此它们不相容,所以char**类型的操作数不能赋值给const char**类型的操作数。

        即对于下列代码,编译器会报错:char** src; 
    const char** dest; 
    dest = src; 
    // error: invalid conversion from `char**' to `const char**' 
    -------------------------------------------------------------------------------------------------------------

    const
    1. 限定符声明变量只能被读
       const int i=5;
       int j=0;
       ...
       i=j;   //非法,导致编译错误
       j=i;   //合法
    2. 必须初始化
       const int i=5;    //合法
       const int j;      //非法,导致编译错误
    3. 在另一连接文件中引用const常量
       extern const int i;     //合法
       extern const int j=10; //非法,常量不可以被再次赋值
    4. 便于进行类型检查
       用const方法可以使编译器对处理内容有更多了解。
       #define I=10
       const long &i=10;   /*dapingguo提醒:由于编译器的优化,使得在const long i=10; 时i不被分配内存,而是已10直接代入 以后的引用中,以致在以后的代码中没有错误,为达到说教效 果,特别地用&i明确地给出了i的内存分配。不过一旦你关闭所 有优化措施,即使const long i=10;也会引起后面的编译错误。*/
       char h=I;      //没有错
       char h=i;      //编译警告,可能由于数的截短带来错误赋值。
    5. 可以避免不必要的内存分配
       #define STRING "abcdefghijklmn "
       const char string[]="abcdefghijklm ";
       ...
       printf(STRING);   //为STRING分配了第一次内存
       printf(string);   //为string一次分配了内存,以后不再分配
       ...
       printf(STRING);   //为STRING分配了第二次内存
       printf(string);
       ... 
       由于const定义常量从汇编的角度来看,只是给出了对应的内存地址,
       而不是象#define一样给出的是立即数,所以,const定义的常量在
       程序运行过程中只有一份拷贝,而#define定义的常量在内存中有
       若干个拷贝。
    6. 可以通过函数对常量进行初始化
       int value(); 
       const int i=value();
       dapingguo说:假定对ROM编写程序时,由于目标代码的不可改写,本语句将会无效,不过可以变通一下:
       const int &i=value();
       只要令i的地址处于ROM之外,即可实现:i通过函数初始化,而其值有不会被修改。
    7. 是不是const的常量值一定不可以被修改呢?
       观察以下一段代码:
       const int i=0;
       int *p=(int*)&i;
       p=100;
       通过强制类型转换,将地址赋给变量,再作修改即可以改变const常量值。
    8. 请分清数值常量和指针常量,以下声明颇为玩味:
       int ii=0;
       const int i=0;            //i是常量,i的值不会被修改
       const int *p1i=&i;        //指针p1i所指内容是常量,可以不初始化
       int * const p2i=ⅈ     //指针p2i是常量,所指内容可修改
       const int * const p3i=&i; //指针p3i是常量,所指内容也是常量
       p1i=ⅈ                  //合法
       *p2i=100;                 //合法

    关于C++中的const关键字的用法非常灵活,而使用const将大大改善程序的健壮性,参考了康建东兄的const使用详解一文,对其中进行了一些补充,写下了本文。
    1.       const常量,如const int max = 100; 
    优点:const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误(边际效应)

    2.       const 修饰类的数据成员。如:
    class A
    {   

    const int size;

        … }


    const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类声明中初始化const数据成员,因为类的对象未被创建时,编译器不知道const 数据成员的值是什么。如
    class A
    {
    const int size = 100;    //错误
    int array[size];         //错误,未知的size
    }

    const数据成员的初始化只能在类的构造函数的初始化表中进行。要想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现。如
    class A
    {…
    enum {size1=100, size2 = 200 };
    int array1[size1];
    int array2[size2]; 
    }

    枚举常量不会占用对象的存储空间,他们在编译时被全部求值。但是枚举常量的隐含数据类型是整数,其最大值有限,且不能表示浮点数。

    3.       const修饰指针的情况,见下式:
    int b = 500; 
    const int* a = &           [1] 
    int const *a = &           [2] 
    int* const a = &           [3] 
    const int* const a = &     [4]

    如果你能区分出上述四种情况,那么,恭喜你,你已经迈出了可喜的一步。不知道,也没关系,我们可以参考《Effective c++》Item21上的做法,如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。因此,[1]和[2]的情况相同,都是指针所指向的内容为常量(const放在变量声明符的位置无关),这种情况下不允许对内容进行更改操作,如不能*a = 3 ;[3]为指针本身是常量,而指针所指向的内容不是常量,这种情况下不能对指针本身进行更改操作,如a++是错误的;[4]为指针本身和指向的内容均为常量。

    4.     const的初始化

    先看一下const变量初始化的情况 
    1) 非指针const常量初始化的情况:A b; 
    const A a = b;

    2) 指针const常量初始化的情况:
    A* d = new A(); 
    const A* c = d; 
    或者:const A* c = new A(); 
    3)引用const常量初始化的情况: 
    A f; 
    const A& e = f;      // 这样作e只能访问声明为const的函数,而不能访问一           


    般的成员函数;

        [思考1]: 以下的这种赋值方法正确吗? 
        const A* c=new A(); 
        A* e = c; 
        [思考2]: 以下的这种赋值方法正确吗? 
        A* const c = new A(); 
        A* b = c;


    5.     另外const 的一些强大的功能在于它在函数声明中的应用。在一个函数声明中,const 可以修饰函数的返回值,或某个参数;对于成员函数,还可以修饰是整个函数。有如下几种情况,以下会逐渐的说明用法:A& operator=(const A& a); 
    void fun0(const A* a ); 
    void fun1( ) const; // fun1( ) 为类成员函数 
    const A fun2( );


    1) 修饰参数的const,如 void fun0(const A* a ); void fun1(const A& a); 
    调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,如形参为const A* a,则不能对传递进来的指针的内容进行改变,保护了原指针所指向的内容;如形参为const A& a,则不能对传递进来的引用对象进行改变,保护了原对象的属性。 
    [注意]:参数const通常用于参数为指针或引用的情况,且只能修饰输入参数;若输入参数采用“值传递”方式,由于函数将自动产生临时变量用于复制该参数,该参数本就不需要保护,所以不用const修饰。


    [总结]对于非内部数据类型的输入参数,因该将“值传递”的方式改为“const引用传递”,目的是为了提高效率。例如,将void Func(A a)改为void Func(const A &a)
          对于内部数据类型的输入参数,不要将“值传递”的方式改为“const引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x)不应该改为void Func(const int &x)


    2) 修饰返回值的const,如const A fun2( ); const A* fun3( ); 
    这样声明了返回值后,const按照"修饰原则"进行修饰,起到相应的保护作用。const Rational operator*(const Rational& lhs, const Rational& rhs) 

    return Rational(lhs.numerator() * rhs.numerator(), 
    lhs.denominator() * rhs.denominator()); 
    }

    返回值用const修饰可以防止允许这样的操作发生:Rational a,b; 
    Radional c; 
    (a*b) = c;

    一般用const修饰返回值为对象本身(非引用和指针)的情况多用于二目操作符重载函数并产生新对象的时候。 [总结]


    1.     一般情况下,函数的返回值为某个对象时,如果将其声明为const时,多用于操作符的重载。通常,不建议用const修饰函数的返回值类型为某个对象或对某个对象引用的情况。原因如下:如果返回值为某个对象为const(const A test = A 实例)或某个对象的引用为const(const A& test = A实例) ,则返回值具有const属性,则返回实例只能访问类A中的公有(保护)数据成员和const成员函数,并且不允许对其进行赋值操作,这在一般情况下很少用到。


    2.       如果给采用“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。如:


    const char * GetString(void);


    如下语句将出现编译错误:


    char *str=GetString();


    正确的用法是:


    const char *str=GetString();


    3.     函数返回值采用“引用传递”的场合不多,这种方式一般只出现在类的赙值函数中,目的是为了实现链式表达。如:


    class A
    {…
    A &operate = (const A &other); //负值函数
    }
    A a,b,c;              //a,b,c为A的对象

    a=b=c;            //正常
    (a=b)=c;          //不正常,但是合法


    若负值函数的返回值加const修饰,那么该返回值的内容不允许修改,上例中a=b=c依然正确。(a=b)=c就不正确了。
    [思考3]: 这样定义赋值操作符重载函数可以吗? 
    const A& operator=(const A& a);


    6.     类成员函数中const的使用 
    一般放在函数体后,形如:void fun() const; 
    任何不会修改数据成员的函数都因该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其他非const成员函数,编译器将报错,这大大提高了程序的健壮性。如:


    class Stack
    {
    public:
          void Push(int elem);
          int Pop(void);

          int GetCount(void) const;   //const 成员函数
    private: 
          int m_num;
          int m_data[100];
    };


    int Stack::GetCount(void) const

    {
    ++m_num;              //编译错误,企图修改数据成员m_num
    Pop();                    //编译错误,企图调用非const函数
    Return m_num;
    }


    7.       使用const的一些建议

    1 要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委; 
    2 要避免最一般的赋值操作错误,如将const变量赋值,具体可见思考题; 
    3 在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上; 
    4 const在成员函数中的三种用法(参数、返回值、函数)要很好的使用; 
    5 不要轻易的将函数的返回值类型定为const; 
    6除了重载操作符外一般不要将返回值类型定为对某个对象的const引用;

    [思考题答案] 
    1 这种方法不正确,因为声明指针的目的是为了对其指向的内容进行改变,而声明的指针e指向的是一个常量,所以不正确; 
    2 这种方法正确,因为声明指针所指向的内容可变; 
    3 这种做法不正确; 
    在const A::operator=(const A& a)中,参数列表中的const的用法正确,而当这样连续赋值的时侯,问题就出现了: 
    A a,b,c: 
    (a=b)=c; 
    因为a.operator=(b)的返回值是对a的const引用,不能再将c赋值给const常量。

    C函数形参里面为何要用:const char *inputString
    保护实参指针指向的数据不被意外改写。 
    const char *inputString;//定义指向常量的指针 
    指针本身地址值可以增减,也可以给指针更换指向的常量,但是指向的内容有常量性质,指向的内容不能被更改。即: 
    inputString++;//这是可以的。 
    (*inputString)++;//这是禁止的。

    强制类型转换的实质是告诉编译器,"这可行, 这行的通... 至于程序会不会出错, 编译器则不管, 因为你都说可行了."

    两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须具有右边指针所指向类型的全部限定符。

    正是这个条件,使得函数调用中实参char *能够与形参const char *匹配(在C标准库中,所有的字符串处理函数就是这样的)。

    函数那部分的时候,有一个很重要的概念是区别:值传递、指针传递、引用传值(好像是这三种说法)。

    我觉得要理解这部分知识点,首先应该知道不同种类的变量在内存中是如何分配存储的,它们的生命周期多长等这些问题,然后在理解哪三种情况就好理解了。函数的参数一般都

    是在stack栈上分配的,所以它的生命周期就在它所属的函数内,函数执行完毕的时候,它的内存将被回收。

    如果我们想在函数内对实际参数进行操作(不是对形式参数的副本)的话,一般会使用引用,即声明函数的形式参数为引用类型,比如char * fun(char * &p),这样实参和形参为同一个变量,我们在函数中操作形参p就等于直接在操作实参变量。我看C++语法书的时候,书上说这样用还有一个好处是,在调用函数的时候,不用再为形式参数分配内存了,这样执行效率会高一点儿。

    下面是函数形参为指针的几种情况:

    #include <iostream>
    using namespace std;

    char* func1(char *p);
    void func2(char *p);
    void func3(char * &p);

    char s1[]="原来的";
    char s2[]="指向我了吗";

    int main()
    {
        char *ptr=s1;
        cout<<ptr<<endl;
        ptr=func1(ptr);      //返回值改变ptr使它指向另一个地址
    //func2(ptr);   //ptr的指向没有改变,func2函数中改变的只是它的副本(一个局部变量)
    //func3(ptr);   //改变了ptr的指向,func3函数的形式参数为引用类型,实参和形参是同一个变量
        cout<<ptr<<endl;
        return 0;
    }

    char* func1(char *p)
    {
        p=s2;
        return p;
    }
    void func2(char *p)
    {
        p=s2;
    }
    void func3(char * &p)
    {
        p=s2;
    }

    深入理解const char*p,char const*p,char *const p,const char **p,char const**p,char *const*p,char**const p

    一、可能的组合:

    (1)const char*p

    (2)char const*p

    (3)char *const p
    (4)const char **p

    (5)char const**p

    (6)char *const *p

    (7)char **const p

    当然还有在(5)、(6)、(7)中再插入一个const的若干情况,不过分析了以上7中,其他的就可类推了!

    二、理解助记法宝:

    1。关键看const 修饰谁。

    2。由于没有 const *的运算,若出现 const * 的形式,则const实际上是修饰前面的。

    比如:char const*p,由于没有const*运算,则const实际上是修饰前面的char,因此char const*p等价于const char*p。也就是说上面7种情况中,(1)和(2)等价。 同理,(4)和(5)等价。在(6)中,由于没有const*运算,const实际上修饰的是前面的char*,但不能在定义时转换写成 const(char *)*p,因为在定义是"()"是表示函数。

    三、深入理解7种组合

    (0)程序 在执行时为其开辟的空间皆在内存(RAM)中,而RAM里的内存单元是可读可写 的;指针只是用来指定或定位要操作的数据的工具,只是用来读写RAM里内存单元的工作指针 。若对指针不加任何限定,程序中一个指针可以指向RAM中的任意位置(除了系统敏感区,如操作系统内核所在区域)并对其指向的内存单元进行读和写操作(由RAM的可读可写属性决定);RAM里内存单元的可读可写属性不会因为对工作指针的限定而变化(见下面的第4点),而所有对指针的各种const限定说白了只是对该指针 的 读写权限 (包括读写位置)进行了限定 。


    (1)char *p:p是一个工作指针,可以用来对任意位置 (非系统敏感区域)进 行读操作和写操作 ,一次读写一个字节(char占一个字节)。
    (2)const char*p或者char const *p(因为没有const*p运算,因此const修饰的还是前面的char):可以对任意位置(非系统敏感区域)进行“只读” 操作。(“只读”是相对于char *p来说所限定的内容)
    (3)char *const p(const 修饰的是p):只能对“某个固定的位置” 进 行读写操作,并且在定义p时就必须初始化(因为在后面不能执行“p=..”的操作,因此就不能在后面初始化,因此只能在定义时初始化)。(“某个固定的位 置”是相对于char *p来说所限定的内容)
    可以总结以上3点为:char *p中的指针p通常是”万能”的工作指针 ,而(2)和(3)只是在(1)的基础上加了些特定的限制 ,这些限制在程序中并不是必须的,只是为了防止程序员的粗心大意而产生事与愿违的错 误。
    另外,要明白“每块内存空间都可有名字;每块内存空间内容皆可变(除非有所限) ” 。比如函数里定义的char s[]="hello";事实上在进程的栈内存里开辟了6个变量共6个字节的空间,其中6个字符变量的名字分别为:s1[0]、s1[1]、 s1[2]、s1[3]、s1[4]、s1[5](内容是'')

    {

    待验证 : 还有一个4字节的指针变量s 。不过s是“有所限制”的,属于char *const类型,也就是前面说的 (3)这种情况,s一直指向s[0], 即(*s==s[0]=='h'),可以通过*s='k'来改变s所指向的 s[0]的值,但不能执行(char *h=“aaa”;s=h;)来对s另外赋值。

    }

    (4)上面的(2)和(3)只是对p进行限定,没有也不能对p所指向的空间进行限定,对于"char s[]="hello";const char*p=s;" 虽然不能通过*(p+i)='x'或者p[i]='x'来修改数组元素s[0]~s[4]的值,但可以通过*(s+i)='x'或者s[i]='x'来修改原数组元素的值--RAM里内存单元的可读可写属性不因对工作指针的限定而改变,而只会因对其本身的限定而改变。如const char c=‘A’,c是RAM里一个内存单元(8字节)的名字,该内存单元的内容只可读,不可写。

    (5)const char **p或者char const**p :涉及两个指针p和 *p。由于const修饰char ,对指针p没有任何限定,对指针*p进行了上面情况(2)的限定。

    (6)char *const *p:涉及两个指针p和 *p。由于const修饰前面的char*,也就是对p所指向的内容*p进行了限定(也属于前面的情况(2))。而对*p来说,由于不能通过"*p=..."来进行另外赋值,因此属于前面的情况(3)的限定。

    (7)char **const p : 涉及两个指针p和 *p,const修饰p,对p进行上面情况(3)的限定,而对*p,没有任何限定。

    四、关于char **p 、const char **p的类型相容性问题

    1。问题

    char *p1;const *p2=p1;//合法

    char **p1;const char**p2=p1;//不合法,会有警告warning: initialization from incompatible pointer type

    char **p1;char const**p2=p1;//不合法,会有警告warning: initialization from incompatible pointer type

    char**p1;char*const*p2=p1;//合法

    2。判断规则

    明确const修饰的对象!对于指针p1,和p2,若要使得p2=p1成立,则可读做 :

    “p1是指向X类型的指针,p2是指向“带有const限定”的X类型的指针 “。只要二者的X类型一样,就是合法的

    char *p1;const *p2=p1;//合法:p1是指向(char)类型的指针,p2是指向“带有const限定"的(char)类型的指针。

    char **p1;const char**p2=p1;//不合法:p1是指向(char*)类型的指针,p2是指向 ((const char)*)类型的指针。

    char **p1;char const**p2=p1;//不合法;与上等价。

    char**p1;char*const*p2=p1;//合法: p1是指向(char *)类型的指针,p2是指向“带有const限定"的(char*)类型的指针。

    五、其他

    1。 含有const的单层或双层指针的统一读法:

    “p是一个指针,是一个[“带有const限定”的]指向[”带有const限定”的]X类型的指针”。

    l例如:const char* const *p就是说:p是一个带有const限定的指向带有const限定的(char*)类型的指针。

    2。定义时const修饰的对象是确定的,但不能在定义时加括号,不然就和定义时用“()”表示的函数类型相混淆了!因此定义时不能写(char *)const *p或者(const char) **p。

    六、问题探讨(由于博文后的留言有字符数目限制,将回复移到这里)

    问题1 (见博文后留言):讲解非常好,不过有个问题想探讨下: 例如: const char wang[]={"wang"}; char *p; p=wang; 是错误的。 所以char *p中的P不能指向常变量。 (1)需要补充纠正。

    回复 : 你好!谢谢指正!我在ubuntu 10.04(gcc 4.4.3)下做了如下测试: 
    //test_const.c
    #include<stdio.h>
    int main()
    {
    const char wang[]={"wang"};
    char *p;
    p=wang;
    p[2]='c';
    printf("p is %s ",p);
    return 0;
    }
    编译 :

    gcc -o test_const test_const.c
    输出如下 :
    test_const.c: In function ‘main’:
    test_const.c:17: warning: assignment discards qualifiers from pointer target type
    执行:

    ./test_const
    p is wacg
    结论: 根据本博文中第四点--相容性问题,将const型的wang赋值给p是不合法的。但编译器对其的处理只是警告,因此执行时通过p修改了只读区域的数据。这应该是该编译器处理不严所致后果,不知你用的什么编译器?

    问题2 回答 http://www.linuxsir.org/bbs/showthread.php?t=239058 提到的问题

    在c语言里

    // test.c
    int main() {
    const char* s1 = "test";
    char *s2 = s1;
    s2 = "It's modified!";
    printf("%s ",s1);
    }
    out: It's modified!;
    这样也可以吗? 照我的理解岂不是const限定符在c语言里只是摆设一个?

    回复:

    (1)首先,以上代码编译时会出错warning: initialization discards qualifiers from pointer target type,

    因为char *s2 = s1和问题1提到的一样,不符合相容规则。


    (2)输出结果是正确的,代码分析如下:

    int main() {
    const char* s1 = "test"; // 在只读数据区(objdump -h test后的.rodata区)开辟5字节存储"test",并用s1指向首字符‘t’。 
    char *s2 = s1; // s2也指向只读数据区中“test”的首字符't'。 
    s2 = "It's modified!"; // 在只读数据区开辟15字节存储"It's modified!",并将s2由指向't'转而指向"It's modified!"的首字符'I'。 
    printf("%s ",s1); // 从s1所指的‘t’开始输出字符串"test"。 
    }


    (3)总结:提问者的误区在于,误以为s2 = "It's modified!"是对“test”所在区域的重新赋值,其实这里只是将“万能”工作指针s2指向另外一个新开辟的区域而已。比如若在char *s2 = s1后再执行s2[2]='a'则是对“test”的区域进行了写操作,执行时会出现段错误。但这个段错误其实与const没有关系,因为“test”这块区域本身就是只读的。为了防止理解出错,凡事对于对指针的赋值(比如 s2 = "It's modified!" ),则将其读做:将s2指向“ It's modified! ”所在区域的首字符。


    (4)额外收获:执行gcc -o test test.c后,“test”、“It's modified!”、"%s "都被作为字符串常量存储在二进制文件test的只读区

    域 (.rodata)。事实上,一个程序从编译到运行,对变量空间分配的情况如下:

    A。赋值了的全局变量或static变量=>放在可执行文件的.data段。

    B。未赋值的全局变量或static变量=>放在可执行文件的.bss段。

    C。代码中出现的字符串常量或加了const的A=>放在可执行文件的.rodata段。

    D。一般的局部变量=>在可执行文件中不占空间,在该二进制文件作为进程在内存中运行时才为它分配栈空间。

    E。代码中malloc或new出的变量=>在可执行文件中不占空间,在该二进制文件作为进程在内存中运行时才为它分配堆空间。

    问题3:(待进一步分析) 验证博文中 三(3)提到的是否为s分配空间,初步分析结果为:不分配!文中的s只是s[0]的地址的代号而已。

    #include<stdio.h>
    int main() {
    int a=3;
    char s1[] = "test";
    int b=4;
    char s2[] ="test2";
    printf("the address of a is %u ",&a);
    printf("s1 is %u ",s1);
    printf("the address of s1 is %u ",&s1);
    printf("the address of b is %u ",&b);
    printf("s2 is %u ",s2);
    printf("the address of s2 is %u ",&s2);
    }

    输出结果:


    the address of a is 3213037836
    s1 is 3213037827
    the address of s1 is 3213037827
    the address of b is 3213037832
    s2 is 3213037821
    the address of s2 is 3213037821


    由结果可以看出,编译器做了些优化。

    七、其他相关经典文章转载


    王海宁,华清远见嵌入式学院讲师,对const关键字的理解

    http://www.embedu.org/Column/Column311.htm


    目前在进行C语言补习时,发现很多的同学对于const这个关键字的理解存在很大的误解。现在总结下对这个关键字理解上的误区,希望在以后的编程中,能够灵活使用const这个关键字。

    1、 const修饰的变量是常量还是变量

    对于这个问题,很多同学认为const修饰的变量是不能改变,结果就误认为该变量变成了常量。那么对于const修饰的变量该如何理解那?

    下面我们来看一个例子:

    int main
    {
    char buf[4];
    const int a = 0;

    a = 10;
    }

    这个比较容易理解,编译器直接报错,原因在于“a = 10;”这句话,对const修饰的变量,后面进行赋值操作。这好像说明了const修饰的变量是不能被修改的,那究竟是不是那,那么下面我们把这个例子修改下:

    int main
    {
    char buf[4];
    const int a = 0;

    buf[4] = 97;
    printf(“the a is %d ”,a);
    }

    其中最后一句printf的目的是看下变量a的值是否改变,根据const的理解,如果const修饰的是变量是不能被修改的话,那么a的值一定不会改变,肯定还是0。但是在实际运行的结果中,我们发现a的值已经变为97了。这说明const修饰的变量a,已经被我们程序修改了。

    那综合这两个例子,我们来分析下,对于第二例子,修改的原因是buf[4]的赋值操作,我们知道buf[4]这个变量已经造成了buf这个数组变量的越界访问。buf数组的成员本身只有0,1,2,3,那么buf[4]访问的是谁那,根据局部变量的地址分配,可以知道buf[4]的地址和int a的地址是一样,那么buf[4]实际上就是访问了const int a;那么对buf[4]的修改,自然也修改了const int a的空间,这也是为什么我们在最后打印a的值的时候看到了97这个结果。

    那么我们现在可以知道了,const修饰的变量是不具备不允许修改的特性的,那么对于第一个例子的现象我们又如何解释那。

    第一个例子,错误是在程序编译的时候给出的,注意这里,这个时候并没有生成可执行文件,说明const修饰的变量可否修改是由编译器来帮我们保护了。而第二个例子里,变量的修改是在可执行程序执行的时候修改的,说明a还是一个变量。

    综上所述,我们可以得出一个结论,那就是const修饰的变量,其实质是告诉程序员或编译器该变量为只读,如果程序员在程序中显示的修改一个只读变量,编译器会毫不留情的给出一个error。而对于由于像数组溢出,隐式修改等程序不规范书写造成的运行过程中的修改,编译器是无能为力的,也说明const修饰的变量仍然是具备变量属性的。

    2、 被const修饰的变量,会被操作系统保护,防止修改

    如果对于第一个问题,有了理解的话,那么这个问题,就非常容易知道答案了。Const修饰的变量是不会被操作系统保护的。

    其原因是操作系统只保护常量,而不会保护变量的读写。那么什么是常量?比如“hello world”这个字符串就是被称为字符串常量。

    对于这个问题的另一种证明方法,可以看下面这个程序:

    int main
    {
    const int a;
    char *buf = “hello world”;

    printf(“the &a is %p, the buf is %p ”,&a, buf);
    }

    可以发现buf保存的地址是在0x08048000这个地址附近的,而a的地址是在0xbf000000这个地址附近的,而0x08048000附近的地址在我们linux操作系统上是代码段。这也说明了常量和变量是存放在不同区域的,自然操作系统是会保护常量的。

    如果我们知道这个道理后,再看下面的题目:

    int main
    {
    char *buf = “hello”;

    buf[0] = ‘a’;
    printf(“the buf is %s ”,buf);
    }

    我们可以思考下,这个程序的运行结果会是什么呢?

  • 相关阅读:
    最近工作状态异常的原因追寻。
    当“逻辑”与“UE”冲突时
    面对一个“丢失了与用户“签订”的协议的修改”时进行的思考。
    如果公司的需求总是让研发部门有怨言…
    安装sybase12.0,运行时报错异常。
    你看到这份文档,我就想摔鼠标!
    对于研发组长的责任产生了疑惑。
    关于html中空格导致的排版问题
    如何配置你的工作环境。
    今天的笔记:2014年6月3日
  • 原文地址:https://www.cnblogs.com/GoAhead/p/3145272.html
Copyright © 2011-2022 走看看