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 linkage
static int one_file = 2048;//static duration,internal linkage
int main()
{
...
}
void fun(int n)
{
static int count = 0;//static duration,no linkage
}
5种变量存储方式
存储描述 | 持续性 | 作用域 | 链接性 | 如何声明 |
自动 | 自动 | 代码块 | 无 | 在代码块中(可使用关键字auto) |
寄存器 | 自动 | 代码块 | 无 | 在代码块使用关键字register |
静态,无链接性 | 静态 | 代码块 | 无 | 在代码块使用关键字static |
静态,外部链接性 | 静态 | 文件 | 外部 | 在函数外面 |
静态,内部链接性 | 静态 | 文件 | 内部 | 在函数外面,使用关键字static |
//file 1
int errors = 20;
....
-------------------------------
//file 2
extern int errors;
void fun()
{
cout<<errors;
}
(3)如果初始化了静态局部变量,则程序在第一次调用的时候初始化,以后再调用该函数时,将不会像自动变量那样再次被初始化.
2.4说明符和限定符
存储说明符(storage class specifier),cn-限定符(cv-qualifier);下面是存储说明符:
auto,register,static,extern,mutable;
cv限定符:const ,volatile;
volatile:表明程序代码没有对内存单元修改,其值也是可能发生变化的.
volatile:表明程序代码没有对内存单元修改,其值也是可能发生变化的.
mutable:表明即使类或结构变量为const,某个成员也是可以被修改的.
struct data
{
char name[100];
mutable int accesses;
...
};
const data veep={"hongzong.lin",0,...}
strcpy(name,"dizong.lin");//not allowed
veep.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-up
extern "C++" void fun2();//use c++protocol for name look up
extern 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 memory
int 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 available
using 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声明,首选将其作用域设置为局部而不是全局。