纯净、安全、绿色的下载网站

首页|软件分类|下载排行|最新软件|IT学院

当前位置:首页IT学院IT技术

C++内存模型和名称空间 C++内存模型和名称空间详解

Briwisdom   2021-09-13 我要评论
想了解C++内存模型和名称空间详解的相关内容吗Briwisdom在本文为您仔细讲解C++内存模型和名称空间的相关知识和一些Code实例欢迎阅读和指正我们先划重点:C++内存模型,C++名称空间下面大家一起来学习吧

本章内容包括:

  • 单独编译
  • 存储持续性、作用域和链接性
  • 定位new运算符
  • 名称空间

1. 单独编译

和C语言一样C++允许甚至鼓励程序员将组件放在独立的文件中可以单独编译这些文件然后将它们连接成可执行程序(通常C++编译器即编译程序也管理连接器)如果只修改了一个文件则可以只重新编译该文件然后将它与其他文件的编译版本链接这使得大程序的管理更便捷

C++开发人员使用 #include 导入头文件与其将结构声明加入到每一个文件中不如将其放在头文件中然后在每一个源代码文件中包含该头文件这样要修改结构声明时只需在头文件中做一次改动即可另外可以将函数原型放在头文件中因此可以将原来的程序分成三部分

  • 头文件:包含结构声明和使用这些结构的函数的原型
  • 源代码文件:包含与结构有关的函数的代码
  • 源代码文件:包含调用与结构相关的函数的代码

请不要将函数定义或变量声明放到头文件中这样做对简单情况可能是可行的但通常会引来麻烦例如如果在头文件包含一个函数定义然后在其他两个文件中包含该头文件则同一个程序中将包含同一个函数的两个定义除非函数是内联的否则将会出错下面列出了头文件中常包含的内容

  • 函数原型
  • 使用#define或const定义的符号常量
  • 结构声明
  • 类声明
  • 模板声明
  • 内联函数

将结构声明放在头文件中是可以的因为它们不创建变量而只是在源代码文件中声明结构变量时告诉编译器如何创建该结构变量同样模板声明不是将被编译的代码它们指示编译器如果和生成与源代码中的函数调用相匹配的函数定义被声明为const的数据和内联函数有特殊的链接属性因此可以将其放在头文件中而不会引起问题

头文件管理

在同一个文件中只能将同一个头文件包含一次有一种标准的C/C++技术可以避免多次包含同一个头文件它是基于预处理器编译指令 #ifndef (即 if not defined)的下面代码片段意味着仅当以前没有使用预处理器编译指令#define定义名称COORDIN_H_时才处理 #ifndef 和 #endif之间的语句:

#ifndef COORDIN_H_
...
#endif

通常使用#define语句来创建符号常量如下所示:

#define MAXIMUM 4096

但只要将#define用于名称就足以完成该名称的定义如下所示:

#ifndef COORDIN_H_
#define COORDIN_H_
// place include file contents here
#endif

多个库的链接

C++标准允许每个编译器设计人员以他认为合适的方式实现名称修饰因此由不同编译器创建的二进制模块(对象代码文件)很可能无法正确地链接也就是说两个编译器将为同一个函数生成不同的修饰名称名称的不同将使链接器无法将一个编译器生成的函数调用与另一个编译器生成的函数定义匹配在链接编译模块时请确保所有对象文件或库都是由同一个编译器生成的如有源代码通常可以用自己的编译器重新编译源代码来消除链接错误

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

接下来扩展存储类别如何影响信息在文件间的共享C++使用三种(在C++11中是四种)不同的方案来存储数据这些方案的区别就在于数据保留在内存中的时间

  • 自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的它们在程序开始执行其所属的函数或代码块时被创建在执行完函数或代码块时它们使用的内存被释放C++有两种存储持续性为自动的变量
  • 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态它们在程序整个运行过程中都存在C++有3中存储持续性为静态的变量
  • 线程存储持续性:当前多核处理器很常见这些CPU可同时处理多个执行任务这让程序能够将计算放在可并行处理的不同线程中如果变量是使用关键字thread_local声明的则其声明周期与所属的线程一样长
  • 动态存储持续性:用new运算符分配的内存将一直存在直到使用delete运算符将其释放或程序结束为止这种内存的存储持续性为动态有时被称为自由存储或堆

2.1 作用域和链接

作用域描述了名称在文件的多大范围内可见例如函数中定义的变量可在该函数中使用但不能在其他函数中使用;而在文件中的函数定义之前定义的变量则可在所有函数中使用链接性(linkage)描述了名称如何在不同单元间共享链接性为外部的名称可在文件间共享链接性为内部的名称只能由一个文件中的函数共享自动变量的名称没有链接性因为它们不能共享

C++变量的作用域有多种作用域为局部的变量只在定义它的代码块中可用代码块是由花括号括起的一系列语句

C++函数的作用域可以是整个类或整个名称空间(包括全局的)但不能是局部的(因为不能在代码块内定义的函数如果函数的作用域为局部则只对它自己是可见的因此不能被其他函数调用这样的函数将无法运行)

2.2 自动存储持续性

默认情况下在函数中声明的函数参数和变量的存储持续性为自动作用域为局部没有链接性也就是说如果main()中声明了一个名为texas的变量并在函数oil()中也声明了一个名称texas的变量则创建了两个独立的变量——只有在定义他们的函数中才能使用它们堆iol()的texas执行的任何操作都不会影响main()中的texas反之亦然另外当程序开始执行这些变量所属的代码块时将为其分配内存;当函数结束时这些变量都将消失

使用C++11中的auto

在C++11中关键字auto用于自动类型推断但在C语言和以前的C++版本中auto的含义截然不同它用于显式地指出变量为自动存储:

int froob(int n)
{
    auto float ford; //ford has automatic stroage
    ...
}

由于只能将关键字用于默认为自动的变量因此程序员几乎不使用它它的主要用途是指出当前变量为局部自动变量在C++11中这种用法不再合法

1自动变量的初始化

可以使用任何在声明时其值已知的表达式来初始化自动变量下面的示例初始化变量x,y,z:

int w;
int x=5;
int big =INT_MAX -1;
int y=x *x;
int y=2*x;
cin>>w;
int z=3*w;

2自动变量的初始化

了解典型的C++编译器如何实现自动变量有助于更深入地了解自动变量由于自动变量的数目随函数的开始和结束而增减因此程序必须在运行时对自动变量进行管理常用的方法是留出一段内存并将其视为栈以管理变量的增减之所以被称为栈是由于新数据被象征性地放在原有数据的上面(也就是说在相邻的内存单元中而不是在同一个内存单元中)当程序使用完后将其从栈中删除栈的默认长度取决于实现但编译器通常提供改变栈长度的选项程序使用两个指针来跟踪栈一个指针指向栈底——栈的开始位置另一个指针指向堆顶——下一个可用内存单元当函数被调用时其自动变量被加入到栈中栈顶指针指向变量后面的下一个可用的内存单元函数结束时栈顶指针被重置为函数被调用前的值从而释放新变量使用的内存

栈时LIFO(后进先出)的即最后加入到栈中的变量首先被弹出这种设计简化了参数传递函数调用将其参数的值放在栈顶然后重新设置栈顶指针被调用的函数根据其形参描述来确定每个参数的地址下图中函数fib()被调用时传递一个2字节的int和一个4字节的long这些值被加入到栈中当fib()开始执行时它将real和tell同这两个值关联起来当fib()结束时栈顶指针重新指向以前的位置新值没有被删除但不再被标记它们所占据的空间将被下一个将值加入到栈中的函数调用所使用

3寄存器变量

关键字register最初是由C语言引入的它建议编译器使用CPU寄存器来存储自动变量:

register int count_fast; //request for a rregister variable

这旨在提高访问变量的速度

在C++11之前这个关键字在C++中的用法始终未变只是随着硬件和编译器变得越来越复杂这种提示表明变量用的很多编译器可以对其做特殊处理在C++11中这种提示作用也失去了编辑案子register只是显式地指出变量是自动的鉴于关键字register只能用于原本就是自动的变量使用它唯一的原因是指出程序员想使用一个自动变量这个变量的名称可能与外部变量相同这与auto以前的用途完全相同然而保留关键字register的重要原因是避免使用了该关键字的现有代码非法

2.3 静态持续变量

和C语言一样C++也为静态存储持续性变量提供了3种链接性:外部链接性(可在其他文件中访问)、内部链接性(只能在当前文件中访问)和无链接性(只能在当前函数或代码块中访问)这3种链接性都在整个程序执行期间存在与自动变量相比它们的寿命更长由于静态变量的数目在程序运行期间是不变的因此程序不需要使用特殊的装置(如栈)来管理它们编译器将分配固定的内存块来存储所有的静态变量这些变量在整个程序执行期间一直存在另外如果没有显式地初始化静态变量编译器将把它设置为0在默认情况下静态数组和结构将每个元素或成员的所有位都设置为0

创建外部静态持续变量在代码块外面声明它;

创建内部的静态持续变量在代码块的外面声明它并使用static限定符;

创建无链接的静态持续变量在代码块中声明它并使用static限定符

...
int global =1000;  // static duration, external linkage
static int one_file =50; //static duration, internal linkage
int main()
{
    ...
}
void funct1(int n)
{
    static int count =0; // static duration, no linkage
    int llama =0; 
}

所有的静态持续变量都有下述初始化特征: 未被初始化的静态变量的所有位都被设置为0这种变量称为零初始化的(zero-initialized)

下表9.1总结了引入名称空间之前使用的初始化特征指出了关键字static的两种用法但含义有些不同:用于局部声明以指出变量是无链接性的静态变量static表示的是存储持续性;而用于代码块外的声明时static表示内部链接性而变量已经是静态持续性了有人称之为关键字重载即关键字的含义取决于上下文

2.4 静态持续性、外部链接性

链接性为外部的变量通常称为外部变量它们的存储持续性为静态作用域为整个文件外部变量是在函数外部定义的因此对所有函数而言都是外部的例如可以在main()前面或头文件中定义它们可以在文件中位于外部变量定义后面的任何函数中使用它因此外部变量也称为全局变量

1单定义规则

一方面在每个使用外部变量的文件中都必须声明它;另一方面C++有“单定义规则”(One Definition Rule, ODR), 该规则指出变量只能有一次定义为满足这种需求C++提供了两种变量声明一种是定义声明或简称定义它给变量分配存储空间;另一种是引用声明或简称声明它不给变量分配存储空间因为它引用已有的变量

引用声明使用关键字extern且不进行初始化;否则声明为定义导致分配存储空间

double up;  //definition, up is 0;
extern int blem;     //blem defined elsewhere
extern char gr ='z';  //definition because initialized

如果要在多个文件中使用外部变量只需在一个文件中包含该变量的定义(单定义规则)但在使用变量的其他所有文件中都必须使用关键字extern声明它

全局变量和局部变量

既然可以选择使用全局变量或局部变量那么到底应使用哪种呢?首先全局变量很有吸引力——因为所有的函数能访问全局变量因此不用传递参数但易于访问的代价很大——程序不可靠计算经验表明程序越能避免对数据进行不必要的访问就越能保持数据的完整性通常情况下应使用局部变量应在需要知晓时才传递数据而不应不加区分地使用全局变量来使数据可用

2.5 静态持续性、内部链接性

将static限定符用于作用域为整个文件的变量时该变量的链接性将为内部的在多文件程序中内部链接性和外部链接性之间的差别很有意义链接性为内部的变量只能在其所属的文件中使用但常规外部变量都具有外部链接性即可以在其他文件中使用

可使用外部变量在多文件程序的不同部分之间共享数据;可使用链接性为内部的静态变量在同一个文件中的多个函数之间共享数据(名称空间提供了另外一种共享数据的方法)另外如果将作用域为整个文件的变量变为静态的就不必担心其名称与其他文件中的作用域为整个文件的变量发生冲突

2.6 静态存储持续性、无链接性

无链接性的局部变量是这样创建的将static限定符用于在代码块中定义的变量在代码块中使用static时将导致局部变量的存储持续性为静态的这意味着虽然该变量只在该代码块中可用但它在该代码块不处于活动状态时仍然存在因此在两次函数调用之间静态局部变量的值将保持不变(静态变量适用于再生)另外如果初始化了静态局部变量则程序只在启动时进行一次初始化以后再调用函数时将不会像自动变量那样再次被初始化

2.7 说明符和限定符

有些被称为存储说明符或cv-限定符的C++关键字提供了其他有关存储的信息下面是存储说明符:

  • auto(在C++11中不再是说明符)
  • register;
  • static
  • extern
  • thread_local(C++11新增的)
  • mutable

其中的大部分已经介绍过了在同一个声明中不能使用多个说明符但thread_local除外它可与static或extern结合使用前面讲过在C++11之前可以在声明中使用关键字auto指出变量为自动变量;但在C++11中auto用于自动类型推断关键字register用于在声明中指示寄存器存储而在C++11中它只是显式地指出变量是自动的关键字static被用在作用域为整个文件的声明中时表示内部链接性;被用于局部声明中表示局部变量的存储持续性为静态的关键字extern表明是引用声明即声明在其他地方定义的变量关键字thread_local指出变量的持续性与其所属线程的持续性相同thread_local变量之于线程犹如常规静态变量之于整个程序关键字mutable的含义将根据const来解释因此先来介绍cv-限定符然后再解释它

1. cv-限定符

下面就是cv限定符

  • const
  • volatile

const是常见的cv-限定符它表明内存被初始化后程序便不能再对它进行修改

关键字volatile表明即使程序代码没有对内存单元进行修改其值也可能发生变化听起来似乎很神秘实际上并非如此例如可以将一个指针指向某个硬件位置其中包含了来自串行端口的时间或信息在这种情况下硬件(而不是程序)可能修改其中的内容或者两个程序可能互相影响共享数据该关键字的作用是改善编译器的优化能力例如假设编译器发现程序在几条语句中两次使用了某个变量的值则编译器可能不是让程序查找这个值两次而是将这个值缓存到寄存器中这种优化假设变量的值在这两次使用之间不会变化如果不将变量声明为volatile则编译器将进行这种优化将变量声明为volatile,相当于告诉编译器不要进行这种优化

2. mutable

现在回到mutable可以用它来指出即使结构(或类)变量为const其某个成员也可以被修改例如:

struct data
{
    char name[30];
    mutable int accesses;
    ...
};
const data veep = {"Claybourne Clodde",0, ...};
strcpy(veep.name, "Joye Joux"); //not allowed
veep.accesses++;                //allowed

veep的const限定符禁止程序修改veep的成员但access成员的mutable说明符使得access不受这种限制

3. 再谈const

在C++(但不是在C语言)中const限定符对默认存储类型稍有影响在默认情况下全局变量的链接性为外部的但const全局变量的链接性为内部的也就是说在C++看来全局const定义就像使用了static说明符一样

const int fingers =10;  //same as static const int fingers =10;
int main(void)
{
        ...
}

C ++修改了常量类型的规则让程序员更轻松例如假设将一组常量放在头文件中并在同一个程序的多个文件中使用该头文件那么预处理器将头文件的内容包含到每个源文件中所有的源文件都将包含类似下面的定义:

const int finers =10;
const char * warning ="Wak!";

如果全局const声明的链接性像常规变量那样是外部的则根据单定义规则这将出错也就是说只能有一个文件可以包含前面的声明而其他文件必须使用extern关键字来提供引用声明另外只有使用extern关键字的声明才能进行初始化:

//extern would be required if ocnst had external linkage
extern const int fingers;  //can't be initialized
extern const char *warning;

因此需要为某个文件使用一组定义而其他文件使用另一组声明然而由于外部定义const数据的链接性为内部的因此可以在所有文件中使用相同的声明

内部链接性还意味着每个文件都有自己的一组常量而不是所有文件共享一组常量每个定义都是其所属文件私有的这就是能够将常量定义放在头文件中的原因这样只要在两个源代码文件中包括同一个头文件则他们将获得同一组常量

如果出于某种原因程序员希望某个常量的链接性为外部的则可以使用extern关键字来覆盖默认的内部链接性

在函数或代码块中声明const时其作用域为代码块即仅当程序执行该代码块中的代码时该常量才是可用的这意味着在函数或代码块中创建常量时不必担心其名称与其他地方定义的常量发生冲突

2.8 函数和链接性

和变量一样函数也有链接性虽然可选择的范围比变量小和C语言一样C++不允许在一个函数中定义另外一个函数因此所有的存储持续性都自动为静态的即在整个程序执行期间都一直存在在默认情况下函数的链接性为外部的即可以在文件间共享

实际上可在函数原型中使用关键字extern来指出函数是在另一个文件中定义的不过这是可选的(要让程序在另一个文件中查找函数该文件必须作为程序的组成部分被编译或者是由链接程序搜索的库文件)还可以使用关键字static将函数的链接性设置为内部的使之只能在一个文件中使用必须同时在原型和函数定义中使用该关键字

static int private(double x);
...
static int private(double x)
{
    ...
}

这意味着该函数只在这个文件中课件还意味着可以在其他文件中定义同名的函数和变量一样在定义静态函数的文件中静态函数将覆盖外部定义因此即使在外部定义了同名的函数该文件仍将用静态函数

单定义规则也适用于非内联函数因此对于每个非内联函数程序只能包含一个定义对于链接性为外部的函数来说这意味着在多文件程序中只能有一个文件(该文件可能是库文件而不是您提供的)包含该函数的定义但使用该函数的每个文件都应包含其函数原型

内联函数不受这项规则的约束这允许程序员能够将内联函数的定义放在头文件中这样包含了头文件的每个文件都有内联函数的定义然而C++要求同一个函数的所有内联定义都必须相同

2.9 语言链接性

另一种形式的链接性——称为语言链接性也对函数有影响链接程序要求每个不同的函数都有不同的符号名在C语言中一个名称只对应一个函数因此这很容易实现为满足内部需求C语言编译器可能将spiff这样的函数名翻译为 _spiff这种方法被称为C原因链接性但在C++中同一个名称可能对应多个函数必须将这些函数翻译为不同的符号名称因此C++编译器执行名称矫正或名称修饰为重载函数生成不同的符号名称例如可能将spiff(int)转换为_spoff_i,而将spiff(double, double)转换为_spiff_d_d这种方法被称为C++语言链接

链接程序寻找与C++函数调用匹配的函数时使用的方法与C语言不同但如果要在C++程序中使用C库中预编译的函数将出现什么情况呢?例如假设有下面的代码:

spiff(22); //want spiff(int) from a C library

它在C库文件中的符号名称为_spiff, 但对于我们假设的链接程序来说C++查询约定时查找符号名称 _spiff_i为解决这种问题可以用函数原型来指出要使用的约定:

extern "C" void spiff(int); //use C protocol for name look-up
extern void spoff(int);     //use C++ protocol for name look-up
extern "C++" void spaff(int);  //use C++ protocol for name look-up

第一个原型使用C语言链接性;而后面的两个使用C++语言链接性第二个原型是通过默认方式指出这一点的而第三个显式地指出了这一点

C和C++链接性是C++标准制定的说明符但实现可提供其他语言链接性说明符

2.10 存储方案和动态分配

前面介绍C++用来为变量(包括数组和结构)分配内存的5种方案(线程内存除外)他们不适用于使用C++运算符new(或C函数malloc())分配的内存这种内存被称为动态内存动态内存运算符由new和delete控制而不是由作用域和链接性规则控制因此可以在一个函数中分配动态内存而在另一个函数中将其释放与自动内存不同动态内存不是LIFO其分配和释放顺序要取决于new和delete在何时以何种方式被使用通常编译器使用三块独立的内存:一块用于静态变量一块用于自动变量另外一块用于动态存储

虽然存储方案概念不适用于动态内存但适用于用来跟踪动态内存的自动和静态指针变量例如假设在一个函数中包含下面的语句:

float * p_free =new float [20];

由new分配的80个字节(假设float为4个字节)的内存将一直保留在内存中直到使用delete运算符将其释放但当包含该声明语句块执行完毕时p_fees指针将消失如果希望另一个函数能够使用这80个字节的内存则必须将其地址传递或返回给该函数另一方面如果将p_fees的链接性声明为外部的则文件中位于该声明后面的所有函数都可以使用它另外通过在另一个文件中使用下述声明便可在其中使用该指针:

extern float * p_fees;

1. 使用new运算符初始化

如果要初始化动态分配的变量该如何办呢?在C++98中有时候可以这样做C++11增加了其他可能性

如果要为内置的标量类型(如int或double)分配存储空间并初始化可在类型名后面加上初始值并将其用括号括起:

int *pi =new int(6); 
double * pd =new double (99.99);

这种括号语法也有可用于构造函数的类这将在本书后面介绍

然而要初始化常规结构或数组需要使用大括号的列表初始化这要求编译器支持C++11C++11允许您这样做:

struct where {double x; double y; double z;};
where * one =new where {2.5, 5.3, 7.2}; //C++11
int * ar =new int [4] {2,4,6,7}; //C++11

在C++11中还可将列表初始化用于单值变量:

int *pin =new int {};
double *pdo= new double {99.99};

2. new 失败时

new可能找不到请求的内存量在最初10年中C++在这种情况下让new返回空指针但现在将引发一场std::bad_alloc

3. new: 运算符、函数和替换函数

运算符new和new[]分别调用如下函数:

void * operator new(std::size_t); //used by new
void * operator new[] (std:: size_t) //used by new[]

这些函数被称为分配函数他们位于全局名称空间中同样也有delete和delete[]调用的释放函数:

void operator delete(void *);
void operator delete [](void *);

4. 定位new运算符

通常new负责在堆中找到一个足以能够满足要求的内存块new运算符还有另一种变体被称为定位new运算符它让您能够指定要使用的位置程序员可能使用这种特性来设置其内存管理规程、处理需要通过特定地址进行访问的硬件或在特定位置创建对象

要使用定位new特性首先需要包含头文件new它提供了这种版本的new运算符的原型;然后将new运算符用于提供了所需地址的参数除需要指定参数外句法与常规new运算符相同具体地说使用定位new运算符变量后面可以有方括号也可以没有下面演示了new运算符的4种用法:

#include <new>
struct chaff
{
	char dross[20];
	int slag;
 } ;
 char buffer1[50];
 int *p3, *p4;
 //first, the regular forms of new
 p1 = new chaff;    //place structure in heap
 p3 = new int [20]; // place int array in heap
 //now, the two forms of placement new
 p2 = new (buffer1) chaff;   //place structure in buffer1
 p4 = new (buffer2) int[20]; //place int array in buffer2
 ...

出于简化的目的这个示例使用两个静态数组来为定位new运算符提供内存空间因此上述代码从buffer1中分配空间给结构chaff, 从buffer2中分配空间给一个包含20个元素的int数组

3. 名称空间

C++中名称可以是变量、函数、结构、枚举类以及类和结构的成员当随着项目的增大名称相互冲突的可能性也将增加使用多个厂商的类库时可能导致名称冲突例如两个库可能都定义了名称为List、Tree和Node的类但定义的方式不兼容用户可能希望使用一个库的List类而使用另一个库的Tree类这种冲突被称为名称空间问题

C++标准提供了名称空间工具以便更好地控制名称的作用域经过了一段时间后编译器才支持名称空间但现在这种支持很普遍

3.1 传统的C++名称空间

介绍C++中新增的名称空间特性之前先复习一下C++中已有的名称空间属性以及概念

声明区域声明区域是可以在其中进行声明的区域例如可以在函数外面声明全局变量对于这种变量其声明区域为其所在的文件对于在函数中声明的变量其声明区域为其声明所在的代码块

潜在作用域变量的潜在作用域从声明点开始到其声明区域的结尾因此潜在作用域比声明区域小这是由于变量必须定义后才能使用

然而变量并非在其潜在作用域内的任何位置都是可见的例如它可能被另一个嵌套声明区域中声明的同名变量隐藏例如在函数中声明的局部变量将隐藏在同一个文件中声明的全局变量变量对程序而言可见的范围被称为作用域

C++关于全局变量和局部变量的规则定义了一种名称空间层次每个声明区域都可以声明名称这些名称独立于在其他声明区域中声明的名称在一个函数中声明的局部变量不会与在另一个函数中声明的局部变量发生冲突

3.2 新的名称空间特性

C++新增了这样一种功能即通过定义一种新的声明区域来创建命名的名称空间这样做得目的之一是提供一个声明名称的区域一个名称空间中的名称不会与另外一个名称空间的相同名称发生冲突同时允许程序的其他部分使用该名称空间中声明的东西

名称空间可以是全局的也可以位于另一个名称空间中但不能位于代码块中因此在默认情况下在名称空间中声明的名称的链接性为外部的

除了用户定义的名称空间外还存在另一个名称空间——全局名称空间它对应于文件级声明区域因此前面所说的全局变量现在被描述为位于全局名称空间中

任何名称空间中的名称都不会与其他名称空间中的名称发生冲突名称空间中的声明和定义规则同全局声明和定义规则相同

名称空间是开放的即可以把名称空间加入到已有的名称空间中下面这条语句将名称goose添加到Jill中已有的名称列表中:

namespace Jill {
    char * goose(const char *);
}

同样原来的Jack名称空间为fetch()函数提供了原型可以在该文件后面(或另外一个文件中)再次使用Jack名称空间来提供该函数的代码:

namespace Jack{
    void fetch()
    {
        ...
    }
}

当然需要有一种方法来访问给定名称空间中的名称最简单的方法是通过作用域解析运算符::使用名称空间来限定该名称:

Jack:: pail =12.34; //use a variable
Jill::Hill mole;    //create a type Hill structure
Jack::fetch();      //use a function

未被装饰的名称称为未限定的名称;包含名称空间的名称称为限定的名称

1. using声明和using编译指令

C++提供两种机制(using声明和using编译指令)来简化对名称空间中名称的使用using声明使特定的标识符可用using编译指令使整个名称空间可用

using声明由被限定的名称和它前面的关键字using组成using声明将特定的名称添加到它所属的声明区域中

using声明使一个名称可用而using编译指令使所有的名称都可用using编译指令由名称空间名和它前面的关键字using namespace组成它使名称空间中的所有名称都可用而不需要使用作用域解析运算符

在全局声明区域中使用using编译指令将使该名称空间的名称全局可用在函数中使用using 编译指令将使其中的名称在该函数中可用

2. using编译指令和using声明之比较

使用using编译指令导入一个名称空间中所有的名称与使用多个using声明使不一样的而更像是大量使用作用域解析运算符使用using声明时就好像声明了相应的名称一样如果某个名称已经在函数中声明了则不能用using声明导入相同的名称然而使用using编译指令时将进行名称解析就像在包含using声明和名称空间本身的最小声明区域中声明了名称一样

注意:假设名称空间和声明区域定义了相同的名称如果试图使用using声明将名称空间的名称导入该声明区域则这两个名称会发生冲突从而出错如果使用using编译指令将该名称空间的名称导入该声明区域则局部版本将隐层名称空间版本

一般来说使用using声明比使用using编译指令更安全这是由于它只导入指定的名称如果该名称与局部名称发生冲突编译器将发出指示using编译指令导入所有名称包括可能并不需要的名称如果与局部名称发生冲突则局部名称将覆盖名称空间版本而编译器并不会发出警告另外名称空间的开放性意味着名称空间的名称可能分散在多个地方这使得难以准确知道添加了哪些名称

3. 名称空间的其他特性

可以将名称空间声明进行嵌套:

namespace elements
{
    namespace fire
    {
        int flame;
        ...
    }
    float water;
}

4. 未命名的名称空间

可以通过省略名称空间的名称来创建未命名的名称空间

namespace  //unnamed namespace
{
    int ice;
    int bandycoot;
}

这就像后面跟着using编译指令一样也就是说在该名称空间中声明的名称潜在作用域为:从声明点到该声明区域末尾从这个方面看它们与全局变量相似然而由于这种名称空间没有名称因此不能显式地使用using编译指令或using声明来使它在其他位置都可用具体的说不能在未命名名称空间所属文件之外的其他文件中使用该名称空间中的名称这提供了链接性为内部的静态变量的替代品

3.3 名称空间及其前途

随着程序员逐渐熟悉名称空间将出现统一的编程理念下面是当前的一些指导原则

  • 使用在已命名的名称空间中声明的变量而不是使用外部全局变量
  • 使用在已命名的名称空间中声明的变量而不是使用静态全局变量
  • 如果开发了一个函数库或类库将其放在一个名称空间中事实上C++当前提倡将标准函数库放在名称空间std中这种做法扩展到了来自C语言中的函数例如头文件math.h是与C语言兼容的没有使用名称空间但C++头文件cmath应将各种数学库函数放在名称空间std中实际上并非所有的编译器都完成了这种过渡
  • 仅将编译指令using作为一种将旧代码转换为使用名称空间的权宜之计
  • 不要在头文件中使用using编译指令首先这样做掩盖了要让哪些名称可用;另外包含头文件的顺序可能影响程序的行为如果非要使用编译指令using应将其放在所有预处理器编译指令#include之后
  • 导入名称时首选使用作用域解析运算符或using声明的方法
  • 对于using声明首选将其作用域设置为局部而不是全局

使用名称空间的主旨是简化大型编程项目的管理工作对于只有一个文件的简单程序使用using编译指令并非什么大逆不道的事

4 .总结

本篇文章就到这里了希望能够给你带来帮助也希望您能够多多关注的更多内容!


相关文章

猜您喜欢

  • js setinterval延迟一秒解决 JavaScript setinterval延迟一秒解决方案

    想了解JavaScript setinterval延迟一秒解决方案的相关内容吗Nanchen_42在本文为您仔细讲解js setinterval延迟一秒解决的相关知识和一些Code实例欢迎阅读和指正我们先划重点:js,setinterval延迟一秒解决,js,setinterval延迟一秒解决方案下面大家一起来学习吧..
  • SpringIOC Bean对象 Spring IOC中的Bean对象用法

    想了解Spring IOC中的Bean对象用法的相关内容吗.SOLO.在本文为您仔细讲解SpringIOC Bean对象的相关知识和一些Code实例欢迎阅读和指正我们先划重点:Spring,IOC,Bean对象下面大家一起来学习吧..

网友评论

Copyright 2020 www.sopisoft.net 【绿软下载站】 版权所有 软件发布

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 点此查看联系方式