【C 语言】const 关键字用法 ( 常量指针 - const 在 * 左边 - 修饰数据类型 - 内存不变 | 指针常量 - const 在 * 右边 - 修饰变量 - 指针不变 )
普通类型数据的常量定义时 , const 关键字 在 数据类型 的 左边 和 右边 其作用 是相同的 ;
// 下面两种 const 用法效果相同
// 定义普通类型 ( 非指针类型 ) 的常量 const 在 类型左右 都是相同的
const int a = 10;
int const b = 20;
指针数据的相关常量类型 :
// 左数右指 : const 在指针左边 数据是常量 , const 在指针右边 指针是常量
// 下面两种情况 const 在指针左边 , 数据是常量 , 内存中的数据不能修改
// 但是 , c 和 d 指针的指向可以修改
// 下面两种情况是相同的
const int* c;
int const* d;
// 左数右指 : const 在指针左边 数据是常量 , const 在指针右边 指针是常量
// 下面的情况 const 在指针右边 , 指针是常量 , 指针地址不能修改
// 但是 , 指针指向的内存中的数据可以修改
int* const e = (int*)malloc(10);
指针常量与常量指针 : 需要查看 const 修饰的是 指针变量 , 还是 修饰 指针变量 指向的内存空间 ;
const 在 *
右边 ( 指针常量 | const 修饰的是变量 ) : 如果 const 修饰的是 指针变量 , 如 char * const d
, const 修饰的是 char *
, 指针不能被修改 ; 这是 指针常量 ;
const 在 *
左边 ( 常量指针 | const 修饰的是数据类型 ) : 如果 const 修饰的是 指针变量 指向的内存空间 , 如 const char *c
, const 修饰的是 char
, char
数据不能被修改 , 这是 常量指针 , 指向常量的指针 ;
下面的代码中 , 列出了 const 关键字的所有情况 , 看注释即可理解左数右指原则 ;
代码示例 :
// 导入标准 io 流头文件
// 其中定义了 std 命名空间
//#include
// 导入 std 命名空间
//using namespace std;
#include
#include
int main() {
// 下面两种 const 用法效果相同
// 定义普通类型 ( 非指针类型 ) 的常量 const 在 类型左右 都是相同的
const int a = 10;
int const b = 20;
// 左数右指 : const 在指针左边 数据是常量 , const 在指针右边 指针是常量
// 下面两种情况 const 在指针左边 , 数据是常量 , 内存中的数据不能修改
// 但是 , c 和 d 指针的指向可以修改
// 下面两种情况是相同的
const int* c;
int const* d;
// 左数右指 : const 在指针左边 数据是常量 , const 在指针右边 指针是常量
// 下面的情况 const 在指针右边 , 指针是常量 , 指针地址不能修改
// 但是 , 指针指向的内存中的数据可以修改
int* const e = (int*)malloc(10);
// 左数右指 : const 在指针左边 数据是常量 , const 在指针右边 指针是常量
// 下面两种情况 const 在指针左边和右边 , 数据和指针是常量 , 都不能修改
// 下面两种情况是相同的
const int* const f = (int*)malloc(10);
int const* const g = (int*)malloc(10);
return 0;
}
const 关键字 一般用于修饰 函数参数 , 给函数传入的参数 如果不想 用户在方法中 修改 数据 或 指针 , 可以使用 const 关键字修饰 形参 ;
定义结构体 :
struct Student
{
char name[64];
int age;
};
函数接收上述结构体类型变量作为参数 , 如果参数中 const 在 * 左边 , const Student *pS
, 根据 左数右指原则 , 指针指向的数据是常量 , 不能被修改 ;
下面是错误示范 :
// 左数右指 , const 在指针左边 , 指针指向的数据不能被修改
int fun0(const Student *pS) {
pS->age = 20;
return 0;
}
如果强行修改指针指向的数据值 , 就会在编译时报错 :
表达式必须是可修改的左值
函数接收上述结构体类型变量作为参数 , 如果参数中 const 在 * 右边 , Student* const pS
, 根据 左数右指原则 , 指针本身是常量 , 指针指向不能被修改 ;
下面是错误示范 :
// 左数右指 , const 在指针右边 , 指针本身的指向不能被修改
int fun2(Student* const pS) {
pS = NULL;
return 0;
}
如果强行修改指针指向 , 就会在编译时报错 :
表达式必须是可修改的左值
上述完整代码示例 :
// 导入标准 io 流头文件
// 其中定义了 std 命名空间
//#include
// 导入 std 命名空间
//using namespace std;
#include
#include
struct Student
{
char name[64];
int age;
};
// 左数右指 , const 在指针左边 , 指针指向的数据不能被修改
int fun0(const Student *pS) {
//pS->age = 20;
return 0;
}
// 左数右指 , const 在指针右边 , 指针本身的指向不能被修改
int fun2(Student* const pS) {
//pS = NULL;
return 0;
}
int main() {
// 下面两种 const 用法效果相同
// 定义普通类型 ( 非指针类型 ) 的常量 const 在 类型左右 都是相同的
const int a = 10;
int const b = 20;
// 左数右指 : const 在指针左边 数据是常量 , const 在指针右边 指针是常量
// 下面两种情况 const 在指针左边 , 数据是常量 , 内存中的数据不能修改
// 但是 , c 和 d 指针的指向可以修改
// 下面两种情况是相同的
const int* c;
int const* d;
// 左数右指 : const 在指针左边 数据是常量 , const 在指针右边 指针是常量
// 下面的情况 const 在指针右边 , 指针是常量 , 指针地址不能修改
// 但是 , 指针指向的内存中的数据可以修改
int* const e = (int*)malloc(10);
// 左数右指 : const 在指针左边 数据是常量 , const 在指针右边 指针是常量
// 下面两种情况 const 在指针左边和右边 , 数据和指针是常量 , 都不能修改
// 下面两种情况是相同的
const int* const f = (int*)malloc(10);
int const* const g = (int*)malloc(10);
return 0;
}
C 语言中的 const 关键字 并不是 真正的 " 常量 " , 是一个 " 冒牌货 " ;
C 语言中的 const 关键字定义的常量 , 其本质是在 内存 中分配的空间 ;
C 语言 中 , 会为 const 常量 单独分配内存 , 导致 用户可以 通过取地址符 & 获取该内存的地址指针 , 通过该指针可以修改内存中的数据 ;
定义一个常量 const int a = 10;
, 为该常量值 a
赋值 , 会报错 error: assignment of read-only variable 'a'
;
代码示例 :
#include
int main() {
// 定义常量
const int a = 10;
// 下面的代码会报错 , 貌似 a 是常量
a = 20;
return 0;
}
编译时的报错信息 :
C:\Users\octop\Desktop>gcc hello.c
hello.c: In function 'main':
hello.c:9:7: error: assignment of read-only variable 'a'
a = 20;
^
C:\Users\octop\Desktop>
如果使用 指针 变量 , 接收 常量 a 的地址 , 然后通过该指针修改 指针指向的 内存空间的值 , 然后再打印 常量 a 的值 , 发现 常量 a 的值发生了改变 ;
因此 , C 语言中的常量 , 是可以通过指针进行修改的 ;
代码示例 :
#include
int main() {
// 定义常量
const int a = 10;
// 下面的代码会报错 , 貌似 a 是常量
//a = 20;
// 定义一个指针
int* p = NULL;
// 将 常量 a 的地址赋值给指针
p = (int *)&a;
// 通过指针修改 常量 a 的值
*p = 20;
// 打印 a 的值
printf("a = %d\n", a);
return 0;
}
执行结果 :
C:\Users\octop\Desktop>gcc hello.c
C:\Users\octop\Desktop>a.exe
a = 20
C:\Users\octop\Desktop>
将相同的代码 , 拷贝到 C++ 环境中 , 编译运行的结果 , 与 C 语言环境中的编译运行结果不同
a = 10
Y:\002_WorkSpace\002_VS\HelloWorld\HelloWorld\Debug\HelloWorld.exe (进程 18604)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
出现上述问题 , 是因为 C 语言 中 , 会为 const 常量 单独分配内存 , 导致 用户可以 通过取地址符 & 获取该内存的地址指针 , 通过该指针可以修改内存中的数据 ;
C++ 语言中 使用 const 关键字 定义的常量 , 是真正的 " 常量 " ;
C++ 编译器 对 const 关键字 修饰 的常量 , 进行了 特殊处理 ;
C++ 编译器 扫描到 const int a = 10;
代码后 , 发现 const 常量 , 不会为其单独分配内存 , 而是 将 常量 a 放在 符号表 中 ,
符号表 中的数据是以 " 键值对 " 的形式存在的 , 一个 键 Key , 对应一个值 Value ;
反映到 const int a = 10;
代码中 , 键 Key 是 a
, 值 Value 是 10
, 在之后的代码 使用 常量 a
时 , 会直 从 符号表 中取出 10
;
在下面的代码中 , 使用指针 p 获取 常量 a 的地址 , 获取的并不是 符号表 中 常量 a 的地址 , 而是 从 符号表中 取出常量 const int a = 10
, 为其 分配一个内存空间 , 将 10 存进去 , 然后将首地址返回 赋值给指针 p ;
实际上 指针 p 指向的是一个内存空间 , 内存空间中的值是 常量 a 的值 , 但是此时与常量 a 没有关系了 , 该值可以被修改 ;
// 定义常量
const int a = 10;
// 下面的代码会报错 , 貌似 a 是常量
//a = 20;
// 定义一个指针
int* p = NULL;
// 将 常量 a 的地址赋值给指针
p = (int *)&a;
// 通过指针修改 常量 a 的值
*p = 20;
对比 C 语言 中 , 会为 const 常量 单独分配内存 , 导致 用户可以 通过取地址符 & 获取该内存的地址指针 , 通过该指针可以修改内存中的数据 ;
修改上述代码 , 在不同的时间获取 *p 指向的内存空间值 和 常量 a 的值 ;
发现 使用指针 接收 常量 a 的地址 , 是在内存中重新分配内存并赋值为 10 , 并没有获取到符号表的内存地址 ;
修改内存中的值 , 不会影响到 符号表 中常量 a 的值 ;
代码示例 :
#include
int main() {
// 定义常量
// 该常量定义在了 符号表 中
// 符号表 不在内存四区中 , 是另外一种机制
const int a = 10;
// 下面的代码会报错 , 貌似 a 是常量
//a = 20;
// 定义一个指针
int* p = NULL;
// 将 常量 a 的地址赋值给指针
// 在 堆内存中重新 分配一个 4 字节的空间
// 将 常量 a 的值 10 存储进去
p = (int *)&a;
// 打印 a 和 *p 的值
// 此时 还没有修改 *p 的值 , 两个值都是 10
printf("a = %d , *p = %d\n", a, *p);
// 通过指针修改 常量 a 的值
*p = 20;
// 打印 a 和 *p 的值
// 此时 通过你指针修改了 *p 的值 , *p 是 20 , 常量 a 仍为 10
printf("a = %d , *p = %d\n", a, *p);
return 0;
}
执行结果 :