封面pid:146476534Acid好可爱啊啊啊wwwwww哥特萝莉嘿嘿嘿
C语言在声明变量时可以添加修饰符,其有以下几种:const,volatile,static,register,extern,auto。
const
const即constant,常量,或者说只读的,告诉编译器该对象在初始化之后不允许被修改。可用于修饰变量,指针,函数参数。如果在编程中确实有某个值保持不变,就应该明确使用const。
- 普通 const 变量:初始化后不可修改
max是一个const常量,无法直接修改:如果尝试直接对其进行修改,则会在编译时就报错:const int max=100;max=200; -> error: assignment of read-only variable 'max' - 指向 const 数据的指针:不能通过指针修改所指内容
p是一个指向const int类型的值的指针:若尝试进行修改,同样会报错:const int data=42;const int * p=&data;*p=99; -> error: assignment of read-only variable 'data' - const 指针:指针本身不能改指向,而所指内容的值可以改
cp是一个指向a的const指针:这个过程中int a=1,b=2;int * const cp=&a;*cp = 100;a被成功赋值100,但是若尝试修改指针cp的指向,则会产生报错:cp=&b; -> error: assignment of read-only variable 'cp' - 双重 const:指针和内容都不能改
ccp是一个指向const int类型的值的const类型指针:此时不论是修改const int * const ccp = &a;ccp指向对象的值,还是修改ccp的指向,都会产生报错:*ccp=b; -> error: assignment of read-only variable 'ccp'ccp=&b; -> error: assignment of read-only variable 'ccp' - const 数组:所有元素只读
arr数组中的所有元素都无法直接更改:与普通 const 变量类似,尝试直接修改也会报错:const int arr[] = {1, 2, 3};arr[0] = 9; -> error: assignment of read-only variable 'arr[0]' - const 函数参数:函数内部只能读不能写
在函数print_value内部,指针n指向的变量是只读的:若尝试在//main()print_value(&data);void print_value(const int * n){printf("value=%d\n", *n);}print_value内写入*n=xxx,则编译器也同样会报错:*n=1; -> error: assignment of read-only location '*n'
volatile
volatile即易变的,告诉编译器这个变量的值可能在程序自身不知道的情况下被意外地改变(例如中断处理函数,其他线程,以及硬件寄存器),因此每次都从内存中读取变量的值,而不对其进行激进的优化。
在以下程序中,volatile指出,该变量i是会随时变化的,每次使用它的时候必须从其内存地址中重新读取。而在不使用volatile的优化做法下,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次写入a时获取到的i的数据放在b中。而不是重新从i的内存地址里面读取。这样一来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
volatile int i=10;int a=i;//其他代码,并未明确告诉编译器对i进行过操作int b=i;一般说来,volatile用在如下的几个地方:
- 中断服务程序中修改的供其它程序检测的变量需要加
volatile; - 多任务环境下各任务间共享的变量与标志应该加
volatile; - 存储器映射的硬件寄存器通常也要加
volatile说明,因为每次对它的读写都可能有不同意义;
static
static即静态的。普通局部变量在函数每次调用时创建,返回时销毁,而static局部变量只初始化一次,函数结束后不会销毁,下次调用时会保留上一次的值。
在以下例程中:
- 变量
counter有修饰符volatile,仅初始化一次,并且在调用函数function()结束时不会被销毁,因此其数值可以保留到下一次调用function(),并且不会再次初始化; - 普通变量
local每次调用function()时都会经历:初始化 -> 自加 -> 打印 -> 销毁,因此多次调用时,它的值始终经历:初始化:0 -> 自加:1 的过程;
void function(void){ static int counter=0;//只初始化一次,调用之间保留值 int local=0;//每次调用都重新初始化为0 counter++; local++; printf("counter=%d,local=%d\n",counter,local); return;}int main(void){ for (int i=0;i<5;i++){ function(); } return 0;}在最终的输出结果中,可以看到counter值被保留并不断自加,最终累计到5,而local的值始终是1:
counter=1,local=1counter=2,local=1counter=3,local=1counter=4,local=1counter=5,local=1上述情况是对于局部变量的static修饰,若static作用于全局变量或者函数,则会为其添加”内部链接”,即只能在本文件内可见,其他文件即使写extern声明也无法访问,从而隐藏模块内部实现细节,避免多文件项目中的命名冲突,防止别的文件意外修改本变量的状态,即对此模块实现封装。
在文件utils.c中用static修饰定义全局变量hidden_counter:
static int hidden_counter = 0;int bump(void){ hidden_counter += 2; return hidden_counter;}int peek(void){ return hidden_counter;}main.c尝试extern外部文件utils.c中的变量hidden_counter,则在链接阶段会报错undefined reference to ‘hidden_counter’,证明static全局变量外部不可见。
extern int hidden_counter;register
register即请求编译器把变量放在CPU寄存器中,以便频繁访问时更快。当然register 只是一个”建议”,编译器也可以不采纳,现代编译器会自动决定把哪些变量放在寄存器中,具有更好的效果。
#include <stdio.h>#include <time.h>#define N 200000000LLint main(void){ clock_t t1, t2; t1 = clock(); register long long r = 1; for (long long i = 0; i < N; i++) { r = r * 3 + 1; } t2 = clock(); printf("register 变量: 结果 = %lld, 耗时 %.3f 秒\n", r, (double)(t2 - t1) / CLOCKS_PER_SEC); t1 = clock(); long long n = 1; for (long long i = 0; i < N; i++) { n = n * 3 + 1; } t2 = clock(); printf("普通变量: 结果 = %lld, 耗时 %.3f 秒\n", n, (double)(t2 - t1) / CLOCKS_PER_SEC); return 0;}从输出的结果可以看出:两者耗时基本相同,说明把优化交给编译器更可靠。
register 变量: 结果 = 5797424771318111233, 耗时 0.083 秒普通变量: 结果 = 5797424771318111233, 耗时 0.082 秒NOTE需要注意的是,由于寄存器没有地址,对于
register变量不能使用取地址符(&),否则会报错:printf("r 的地址: %p\n", (void *)&r);-> error: address of register variable 'r' requested
extern
extern即表示此变量/函数定义在其他文件中,从而实现变量/函数跨文件共享。extern表示”该变量或函数的定义在其他文件中,这里只是声明,可以先用”,而且其本身不分配内存。
在文件extern_definition.c中,先对变量global_count以及函数increase()进行声明与分配内存:
int global_count = 0;int increase(void){ return ++global_count;}随后,在文件extern.h中进行extern声明,任何.c文件只要include了这个.h文件即可共享变量和函数:
#ifndef EXTERN_H#define EXTERN_H
extern int global_count;extern int increase(void);
#endif最后,在文件main.c中只需#include "extern.h",即可将变量global_count和函数increase()暴露给本文件使用:
#include <stdio.h>#include "extern.h"int main(void){ printf("初始 global_count = %d\n", global_count); printf("increase() = %d\n", increase()); printf("increase() = %d\n", increase()); printf("此时 global_count = %d\n", global_count); global_count = 100; printf("改成 100 后再 increase() = %d\n", increase()); return 0;}在main()中修改变量global_count,extern_definition中的increase()也能看见新值101,这正证明了两个文件共用同一个变量:
初始 global_count = 0increase() = 1increase() = 2此时 global_count = 2改成 100 后再 increase() = 101auto
auto即自动变量,进入所在代码块时创建、离开代码块时销毁(存放在栈上)。
不过C语言中局部变量默认就是 auto,因此几乎从不写这个关键字,无论有没有写auto,最终的效果都是一样的。
#include <stdio.h>
int main(void){ auto int x = 10; /* 等价于 int x = 10; */ int y = 20; /* 默认就是 auto */ printf("auto int x = %d\n", x); printf("int y = %d\n", y); { auto int x = 5; /* 块内可再声明同名变量(遮蔽外层) */ printf("块内的 auto x = %d\n", x); } printf("离开块后 x = %d\n", x); /* 仍然是最外层的 10 */ return 0;}输出:
auto int x = 10int y = 20块内的 auto x = 5离开块后 x = 10NOTE
auto不能用于全局变量(全局变量是静态存储期)。
WARNINGC++ 中
auto的含义完全不同(自动类型推导),它允许编译器根据初始化表达式自动推导变量的类型,从而减少了手动指定类型的繁琐工作。在此仅简单说明:
C++11引入了auto关键字:auto n = 1;// 自动推导为int类型auto p = &n;// 自动推导为int*类型auto ch = "Hello world!";// 自动推导为const char*类型在C++14中,
auto可以作为函数的返回类型,进一步简化了代码:auto add(int a, int b) {return a + b;}
总结
| 修饰符 | 含义 | 作用 |
|---|---|---|
| const | 常量 | 初始化后只读,禁止修改 |
| volatile | 易变量 | 提示此变量的值可能意外改变,每次读取数值都从内存中读取,保证对特殊地址的稳定访问。 |
| static(局部变量) | 静态变量 | 将局部变量生命周期延长到整个程序,函数返回后仍保留值 |
| static(全局变量) | 静态变量 | 使全局变量仅有本文件可读,外部文件即使使用extern也无法访问,实现封装本模块 |
| register | 注册变量 | 请求编译器将此变量放入寄存器以快速读取(现代编译器基本忽略此请求) |
| extern | 外部变量 | 提示此变量的定义在外部文件,实现变量跨文件共享 |
| auto(C语言) | 自动变量 | 局部变量离开代码块自动销毁,C语言默认均为auto变量 |
| auto(C++) | 自动类型推导 | 根据初始化表达式自动推导变量的类型 |
部分信息可能已经过时





