变量作用范围 存储持续性
自动存储持续性:在程序开始执行其所属的函数或者代码块(花括号括起来的部分)时被创建,执行完函数或者代码块后,其内存空间被释放。
静态存储持续性:在程序的整个运行过程中都存在
线程存储持续性(c++11)使用thread_local声明,其生命周期与所属线程一样长
动态存储持续性:使用new和delete来动态管理
连接性
外部连接性:可在其他文件中访问
内部链接性:只能在当前文件中访问
无链接性:只能在当前函数或代码块中访问
静态初始化和动态初始化
静态初始化:在编译器处理文件时对变量进行初始化
动态初始化:编译后初始化
静态持续变量 编译器将分配固定的内存块来存储所有的静态变量,这些变量在整个程序执行期间一直存在。并且,如果没有显示地初始化静态变量,编译器将把它设置为0。在默认情况下,静态数组和结构将每个元素或成员的所有位(bit)都设置为0。
声明三种链接性的静态持续变量的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int global = 1000 ;static int one_file = 50 ;int main () { ...; } void func () { static int count = 0 ; }
静态外部变量 静态持续性,外部链接性的变量,定义有两种方式:
1 2 3 4 5 double up = 0.0 ;extern double up = 0.0 ;
声明外部变量的方式:
静态内部变量 使用static定义并同时初始化声明的变量就是静态内部变量
static声明的静态内部变量将会隐藏常规同名外部变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int errors = 20 ;static int errors = 5 ;void func () { cout << errors; }
同名变量覆盖规则:外部可见变量被本文件可见变量覆盖,局部变量覆盖全局变量
const定义的全局变量 默认情况下全局变量的链接性为外部的,但const全局变量的链接性为内部的。也就是全局const定义就像使用了static说明符一样。
但是const可以和extern连用,声明链接性为外部的常量:
1 extern const int value = 10 ;
函数的持续性和链接性
函数默认情况下是静态的,即在整个程序执行期间都一直存在,默认情况下,函数的链接性为外部的,即多个文件可见
可以使用static关键字将函数的链接性设置为内部,且静态定义将覆盖外部定义,且必须在函数的定义和实现中都使用static关键字
内联函数不受这项规则的约束,内联函数的链接性为内部的,但是c++要求同一个函数的所有内联定义都必须相同
如果定义了一个与库函数同名的函数,编译器将使用程序员定义的版本
语言链接性 链接程序要求每个不同的函数都有不同的符号名,在C语言中,一个名称只对应一个函数。例如c编译器可能将spiff这样的函数翻译为_spiff。
但在c++中,由于函数重载,一个名称可能对应多个函数,必须将这些函数翻译为不同的符号名称。例如可能将spiff(int)翻译为spiff_i,将spiff(double, double)翻译成spiff_d_d。
如果要在c++程序中使用c库中预编译的函数,由于有c编译器预编译的函数符号和c++要寻找的函数符号不相同,所有可能c++编译器会找不到这些函数。为了解决这个问题,可以用函数原型来指出要使用的约定:
1 2 3 extern "C" void spiff (int ) ;extern void spoff (int ) ;extern "C++" void spaff (int ) ;
另外的形式:
1 2 3 4 5 6 7 8 #ifdef __cplusplus extern "C" {#endif void some_func () ; #ifdef __cplusplus } #endif
注意由于extern "C"之后将按照C的方式翻译函数的名称,所以此时将不能在extern "C" 中进行函数重载。
new、new[]和定位new new、new[]的调用实质上是分别调用:
1 2 void * operator new (std ::size_t ) ;void * operator new [](std ::size_t );
要使用定位new特性,首先需要包含头文件new。定位new的用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct chaff { char *dross; int slag; chaff(const char *str = "" , int slag = 0 ) : slag(slag) { dross = new char [strlen (str) + 1 ]; strcpy (dross, str, strlen (str)); } } char buffer1[50 ];char buffer2[500 ];chaff *p1 = new (buffer1) chaff("1" ); chaff *p2 = new (buffer2) int [20 ]; chaff *p3 = new (buffer1) chaff("2" ); chaff *p4 = new (buffer1 + sizeof (chaff)) chaff("3" );
delete和delete[]只能用于指向常规new运算符分配的堆内存,所以delete不一定能作用于定位new运算符所指向的区域,所以最好不要delete定位new运算符返回的指针
new 和 delete、new[] 和 delete[]要配套使用,delete和delete[]可以作用于空指针,delete和delete[]不能同时释放同一内存空间两次,除非是空指针,否则将会出现runtime error。
说明符和限定符 cv-限定符
假设编译器发现,程序在几条语句中两次使用了某个变量的值,则编译器可能不是让程序查找这个值两次,而是将这个值缓存到寄存器中。这种优化假设变量的值在这两次使用之间不会发生变化。如果不将变量声明为volatile,则编译器讲进行这种优化;将变量声明为volatile则相当于告诉编译器,不要进行这种优化。volatile的使用一般在多进程或多线程的情况下,可能其他进程或线程或改变某个内存空间的值,如果不两次获取的话,程序就会出错。
mutable限定符 mutable用来指出即使结构或类变量为const,其某个成员也可以被修改。
1 2 3 4 5 6 7 struct data { char name[30 ]; mutable int access; } const data veep{"tom" , 0 };veep.access++;
命名空间 “::”放在变量前面,该运算符表示使用变量的全局版本
使用namespace创建名称空间
1 2 3 4 5 6 7 namespace Tom { int pal; } namespace Jack { int pal; }
名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中。
名称空间是开放的,即可以把名称加入到已有的名称空间中:
1 2 3 4 5 6 namespace Tom { void func () { ... } }
using声明和using编译指令 using声明使特定的标示符可用,using编译指令使整个名称空间可用
1 2 using Tom::func;using namespace Tom;
using编译指令和using声明的区别 如果某个名称已经在函数中声明了,则不能用using声明导入相同的名称。然而,使用了using编译指令(using namespace)时,将进行名称解析。如果使用using编译指令导入一个已经在函数中声明的名称,则局部名称将隐藏名称空间名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 namespace Jill{ double bucket (double n) { ... } double fetch; struct Hill { ... }; } char fetch;int main () { using namespace Jill; Hill Thrill; double water = bucket(2 ); double fetch; cin >> fetch; cin >> ::fectch; cin >> Jill::fetch; ... } int foom () { int fetch; using Jill::fetch; Hill top; Jill::Hill hill; }
关于命名空间的一些小tip