C/C++ Const 小知识

Const

Const 特点

[描述] 在 C、C++、D 和 JavaScript 编程语言中,const是一个类型限定符一个应用于数据类型的关键字,表示数据是只读的Const - Wiki

  • 它是一个默认关键字
  • 它是一个修饰符(类型限定符:专门用于修饰数据类型的修饰符)
  • 它修饰的数据是只读

Const 作用推导

基本类型 - 读写

一个正常的类型定义:

int num;

首先,编译器在读取 int num; 的时候,从左往右读取:

  • 先是 int 表明是整型数据,分配 xxx 个字节 ( xxx 根据不同平台数量不同 ),
  • num 表明是一个名为 num 的数据 ,
  • 再读取到 ; 表示结束 ( 当然有些语言是没有 ; 但是有换行,即读取到换行符,表示结束 ),
  • 那么 int num; 最后表示是一个名为 num 且可读写的整型数据。

画内存区块:(这很简单,不用解释了~)

// 读 (read)
int var = num; // OK
// 写 (write)
num = 100; // OK

/****** RW *****/           /***** RW *****/
/**/  var   /**/ <-- read - /**/  num   /**/  <-- write - 100
/**************/            /**************/

那么加上 const 会变成怎样呢?

基本类型 - 只读

// [] 表示可选
[const] int [const] num;
// 即 有:
const int num; // 第一种
// 或
int const num; // 第二种
// 或 , 这里会报 Duplicate 'const' declaration specifier.
// 即至左向右的第二个 const 不能加。
const int const num; // 第?种
// 所以共 两种 写法。

[ 第一种 ] 首先,编译器在读取 const int num; 的时候,从左往右读取:

  • 先是 const 表明是只读的数据类型,
  • 再到 int 表明是整型数据,分配 xxx 个字节 ( xxx 根据不同平台数量不同 ),
  • 这时候 const int 就表明是一个只读的整型数据;
  • num 表明是一个名为 num 的数据 ,
  • 再读取到 ; 表示结束 ( 当然有些语言是没有 ; 但是有换行,即读取到换行符,表示结束 )
  • 那么 const int num; 最后表示是一个名为 num 的只读整型数据。

画内存区块:

// 读 (read)
int var = num; // OK
// 写 (write) 🚫
num = 100; // error

/****** RW *****/           /***** RO *****/
/**/  var   /**/ <-- read - /**/  num   /**/  <-X- write 🚫 - 100
/**************/            /**************/

[ 第二种 ] 首先,编译器在读取 int const num; 的时候,从左往右读取:

  • 先是 int 表明是整型数据,分配 xxx 个字节 ( xxx 根据不同平台数量不同 ),
  • 再到 const 表明是只读的数据类型,
  • 这时候 int const 就表明是一个只读的整型数据;
  • num 表明是一个名为 num 的数据 ,
  • 再读取到 ; 表示结束 ( 当然有些语言是没有 ; 但是有换行,即读取到换行符,表示结束 )
  • 那么 const int num; 最后表示是一个名为 num 的只读整型数据。

画内存区块:

// 读 (read)
int var = num; // OK
// 写 (write) 🚫
num = 100; // error

/****** RW *****/           /***** RO *****/
/**/  var   /**/ <-- read - /**/  num   /**/  <-X- write 🚫 - 100
/**************/            /**************/

明显地,第一种和第二种结果是一样的,因为编译器在读取的时候,最后产生的语义是一样的,那么结果当然是一样的。

结:int num;[const] int [const] num; 唯一区别是 num 自身这块内存是否可写,即后者的内存写功能被关闭了。

指针类型 - 读写

int * num;

首先,编译器在读取 int * num; 的时候,从左往右读取:

  • 先是 int 表明是整型数据,分配 xxx 个字节 ( xxx 根据不同平台数量不同 ),
  • 再读取到 * ,由于是在数据定义中存在,即表明是指针类型,
  • int *, 表明是一个整型的指针类型数据 ,
  • 再读取到 num 表明是一个名为 num 的数据 ,
  • 最后读取到 ; 表示结束 ( 当然有些语言是没有 ; 但是有换行,即读取到换行符,表示结束 ),
  • 那么 int * num; 最后表示是一个名为 num 且保存着可读写的整型的指针数据 ( 保存整型数据内存地址的数据 )。

画内存区块:

int no;
int * num = &no;

// *num 读 (read)
int var = *num; // OK, var = no
// *num 写 (write)
*num = 200; // OK

// num 读
int * n1 = num; // OK
// num 写
num = &no; // OK

/****** RW *****/           /************* RW ***********/
/**/  var   /**/ <-- read - /**/         no           /**/  <-- write - 100
/**************/            /***** RW(对于 num 而言) *****/
                             ⬇️       ⬆️
                             ⬇️(read) ⬆️(write, 200)  (*num)
                             ⬇️       ⬆️
/****** RW *****/           /************* RW *************/
/**/   n1   /**/ <-- read - /**/           num          /**/  <-- write - &no
/**************/            /******************************/

对于 num 指针类型,这里就出现了两个内存区块:

  • 保存 num 自身的内存区块 ( num 保存了 no 变量的内存区块地址 )
  • no 自身的内存区块地址

同样的方法加个 const 看看?

指针类型 - 只读

从形式上看无非是增加了一个修饰位。

// [] 表示可选
[const] int [const] * [const] num;
// 即 有:
const int * num; // 第一种
// 或
int const * num; // 第二种
// 或
int * const num; // 第三种
// 或
const int * const num; // 第四种, 等价于 int const * const num; 
// 或 , 这里会报 Duplicate 'const' declaration specifier.
// 即至左向右的第二个 const 不能加。
const int const * const num; // 第?种
// 所以一共四种写法。

[第一种] 首先,编译器在读取 const int * num; 的时候,从左往右读取:

  • 先是 const 表明是只读的数据类型,
  • int 表明是整型数据,分配 xxx 个字节 ( xxx 根据不同平台数量不同 ),
  • 这时候 const int 就表明是一个只读的整型数据;
  • 再读取到 * ,由于是在数据定义中存在,即表明是指针类型数据,
  • 即 constint *, 表明是一个只读的整型的指针类型,
  • 再读取到 num 表明是一个名为 num 的数据 ,
  • 最后读取到 ; 表示结束 ( 当然有些语言是没有 ; 但是有换行,即读取到换行符,表示结束 ),
  • 那么 const int * num; 最后表示是一个名为 num 且保存只读的整型的指针数据

画内存区块:

int no;
const int * num = &no;

// *num 读 (read)
// 右值 *num 可理解成,从 num 中得到 no 的内存地址,再从 no 中读取内容。
int var = *num; // OK
// *num 写 (write) 🚫
// 左值 *num 可理解成,从 num 中得到 no 的内存地址,由于表明了 no 的地址是只读指向,所以无法从 no 中读取内容。
*num = 200; // error

// num 读 
int * n1 = num; // OK
// num 写
num = no; // OK
                    
/****** RW *****/           /************* RW ***********/
/**/  var   /**/ <-- read - /**/         no           /**/  <-- write - 100
/**************/            /***** RO(对于 num 而言) *****/
                           ⬇️       ⬆️
                           ⬇️(read) ⬆️(write, 200 🚫) (*num)
                           ⬇️       ⬆️
/****** RW *****/           /************* RW *************/
/**/   n1   /**/ <-- read - /**/           num          /**/  <-- write - &no
/**************/            /******************************/

图可能不好理解 ,这里表明的是 num 自身的内存可读写,但是 num 保存的内存地址是只读的。( num 自身的内存地址由系统(程序)保存着 )

即,系统保存着 num 内存地址且可读写,num 保存着 no 的内存地址,但修饰成了只读。

[第二种] 首先,编译器在读取 int const * num; 的时候,从左往右读取:

  • int 表明是整型数据,分配 xxx 个字节 ( xxx 根据不同平台数量不同 ),
  • 再是 const 表明是只读的数据类型,
  • 这时候 int const 就表明是一个只读的整型数据;
  • 再读取到 * ,由于是在数据定义中存在,即表明是指针类型,
  • int const *, 表明是一个只读的整型的指针类型数据,
  • 再读取到 num 表明是一个名为 num 的数据 ,
  • 最后读取到 ; 表示结束 ( 当然有些语言是没有 ; 但是有换行,即读取到换行符,表示结束 ),
  • 那么 int const * num; 最后表示是一个名为 num 且保存只读的整型的指针数据

第一种和第二种,语义上是一样的所以结果肯定是一样的。

[第三种] 首先,编译器在读取 int * const num; 的时候,从左往右读取:

  • int 表明是整型数据,分配 xxx 个字节 ( xxx 根据不同平台数量不同 ),
  • 再读取到 * ,由于是在数据定义中存在,即表明是指针类型,
  • 这时候 int * 就表明是一个整型的指针类型数据;
  • 再是 const 表明是只读的数据类型,
  • int * const, 表明是一个整型的指针类型的只读的数据,
  • 再读取到 num 表明是一个名为 num 的数据 ,
  • 最后读取到 ; 表示结束 ( 当然有些语言是没有 ; 但是有换行,即读取到换行符,表示结束 ),
  • 那么 int * const num; 最后表示是一个名为 num 且整型的指针类型只读的数据。

画内存区块:

int no;
int * const num = &no;

// *num 读 (read)
// 右值 *num 可理解成,从 num 中得到 no 的内存地址,再从 no 中读取内容。
int var = *num; // OK
// *num 写 (write)
// 左值 *num 可理解成,从 num 中得到 no 的内存地址,再向 no 中写入内容。
*num = 200; // OK

// num 读 
int * n1 = num; // OK
// num 写 🚫
num = no; // error
                    
/****** RW *****/           /************* RW ***********/
/**/  var   /**/ <-- read - /**/         no           /**/  <-- write - 100
/**************/            /***** RW(对于 num 而言) *****/
                            ⬇️       ⬆️
                            ⬇️(read) ⬆️(write, 200) (*num)
                            ⬇️       ⬆️
/****** RW *****/           /************* RO *************/
/**/   n1   /**/ <-- read - /**/           num          /**/  <-X- write 🚫 - &no
/**************/            /******************************/

[第四种] 首先,编译器在读取 const int * const num; 的时候,从左往右读取:

  • 先是 const 表明是只读的数据类型,
  • 再读取到 int 表明是整型数据,分配 xxx 个字节 ( xxx 根据不同平台数量不同 )
  • 这时候 const int 就表明是一个只读的整型数据,
  • 再读取到 * ,由于是在数据定义中存在,即表明是指针类型,
  • int const *, 表明是一个只读的整型的指针类型的数据,
  • 再读取到 const 表明是只读的数据类型,
  • int const * const 表明是一个只读的整型的指针类型的只读的数据
  • 再读取到 num 表明是一个名为 num 的数据 ,
  • 最后读取到 ; 表示结束 ( 当然有些语言是没有 ; 但是有换行,即读取到换行符,表示结束 ),
  • 那么 int * const num; 最后表示是一个名为 num只读的整型的指针类型只读的数据。

画内存区块:

int no;
int * const num = &no;

// *num 读 (read)
// 右值 *num 可理解成,从 num 中得到 no 的内存地址,再从 no 中读取内容。
int var = *num; // OK
// *num 写 (write) 🚫
// 左值 *num 可理解成,从 num 中得到 no 的内存地址,由于表明了 no 的地址是只读指向,所以无法从 no 中读取内容。
*num = 200; // error

// num 读 
int * n1 = num; // OK
// num 写 🚫
num = no; // error
                    
/****** RW *****/           /************* RW ***********/
/**/  var   /**/ <-- read - /**/         no           /**/  <-- write - 100
/**************/            /***** RO(对于 num 而言) *****/
                            ⬇️       ⬆️
                            ⬇️(read) ⬆️(write, 200 🚫 ) (*num)
                            ⬇️       ⬆️
/****** RW *****/           /************* RO *************/
/**/   n1   /**/ <-- read - /**/           num          /**/  <-X- write 🚫 - &no
/**************/            /******************************/

内容压缩汇总

/// 基本类型

// num 的内存可读写
int num;
// num 的内存只读,不可写
const int num; // 等价于 int const num;

/// 一级指针类型

// num 自身内存可读写; num 指向的内存可读写
int * num;
// num 自身内存可读写; num 指向的内存只读,不可写
const int * num; // 等价于 int const * num;
// num 自身内存只读,不可写; num 指向的内存可读写 
int * const num;
// num 自身内存只读,不可写; num 指向的内存只读,不可写
const int * const num;
  1. 对于基本类型,只需要考虑自身的内存的读写性,一旦加 const 修饰不管放那都是只读。( 因为从语义上看不管 const 放那它也只有一种结果 )
  2. 对于一级指针类型,考虑的内存有两块,一个是自身的内存,一个是指向的内存;const 一旦紧挨着变量名,即自身的内存只读,其它情况都是指向的内存只读
  3. 对于多级指针类型,若有 x 个 * ,那么要考虑的就是 ( x + 1 ) 个内存块的问题了。
  4. 还有 int constconst int ,编译器都会直接转换成 const int

知识扩展

二级指针类型的例子:

int N = 8;
int const NC = 8;
int * n;
int * const n0 = NULL;
int const * n1 = NULL;
int const * const n2 = NULL;
int const ** const n3 = NULL;
int * const * const n4 = NULL;

// [] 表示可选
// [const] int [const] * [const] * [const] num;

// num 自身的内存,*num 指向的内存, **num 指向的内存,一共三块内存
// num 的数据类型是:int ** const, *num 的数据类型是:int * [const], **num 的数据类型是:int .
// const 修饰 num 自身内存,即 num 自身的内存只读
int ** const num = NULL;
**num = N; // OK
*num = n;  // OK
*num = n0; // OK
num = &n0; // error 🚫

// num 自身的内存,*num 指向的内存, **num 指向的内存,一共三块内存
// num 的数据类型是:int * [const] * , *num 的数据类型是:int * const, **num 的数据类型是:int .
// const 修饰 *num 指向的内存,即 *num 指向的内存只读
int * const * num;
**num = N; // OK
*num = n0; // error 🚫
num = &n0; // OK
num = &n;  // OK

// num 自身的内存,*num 指向的内存, **num 指向的内存,一共三块内存
// num 的数据类型是:int const **, *num 的数据类型是:int [const] * [const], **num 的数据类型是:int const .
// const 修饰 **num 指向的内存,即 **num 指向的内存只读
int const ** num;
**num = NC; // error 🚫
*num = n;  // OK
*num = n0; // OK
*num = n1; // OK
*num = n2; // OK
num = n3;  // OK

// num 自身的内存,*num 指向的内存, **num 指向的内存,一共三块内存
// num 的数据类型是:int * const * const, *num 的数据类型是:int * const, **num 的数据类型是:int .
// 最右边的 const 修饰 num 自身内存,即 num 自身的内存只读
// const 修饰 *num 指向的内存,即 *num 指向的内存只读
int * const * const num = NULL;
**num = N; // OK
*num = n0; // error 🚫
num = n4;  // error 🚫

如果对上面的内容有疑惑,那么就动手画画内存图~~吧 !!!

现在掌握了吗?

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容