一、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 位于 * 的右侧,const 就是修饰指针本身,即指针的值(一个地址)是常量,指针所指向的内容不是常量,在情况 [3] 中,a++
操作是错误的。
情况 [4] 为指针本身和指向的内容均为常量。
const 与引用
如果在声明引用时用 const 修饰,那么该引用就被称为常引用。常引用所引用的对象不能被更新。如果用常引用做为形参,便不会产生对实参不希望的修改。如:
int a = 5;
const int &b = a;
其中,b 是一个常引用,它所引用的对象不允许更改,如果出现:
b = 6;
则是非法的(如果写 a = 6
不会出问题,因为 a 不是 const 类型变量)。
const 与类成员
-
常数据成员
类的数据成员可以是常量或常引用。如果在一个类中声明了常数据成员,那么构造函数就只能通过初始化列表对该数据成员进行初始化,而任何其他的函数都不能对该成员函数赋值。 -
常成员函数
在类中使用关键字 const 的函数称为常成员函数,const 是函数类型的组成部分,所以在函数的实现部分也要带关键字 const。在常成员函数中不得修改类中的任何数据成员的值。
网友评论