命名空间

C++·语法 2019-01-10 6512 字 1210 浏览 点赞

概念

声明区域(declaration region):

  • 所在文件 (函数外的全局变量)
  • 所在代码块 (花括号内的声明)

潜在作用域(potential scope):从声明点开始,到声明区域的结尾。
作用域:变量对程序而言,可见的范围。

命名空间:避免相同名称的变量发生冲突。可以是全局,可以位于另一个名称空间中,但不能在代码块中
全局命名空间:对应文件级的声明区域。
访问命名空间::作用域解析运算符,如:std::cout << "xxx" << endl;
未限定名称, 如:pail限定名称,如:jack::pail
using声明using jack::pail; 只能直接使用jack中的pail。
using编译指令using namespace jack; 可以使用jack中的全部名称。

示例

#include <iostream>

using namespace std;

namespace Jack {
    int age = 12;
    void greet();
}

void Jack::greet()
{
    // 命名空间中的函数,可以默认的
    // 使用同一命名空间中的其他名称
    cout << "I'm " << age << endl;
}

int main(int argc, char *argv[])
{
    // 其他函数中则需要以下格式:
    // 空间名::变量名
    Jack::greet();
    cout << Jack::age << endl;
    return 0;
}

两个要点:

  • 命名空间中的函数,可以默认的使用同一空间中的其他名称
  • 通过作用解析运算符::访问命名空间中的名称:Jack::pail

使用using

using声明

通过using声明,将名称添加到局部声明区域中:

#include <iostream>

using namespace std;

namespace Jack {
    int age = 12;
    void greet();
}

void Jack::greet()
{
    cout << "I'm " << age << endl;
}

int main(int argc, char *argv[])
{
    using Jack::age;  // 针对变量
    using Jack::greet;  // 针对函数
    
    cout << age << endl;  // 使用变量
    greet();  // 使用函数
    return 0;
}

利用using声明, 不论是变量还是函数,都只需要使用名称。但每个using声明,只能使一个名称可用。

using编译指令

using编译指令能使所有名称可用:

#include <iostream>

using namespace std;

namespace Jack {
    int age = 12;
    void greet();
}

void Jack::greet()
{
    cout << "I'm " << age << endl;
}

int main(int argc, char *argv[])
{
    using namespace Jack;  // using编译指令
    cout << age << endl;
    greet();
    return 0;
}

比较

相同名称的处理(局部变量)

using声明不允许导入已经存在的相同的名称:

// 不允许声明方法
int main(int argc, char *argv[])
{
    int age = 40;  // 已经存在的age
    using Jack::age;  // 再导入Jack中的age
    cout << age << endl;
    return 0;
}

此时编译器报错:

  • error: 'age' is already declared in this scope
  • error: redeclaration of 'int Jack::age'

但using编译指令是被允许的:

#include <iostream>

using namespace std;

namespace Jack {
    int age = 12;
}

int main()
{
    using namespace Jack;
    cout << age << endl;  // age = 12
    int age = 20;
    cout << age << endl;  // age = 20
    cout << Jack::age << endl;  // age = 12
}

然而,局部变量会隐藏命名空间中的同名名称。当定义局部变量age之后,使用age,得到的会是局部变量的age。再向使用命名空间中的age,则得利用作用域解析运算符::

==需要注意的是:== 使用using编译指令和声明局部变量,二者之间的使用顺序并不会影响上述结果。

结论:using声明像是声明了相同的名称;而using编译指令则更像在包含using声明和命名空间本身的最小声明区域中声明了名称

相同名称的处理(全局变量)

using 声明:

#include <iostream>

using namespace std;

namespace Jack {
    int age = 12;
    void greet();
}

int age = 20;
int main(int argc, char *argv[])
{
    using Jack::age;
    cout << age << endl;  // age = 12
    cout << ::age << endl;  // age = 20
    return 0;
}

using编译指令:

#include <iostream>

using namespace std;

namespace Jack {
    int age = 12;
    void greet();
}

int age = 20;
int main()
{
    using namespace Jack;
    cout << age << endl;
    return 0;
}

编译器报错: error: reference to 'age' is ambiguous

总结:using编译指令将命名空间中的名称视为在函数之外声明的。

作用域

无论using声明还是using编译指令,在A函数中导入的名称,都不能在B函数中使用:

#include <iostream>

using namespace std;

void func();

namespace Jack {
    int age = 12;
    void greet();
}

int main()
{
    using Jack::age;
    func();
    return 0;
}

void func()
{
    cout << age << endl;  // 错误用法
}
...
int main()
{
    using namespace Jack;
    func();
    return 0;
}

void func()
{
    cout << age << endl;  // 错误用法
}

报错: error: 'age' was not declared in this scope

结论

相比using编译指令导入所有名称,使用using声明会好一些。

嵌套式命名空间

C++允许在一个命名空间中嵌套其他命名空间:

#include <iostream>

using namespace std;

namespace outter {
    namespace inner {
        int num = 512;
    }
}

int main()
{
    cout << outter::inner::num << endl;  // 输出:12
}

也允许在命名空间中使用using声明或是using编译指令:

#include <iostream>

using namespace std;

namespace a {
    int A = 512;
}

namespace b {
    int B = 1024;
}

namespace c {
    using a::A;
    using namespace b;
}

int main()
{
    cout << c::A << endl;
    cout << c::B << endl;
}

可传递性

如果A op B 且 B op C, 则A op C:

#include <iostream>

using namespace std;

namespace c {
    int num = 512;
}

namespace b {
    using namespace c;
}

namespace a {
    using namespace b;
}

int main()
{
    cout << a::num << endl;  // num = 512
}

或者:

#include <iostream>

using namespace std;

namespace c {
    int num = 512;
}

namespace b {
    using c::num;
}

namespace a {
    using b::num;
}

int main()
{
    cout << a::num << endl;  // num = 512
}

注意命名空间定义的顺序:

  • 如果using编译指令顺序有误报错:error: 'c' is not a namespace-name
  • 如果using声明顺序有误报错:error: 'c' has not been declared

未命名的命名空间

#include <iostream>

using namespace std;

namespace {
    int num = 512;
}

int main()
{
    cout << num << endl;  // num = 512
}

意义:未命名空间中的名称可当作全局变量使用,但不能在其他文件中使用该命名空间中的名称(类比static修饰的变量)。

命名空间注意事项

注意1

在头文件中定义了变量(未初始化),便不可以在源代码的对应命名空间中为其赋值:

// neamesp.h
#ifndef NAMESP_H
#define NAMESP_H

namespace Jack {
    int age;
}

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

using namespace std;

namespace Jack {
    age = 20;
}

int main()
{
    cout << Jack::age << endl;
}

报错:‘age’ does not name a type


但可以在头文件中声明一个函数,然后在源代码文件中的对应命名空间中定义:

// namesp.h
#ifndef NAMESP_H
#define NAMESP_H


namespace Jack {
    void func();
}

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

using namespace std;

namespace Jack {
    void func()
    {
        cout << "hello world";
    }
}


int main()
{
    Jack::func();  // 输出: hello world
}

也可以这样定义:

// namesp.cpp
...
void Jack::func()
{
    cout << "hello world" << endl;
}
...

注意2

因为using声明没有描述函数返回类型或是函数特征,所以如果函数被重载,则一个using声明导入全部版本

指导原则

  • 使用在已命名的命名空间中声明的变量,而不是使用外部全局变量
  • 使用在已命名的命名空间中声明的变量,而不是使用静态全局变量
  • 如果开发了一个函数库或类库,将其放在命名空间中
  • 仅将using编译指令当做“兼容旧式代码”的产物
  • 尽量不要在头文件中使用using编译指令
  • 导入名称时,首选作用域解析和using声明
  • 对using声明,首选其作用域为局部而不是全局


本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论