C/C++中const关键字相关

     const

    本文根据《C和指针》3.4节,《C专家编程》1.9节以及相关网络资源整理总结const三个不易理解的地方: 声明,函数参数赋值,存储类型。如有偏误,欢迎指正,共同学习。

1.const声明

    ANSI C 允许使用const关键字来声明“常量”,const“常量”和普通变量一样,只是它们的值不能修改。该处“常量”加引号的原因是,const变量只是read-only,不能修改其值,的普通变量,只能用于允许使用变量的地方。故在编译器没有进行常量折叠的时候(一般的C语言编译器,也有其他的可以定义数组),不能用来定义数组长度。

1.1 普通声明

int   const a = 10;
const int   a = 10;

    这两种声明都是一样的,const变量需要在声明的时候定义。

1.2 不易区别的地方

/*1*/int   const   *pci;//指向整型常量的指针,可以修改指针的值,但是不能修改它所指向的值
/*2*/int   * const  cpi;//指向整型的常量指针,指针是常量,可其值不能修改,但可以修改它所指向的值

    区别的方式是,在声明中重点看const右边的值:在1中,const 右边是*pci,修饰的是指针取值后结构(其结构的类型为int),故为指针指向的值不能修改;在2中,const右边是cpi,修饰的是指针(指针类型为int *),故指针的值不能修改。

1.3 用其他方式修改const 的值

    关键字const并不能把变量变成常量!在一个符号前面加上const限定符只是表示这个符号不能被赋值。也就是它的值对于这个符号来说是只读的,但是它并不能防止通过程序的内部(甚至外部)的方法来修改这个值。

    如下代码所示,const int *limitp声明limit所指向的值不能修改,对其所指向的值进行赋值将导致出错。

#include 
//使用code::Blocks 12.11 GUN GCC Complier 编译.cppwenjia
int main()
{
    int change = 0;
    const int limit = 1;
    const int *limitp = &limit;
    *limitp = change;             //error: assignment of read-only location '* limitp'|
    printf("%d\n",*limitp);

    return 0;
}

    但是可以,修改指针指向的值来修改指针解引用的值:

#include 
//使用code::Blocks 12.11 GUN GCC Complier 编译.cpp文件
int main()
{        
	int change = 0;        
	const int limit = 1;        
	const int *limitp = &limit;        
	limitp = &change;           //将指针指向别处,解引用后值改变,但是limit的值不变        
        printf("%d\n",*limitp);        
	return 0;
}

    更奇葩的是,在一些类型转换检测不严格的编译其中,不仅可以修改*limitp指针指向何处,甚至可以改变limit的值,如下所示:

#include 
//使用code::Blocks 12.11 GUN GCC Complier 编译.c文件
int main()
{
    int change = 0;
    const int limit = 1;
    int *limitp = &limit; //warning: initialization discards 'const' qualifier from pointer target type
    *limitp = change;     //[enabled by default]
    printf("%d\n%d\n",limit,*limitp);

    return 0;
}

    编译器只是报了一个警告,在运行后,发现limit和*limitp的值都为0!

    使用code::Blocks 12.11 GUN GCC Complier 编译.cpp文件时,编译会在上述地方报错:

#include 
//使用code::Blocks 12.11 GUN GCC Complier 编译.c文件
int main()
{
    int change = 0;
    const int limit = 1;
    int *limitp = &limit;       //error: invalid conversion from 'const int*' to 'int*' [-fpermissive]|
    *limitp = change;
    printf("%d\n%d\n",limit,*limitp);

    return 0;
}

  由于GUN GCC Complier 编译.c文件时,对类型转换检测不严格,故使用GUN GCC Complier 编译.cpp文件进行验证与分析。此外,根据上述分析,了解项目使用的编译器,以及分析编译弹出的任何警告消息都显得很重要。

2.const做参数、赋值

    const最有用之处就是用他来限定函数的形参,这样该函数将不会修改实参指针所指的数据,也许就是C和C++中const最一般的用法。

    在ANSIC标准的第6.3.2.2节中,有这么一句话“每个实参都应该具有自己的类型,这样它的值就可以赋值给与它所对应的形参类型的对象中(该对象的类型不能含有限定符)。”也就是说,参数传递的过程类似于赋值。 在ANSIC标准的第6.3.16.1节中描述了赋值合法的约束条件:“两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须具有右边指针所指向类型的全部限定符

2.1 类型相容,所指限定符不同

    正是这个条件,使得函数调用中实参char *能够与形参const char *(在c标准库中,所有的字符串处理函数就是这样的。)它之所以合法,是应为在下面的代码中:

 const char *ccp;
 char *cp;
 ccp = cp;
  • 左边操作数是一个指向有const限定符的char指针(const限定符是修饰指针所指向的类型,而不是指针本身)
  • 右边操作数是一个指向没有限定符的char的指针
  • char类型与char类型是相容的,左操作数所指向的类型具有右操作数所指向类型的限定符(无),再加上自身的限定符(const)
    故上述赋值是成立的,但是反过来的话,编译器就会产生错误信息:
       char *cp;
       const char *ccp;
       cp = ccp;        //error: invalid conversion from 'const char*' to 'char*' [-fpermissive]|

2.2 类型不相容,不论限定符是否不同

    下面的代码,展示了实参和形参类型不相容时,参数传递不成功的情形:
#include 
//使用code::Blocks 12.11 GUN GCC Complier 编译.c文件
void foo(const char **ccp){};
int main()
{
    char **cp;
    foo(cp);   //error: invalid conversion from 'char**' to 'const char**'
    return 0;
}

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

                char ** cp是一个指向 “char类型指针”的指针,                         指向 char *

故他们指向的类型是不兼容的,参数传递会失败。

下面的代码同理:

void foo(char **cp){};
int main()
{
    const char **ccp;
    foo(ccp);   //error: invalid conversion from 'const char**' to 'char**'
    return 0;
}

3.const存储类型

  const修饰的全局变量存放在常量区;局部变量被const修饰后仍存放在栈区,仅仅意味着在表达式上不能显式地改变该变量值,否则编译器会报语法错误。C++不同的地方就在此,C++鼓励使用const来取代#define,因为C++对const进行了优化,如果该变量的值是常量表达式,在C++中就会进行常量折叠(constant folding),何为const folding,百度一大堆,简单来说就是在编译期间任何出现该变量的地方都会被替换成常量表达式的值,正因此,这种情况下const定义的变量是可以用于定义数组的维度的,而C语言就没有这个优化特性,所以C语言的const修饰的变量是一般都不能进行数组维度的定义的(注意,C++中不进行常量折叠的情况下,const修饰的仍无法进行数组定义,具体什么时候常量折叠请参看博文http://www.cnblogs.com/yanqi0124/p/3795019.html

有关变量的存储类型的详细资料,请参看:

浅谈C语言的数据存储(一):http://www.embedu.org/Column/Column540.htm

浅谈C语言的数据存储(二) :http://www.embedu.org/Column/Column558.htm

C语言bss,data,text,rodata,堆,栈,常量段:https://blog.csdn.net/OUYANG_LINUX007/article/details/7448814



你可能感兴趣的:(C,C++)