C++内存模型和名称空间
c++ 头文件常包含的内容
- 函数原型
- 使用#define 和const 定义的符号常量
- 结构声明
- 模板声明
- 内联函数
包含头文件时 " " 和 < >的区别 如果文件名包含在尖括号<>内,则c++编译器将在存储标准头文件的主机系统的文件系统中查找,如果包含在""内,则编译器有限查找当前工作目录或源代码目录,如果没有找到,则将在标准位置查找.
c/c++避免头文件重复包含
#ifndef 符号常量
#define 符号常量
......
#endif
多个库的链接必须保证所有库都是由同一个编译器生成的. 这是因为每个编译器的设计人员以他自己觉得合适的方式实现名称修饰,这就导致不同的编译器将为同一个函数生成不同的名称修饰,由此 ,它们创建的二进制文件无法正确的链接
存储连续性 作用域和链接性
- 自动存储连续性: 在函数中声明的变量. 函数执行时被创建,函数执行完时被释放
- 静态存储连续性: 在函数定义外定义的变量和使用static关键字定义的变量. 在整个程序运行期间都存在
- 线程存储连续性: 使用关键字thread_local声明的变量. 生命周期和线程一样
- 动态存储连续性: 用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或者程序结束.
作用域和链接 作用域描述了名称在文件(c++标准称为翻译单元) 的多大范围内可见. 例如: 函数中定义的只在函数中可见 - 作用域为局部的变量: 只能在定义该变量的代码块中可见 - 作用域为全局的变量: 在定义位置到文件结尾都可见
链接性: 描述了名称如何在不同的翻译单元之间共享. - 外部链接性 可在文件之间共享 - 内部链接性 只能由一个文件中的函数共享 - 无链接性: 不可共享
自动存储连续性 在函数中声明的变量的存储性连续性默认为自动,作用域为局部,没有链接性.只能在函数体中使用它. 寄存器变量: 为了提高访问速度,register关键字建议编译器使用cpu寄存器来存储自动变量
静态连续变量 c++为静态连续存储性变量提供了三种链接性:
- 外部链接性 可在其他文件中访问 代码块外面声明
- 内部链接性 只能在当前文件中访问 代码块外面声明,并用static修饰
- 无连接性只能在当前函数或代码中访问 代码块内声明 并用static 修饰
编译器将分配固定的内存块来存储所有的静态变量,直到程序执行结束. 如果没有显示的初始化这些变量,编译器将将他们设为0. 在默认情况下讲台数组和机构将每一个元素或者成员的所有位都设为0
静态变量的初始化:
- 零初始化
- 常量表达式初始化
- 动态初始化
前面两种初始化被统称为 静态初始化. 意味着编译器在处理文件时初始化变量.动态初始化意味着在编译后初始化. 初始化流程: 首先 所有静态比那辆都被零初始化,而不管程序员是否显式的初始化了它,接下如果使用了常量表达式,就计算表达式,编译器将进行常量表达式初始化. 若果没有足够的信息,将进行动态初始化.
静态持续性 外部链接性 链接性为外部的变量统称为外部变量,他们的存储持续性为静态的,作用域为整个文件.又称全局变量,只要在定义位置之后的函数都可以使用它,可以在其他文件中使用. 单定义规则 : 所有的变量只能有一次定义. c++为此提供了两种变量声明: 定义声明和引用声明
- 定义声明: 给变量分配存储空间
- 引用声明: 不给变量声明分配 存储空间,因为它引用已有的变量.
引用变量使用extern 关键字 且不进行初始化,否则 就是定义声明,导致分配存储空间
在多文件程序中 可以在一个文件(且只能在一个文件)中定义一个外部变量. 使用该变量的其他文件必须使用关键字extern , 否则就违背了单定义规则.
#include <iostream>
using namespace std;
double warming = 0.3;
void update(double dt);
void local();
int main ()
{
cout<< "Gloabal warming is " << warming<<"\n";
update(0.1);
cout<< "Gloabal warming is " << warming<<"\n";
local();
cout<< "Gloabal warming is " << warming<<"\n";
}
#include <iostream>
using namespace std;
extern double warming;
void update(double dt);
void local();
void update(double dt)
{
extern double warming;
warming+=dt;
cout<< warming<<"\n";
}
void local()
{
double warming = 0.8;
cout<< warming<<"\n";
cout<< ::warming<<"\n";
}
静态持续性 内部链接性 将static 限定符用于作用为整个文件的变量时,该变量的链接性将为内部的;只能在其所属的文件中使用
静态持续性 无链接性 将static 限定符用于定义在代码块中的变量. 这种变量虽然只在代码块中使用,但它在代码块不处于活跃状态仍然存在,因此在两次函数调用之间静态局部变量的值将保持不变,如果初始化了该变量,则程序只在启动时进行一次初始化,第二次在调用时将不会在进行初始化.
c++中的说明符和限定符
- auto (c++11中不在是说明符,用于自动类型推断)
- register 将变量存放到cpu寄存器中,c++11中显示的表示变量是自动存储类型的
- static
- extern
- thread_local
- mutable 即使结构或类是const 其某个成员也可以被修改
struct data{
char name [30];
mutable int money;
}
const data mp = {"dsdafa",80000};
cv限定符
-
volatile 表明程序代码么有对内存进行修改 其值也可能发生改变 用于编译器优化. 假设编译器发现程序在几条语句中两次使用了某个变量,则编译器可能不是让程序查找这个值两次,而是将这个值 缓存到寄存器中,这是基于这个值在这两次使用之间不会变化,如果不将变量声明为volatile,编译器将进行这种优化. -
const 内存被初始化后不能被修改. 并且对默认存储类型有影响. 在默认情况下全局变量的链接性为外部的,但是加上const修饰的全局变量的链接性为内部的.全局const 定义就像是使用了static 限定符一样. 这意味着每个文件都可以有自己的一组常量 而不是所有文件共享一组常量,每个定义都是该文件私有的,这就是能将常量放在头文件中的原因.这样只要在两个源代码中包含同一个头文件 他们将获得同一组常量. 如果你希望某个常量的链接性为外部的,可以使用extern覆盖默认的内部链接性; extern const int a = 0; 在这种情况下,必须在所有使用该常量的文件中使用extern 关键字来声明他. 同时注意单个const 在多个文件之间共享 ,只有一个文件可以初始化他.
函数的链接性 所有的函数的链接性都自动为静态的.链接性为外部的,只能有一个文件包含函数定义,在其他文件中使用函数必须包含其原型. 可以通过关键子static将函数的链接性设置为内部的,这样该函数仅在文件内部可见,不妨碍其他文件中定义同名函数.和变量一样在定义静态函数的文件中,静态函数定义将覆盖外部定义.
语言链接性 c和c++的名称矫正或名称修饰的规则不同. c将spiff这样的函数名翻译为_spiff , c++因为有重载,所以它将spiff(int) 这样的函数翻译为_spiff_i,将spiff(double)这样的函数名翻译为_spiff_d_d,就会出现在c++中使用c库会出现找不到函数的情况. 可以用函数原型来指出要使用的约定
extern "c" void spiff(int);
extern void spiff(int);
extern "c++" void spiff(int);
动态存储 new 和delete 控制动态内存,其分配和释放取决于new 和delete什么时候以什么方式被使用.
定位new 运算符 : 通常new 负责在堆(heap)中找到一个足以满足要求的内存块.定位new 运算符让我们能够指定要使用的位置
#include <iostream>
const int BUF = 512;
const int N =5;
char buffer[BUF];
int main()
{
using namespace std;
double *pd1,*pd2;
int i;
pd1 = new double [N];
pd2 = new(buffer)double [N];
for(int i = 0;i<N;i++)
pd2[i] = pd1[i] = 1000+20.0*i;
cout<<"内存地址:\n"<<"heap:"<<pd1
<<"static:"<<(void*)buffer <<endl;
cout<<"元素地址:\n";
for(int i = 0;i<N;i++)
{
cout<<"pd1"<<pd1[i]<<"at"<<&pd1[i]<<";";
cout<<"pd2"<<pd2[i]<<"at"<<&pd2[i]<<";";
}
double *pd3,*pd4;
pd3 = new double [N];
pd4 = new(buffer)double [N];
for(int i = 0;i<N;i++)
pd4[i] = pd3[i] = 1000+40.0*i;
cout<<"内存地址:\n"<<"heap:"<<pd3
<<"static:"<<(void*)buffer <<endl;
cout<<"元素地址:\n";
for(int i = 0;i<N;i++)
{
cout<<"pd3"<<pd3[i]<<"at"<<&pd3[i]<<";";
cout<<"pd4"<<pd4[i]<<"at"<<&pd4[i]<<";";
}
double *pd5,*pd6;
pd5 = new double [N];
pd6 = new(buffer)double [N];
for(int i = 0;i<N;i++)
pd6[i] = pd5[i] = 1000+60.0*i;
cout<<"内存地址:\n"<<"heap:"<<pd5
<<"static:"<<(void*)buffer <<endl;
cout<<"元素地址:\n";
for(int i = 0;i<N;i++)
{
cout<<"pd5"<<pd5[i]<<"at"<<&pd5[i]<<";";
cout<<"pd6"<<pd6[i]<<"at"<<&pd6[i]<<";";
}
delete [] pd1;
delete [] pd3;
delete [] pd5;
}
名称空间
声明区域是可以在其中进行声明的区域。 例如可以在函数外声明变量,对于这种变量,其声明区域为其声明所在的文件。 潜在作用域: 变量的潜在作用域从声明点开始到其声明区域的结尾。 所以潜在作用域比声明区域小,这是由于变量必须先定义才能使用决定的。变量也不是在其亲啊在作用域内都是可见的,外部变量的潜在作用域就会被内部的同名变量隐藏掉一段。 c++新增了这样一种功能,即通过定义一种新的声明区域来创建命名的空间。 一个名称空间中的名称不会与另一个名称空间中同名名称发生冲突。
名称空间可以是去全局的,也可以位于另一个名称空间中,但是不可以位于代码块中。 文件级声明区域: 特殊的名称空间 ----- 全局名称空间
通过域解析运算符来访问名称空间中的名称 “::” 。未被装饰的名称称为未限定的名称,包含名称空间的名称称为限定的名称。
using声明 和using编译指令
-
using声明 由被限定的名称和它前面的关键字using组成; -
代码块中使用using声明 ,限定的名称会被添加到代码块中;全局中使用将会被添加到全局变量中 -
using声明使一个名称可用 -
using指令使所有名称可用,不需要使用域解析运算符。 格式: using namespace 名称空间名 -
在代码块中使用using 编译指令将使该名称空间的名称在函数中可用,对于在函数中定义与名称空间中名称同名的局部变量,结果是定义的局部变量隐藏名称空间中的名称。 -
在全局中使用using编译指令,对于在全局中定义与名称空间中名称同名的局部变量,结果是定义的全局变量隐藏名称空间中的名称。
名称空间可以嵌套,可以在名称空间内使用using声明和using编译指令,using编译指令是可以传递的,可以创建别名以简化对嵌套名称空间的使用。
|