C++ Primer Plus书之--C++命名空间(名称空间)

名称空间:

首先介绍几个概念:

1.声明区域(declaration region): 是可以在其中进行声明的区域. 例如:可以在函数外面声明全局变量, 其声明区域为其声明所在的文件; 对于在函数中声明的变量, 其声明区域为其声明所在的代码块.

2.潜在作用域(potential scope): 变量的潜在作用域从声明点开始, 到其声明区域的结尾. 因此潜在作用域比声明区域小, 这是由于变量必须定以后才能使用.

3.作用域: 变量对程序而言课件的范围被称为作用域.

 

作用域与潜在作用域的关系图:

 

 

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

namespace Jack{
    // 声明变量
    double pail;
    // 函数原型
    void fetch();    
    int pal;
    // 结构体声明
    struct Well{...};
}
namespace Jill{
    // 函数定义
    double bucket(double n)(...)
    double fetch;  
    int pal;
    // 结构体声明
    struct Hill {...};
}

名称空间可以使全局的, 也可以位于另一个名称空间中, 但不能位于代码块中. 因此默认情况下, 在名称空间中声明的名称的链接性为外部的(除非它引用了常量).

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

名称空间是开放的, 即可以把名称加入到已有的名称空间中, 例如: 下面这条语句就将函数原型goose添加到Jill中已有的名称列表中

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

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

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

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

// 使用Jack名称空间中的变量pail
Jack::pail = 12.34;
// 使用Jill名称空间中的结构体Hill
Jill::Hill mole;
// 使用Jack名称空间中的函数fetch
Jack::fetch();

未被修饰的名称(如 pail)称为未限定的名称(unqualified name), 包含名称空间的名称(如Jack::pail)称为限定的名称(qualified name)

 

using声明和using编译指令

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

using声明由被限定的名称和它前面的关键字using组成:

// 使用using声明
using Jill::fetch;

using声明将特定的名称添加到它所属的声明区域中. 例如main()中的using声明Jill::fetch将fetch添加到main()定义的声明区域中, 完成该声明后, 便可以使用名称fetch代替Jill::fetch. 例如:

// 名称空间
namespace Jill{
    double bucket(double n) {...}
    double fetch;
    struct Hill {...};
}

char fetch;
int main()
{
    // 将名称空间Jill中的fetch添加到main的声明区域中
    // 并且这个fetch会屏蔽全局声明的那个char fetch
    using Jill::fetch;
    // 下面这行代码会报错, 试图再次声明fetch, 因为上一行代码已经声明过fetch了
    double fetch;
    // 将一个数读入Jill::fetch中
    cin >> fetch;
    // 将一个数读入全局fetch中, 也就是main()前面声明的那个char fetch
    cin >> ::fetch;
}

在函数外面使用using声明时, 将吧名称添加到全局名称空间中.

void other();
namespace Jill{
    double bucket(double n) {...}
    double fetch;
    struct Hill {...};
}

// 将Jill名称空间中的fetch声明为全局的
using Jill::fetch;

int main()
{   
    // 将读取一个数设置到Jill::fetch中
    cin >> fetch;
    other();
    ...
}

void other()
{
    // 这里显示的就是Jill::fetch
    cout << fetch;
    ...
}

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

// 时所有Jack名称空间中的名称都可以使用
using namespace Jack;
// 在函数中使用using编译指令, 使其中的名称在该函数中可用, 例如:
int main()
{
    using namespace Jack;
    ...
}

using编译指令和using声明比较:

使用using编译指令导入一个名称, 空间中所有的名称与使用多个using声明是不一样的, 而更像是大量使用作用域解析运算符. 使用using声明时, 就好像声明了相应的名称一样. 如果某个名称已经在函数中声明了, 则不能用using声明导入相同的名称. 然而, 使用using编译指令时, 将进行名称解析, 就像在包含using声明和名称空间本身的最小声明区域中声明了名称一样. 看下面这个例子, 名称控件为全局的, 如果使用using编译指令导入一个已经在函数中声明的名称, 则局部名称将隐藏名称空间名, 就像隐藏同名的全局变量一样, 不过仍可以使用作用域解析运算符.

namespace Jill {
    double bucket(double n){...}
    double fetch;
    struct Hill{...};
}
// 全局名称空间
char fetch;
int main()
{
    // 导入全部的Jill名称空间的内容
    using namespace Jill;
    // 创建一个Jill::Hill 结构体
    Hill Thrill;
    // 调用Jill::bucket()
    double water = bucket(2);
    // 定义局部变量fetch, 会隐藏Jill::fetch
    double fetch;
    cin >> fetch;
    // 使用的是全局变量
    cin >> ::fetch;
    // 使用的是Jill名称控件的变量
    cin >> Jill::fetch;
}

int foom()
{
    // 会报错, 因为引用不到Jill名称空间的内容, 没有using namespace
    // 虽然函数中的using编译指令将名称空间的名称视为在函数之外声明的, 但它不会使得该文件中的其他函数能够使用这些名称
    Hill top;
    // 正确
    Jill::Hill crest;
}

假设名称空间和声明区域定义了相同的名称. 如果视图使用using声明将名称控件的名称导入该声明区域, 则这两个名称会发生冲突, 如果使用using编译指令将该名称空间的名称导入该声明区域, 则局部变量则隐藏名称控件的变量

 

名称空间的嵌套:

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

这里flame指的是element::fire::flame; 同样, 可以使用下面的using编译指令是内部的名称可用:

using element::fire;

另外, 也可以在名称空间中使用using编译指令和using声明, 例如:

namespace myth
{
    using Jill::fetch;
    using namespace elements;
    using std::cout;
    using std::cin;
}

假设要访问Jill::fetch, 由于Jill::fetch现在位于名称控件myth中, 因此可以这样访问:

std::cin >> myth::fetch;

由于fetch也位于Jill名称空间中, 因此也可以这么访问:

Jill::fetch;
// 显示的就是myth::fetch
std::cout << Jill::fetch;

如果没有与之冲突的局部变量, 则也可以这么做:

using namespace myth;
// 和Jill::fetch, myth::fetch一样的效果
cin >> fetch;

using编译指令是可以传递的, 例如:

using namespace myth;

和下面这两条编译指令等价:

using namespace myth;
using namespace elements;

可以给名称空间创建别名例如:

// 定义一个名称空间
namespace my_very_favorite_things{...};

可以使用下面的语句让mvft称为my_very_favorite_things的别名:

namespace mvft = my_very_favorite_things;

可以使用这种技术来简化对嵌套名称空间的使用:

namespace MEF = myth::elements::fire;
using MEF::flame;

 

未命名的名称空间

namespace
{
    int ice;
    int bandycoot;
}

这就像后面跟着using编译指令一样, 也就是说, 该名称空间中声明的名称的潜在作用域为: 从声明点到该生命区域末尾. 他们与全局变量类似, 然而由于这种名称空间没有名称, 因此不能显示的使用using编译指令或using声明来在其他位置使用.

这种特性可以作为 链接性为内部的静态变量的替代品, 例如:

// 静态存储, 内部链接
static int counts;
int other();
int main()
{
    ...
}
int other()
{
    ...
}

和下面的代码作用一样:

namespace
{
    int counts;
}
int other();
int main()
{
    ...
}
int other()
{
    ...
}

来看一个完整的名称空间的例子:

// 第一个文件
// namesp.h
#include <string>
// 创建命名空间
namespace pers
{
    struct Person
    {
        std::string fname;
        std::string lname;
    };
    // 引用
    void getPerson(Person &);
    // 引用常量, 表示内部不能对persion进行修改
    void showPerson(const Person &);
}

namespace debts
{
    // using编译指令, 导入pers名称空间
    using namespace pers;
    struct Debt
    {
        Person name;
        double salary;
    };
    void getDebt(Debt &);
    void showDebt(const Debt &);
    double sumDebts(const Debt arr[], int n);
}

 

// 第二个文件

第二个文件是源代码文件, 它提供了头文件中的函数原型对应的定义. 在名称空间中声明的函数名的作用于为整个名称空间, 因此定义和声明必须位于同一个名称空间中. 这正是名称空间开放性发挥作用的地方. 通过namesp.h导入了原来的名称空间. 然后该文件将函数定义添加入到两个名称空间中.

// namesp.cpp
#include <iostream>
#include "namesp.h"

namespace pers
{
    // using 声明导入cout和cin
    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)
    {
        // 也可以通过::调用
        std::cout << rp.lname << ", " << rp.fname;
    }
}

namespace debts
{
    void getDebt(Debt & rd)
    {
        getPerson(rd.name);
        std::cout << "Enter debt: ";
        std::cin >> rd.salary;
    }
    
    void showDebt(const Debt & rd)
    {
        showPerson(rd.name);
        std::cout << " : $ " << rd.salary << std::endl;
    }
    
    double sumDebts(const Debt arr[], int n)
    {
        double total = 0;
        for(int i = 0; i < n; i++)
            total += arr[i].salary;
        return total;
    }
}

第三个文件:

// usenamespace.cpp
#include <iostream>
#include "namesp.h"

void other(void);
void another(void);

int main(void)
{
    using debts::Debt;
    using debts::showDebt;
    Debt golf = {{"Benny", "Goatsniff"}, 120.0};    
    showDebt(golf);
    other();
    another();
    return 0;
}

void other(void)
{
    using std::cout;
    using std::endl;
    using namespace debts;
    Person dg = {"Doodles", "Glister"};
    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;
    return ;
}

void another()
{
    using pers::Person;
    Person collector = {"Milo", "Rightshift"};
    pers::showPerson(collector);
    std::cout << std::endl;
}

运行结果为:

 

 

名称空间(命名空间)的指导原则:

1.使用在已命名的名称空间中声明的变量, 而不是使用外部全局变量;

2.使用在已命名的名称空间中声明的变量, 而不是使用静态全局变量;

3.如果开发一个函数库或类库, 将其放在一个名称空间中

4.仅将编译指令using作为一种将旧代码转换为使用名称空间的权宜之计;

5.不要在头文件中使用using编译指令. 首先这样做掩盖了要让哪些名称可用; 另外包含头文件的顺序可能影响程序的行为. 如果非要使用编译指令using, 应将其放在所有预处理器编译指令#include之后;

6.导入名称时, 首选使用作用域解析运算符或using声明的方法;

7.对于suing声明, 首选将其作用域设置为局部而不是全局.

 

 

 

 

猜你喜欢

转载自blog.csdn.net/c1392851600/article/details/84887580
今日推荐