C++中的extern和const总结

作者:云梦泽
时间:2013.11.08

一、extern

extern用于告诉编译器存在着一个变量或者函数,当前编译语句前如果没有定义的话,那么该变量或者函数定义在文件的后头或者其它文件中,即提示编译器遇到此变量和函数时在其他模块中寻找其定义。且必须加上修饰符说明类型,如:

extern int i;
extern void func();

在那么其他文件中的变量和函数如何来调用呢?有如下两种方法

(1)将变量或函数定义在头文件中

(2)通过extern调用,此时变量或函数定义声明在其他源文件中

注意一:如果一个工程编译cpp文件时,在把多个目标文件链接成为可执行文件,而两个或多个文件中,定义了相同的全局变量,那么,程序编译的时候不会报错,因为编译器单独编译每个文件,在链接可执行文件的时候,由于多个目标文件中含有相同的全局变量,而生成可执行文件的时候,任何文件中定义的全局变量对其它目标文件都是可见的,此时由于变量定义冲突而会发生错误。

即extern的同一名称变量或函数在同一工程下来源应该是唯一的。

 

注意2:定义在.h文件中的函数和变量不能使用extern变量声明,原因是#include <filename>在预编译的时候将.h文件中的内容插入了cpp文件中,因此编译器找得到在其它.h文件中定义的变量或者函数。编译的时候,只编译cpp文件的内容,.h文件时不参与编译,如果使用extern声明在.h文件中定义的变量或者函数,那么声明为extern的变量和函数在其它.cpp文件中找不到时(找到了也不是我们想要的)。

程序编译就会发生了错误。即extern的变量或函数都是在源文件中找,而不是去头文件中找。

只在头文件中做声明,真理总是这么简单

一般定义static全局变量时,都把它放在原文件中而不是头文件,这样就不会给其他模块造成不必要的信息污染,同样记住这个原则吧!

 

二、const

先来扯一句原则:Use const whenever you need。

因为被const 修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。

1.定义const对象

const int buffsize=512;   //常量定义后不能再修改,定义时必须初始化

2.const对象默认为文件的局部变量,

和static具有相同的特性,const对象只能作用于本编译模块,但是const可以与extern连用来声明该常量可以作用于其他编译模块,即被其他文件访问。例如

//file1.cc
extern const int bufsize=512; //定义并初始化

//file2.cc
extern const int bufsize;  //声明
for(int index=0;index!=bufsize;++index)
{
//...
}
非const变量默认为extern,即可被其它文件extern调用,但要使const变量能够在其它的文件中访问,必有显示指定为extern。


3.指向const对象的指针

const int *p;

p是一个指向int类型const对象的指针,const限定了指针p所指向的类型对象是死的,即指向的对象是个常量不可改变,而并非p本身。也就是说p本身并不是const。在定义时不需要对它进行初始化,还可以给p重新赋值,使其指向另一个const对象或对应类型int的对象(即虽然p为const int,但可指向int对象,反之不可,下面会说道)。但不能通过p修改所指向对象的值。

const int a=0; p=&a;     //可以。
p=20;              //不可以。

结论:这种指向const对象的指针只是限制不能修改p指向对象的数值,而不是限制p指向什么对象。

另外:把一个const对象的地址赋给一个不是指向const对象的指针也是不行的。例如

const int b=10;
int *p2=&b;       //error
const int *p3=&b; //ok

结论:因为变量b有const修饰,不能被修改。但指针p2是一个普通指针,可以修改指向对象的值(特殊指针指向特殊对象,但也可指向普通对象,只是普通指针不能指向特殊对象),两种声明矛盾,所以不合法。而指向const对象的指针不允许修改指针指向对象的数值,所以这种方式合法。

另外C++还规定,const关键字放在类型或变量名之前是等价的.

const int n=5;    //same as below
int const m=10;

const int *p;    //same as below  const (int) * p
int const *q;    // (int) const *p


4.const指针

int c=20;
int *const p4=&c;


指针p4称为const指针。它和指向const对象的指针恰好相反,它不能够修改所指向对象,但却能够修改指向对象的数值。另外,这种指针在声明时必须初始化。

 

5.指向const对象的const指针

const int d=30;
const int *const d5=&d;

指针d5既不能修改指向的对象,也不能修改只想对象的值。

 

6.总结

指针本身是常量不可变

(char*) const pContent; 
char *   const pContent; 
const (char*) pContent; 

指针所指向的内容是常量不可变

const (char) *pContent; 
const char *   pContent
(char) const *pContent; 
char const    *pContent; 

两者都不可变

const char* const pContent; 

7. const修饰函数参数

const修饰函数参数是最广泛的一种用途,表示函数体中不能修改参数的值(包括参数本身的值或者参数其中包含的值)。如果输入参数采用“指针传递”,那么加const 修饰可以防止意外地改动该指针,起到保护作用。而如果参数作输出用,不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加const 修饰,否则该参数将失去输出功能。例如:
void StringCopy(char *strDestination, const char *strSource);

其中strSource 是输入参数,strDestination 是输出参数。给strSource 加上const修饰后,如果函数体内的语句试图改动strSource 的内容,编译器将指出错误。

如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加const 修饰。

另外:

为了提高效率,可以将函数声明改为void Func(A &a),因为“引用传递”仅借用一下参数的别名而已,不需要产生临时对象。但是函数void Func(A &a) 存在一个缺点:“引用传递”有可能改变参数a,这是我们不期望的。解决这个问题很容易,加const修饰即可,因此函数最终成为void Func(const A &a)。

以此类推,是否应将void Func(int x) 改写为void Func(const int &x),以便提高效率?完全没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。

总结:对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const 引用传递”,目的是提高效率。例如将void Func(A a) 改为void Func(const A &a)。

对于内部数据类型的输入参数,不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x) 不应该改为void Func(const int &x)。

void function(const int Var); //传递过来的参数在函数内不可以改变(无意义,因为Var本身就是形参,只是一个副本)

void function(const char* Var); //参数指针所指内容为常量不可变

void function(char* const Var); //参数指针本身为常量不可变(也无意义,因为char* Var也是形参)

参数为引用,为了增加效率同时防止修改。

修饰引用参数时:

void function(const Class& Var);//引用参数在函数内不可以改变

void function(const TYPE& Var); //引用参数在函数内为常量不可变

 

8.用const 修饰函数的返回值

如果给以“指针传递”方式的函数返回值加const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针(不能赋给普通指针啦,和前面一样)。例如

const char * GetString(void);
char *str = GetString();//错误
const char *str = GetString();//正确

如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值
例如不要把函数int GetInt(void) 写成const int GetInt(void)。
同理不要把函数A GetA(void) 写成const A GetA(void),其中A 为用户自定义的数据类型。
如果返回值不是内部数据类型,将函数A GetA(void) 改写为const A & GetA(void)的确能提高效率。

但此时千万千万要小心,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错。

9.const修饰类对象/对象指针/对象引用

const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改。对于对象指针和对象引用也是一样。 
const修饰的对象,该对象的任何非const成员函数都不能被调用,因为任何非const成员函数会有修改成员变量的企图。

例如:

class AAA 

{
void func1(); 

void func2() const; 

}

10. const修饰成员变量

const修饰类的成员函数,表示成员常量,不能被修改,同时它只能在初始化列表中赋值。

class A 

{ 

   const int nValue;       //成员常量不能被修改 

   A(int x): nValue(x) {}; //只能在初始化列表中赋值 

} 

规则:

a. const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数.
b. const对象的成员是不可修改的,然而const对象通过指针维护的对象却是可以修改的.
c. const成员函数不可以修改对象的数据,不管对象是否具有const性质.它在编译时,以是否修改成员数据为依据,进行检查.
e. 然而加上mutable修饰符的数据成员,对于任何情况下通过任何手段都可修改,自然此时的const成员函数是可以修改它的。

11. const修饰成员函数

const修饰类的成员函数,则该成员函数不能修改类中任何非const成员函数。一般写在函数的最后来修饰。
class A 

{  
void function()const; //常成员函数, 它不改变对象的成员变量. 也不能调用类中任何非const成员函数。 

} 


对于const类对象/指针/引用,只能调用类的const成员函数,因此,const修饰成员函数的最重要作用就是限制对于const对象的使用。

12. const常量与define宏定义的区别

(1)编译器处理方式不同

define宏是在预处理阶段展开。

const常量是编译运行阶段使用。

(2)类型和安全检查不同

define宏没有类型,不做任何类型检查,仅仅是展开。

const常量有具体的类型,在编译阶段会执行类型检查。

(3)存储方式不同

define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。

const常量会在内存中分配(可以是堆中也可以是栈中)

 

 



 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值