const、static、extern

加上const修饰符,这个值就不可改了,
所以必须在初始化的时候给值,和加不加引用无关。
const全局/局部变量 (1)const 全局变量

#include using namespace std; extern const int a; int main() { //const volatile int a = 7; int *p = (int *)(&a); *p = 8; cout << "a=" << a << endl; cout << "*p=" << *p; return 0; }

const、static、extern
文章图片

const全局变量使得我们指定了一个语义约束,即被修饰的全局变量不允许被修改,而编译器会强制实施这个约束。
(2)const局部变量
#include using namespace std; int main() { //const volatile int a = 7; const int a = 7; int *p = (int *)(&a); *p = 8; cout << "a=" << a << endl; cout << "*p=" << *p; system("pause"); return 0; }

const、static、extern
文章图片

运行结果显示const局部变量被修改了,但是在使用变量名输出时,编译器会出现一种类似宏定义的功能一样的行为,将变量名替换为初始值。可见,const局部变量并不能做到真正的不变,而是编译器对其进行了一些优化行为,这导致了const局部变量与真实值产生了不一致。(如果想获取修改后的const局部变量真实值,该怎么办呢?答案是使用volatile关键字。)
(3)const修饰指针
const修饰指针,涉及到两个很重要的概念,顶层const和底层const
指针自身是一个对象,它的值为一个整数,表明指向对象的内存地址。因此指针长度所指向对象类型无关,在32位系统下为4字节,64位系统下为8字节。进而,指针本身是否是常量以及所指向的对象是否是常量就是两个独立的问题。
【const、static、extern】顶层const(top-level const): 指针本身是个常量
底层const(low-level const): 指针指向对象是一个常量
int a = 1; int b = 2; const int* p1 = &a; // 底层 int* const p2 = &a; // 顶层

(4)const修饰函数参数
  • 函数参数为值传递:值传递(pass-by-value)是传递一份参数的拷贝给函数,因此不论函数体代码如何运行,也只会修改拷贝而无法修改原始对象,这种情况不需要将参数声明为const。
  • 函数参数为指针:指针传递(pass-by-pointer)只会进行浅拷贝,拷贝一份指针给函数,而不会拷贝一份原始对象。因此,给指针参数加上顶层const可以防止指针指向被篡改,加上底层const可以防止指向对象被篡改。
  • 函数参数为引用:引用传递(pass-by-reference)有一个很重要的作用,由于引用就是对象的一个别名,因此不需要拷贝对象,减小了开销。这同时也导致可以通过修改引用直接修改原始对象(毕竟引用和原始对象其实是同一个东西),因此,大多数时候,推荐函数参数设置为pass-by-reference-to-const。给引用加上底层const,既可以减小拷贝开销,又可以防止修改底层所引用的对象。
(5)const 成员函数
将const实施于成员函数上,主要是防止成员函数修改类对象的内容。良好的类接口设计应该确保如果一个成员函数功能上不需要修改对象的内容,该成员函数应该加上const修饰。
如果const成员函数想修改成员变量值,可以用mutable修饰目标成员变量。
如果一个类对象为const 对象,语义上说明该对象的值不可改变,因此该const对象只能调用const成员函数,因为非const成员函数不能保证不修改对象值,编译器会禁止这种会造成隐患的行为。
static 关键字总结(4种) (1)隐藏 (与extern相反,表明该变量只在本文件.c中有用) 当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。
举例来说明。同时编译两个源文件,一个是a.c,另一个是main.c。
(2)保持变量内容的持久。(static变量中的记忆功能和全局生存期,只能被初始化1次) 存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。虽然这种用法不常见
(3)static的第三个作用是默认初始化为0(static变量) 其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加‘\0’; 太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是‘\0’; 不妨做个小实验验证一下。
最后对static的三条作用做一句话总结。首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0.
(4)static的第四个作用:C++中的类成员声明static 在类中声明static变量或者函数时,初始化时使用作用域运算符(类名::函数名–来访问)来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员,这样就出现以下作用:
(1)类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致 了它仅能访问类的静态数据和静态成员函数。
(2)不能将静态成员函数定义为虚函数
(3)由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊 ,变量地址是指向其数据类型的指针 ,函数地址类型是一个“nonmember函数指针”。
(4)由于静态成员函数没有this指针,所以就差不多等同于nonmember函数,结果就 产生了一个意想不到的好处:成为一个callback函数,使得我们得以将C++和C-based X W indow系统结合,同时也成功的应用于线程函数身上。 (这条没遇见过)
(5)static并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问 时间,节省了子类的内存空间。
(6)静态数据成员在<定义或说明>时前面加关键字static。
(7)静态数据成员是静态存储的,所以必须对它进行初始化。 (程序员手动初始化,否则编译时一般不会报错,但是在Link时会报错误)
(8)静态成员初始化与一般数据成员初始化不同:

初始化在**类体外**进行,而前面不加static,以免与一般静态变量或对象相混淆;
初始化时不加该成员的访问权限控制符private,public等;
初始化时使用作用域运算符来标明它所属类;
所以我们得出静态数据成员初始化的格式:
<数据类型><类名>::<静态数据成员名>=<值>

(9)为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志。
extern关键字 (1)声明外部变量(用在变量声明与定义中) 变量能且只能被定义一次,但是可以被声明多次。
extern 用来修饰变量或者函数名,用以在一个文件中定义,但是可以在其他文件中进行使用的。
  • 声明extern关键字的全局变量(注意必须全局)和函数可以使得它们能够跨文件被访问
  • 如果函数的声明中带有关键字extern,仅仅是暗示这个函数可能在别的源文件里定义,没有其它作用
(2)在C++文件中调用C方式编译的函数 C++语言在编译的时候为了解决函数的多态问题,会将函数名和参数联合起来生成一个中间的函数名称,而C语言则不会,因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern “C”进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间函数名。

    推荐阅读