细说C++之const

1、C语言中const与 C++中的const

void main()
{
  const int a = 10;
  int *p = (int*)&a;
  *p = 20;
  printf("%d", a);
}

比较上述代码在C中和C++中运行结果:C:打印20;C++:打印0;

由此可见,C语言中的const是个“冒牌货”,C++中
的const是一个真正的常量。

原因:C语言直接为a分配了内存。C语言中的const变量是只读变量,有自己的存储空间。而C++编译器对const的处理是:当碰见常量声明时,在“符号表”(C++编译器专门设置的)中放入常量,在使用a的时候直接在符号表中取值!
那么问题是那又如何解释取地址呢?

A.编译过程中若发现使用常量则直接以符号表中的值替换;

B.编译过程中若发现对const使用了extern(需要在其他文件中使用)
或者 &操作符,则给const常量另外再专门分配存储空间。

如上图所示,对const变量取地址后,为“20”单独专门分配了空间,而与符号表中的a没有关系。

2、const与基础数据类型:

   const int bufsize = 512;bufsize的值不能再被修改,任何试图为bufsize赋值的行为都将引发错误。因为const的对象一旦创建后其值不能再改变,所以const对象必须初始化。
   int i=12;const int ci=i;还可以利用另外一个对象(是不是const无关紧要)去初始化const对象。。。int j = ci;一旦拷贝完成,新的对象和原来的对象就没有什么关系了。
{
   int i = 50;
   const int j = i;      //初始化
   int k = j;
   int m = 100;
   //j = m;              //赋值,编译错误
}

默认状态下,const对象仅在文件内有效,当多个文件中出现了同名的const对象,其实等同于在不同文件定义了独立的变量。

如果const对象需要在不同文件共享,那就需要对const对象不管是声明还是定义都添加extern关键字。

注意只能定义一次:extern const int bufsize = 512;一般被包含在头文件。

在使用时直接声明:extern const int bufsize;不能再定义,否则出错。extern就说明了bufsize并不是本文件独有,它的定义在别处。

3、const与引用

void main()
{
   //对常量对象 只能 用 常量引用
   const int ci = 100;  //常量对象
   const int &ri = ci;  //正确:引用及其对应的对象都是常量
   //ri = 100;          //error:ri是对常量的引用
   //int &r2 = ci;      //error:将一个非常量引用指向一个常量对象,如果正确,那么将可以通过修改r2来改变ci的值,ci显然不能改变。

   //常量引用 允许被 绑定到 非常量的对象、 字面值、 一般表达式 ————例外1
   int i = 50;
   const int &CRi1 = i;        //非常量的对象
   const int &CRi2 = 500;      //字面值
   const int &CRi3 = i * 2;    //一般表达式
   int r4 = CRi1 * 2;          //正确:CRi1的常量特征仅仅在执行改变的时候才会发生作用,拷贝赋值并不会改变,一旦拷贝完成,新的对象和原来的对象就没有什么关系了。
   //int &r5 = CRi1 * 2;        //error:将一个非常量引用指向一个常量对象,如果正确,那么将可以通过修改r5来改变CRi1的值,CRi1显然不能改变。
          /* * * * * * * * *
             *一般来说,引用的类型必须与其所引用对象的类型一致。
             *当一个常量引用被绑定到另外一种类型上到底发生了什么?
             *   double d = 3.14;
                 const int &ri = d;

             *   const int tmp = d;   // 由双精度浮点数生成一个临时的整型常量
                 const int &ri = d;   // 让ri绑定这个临时的常量
             * * * * * * * * */

   //常量引用仅仅对自己可参与的操作做了限定,如果引用的对象是个非常量,那么允许通过其他途径改变它的值
   int j = 10;
   int &rj = j;             //引用rj 绑定 对象j
   const int &cj = j;       //常量引用cj 也绑定 对象j,但是不允许通过cj 去修改j的值
   rj = 1000;               //通过rj 修改j,j被修改为1000
   //cj = 1000;
   cout << "j: " << j << "  ,cj: " << cj << endl;   //j: 1000  ,cj: 1000
}  

4、const与指针

所谓指向常量 的指针或者引用,不过是指针或引用“自以为是”罢了,它们自己觉得自己指向了常量,所以自觉地不去改变它所指向的对象的值。(但是常量 的指针或者引用 是可以 指向非常量的,而非常量是可以通过其他途径改变的)。

指针是对象,而引用不是!因此,指针本身是不是常量 和 指针所指的是不是一个常量 是两个相互独立的问题!

顶层const——指针本身是个常量!

底层const——指针所指的对象是个常量!

void main()
{
    int i = 10, m = 20;
    const int ci = 100, cm = 200;

    //第一、二个意思一样,代表一个  常整形数,任何试图为a、b赋值的行为都将引发错误
    const int a = 0;        //必须初始化
    int const b = 1;        

    //第三个 c是一个指向常整形数的指针(所指向的内存数据不能被修改,但是本身可以修改)
    const int *c1 = &ci;
    //int *c2 = &ci;        //存放常量的地址,只能使用指向常量的指针

    const int *c = &i;
    //*c = m;               //c指向的内存数据是const的(c的自我感觉),也就是不能通过 *c “直接修改”的
    c = &m;                 //c的值是允许修改的(底层const),修改c的指向,让c指向另外一个内存空间
    cout << *c << endl;     //20

    //第四个 d 常指针————指针本身是个常量(指针变量(指向)不能被修改,但是它所指向内存空间可以被修改)
    int ii = 0;
    int * const d = ?    //d 将一直指向 ii (顶层const),不允许修改 d 的值
    *d = 250;               //指针本身是个常量,并不意味着不能通过修改指针所指向的值。(依赖于所指向的对象)
    cout << ii << endl;     // ii 被修改成250,(d指向的是一个非常量)

    //第五个 e 一个指向常整形的常指针(指针和它所指向的内存空间,均不能被修改)
    const int pi = 123;
    const int * const e = π   //e所指的对象值 和 e自己存储的那个地址 都不能改变
}

5、const与成员函数

将const实施与成员函数的目的,是为了确认该成员函数可作用于const对象身上。两个函数如果只是常量性不同,可以被重载。

6、const与define

const修饰的常量编译期间,就确定下来。const常量是由编译器处理的,提供类型检查和作用域检查;宏定义由预处理器处理,单纯的文本替换。

void fun1()
{
    #define a 10
    const int b = 20;
    //#undef a  //将宏定义a卸载,限制作用域
}

void fun2()
{
    printf("a = %d\n", a);
    //printf("b = %d\n", b);   //const定义的b是有作用域的
}
 

7、const与constexpr

constexpr:常量表达式,指的是值不会改变并且在编译过程中就能得到计算结果的表达式。由数据类型和初始值共同决定。

   const int max = 100;         //max是常量表达式
   const int limit = max + 1;   //limit是常量表达式
   int size = 50;               //size不是常量表达式,只一个普通的int类型
   const int sz = get_size();   //sz本身是个常量,但它的值直到运行时才能确定,所以不是常量表达式

   一般说来,如果你认定变量是一个常量表达式,那就把它声明成constexpr类型。
   const int sz = get_size();   //只有get_size()是一个constexptr函数时,才是一条正确的声明语句。

8、const与auto

auto一般会忽略掉顶层const,同时底层const则会保留。

void main()
{
   //auto一般会忽略掉顶层const,同时底层const则会保留。
   int i = 10;
   const int ci = i;
   const int &cr = ci;
   auto b = ci;           //b是一个int(ci的顶层const特性被忽略)
   auto c = cr;           //c是一个int(cr是ci的别名,ci的顶层const特性被忽略)
   auto d = &i;           //d是一个整型指针(整数的地址就是指向整数的指针)
   auto e = &ci;          //e是一个指向整型常量的指针(对常量对象 取地址 是一种底层const)

   //如果希望推断出的auto类型是一个顶层const,则需明确指出
   const auto f = ci;     //ci的推演类型是int,f是const

   //还可以将引用设为auto,原来的初始化规则仍然适用
   auto &g = ci;          //g是一个常量引用,绑定到ci
   //auto &h = 42;        //error:显然不能为一个字面值绑定非常量引用。(怎么说?h是42的引用?字面量42没有内存空间 没有办法做引用)
   const auto &j = 42;    //正确:可以为常量引用绑定字面值
}

9、const与decltype

decltype处理顶层const和引用的方式与auto不同。如果decltype使用的表达式是一个变量,则decltype返回该变量的类型。

  int i = 10;
const int ci = i;
const int &cr = ci;
decltype(ci) x = 0;    //x的类型是const int
decltype(cr) y = x;    //y的类型是const int&,y绑定到x
//decltype(cr) z;      //error:z是一个引用,必须初始化
 
时间: 2024-12-13 00:41:07

细说C++之const的相关文章

细说 const

1.const 简单应用 const int pp=0 //pp 为整形常量,不能修改 还有另外一种不常用的方式 但是最容易误导 int const pp=0 //pp 为整形常量,不能修改 记住这两个等价声明,后面还有用处 const int* ptr=&pp   //ptr 为指向int型常量的指针,ptr可以使用常量或非常量初始化 int* const ptr ==&pp //ptr 为常量指针,初始化后,不能指向其他地址,仅能指向非常量 const int & infer=

细说linux IPC(五):system V共享内存

system V共享内存和posix共享内存类似,system V共享内存是调用shmget函数和shamat函数.           shmget函数创建共享内存区,或者访问一个存在的内存区,类似系统调用共享内存的open和posix共享内存shm_open函数.shmget函数原型为: #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg); key: 函

细说linux IPC(三):mmap系统调用共享内存

前面讲到socket的进程间通信方式,这种方式在进程间传递数据时首先需要从进程1地址空间中把数据拷贝到内核,内核再将数据拷贝到进程2的地址空间 中,也就是数据传递需要经过内核传递.这样在处理较多数据时效率不是很高,而让多个进程共享一片内存区则解决了之前socket进程通信的问题.共享内存 是最快的进程间通信 ,将一片内存映射到多个进程地址空间中,那么进程间的数据传递将不在涉及内核.        共享内存并不是从某一进程拥有的内存中划分出来的:进程的内存总是私有的.共享内存是从系统的空闲内存池中

细说linux IPC(六):pipe和FIFO

在unix系统上最早的IPC形式为管道,管道的创建使用pipe函数: #include <unistd.h> int pipe(int pipefd[2]); 该函数创建一个单向的管道,返回两个描述符 pipefd[0],和pipefd[1],pipefd[0]用于读操作,pipefd[1]用于写操作.该函数一般应用在父子进程(有亲缘关系的进 程)之间的通信,先是一个进程创建管道,再fork出一个子进程,然后父子进程可以通过管道来实现通信.管道具有以下特点:管道是半双工的,数据只能向一个方向流

细说Linux中的信号(signal )

在细说信号之前我们先来了解下什么是信号.信号(signal)是一种软件中断,它提供了一种处理异步事件的方法,也是进程间惟一的异步通信方式.在Linux系统中,根据POSIX标准扩展以后的信号机制,不仅可以用来通知某种程序发生了什么事件,还可以给进程传递数据. 信号的种类有很多,我们可以通过kill -l命令察看系统定义的信号列表: 可以看到一共有62个信号.1-31号为普通信号:34-64号为实时信号.(博主这次只讨论普通信号.) 接下来我们来细说信号的产生.阻塞和捕捉. >>信号的产生: 通

细说智能指针

提到指针,我们就会想到指针的高效,当然,滥用指针也会为我们带来许多的潜在bug.提到指针,我们就会想到内存泄漏.比如,使用指针后忘记释放,久而久之,堆空间就会全部使用完,那么会带来很大的危害.再比如,两个指针指向同一片内存区域,我们对同一片区域进行了多次释放,同样会造成内存泄漏.为了方便大家的理解,我们先来模拟一下,使用指针却忘记释放带来的危害.首先,我们要定义一个类.这次,还是定义女朋友类吧(之前写过一篇<细说C++的友元>用的就是女朋友类,这次还用这个吧,方便说明问题,更何况我们这群马畜怎

const指南

基本词义  意思就就是说利用const进行修饰的变量的值在程序的任意位置将不能再被修改,就如同常数一样使用! 使用方法 const int a=1;//这里定义了一个int类型的const常数变量a; 但对于指针来说const仍然是起作用的,以下有两点要十分注意,因为下面的两个问题很容易混淆! 我们来看一个如下的例子: //程序作者:管宁 //站点:www.cndev-lab.com //所有稿件均有版权,如要转载,请务必著名出处和作者 #include <iostream> using na

PHP 面向对象中常见关键字使用(final、static、const和instanceof)

PHP 面向对象中常见关键字的使用: 00x1.Final :final关键字可以加在类或者类中方法之前,但是不能使用final标识成员属性. 作用: 使用final标识的类,不能被继承. 在类中使用final标识的成员方法,在子类中不能覆盖. 总结:final表示为最终的意思,所以使用final关键字的类或者类中的成员方法是不能被更改的. 00x2.Static :static关键字将类中的成员属性或者成员方法标识为静态的,static标识的成员属性属于整个类,static成员总是唯一存在的,

const 放在函数后

const 放在函数后表示这个函数是常成员函数, 常成员函数是不能改变成员变量值的函数.const 限定符,它把一个对象转换成一个常量.举例:为了使成员函数的意义更加清楚,我们可在不改变对象的成员函数的函数原型中加上const说明:class Point{public:int GetX() const;int GetY() const;void SetPt (int, int);void OffsetPt (int, int);private:int xVal, yVal;};const成员函数