看日期,隔了10天了……痛恨自己的懒惰。来个长篇幅的,以稍补愧疚之心。
2011-10-22(Memory Models And Namespace)
1、头文件一般包括:函数原型、符号常数(#define和const定义)、结构体声明、模板声明、内联函数、类声明。不能有函数的定义或者变量的声明。因为如果在一个大的程序中,有两个子文件同时include了这个头文件,则在同一个程序中包含了两个函数的定义模式不允许的。有一种方法可以避免这样的“重复定义”发生。这个方法是为了避免在不知情的情况下在同一源文件在包含多次同一头文件,而不是作为可以在头文件中定义函数的理由。
#ifndef RARE_NAME
#define RARE_NAME
//... ...
#endif
意思是:如果编译器没有定义(#ifndef
->if not define)这个RARE_NAME这个变量,则从下一行至#endif之间的代码编译。这个RARE_NAME是其他地方不太可能被定义的名称。2、C++存储数据的三种方案。这些方案的区别在于保留在内存中的时间。
①自动储存连续性(Automatic storage duration)。创建于所属函数或者代码块被执行之时,释放于执行完毕。故称“自动”。C++中有两种有这样特性的变量;
②静态储存连续性(Static storage duration)。函数外定义的变量或者被修饰为static,整个程序运行中都存在;
③动态存储连续性(Dynamic storage duration)。用new操作符分配,直到用delete或者程序结束才释放。
3、自动储存连续性。这样的变量在不同函数(包括main)或代码块中互不影响。如果在代码块中存在另一个代码块,且定义了同一的变量,即:
{
int a = 0;
{
int a = 5;
//... ...
}
}
在子代码块中的变量块会屏蔽掉父代码块中的。可以使用关键字auto去强调(注意是强调作用而不是将一个非自动变量转变成自动)这个是一个自动储存连续性的变量。*自动变量一般用栈来管理。当函数被调用,自动变量加入到栈中,结束之时,栈顶指针重设为未加入变量位置。
*另一种有这个特性的是寄存器变量(register variable)。即用寄存器直接储存变量而不是一般的用栈储存,这样做的好处是使用和修改快速。使用关键字register来提醒:
register int fast_counter;
注意是用了字眼“提醒”,编译器不一定会满足上述要求。不过这种“不确定性”不影响整体速度或者影响非常小,因为编译器会自动使用寄存器来储存变量,比如for循环的计数器。
4、静态储存连续性。有这样特性变量有三种:外部连接性(external linkage)、内部连接性(internal linkage)和无连接性,区别在于作用域(scope)的大小。当在函数外面定义变量且无static关键字修饰,为外部连接性;当在函数外定义变量且有static关键字修饰,为内部连接性;当在一个函数内或代码块定义变量且有static关键字修饰,为无连接性。如:
int globe = 5; //external linkage
static int file = 10; //internal linkage
int main(){}
func()
{
static int count = 1; //no linkage
}
不管哪种,都必须赋值为常量(const、enum、sizeof()返回值)。如果没赋值,将自动赋值为零。①外部连接性。有这个性质的变量作用域将是整个程序(可有多个源文件),所以也被称为全局变量(global variable)。如此它与自动变量有交集,便有一些新的处理方式。看下例:
#include <iostream>
void testg();
void modify();
int g = 5;
using namespace std;
int main()
{
cout << "Before modify() & testg(),g = " << g << endl;
modify();
cout << "After modify(), g = " << g << endl;
testg();
cout << "After testg(), g = " << g << endl;
}
void modify()
{
extern int g;
g += 5;
}
void testg()
{
int g = 20;
cout << g << " ";
cout << ::g++ <<endl;
}
输出: Before modify() & testg(),g = 5
After modify(), g = 10
20 10
After testg(), g = 11
*先看函数testg(),这里声明了一个与全局变量同名的局部变量,与前述一致,将屏蔽掉全局变量。操作符"::"作用是调用屏蔽掉的全局变量,对::g的修改即是对全局变量的修改;
*函数modify()出现关键字extern,这个关键字的作用是说明这个函数里g不是局部变量,对g的操作即对全局变量操作。说到这里,有必要说下定义性声明(defining declaration)和引用性声明(referencing declaration)。顾名思义,定义性是带动作的,是要给变量分配内存空间的,简称定义;引用性则是引用别的同名变量,简称声明(狭义)。这里,extern int g;是引用性声明,声明这个g是已经存在的,把它引用到这个modify函数;int g = 5;则是定义性声明。
*另外,语句:
extern int g = 10;
是非法的。毕竟,初始化是包括分配内存空间和赋值两个过程,上面这个语句缺少前者。
②内部链接性。内部和外部连接性的关系与内部连接性和自由变量关系相像:当一个文件声明了一个外部连接性变量,另一个文件要使用它,必须使用引用性声明;如果这个文件定义了一个内部连接性的变量,会屏蔽掉另一个文件中的同名全局变量。
③无连接性。其作用域仍然是代码块(局部的),不过在代码块不活动时,仍然存在。
5、关键字mutable和volatile。
①mutable。这个关键字的作用是当一个整体(比如结构体)被修饰为const的时候,如果里面的成员被修饰为mutable,则在整体被修饰为const的情况下,这个成员的仍可修改的,如:
struct data
{
mutable int i;
double d;
}
//... ...
const data num{5, 7.8};
num.i = 10; //valid
num.d = 12.5; //invalid
②volatile。用作改善编译器的优化能力。比如当编译器发现程序在几条语句中使用了同一变量,它就会将这个变量存在寄存器中而不是连续访问变量几次。但这种优化是建立在变量不被修改的情况下进行的。当然如果是程序自身修改变量,编译器不会进行这样的优化,但如果这种修改是来自物理的呢,比如硬件运行的时候会修改某些值。这是如果把这样不受程序控制,不稳的变量修饰为volatile,编译器就必定不会进行这样的优化。*mutable、auto、static、extern、register都被称为储存修饰符(storage class specifier);const、volatile被称为cv-限定符(cv-qualifier)。
6、默认情况下,在函数外用const修饰变量与用static获得的连接性等价。这可能考虑到符号常量是定义在头文件里面的,一个正常现象是:一个程序里有多个源文件,每个源文件包含了同一头文件。如果这个头文件里面定义的符号常量是全局变量,则会犯重复定义的错误。当然,如果有特殊需要,可以将符号常量设为全局变量:
extern const int gobal = 100;
有与符号常量在声明的同时必须初始化,所以这里被extern修饰仍可初始化。
7、函数的链接性。C++不允许在一个函数中定义另一个函数,所以所有的函数都为静态,即在存在于整个程序生命期间。默认情况下,函数是外部连接性的,即可在文件中共享,但另一个文件想使用别的文件定义的函数必须提供函数原型,这个函数原型可以在前面加个关键字extern,表明这个函数是用别人的,不是这个源文件定义的。可以使用static关键字定义一个内部连接性的函数:
static returnType FName(parameterList);
与变量一样,内部的可以屏蔽外部的。
*C++关于函数的定义有一条规则:非内联函数程序中只能定义一次。内联函数不受这条规则约束,因为它定义在头文件中。即便不是在头文件中定义,在同一程序中不同文件中的内联函数的定义也要求相同。
8、布局new操作符(placement new opeartion)。常规new操作符所找到的空间是随机地,但C++却可以做到,前提是程序员提供一个有名字的内存空间,后使用new操作符使用这个空间。看个例子:
#include <iostream>
#include <new>
using namespace std;
const int LEN = 512;
char buffer[LEN];
int main()
{
int *p1, *p2;
p1 = new int;
p2 = new (buffer) int;
cout << "buffer = " << (void *)buffer << endl;
cout << "p1 = " << p1 << " " << "p2 = " << p2 << endl;
delete p1;
p1 = new int;
p2 = new (buffer) int;
cout << "p1 = " << p1 << " " << "p2 = " << p2 << endl;
delete p1;
p2 = new (buffer + sizeof(int)) int;
cout << "p2 = " << p2 << endl;
return 0;
}
输出:buffer = 00419148
p1 = 003954D8 p2 = 00419148
p1 = 003953A8 p2 = 00419148
p2 = 0041914C
①这里采用定义char数组的方式创建了一个512字节的内存缓冲区,new后面跟这内存缓冲区名称,即可把这个内存分给p2。使用这个操作需要包含头文件new: #include<new>;
②知道了这个分配机制就不难明白输出结果了;
③新式的new不需要使用delete来释放,因为它是静态内存,而delete只作用于动态内存空间。
9、名称空间。C++加入了名称空间这一概念是为了避免同名变量冲突。名称空间其实是将C++可能的名称(结构体名称、类名、变量等)集中管理。
I、基本性质。定义一个名称空间使用关键字namespace,如:
namespace micro
{
int lik;
int add(int, int);
struct st{...};
}
名称空间可以位于另一个名称空间中,也可以是全局的,但不能位于代码块中。除了用户定义的名称空间,还存在一个名称空间,这就是为什么能在全局变量被屏蔽的时候依然可以调用它。*名称空间是开放的(open),即任何时候只要想起需要将某名称加入到一个已存在的名称空间中都可以加入:
namespace micro{ char ch; }
利用这个性质,可以将先前已经存在的函数原型实现:
namespace micro{ int add(int a, int b){...} }
II、名称的调用。有三种方式:直接加空间前缀、using声明和using编译指令。三者越来越方便但也问题越来越多。
①第一种,采用操作符"::"调用。比如micro::lik = 5;就是对名称空间micro中lik的操作。由于名称空间中名称遵循名称的屏蔽原则(如:自由将屏蔽静态的),所以这么使用基本不会有什么问题。
②using声明。如using micro::lik,那么后面使用变量lik时,实际使用的是micro中的lik。这么使用,问题也浮现出来了:
void func() {
using::lik;
int lik; //Error, already have local lik
}
如果using声明在函数外使用,这个名称将加入到全局名称空间中,如:using micro::lik //add to globe namespace
int main()
{
lik = 100;
std::cout << ::lik << std::endl; //print 100
std::cout << micro::lik << std::endl; //print 100, too
}
③using编译指令。作用是使整个名称空间可用,如using namespace micro;
等价于名称空间所有名称都使用了using声明。这样做问题更多,问题之一是上面的提到的重复定义问题。问题之二的来源是程序员不知道声明了那些变量,很可能在代码块中声明同名与名称空间同名的变量,导致被屏蔽。名称空间的开放性使这个问题严重化。
III、高级特性。
①可镶嵌。如:
namespace n1
{
namespace n2
{
int ele;
}
}
需调用之时,这么调用n1::n2::ele,using声明和编译指令同理。②在名称空间中使用using声明和编译指令。
namespace n1
{
using n2::ele;
using namespace n3;
}
则,对n1::ele的操作即是对n2::ele的操作;因为using编译指令在名称空间中,如果想使用n3中的名称,下面的语句是等价的:using namespace n1;
using namespace n3;
③可创建别名。如果一定义名称空间myFavoriteSubject,这个空间名字过长,可以这样写:
namespace mfs = myFavoriteSubject;
这样即可如使用myFavoriteSubject一样使用mfs。另外,上面提到的镶嵌操作,如果层数较多,比如:n1::n2::n3::n4::ele,则可以这样写:
namespace nn = n1::n2::n3::n4
using nn::ele;
④匿名名称变量。比如:
namespace
{
int globe;
}
这样做的结果是其他源文件不能使用globe,只能在当前文件直接使用。如此相当于使用了static修饰了globe,可用于代替static。事实上,C++标准不鼓励使用static而是采取匿名名称空间的方式定义静态内部连接性变量。