3. 命名空间

命名空间(namespace)是 C++ 中一项重要的特性,它可以将全局作用域划分为不同的部分,从而避免不同库或者不同模块之间的命名冲突。标准 C++ 库中的所有标识符(像类、函数、对象等)都被定义在了 std 命名空间里。

1. using namespace std

在没有使用 using namespace std; 时,若要使用标准库中的标识符,就必须明确指定其所属的命名空间,也就是在标识符前面加上 std:: 前缀。例如:

<span class="tag">#include</span> <iostream>
int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

当使用了 using namespace std; 之后,就无需再每次都写 std:: 前缀,代码会变得更加简洁:

<span class="tag">#include</span> <iostream>
using namespace std;

int main() {
    cout << "Hello, World!" << endl;
    return 0;
}

在代码中频繁使用标准库的标识符时,每次都写 std:: 会显得很繁琐。使用 using namespace std; 可以减少重复书写,提高代码的可读性和编写效率。

1.1. 潜在的问题

1.1.1. 命名冲突

使用 using namespace std; 会将 std 命名空间中的所有标识符引入到当前的全局作用域中,这就可能会和用户自定义的标识符产生命名冲突。例如:

<span class="tag">#include</span> <iostream>
using namespace std;

// 自定义一个名为 cout 的函数
void cout() {
    std::cout << "This is a custom cout function." << std::endl;
}

int main() {
    // 这里会产生命名冲突,因为全局作用域中同时存在 std::cout 和自定义的 cout 函数
    cout(); 
    return 0;
}

1.1.2. 代码可读性降低

在大型项目中,过多使用 using namespace std; 会让代码的可读性变差,因为很难一眼看出某个标识符是来自标准库还是用户自定义的。

替代方案: 为了避免上述问题,可以采用更细粒度的方式引入标准库的标识符,比如:

<span class="tag">#include</span> <iostream>

// 只引入需要使用的标识符
using std::cout;
using std::endl;

int main() {
    cout << "Hello, World!" << endl;
    return 0;
}

这种方式既能简化代码书写,又能减少命名冲突的风险。

2. 自定义namespace

可以使用 namespace 关键字来定义一个自定义的命名空间,其基本语法如下:

namespace YourNameSpaceName {
    // 在这里定义变量、函数、类等
    int variable;
    void function() {
        // 函数实现
    }
    class ClassName {
        // 类的定义
    };
}

2.1. 示例

// 定义一个名为 MyNamespace 的命名空间
namespace MyNamespace {
    int num = 10;

    void printNum() {
        std::cout << "MyNamespace 中的 num 值为: " << num << std::endl;
    }
}

2.1.1. 使用命名空间中的成员

2.1.1.1. 作用域解析运算符 ::

可以使用作用域解析运算符 :: 来指定成员所属的命名空间。

<span class="tag">#include</span> <iostream>

namespace MyNamespace {
    int num = 10;

    void printNum() {
        std::cout << "MyNamespace 中的 num 值为: " << num << std::endl;
    }
}

int main() {
    // 使用作用域解析运算符访问命名空间中的成员
    std::cout << "直接访问 MyNamespace 中的 num: " << MyNamespace::num << std::endl;
    MyNamespace::printNum();

    return 0;
}

2.1.1.2. using 声明

使用 using 声明可以让特定的命名空间成员在当前作用域内直接使用,无需每次都指定命名空间。

<span class="tag">#include</span> <iostream>

namespace MyNamespace {
    int num = 10;

    void printNum() {
        std::cout << "MyNamespace 中的 num 值为: " << num << std::endl;
    }
}

int main() {
    // 使用 using 声明引入特定成员
    using MyNamespace::num;
    using MyNamespace::printNum;

    std::cout << "使用 using 声明访问 num: " << num << std::endl;
    printNum();

    return 0;
}

2.1.1.3. using 指令

使用 using 指令可以将整个命名空间的所有成员引入到当前作用域,但要注意可能会引发命名冲突。

<span class="tag">#include</span> <iostream>

namespace MyNamespace {
    int num = 10;

    void printNum() {
        std::cout << "MyNamespace 中的 num 值为: " << num << std::endl;
    }
}

int main() {
    // 使用 using 指令引入整个命名空间
    using namespace MyNamespace;

    std::cout << "使用 using 指令访问 num: " << num << std::endl;
    printNum();

    return 0;
}

3. 嵌套命名空间

命名空间可以嵌套,即在一个命名空间内部再定义另一个命名空间。

<span class="tag">#include</span> <iostream>

namespace OuterNamespace {
    int outerVar = 20;

    namespace InnerNamespace {
        int innerVar = 30;

        void printVars() {
            std::cout << "OuterNamespace::outerVar: " << outerVar << std::endl;
            std::cout << "InnerNamespace::innerVar: " << innerVar << std::endl;
        }
    }
}

int main() {
    // 访问嵌套命名空间中的成员
    std::cout << "访问 OuterNamespace 中的 outerVar: " << OuterNamespace::outerVar << std::endl;
    std::cout << "访问 InnerNamespace 中的 innerVar: " << OuterNamespace::InnerNamespace::innerVar << std::endl;
    OuterNamespace::InnerNamespace::printVars();

    return 0;
}

4. 匿名命名空间

C++ 还支持匿名命名空间,其中定义的成员只在当前文件中可见,类似于使用 static 关键字修饰的全局变量和函数。

<span class="tag">#include</span> <iostream>

// 匿名命名空间
namespace {
    int secretNum = 42;

    void secretFunction() {
        std::cout << "匿名命名空间中的 secretNum: " << secretNum << std::endl;
    }
}

int main() {
    // 直接使用匿名命名空间中的成员
    std::cout << "访问匿名命名空间中的 secretNum: " << secretNum << std::endl;
    secretFunction();

    return 0;
}

5. 跨多文件

可以在不同的源文件(.cpp 文件)和头文件(.h.hpp 文件)中对同一个命名空间进行定义和扩展。

m_ns1.h

// my_namespace.h
<span class="tag">#ifndef</span> MY_NAMESPACE1_H
<span class="tag">#define</span> MY_NAMESPACE1_H

<span class="tag">#include</span> <iostream>

namespace MyNamespace {
    // 声明一个函数
    void printMessage1();
    // 声明一个变量
    extern int myVariable1; 
}

<span class="tag">#endif</span>

m_ns1.cpp

// my_namespace.cpp
<span class="tag">#include</span> "m_ns1.h"
<span class="tag">#include</span> <iostream>

namespace MyNamespace {
    int myVariable1 = 10;

    void printMessage1() {
        std::cout << "This is a message from MyNamespace1. myVariable = " << myVariable1 << std::endl;
    }
}

m_ns2.h

// my_namespace.h
<span class="tag">#ifndef</span> MY_NAMESPACE2_H
<span class="tag">#define</span> MY_NAMESPACE2_H

<span class="tag">#include</span> <iostream>

namespace MyNamespace {
    // 声明一个函数
    void printMessage2();
    // 声明一个变量
    extern int myVariable2; 
}

<span class="tag">#endif</span>

m_ns2.cpp

// my_namespace.cpp
<span class="tag">#include</span> "m_ns2.h"
<span class="tag">#include</span> <iostream>

namespace MyNamespace {
    int myVariable2 = 20;

    void printMessage2() {
        std::cout << "This is a message from MyNamespace2. myVariable = " << myVariable2 << std::endl;
    }
}

ns_test.cpp

// main.cpp
<span class="tag">#include</span> "m_ns1.h"
# 5.1. <span class="tag">#include</span> "m_ns2.h"

int main() {
    // 使用作用域解析运算符访问命名空间中的成员
    MyNamespace::printMessage1();
    MyNamespace::printMessage2();
    std::cout << "Accessing myVariable1 from main: " << MyNamespace::myVariable1<< std::endl;
    std::cout << "Accessing myVariable2 from main: " << MyNamespace::myVariable2<< std::endl;
    return 0;
}

5.1. 编译和运行

修改vscode task.json如下

{
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: clang++ 生成活动文件",
            "command": "/usr/bin/clang++",
            "args": [
                "-fcolor-diagnostics",
                "-fansi-escape-codes",
                "-g",
                "${workspaceFolder}/*ns*.cpp",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "调试器生成的任务。"
        }
    ],
    "version": "2.0.0"
}

也可以通过命令行进行手动编译并执行

/usr/bin/clang++ -std=gnu++14 -fcolor-diagnostics -fansi-escape-codes -g /Users/shinerio/WorkSpace/c_learn/ns_test.cpp /Users/shinerio/WorkSpace/c_learn/m_ns1.cpp /Users/shinerio/WorkSpace/c_learn/m_ns2.cpp -o /Users/shinerio/WorkSpace/c_learn/ns_test

./ns_test
This is a message from MyNamespace1. myVariable = 10
This is a message from MyNamespace2. myVariable = 20
Accessing myVariable1 from main: 10
Accessing myVariable2 from main: 20