静态本地变量
在本地变量定义时加个static修饰符就成为了静态本地变量,当函数离开的时候,静态本地变量会继续存在并保存其值,静态本地变量的初始化只会在第一次进入这个函数时做,以后进入函数时会保持上次离开时的值。
静态本地变量实际上是特殊的全局变量,他们位于相同的内存区域,静态本地变量具有全局的生存期,函数内的局部作用域,static在这里的意思就是局部作用域(本地可访问)
tips:不要使用全局变量来在函数间传递参数和结果,尽量避免使用全局变量。
编译预处理指令:
C语言中所有#开头的是编译预处理指令,他们不是C语言的成分,但是C语言程序离不开他们。
关于const的一些概念:
?一.顶层const与底层const
先重新回顾一下关于指针的const: 如果指针是一个常量时:int *const p=&a;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 而如果所指是一个常量时: const int *p=&a;
这个时候我们就引入两个新的概念——顶层const、底层const
顶层const:表示指针本身就是一个常量。 底层const:表示指针所指的对象是一个常量。
更一般的是,顶层const可以表示任意的对象是常量,这一点对任何数据类型都适用,如算数类型、类、指针等。底层const则与指针和引用等符合类型的基本类型部分有关。比较特殊的是,指针类型既可以是顶层const也可以是底层const,这一点类型和其他类型相比区别明显。
int i =0;
int *const p1 = &i;//这是个顶层const
const int ci = 42;//这是个顶层const
const int *p2 = &ci;//这是个底层const
const int *const p3 = p2; //靠右的const是顶层const,靠左的是底层const
const int &r= ci; //用于声明引用的const都是底层const
那这两者在具体用法中的区别是什么呢?
答案是在执行拷贝的时候,常量是顶层const和底层const的的时候区别明显。执行拷贝操作并不会改变拷贝对象的值,因此拷入拷出的对象是否是常量对顶层const都没什么影响,例如:
i = ci; //正确:拷贝ci的值,ci是一个顶层const,对此操作无影响
p2 = p3; //正确:p2和p3指向的对象类型相同,p3顶层const部分不影响
但是底层const的限制却不能忽视,执行拷贝操作的时候,拷入和拷出地对象必须具有相同的底层const 资格,或者两个对象的数据类型必须能够转换,一般来说,非常量可以转换成常量,反之则不行,例如:
int *p =p3; //错误:p3包含底层const的定义,而p没有
p2 = p3;//正确:他们都是底层const
p2 = &i;//正确:int*能转化成const int*
int &r = ci;// 错误:普通的int&不能绑定到int常量上
const int &r2 = i;// 正确:const int&可以绑定到一个普通int上
对于既是顶层又是底层const 指针时,可以不在乎他是一个顶层const,但是必须是个常量,这个时候看两者是不是都是底层const就行了。
二.一些关于const的引用小知识
对于引用来说,它的类型必须和其所引用对象的类型一致,但是有两个例外。第一种例外情况就是在初始化常量引用时,只要把该表达式的结果能转化成引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式:
int i =42;
const int &r1=i;//允许将const int&绑定到一个普通int对象上
const int &r2=42;//正确:r2是一个常量引用
const int &r3 = r1 *2;//正确:r3是一个常量引用
int &4=r1*2; //错误:r4是一个普通的非常量引用
再举个例子:
double dval = 3.14;
const int &ri = dval;
我们可以探究为什么会发生这种例外的情况,ri引用了一个int型的数,对它操作应该是整数运算,但dval确是一个双精度浮点数而非整数。为了确保让ri绑定一个整数,编译器把上述代码变成了如下形式:
const int temp = dval; // 由双精度浮点数生成一个临时的整型变量
const int &ri = temp; //让ri绑定这个临时量
在这种情况下,ri绑定了一个临时量对象——当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名对象。所以我们由此,还可以知道如果ri不是常量,那么ri这个绑定到临时量对象时,对ri引用dval,来改变dval的值就无法实现了,所以C++语言把这种行为规定为非法。
三.多个文件内关于const的定义:
当以编译时初始化的方式定义一个const对象时,
const int bufSize = 512 //输入缓冲区大小
编译器在编译过程中会把该用到此变量的地方全部替换成对应的值——512。为了要执行上述操作,编译器必须知道变量的初始值才行,而如果多个文件中都要用此const对象时,而又不想在多个文件对同一个变量重复定义的话(默认情况下,const对象仅为当前文件内有效),解决的办法就是,对于const变量不管是声明还是定义都添加extern关键字,这样只需要定义一次就可以了。
//file_1.cc定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fcn();
//file_1.h头文件
extern const int bufSize; //与file_cc中定义的bufSize是同一个
有上述程序可知,在源代码文件中定义并初始化了bufSize。还用了extern加以限定使其被其他文件使用。在这个头文件中的声明也有extern做了限定,其作用是指明bufSize并非本文件独有,它的定义将在别处也出现。
三.constexpr和常量表达式
常量表达式:值不会改变,并且在编译过程中就能得到计算结果的表达式。显然,字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。
一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定,例如:
const int max_files = 20;//max_files是常量表达式
const int limit = max_files +1; //limit是常量表达式
int staff_size = 27; //staff_size 不是常量表达式
const int sz = get_size(); //sz不是常量表达式
这里解释一下第四条,sz本身是一个常量,但他的具体值直到运行时才能获取到,所以也不是常量表达式。
constexpr:C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化:
constexpr int mf = 20;// 20是常量表达式
constexpr int limit = mf+1; // mf + 1是常量表达式
constexpr int sz = size(); //只有当size是一个constexpr函数时,才是一条正确的声明语句
限定符constexper用在指针上面,可以看做是个顶层const,仅与指针有关,与所指对象无关。constexper指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。
函数体内定义的变量一般来说并非存在固定地址中,这意味着不能用constexper指针指向这样的变量。还会有一些,允许函数定义一类有效范围超出函数本身的变量,这类变量和定义在函数体之外的变量一样也有固定地址。
所以我们这时候有一个“字面值类型”:声明constexpr 时用到的类型,这些类型一般比较简单,值也显而易见、容易得到。目前学到的来说,算术类型、引用和指针都属于字面值类型。而自定义的类,Sales_item、IO库、string类型则不属于字面值类型。
|