C++学习笔记(第9章->内存模型和名称空间)

来源:互联网 发布:淘宝直播快速申请成功 编辑:程序博客网 时间:2024/05/01 07:46

1.单独编译

(1)请不要将函数定义或变量声明放到头文件中,否则,在另外两个文件中,包含该头文件时,函数会被定义两次(除非内联函数)。下面列出头文件中常包含的内容:
A.函数原型
B.使用#define 或 const 定义的符号常量
C.结构声明
D.类声明
E.模版声明
F.内联函数
(2)有一种标准的C/C++技术可以避免同一个文件中多次包含同一个头文件.
#ifndef _FILE_H_#define _FILE_H_...#endif

2.存储持续性,作用域,链接性

C++使用3种不同的方案来存储数据,区别在于数据保留在内存的时间.
(1)自动存储持续性:在函数定义中声明的变量.
(2)静态存储持续性:在函数外定义的变量和使用关键字static定义的变量,在程序的整个运行过程中存在.
(3)动态存储持续性:使用new操作符分配的内存将一直存在,直到使用delete操作符将其释放或程序结束为止.

2.1作用域和链接

作用域(scope)描述了名称在文件的多大范围内可见.链接性描述了名称如何在不同单元间共享.链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中的函数共享.自动变量的名称没有链接性,因为他们不能共享.

2.2自动存储持续性

在默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性.C++编译器实现自动变量的方法是,留出一段内存,并将其视为堆栈,易管理变量的增减.自动变量的数目随函数的开始和结束而增减.
寄存器变量是另一种形式的自动变量,也没有链接性.关键字register提醒编译器,用户希望他通过使用CPU寄存器,而不是堆栈来处理特定的变量,从而提供对变量的快速访问.如果变量被存储在寄存器中,则没有内存地址,因此不能将地址操作符用于寄存器变量.

2.3静态持续变量

和C语言一样,C++也为静态存储持续性变量提供了三种链接性:外部链接性,内部链接性,无链接性.
所有静态持续性变量都有下面两个初始化特征:
(1)未被初始化的静态变量的所有位都被设置为0;
(2)只能使用常量表达式来初始化静态变量.
int global = 1024;//static duration,external linkagestatic int one_file = 2048;//static duration,internal linkageint main(){...}void fun(int n){   static int count = 0;//static duration,no linkage}
5种变量存储方式
存储描述持续性作用域链接性如何声明自动自动代码块无在代码块中(可使用关键字auto)寄存器自动代码块无在代码块使用关键字register静态,无链接性静态代码块无在代码块使用关键字static静态,外部链接性静态文件外部在函数外面静态,内部链接性静态文件内部在函数外面,使用关键字static正确使用extern关键字:
//file 1int errors = 20;....-------------------------------//file 2extern int errors;void fun(){cout<<errors;}
(3)如果初始化了静态局部变量,则程序在第一次调用的时候初始化,以后再调用该函数时,将不会像自动变量那样再次被初始化.

2.4说明符和限定符

存储说明符(storage class specifier),cn-限定符(cv-qualifier);下面是存储说明符:
auto,register,static,extern,mutable;
cv限定符:const ,volatile;
volatile:表明程序代码没有对内存单元修改,其值也是可能发生变化的.
mutable:表明即使类或结构变量为const,某个成员也是可以被修改的.
struct data{   char name[100];   mutable int accesses;...};const data veep={"hongzong.lin",0,...}strcpy(name,"dizong.lin");//not allowedveep.accesses++;//allowed
(1)再谈const
在默认情况下,全局变量的链接性为外部的,但const全局变量的链接性为内部的,就像使用了static说明符一样,这也就是为什么可以将一组const常量放在头文件中的原因,这样每个文件都有一个自己的const常量,而不是共享.
如果要是某个常量的链接性为外部的,则可以使用关键字extern来覆盖默认的内部链接性:
extern const int stats = 10;
在代码块中声明const时,其作用域为代码块.

2.5函数和链接性

和变量一样,函数也有链接性.默认情况下,函数的链接性为外部的,即可以在文件间共享.也可以使用关键字static将函数的链接性设置为内部的,使之只能在一个文件中使用.
staitc int fun();static int fun(){}
对于每个非内联函数,程序中只能包含一个定义.在其他文件中使用时,必须包含他的原型.
对于内联函数,则不受这个规则约束,所以它可以放在头文件中.

2.6语言链接性

链接程序要求每个不同的函数都有不同的符号名.在C语言中,一个名称对应一个函数,C语言链接特性;但是在C++中,同一个名称可能对应多个函数,必须将这些函数翻译成不同的符号名称.这种成为C++语言链接.所以链接程序寻找C++函数调用匹配的函数时,使用的方法于C语言不同.所以可以用函数原型来指出要使用哪种链接方法:
extern "C" void fun1();//use c protocol for name look-upextern "C++" void fun2();//use c++protocol for name look upextern void fun3();//use c++protocol for name look up,default

3.布局new操作符

通常,new负责在堆中找到一个足以能够满足要求的内存块.new还有另外一种变体,被称为布局new操作符.让您能够指定要使用的位置.
#include<iostream>#include<new>const int BUF = 512;const int N = 5;char buffer[BUF];//chunk of memoryint main(){using namespace std;double *pd1,*pd2,*pd3,*pd4;cout<<"calling new and placement\n";pd1 = new double[N];pd2 = new (buffer) double[N];for(int i = 0; i < N; i++){pd1[i] = pd2[i] = 1000 + 20*i;}cout<<"buffer addresses:\n"<<" heap: "<<pd1<<" static: "<<(void *)buffer<<endl;cout<<"buffer content:\n";for(int i = 0; i < N; i++){cout<<pd1[i]<<" at "<<&pd1[i]<<"; ";cout<<pd2[i]<<" at "<<&pd2[i]<<endl;}cout<<"\n calling new placement new a second time \n";pd3 = new double[N];pd4 = new (buffer) double[N];for(int i = 0; i < N; i++){pd3[i] = pd4[i] = 1000 + 20*i;}cout<<"buffer content:\n";for(int i = 0; i < N; i++){cout<<pd3[i]<<" at "<<&pd3[i]<<"; ";cout<<pd4[i]<<" at "<<&pd4[i]<<endl;}cout<<"\n calling new placement new a third time \n";delete [] pd1;pd1 = new double[N];pd2 = new (buffer + N * sizeof(double)) double[N];for(int i = 0; i < N; i++){pd1[i] = pd2[i] = 1000 + 20*i;}cout<<"buffer content:\n";for(int i = 0; i < N; i++){cout<<pd1[i]<<" at "<<&pd1[i]<<"; ";cout<<pd2[i]<<" at "<<&pd2[i]<<endl;}delete [] pd1;delete [] pd3;while(1);return 0;}


布局new操作符将数组p2放在了数组buffer中,p2和buffer的地址都是0x011e7308;第二次的时候,还是原来的地址,说明p4数据会覆盖之前分配给p2的内存.接下来,p2重新调用布局new操作符,并计算从buffer开始的偏移量,避免覆盖之前的数据.
buffer指定的内存是静态内存,而delete只能用于这样的指针:指向常规new操作符分配的对内存.也就是说,buffer位于delete的管辖区之外.如果buffer是使用new创建的,则应该使用delete.

4.名称空间

4.1新的名称空间特性

C++通过定义一种新的声明区域来创建命名的名称空间,提供一个声明名称的区域。
下面是使用关键字namespace创建了两个名称空间:Jack,Jill
namespace Jack{    double pail;    void fetch();    int pal;}namespace Jill{    double fetch;    int pal;    double bucket(double n){...}}
名称空间可以是全局的,也可以是位于另一个名称空间中,但不能位于代码块中。
任何名称空间中的名称都不会与其他名称空间中的名称发生突。
(1)using声明和using编译指令
using声明是特定的标示符可用;using编译指令使整个名称空间可用。
using声明有被限定的名称和他前面的关键字using组成:
using std::cin;using Jill::fetch; 
using编译指令由名称空间名和他前面的关键字using namespace组成:
using namespace std;using namespace Jill;
(2)using声明和using编译指令的比较
假设名称空间和声明区域定义了相同的名称。如果试图使用using声明将名称空间的名称导入该声明区域,则这两个名称会发生冲突,从而出错。如果使用using编译指令将该名称空间的名称导入该声明区域,则局部版本将隐藏名称空间版本。
一般来说,使用using声明比使用using编译指令安全。
(3)名称空间的其他特性
可以将名称空间进行嵌套:
namespace elements{   namespace fire   {       int flame;//using element::fire::flame;       ...   }   float water;}//using namespace elements::fire;
另外,也可以在名称空间中使用using编译指令和声明:
namespace my{    using std::cin;    using namespace Jack;    using Jill::fetch;    ...}//std::cout<<my::fetch;
由于他也存在于Jill中,也可以std::cout<<Jill::fetch;
可以使用下面语句来简化对嵌套名称空间的使用:
namespace MEF = elements::fire;using MEF::flame;
(4)未命名的名称空间
namespace{    int ice;    int band;}
由于没有名字,不能使用using声明和编译指令。具体的说,不能在未命名名称空间所属文件之外的其他文件中,使用该名称空间中的名称,因此这种方法可以替代链接性为内部的静态变量。
static int counts;int main(){}/////////////////////C++鼓励下面这样做namespace{    int counts;}int main(){}

4.2名称空间范例

头文件:
#ifndef _NAMESP_H_#define _NAMESP_H_namespace pers{    const int LEN = 40;struct Person{char fname[LEN];char lname[LEN];};void getPerson(Person &);void showPerson(const Person &);}namespace debts{using namespace pers;struct Debt{Person name;double amount;};void getDebt(Debt &);void showDebt(const Debt &);double sumDebts(const Debt ar[], int n);}#endif
定义文件:
#include<iostream>#include"namesp.h"namespace pers{using std::cout;using std::cin;void getPerson(Person & rp){cout<<"enter first name: ";cin>>rp.fname;cout<<"enter last name: ";cin>>rp.lname;}void showPerson(const Person & rp){cout<<rp.fname<<","<<rp.lname;}}namespace debts{void getDebt(Debt & rd){getPerson(rd.name);std::cout<<"enter debt: ";std::cin>>rd.amount;}void showDebt(const Debt & rd){showPerson(rd.name);std::cout<<":$"<<rd.amount<<std::endl;}double sumDebts(const Debt ar[], int n){double total = 0;for(int i = 0; i < n; i++){total += ar[i].amount;}return total;}}
测试文件:
#include<iostream>#include"namesp.h"void other();void another();int main(){using debts::Debt;//make the Debt structure definition availableusing debts::showDebt;//make the show Debt function avaiable,如果函数被重载,将导入所有版本Debt golf = {{"dizong", "lin"}, 120.0};showDebt(golf);other();another();return 0;}void other(){using namespace std;using namespace debts;//make all debts and pers names available to other()Person dg = {"hongzong","lin"};showPerson(dg);cout<<endl;Debt zippy[3];int i;for(i = 0; i < 3; i++){getDebt(zippy[i]);}for(i = 0; i < 3; i++){showDebt(zippy[i]);}cout<<"total debt:$"<<sumDebts(zippy, 3)<<endl;}void another(){using pers::Person;Person collector = {"dizong", "lin"};pers::showPerson(collector);//作用域解析操作符std::cout<<std::endl;}

4.3名称空间极其前途

编程理念,主要是为了减少名称冲突,下面是当前的一些指导原则:
(1)使用在已命名的名称空间中声明的变量,而不是使用外部全局变量。
(2)使用在已命名的名称空间中声明的变量,而不是使用静态全局变量。
(3)如果开发了一个类库或者函数库,将其放在一个名称空间中。
(4)不要在头文件中使用using编译指令。如果非要使用using编译指令,应放在所有预处理器编译指令#include之后。
(5)导入名称时,首选使用作用域解析操作符或using声明的方法。
(6)对于using声明,首选将其作用域设置为局部而不是全局。


0 0