extern、static 与 const 关键字

一、extern 关键字

extern 用在变量或者函数的声明前,用来说明 “此变量/函数是在别处定义的,要在此处引用”。extern 关键字可以重复声明同一个变量而不报错。

1、引用同一个文件中的变量:提前声明。使用 extern 关键字修饰的变量,定义的代码可以晚于使用的代码。对于全局函数来说,这个 extern 可以省略,它默认就是 extern 的。

#include<stdio.h>

void func();    // 默认 extern

int main() {
    func();    // 3
    extern int num;    // 告诉编译器 num 这个变量是存在的, 但不是在这之前声明的, 你到别的地方找找吧
    printf("%d",num);    // 3
    return 0;
}

int num = 3;

void func() {
    printf("%d\n",num);
}

2、引用另一个文件中的变量。extern 不能省略。

// a.c
char a = 'A';
void msg() {
    printf("Hello\n"); 
}

// main.c
extern void msg();    // 先声明再使用
int main(void) {    
    extern char a;    // 先声明再使用
    printf("%c", a);
    msg();
    return 0;
}

程序的运行结果是:

A Hello

问题:如果我想引用一个全局变量或函数,我只要直接在源文件中包含 #include <xxx.h> 不就可以了么,为什么还要用 extern 呢?
头文件实际上只是对用户的说明——函数、参数、各种各样的接口的说明。那么头文件里面放的自然就是关于函数、变量、类的“声明”了,而不是“定义”。最好不要在头文件里定义什么东西。比如全局变量:

#ifndef _XX_头文件.H
#define _XX_头文件.H
int A;
#endif

如果这个头文件被多次引用的话,A 会被重复定义。只不过有了这个 #ifndef 的条件编译,能保证你的头文件只被引用一次。但若多个文件包含这个头文件时还是会出错的,因为宏名有效范围仅限于本源文件,所以在多个文件编译时虽然不会出错的,但在链接时就会报错,说你多处定义了同一个变量。

3、在 C++ 中 extern 还有另外一种作用,用于指示 C 或者 C++ 函数的调用规范。比如在 C++ 中调用 C 库函数,就需要在 C++ 程序中用 extern “C” 声明要引用的函数。告诉链接器在链接的时候用 C 函数规范来链接。主要原因是 C++ 和 C 程序编译完成后在目标代码中命名规则不同,用此来解决名字匹配的问题。

二、static 关键字

static 的用法:修饰函数、局部变量和全局变量。每种用法的意图或作用不尽相同,主要有以下三点:

1、最重要的一点:隐藏

当我们同时编译多个文件时,所有未加 static 前缀的全局变量和函数都具有全局可见性。用 static 进行修饰的话会使其作用域仅限于本文件。如 一.2 中的例子把 a.c 中的 a 变量用 static 修饰,这个变量就对 main.c 不可见了:

main.cpp:(.rdata$.refptr.a[.refptr.a]+0x0): undefined reference to `a'

利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。

在 C++ 中 static 还可以对类中的某个函数用 static 进行修饰,表示该函数属于一个类而不属于此类的任何对象;如果对类中的某个变量进行 static 修饰,表示该变量为类以及其所有的对象所有。它们在存储空间中都只存在一个副本。可以通过类和对象去调用。对于静态成员函数,只能访问静态成员函数和静态成员变量,不能访问非静态成员函数或者变量

对于函数来讲,static 的作用仅限于隐藏。对于变量,static 还有下面两个作用。

2、保持变量内容的持久

存储在静态存储区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和 static 变量,只不过和全局变量比起来,static 可以控制变量的可见范围,说到底 static 还是用来隐藏的。

这种用法不常见,但还是举个简单的例子帮助理解:

#include <stdio.h>

int fun(void) {
    static int count = 10;    // 此赋值语句只有第一次会被执行
    return count--;
}

int count = 1;

int main(void) {    
    printf("global\t\tlocal static\n");
    for(; count <= 10; ++count)
        printf("%d\t\t%d\n", count, fun());    
    return 0;
}

程序的运行结果是:

global          local static
1               10
2               9
3               8
4               7
5               6
6               5
7               4
8               3
9               2
10              1

3、技巧性的作用:默认初始化为 0

全局变量和 static 变量均位于静态存储区,静态存储区内存中所有的字节默认值都是 0x00,某些时候这一特点可以减少程序员的工作量。

比如初始化一个稀疏矩阵,我们可能会一个一个地把所有元素都置 0,然后把不是 0 的几个元素赋值。如果定义成静态的,就省去了一开始置 0 的操作。

再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加 ’\0’ 太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是 ’\0’。

不妨举个例子验证一下:

#include <stdio.h>

int a;
int main(void) {
    int i;
    static char str[10];
    printf("integer: %d;  string: (begin)%s(end)", a, str);
    return 0;
}

程序的运行结果如下

integer: 0;  string: (begin)(end)

总结
首先 static 最主要的功能是隐藏,其次因为 static 变量存放在静态存储区,所以它具备持久性和默认值 0。

问题:应该在什么时候考虑使用 static?
1、当你不想创建对象来调用类的方法,或访问类的变量的时候(例如一些工具类的方法);
2、当你的变量想保存上一次的结果的时候。

三、const 关键字

const 关键字用来修饰右边的基本变量和指针变量。被 const 修饰的变量只读,也就是只能获取,不能修改。

const 与 指针
如果 const 关键字不涉及到指针,我们很好理解,下面是涉及到指针的情况:

int b = 500;
const int* a = &b;    // [1]
int const *a = &b;    // [2]
int* const a = &b;    // [3]
const int* const a = &b;    // [4]

首先 const 与变量类型的顺序不会对修饰效果造成影响,也就是情况 [1] 与 [2] 等价。

其次,如果 const 位于 * 的左侧,那么 const 就是用来修饰指针所指向的变量,即指针指向的是常量,在情况 [1] 与 [2] 中,不能 *a = 3,但用另一个 const int* 来为其赋值是可以的,因为这样相当于只变更了指向的地址;反之如果 const 位于 * 的右侧,const 就是修饰指针本身,即指针的值(一个地址)是常量,指针所指向的内容不是常量,在情况 [3] 中,a++ 操作是错误的。

情况 [4] 为指针本身和指向的内容均为常量。

const 与引用
如果在声明引用时用 const 修饰,那么该引用就被称为常引用。常引用所引用的对象不能被更新。如果用常引用做为形参,便不会产生对实参不希望的修改。如:

int a = 5;
const int &b = a;

其中,b 是一个常引用,它所引用的对象不允许更改,如果出现:

b = 6;

则是非法的(如果写 a = 6 不会出问题,因为 a 不是 const 类型变量)。

const 与类成员

  1. 常数据成员
    类的数据成员可以是常量或常引用。如果在一个类中声明了常数据成员,那么构造函数就只能通过初始化列表对该数据成员进行初始化,而任何其他的函数都不能对该成员函数赋值。

  2. 常成员函数
    在类中使用关键字 const 的函数称为常成员函数,const 是函数类型的组成部分,所以在函数的实现部分也要带关键字 const。在常成员函数中不得修改类中的任何数据成员的值。

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