avatar

目录
334 - cpp Nested

334 - C++ Nested

🧑‍💻 User

In this lecture, we’re going to learn about nested namespaces.And this is a facility we have in the C plus plus programming language to be able to set up namespacesinside other namespaces.For example, here we have the hello namespace, and inside that namespace we have another namespacecalled World.And one thing you should know is that inner namespaces have direct access to things declared in theouter namespace.For example, the world namespace here is going to have direct access to the age name here.It’s not going to have to go through some namespace name to access that and we can see that here.But outer namespaces have to go through the namespace name to access things.We declare an inner namespaces and if you look at this, do something function here.If it wants to access this local variable here, it’s going to go through the world namespace name becausethat’s where this thing is declared.Once you know this, you’re going to head over to the main function or wherever in your code and you’regoing to use your namespaces however you want.One thing you should know is that to get to the inner namespace, you have to go through the namespaces.For example, we say hello, we say Colon, we say world, we say colon.Then we’re going to access something inside our nested namespace here.Now that you know this, we’re going to head over to Visual Studio code and play with this a littlemore.Okay, Here we are in our working folder.The current project is nested namespaces.We’re going to grab our template files pretty quick and we’re going to put them in place and we aregoing to open this in Visual Studio code pretty quick.We’re going to have our main CPP file here.Let’s clean up a little bit.We’re going to remove what we don’t need here.We can close the left sidebar and I am just going to put in the code we’re going to play with becauseI don’t want you to waste your time typing all this things.We have an outer namespace called Hello.It is delimited by the curly braces on line three and line 20 as you see here.And inside that we have another namespace delimited by the curly braces.We have on line six and 13.As you see here, the inner namespace is called World.The outer namespace is called Hello.And we have things inside all these namespaces.The outer namespace, which is Hello, has a variable called age.We have a value inside in the inner namespace.We have a local variable which has a 44 inside and we have a function called say something inside thefunction we are saying just hello there and we are trying to access the variable that was declared inthe outer namespace and we can access that directly without going through the namespace name.That’s what I want you to see here in the outer namespace.We also have another function which is do something here and this function doesn’t have direct accessto the variables we have in our local namespace here or inner namespace to access the local var here,it will have to go through the world namespace and that’s what you see here.If you take out the world here, you’re going to see that we’re going to get a compiler error.Okay, Now that we have this in place, we can try and use this tense in the main function.For example, we can try and call the say something function here.But before we do that, please try and see if you can do this on your own.Please call this function world and run the program and see that you see the output here.Okay.If you have done this, congratulations.But if you had any problem, please watch what I’m going to do here.We’re just going to say hello and we’re going to put colon.Colon.We’re going to say world to access the inner namespace.We’re going to put colon.Colon, and then we’re going to call our function here.Say something.This is the syntax you use to use nested namespaces, and this is really cool.We’re going to build the application using GCC.The world is going to be good.We’re going to bring up a terminal to try things out.We’re going to clear and run Rooster.You’re going to see that we say hello there, which is the message here.And we’re going to say the age is H and we’re going to be accessing a variable that lives in the outerHello namespace here.And this is really cool.If you want, you can also call a function living in our outer namespace.That function happens to be this do something function here.So all we have to do is say hello and say do something.Okay, this is going to do our thing.We’re going to build this.The world is going to be good.As you see here, we’re going to clear and run Rooster.We’re going to say using local Var, and that’s going to be 44 here.Please notice that we are going through the world namespace to access the local variable here.If you take out this world namespace, you’re going to get a compiler error because now local Var isnot going to be accessible or defined in this scope here.That’s the error you’re going to get.Let’s try and build this because I want you to know that I am not making this up.This is what we’re going to see.We’re going to see local.Var was not declared in this scope and that’s going to be a compiler error.To be able to use this, we have to specify the namespace where it is living and if we do this now,this is going to be fine because now the compiler is going to go through this namespace and access thislocal variable here and we’re going to print it out and see it on our console.Let’s bring this up a little bit.You see the world is good.We can clear and run Rooster and we’re going to see that our local variable is 44.This is really all I wanted you to see in this lecture that you can set up nested namespaces if thisis helpful for whatever project you are doing in C plus plus, please use this facility and it is goingto make your life easier.Just remember to go through the syntax like this to access things that lives inside nested namespaces.We are going to stop here in this lecture and the next one we’re going to learn about namespaces, aliases.Go ahead and finish up here and meet me there.


🤖 Assistant

好的,欢迎来到C++的学习之旅!作为您C++旅程的导师,我将尽我所能,用最清晰、最易懂的方式,带您深入理解C++中的重要概念。


深入理解C++嵌套命名空间:组织代码的强大工具

摘要: 本节课将详细讲解C++中如何使用[嵌套命名空间] (nested namespaces) 来更好地组织您的代码,区分同名实体,并探讨内层和外层命名空间成员之间的访问规则。我们将通过详细的示例代码来理解其工作原理,并识别常见的使用误区。


1. 什么是命名空间 (Namespace)?

在深入了解嵌套命名空间之前,我们首先要理解什么是[命名空间] (namespace)。

想象一下,你和你的朋友们都在各自的家里养宠物。你可能养了一只狗叫”旺财”,你的朋友可能也养了一只狗叫”旺财”。当你说”旺财,过来!”的时候,如果你们在同一个屋子里,谁的旺财会过来呢?这就可能造成混乱。

在C++编程中,我们也面临类似的问题。当项目变得越来越大,或者当你使用许多第三方库时,不同的代码库中可能会定义相同名称的[函数] (function)、[变量] (variable) 或[类] (class)。例如,你的代码中有一个 print() 函数,而你使用的某个库中可能也有一个 print() 函数。如果直接使用,编译器就不知道你到底想调用哪一个 print()

为了解决这种“命名冲突”的问题,C++引入了“命名空间”这个概念。命名空间就像一个“房子”,你可以在里面定义你的函数、变量、类等。这样,即使你的“房子”里有一只叫“旺财”的狗,而你朋友的“房子”里也有一只叫“旺财”的狗,它们也不会相互混淆。你可以说“我的房子里的旺财”,或者“你朋友的房子里的旺财”。

在C++中,我们使用 namespace [关键字] (keyword) 来定义一个命名空间,并通过 ::([作用域解析运算符] scope resolution operator)来指明要访问哪个命名空间里的成员。

示例:

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace MyHouse { // 这是一个命名空间,叫做MyHouse
int dog_age = 5;
void bark() {
// 这里的 bark() 属于 MyHouse 命名空间
}
}

namespace FriendHouse { // 这是另一个命名空间,叫做FriendHouse
int dog_age = 3;
void bark() {
// 这里的 bark() 属于 FriendHouse 命名空间
}
}

int main() {
// 访问 MyHouse 命名空间中的 dog_age
int my_dog_age = MyHouse::dog_age;

// 调用 FriendHouse 命名空间中的 bark() 函数
FriendHouse::bark();
return 0;
}

2. 什么是嵌套命名空间?

现在我们知道了命名空间的基本概念,那么[嵌套命名空间] (nested namespaces) 又是什么呢?

嵌套命名空间,顾名思义,就是在一个命名空间内部再定义另一个命名空间。这就像你在一个大房子(外层命名空间)里面,又建造了一个小房间(内层命名空间)。这种结构提供了更细粒度的代码组织方式,使得代码的逻辑结构更加清晰,进一步减少命名冲突的可能性。

为什么需要嵌套命名空间?

当你有一个非常大的项目时,仅仅使用一级命名空间可能还不够。例如,你正在开发一个游戏,你可能有一个 Game 命名空间。在这个 Game 命名空间里,你可能需要管理 Graphics(图形)、Audio(音频)、Physics(物理)等子系统。每一个子系统内部又可能有很多自己的函数和类。这时候,你就可以使用嵌套命名空间来组织它们:

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace Game { // 游戏大命名空间
namespace Graphics { // 图形子系统命名空间
void drawPlayer();
class Texture;
}

namespace Audio { // 音频子系统命名空间
void playSound();
class SoundEffect;
}

// Game 命名空间中可能还有其他成员
int game_version = 1;
}

3. 嵌套命名空间的访问规则 (Access Rules)

这是理解嵌套命名空间最关键的部分。记住以下两个核心规则:

规则1:内层命名空间可以直接访问外层命名空间的成员

这就像你在一个房间里,可以直接看到并使用这个房间所属的整个房子里的东西。

  • 解释: 如果你在 Hello::World 这个内层命名空间里,你可以直接使用 Hello 命名空间里声明的任何变量、函数或类,而不需要加上 Hello:: 前缀。编译器会自动向上查找。
  • 示例: 如果 Hello 命名空间里有一个变量 age,那么在 Hello::World 命名空间内部定义的函数,可以直接访问 age

规则2:外层命名空间需要通过完整路径访问内层命名空间的成员

这就像你在房子的外面,如果你想找到小房间里的东西,你需要先进入房子,然后进入小房间。

  • 解释: 如果你在 Hello 命名空间里,或者在全局命名空间 (main 函数通常在全局命名空间) 中,想要访问 Hello::World 命名空间里的成员,你必须提供完整的路径,例如 Hello::World::成员名称。你不能直接在 Hello 命名空间内访问 World 命名空间中的 local_var,因为它不在 Hello 的直接作用域内。
  • 示例: 如果 Hello::World 命名空间里有一个变量 local_var,那么在 Hello 命名空间内部定义的函数,或者在 main 函数中,必须使用 Hello::World::local_var 来访问它。

4. 详细代码示例

让我们通过一个完整的代码示例来巩固这些概念。这个示例涵盖了您提供的内容中的所有要点,并增加了详细的注释来解释每一步。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream> // 引入输入输出流库,用于打印信息

// 定义一个外层命名空间,名为 Hello
namespace Hello {
// Hello 命名空间内部的一个变量
int age = 30; // 这是一个在 Hello 命名空间中声明的变量

// 定义一个内层命名空间,名为 World
namespace World {
// World 命名空间内部的一个变量
int local_var = 44; // 这是一个在 Hello::World 命名空间中声明的变量

// World 命名空间内部的一个函数
void say_something() {
// 规则1示例:内层命名空间直接访问外层命名空间的成员
// 这里的 'age' 是 Hello 命名空间中的变量,可以直接访问
std::cout << "Hello there from World! "
<< "The age from outer namespace is: " << age << std::endl;
}
} // World 命名空间结束

// Hello 命名空间内部的另一个函数
void do_something() {
// 规则2示例:外层命名空间需要通过完整路径访问内层命名空间的成员
// 这里的 'local_var' 是 World 命名空间中的变量,需要加上 World:: 前缀
std::cout << "Using local_var from inner namespace: "
<< World::local_var << std::endl;

// 演示错误:如果在这里直接写 local_var,会编译错误
// std::cout << local_var << std::endl; // Error: 'local_var' was not declared in this scope
}
} // Hello 命名空间结束

int main() {
// 访问内层命名空间 World 中的 say_something 函数
// 语法: OuterNamespace::InnerNamespace::Function()
std::cout << "--- Calling World::say_something() ---" << std::endl;
Hello::World::say_something();

std::cout << "\n--- Calling Hello::do_something() ---" << std::endl;
// 访问外层命名空间 Hello 中的 do_something 函数
// 语法: OuterNamespace::Function()
Hello::do_something();

std::cout << "\n--- Directly accessing members from main() ---" << std::endl;
// 从 main 函数(全局命名空间)直接访问 Hello 命名空间中的 age
std::cout << "Accessing Hello::age directly: " << Hello::age << std::endl;

// 从 main 函数(全局命名空间)直接访问 Hello::World 命名空间中的 local_var
std::cout << "Accessing Hello::World::local_var directly: " << Hello::World::local_var << std::endl;

// 演示错误:如果想在 main 函数中直接访问 local_var,会编译错误
// std::cout << local_var << std::endl; // Error: 'local_var' was not declared in this scope
// std::cout << World::local_var << std::endl; // Error: 'World' was not declared in this scope (因为World不在全局作用域)

return 0;
}

代码运行输出:

Code
1
2
3
4
5
6
7
8
9
--- Calling World::say_something() ---
Hello there from World! The age from outer namespace is: 30

--- Calling Hello::do_something() ---
Using local_var from inner namespace: 44

--- Directly accessing members from main() ---
Accessing Hello::age directly: 30
Accessing Hello::World::local_var directly: 44

5. QA 闪卡 (Flash Cards)

为了帮助您记忆和理解,这里有一些问答闪卡:

Q1: C++中命名空间的作用是什么?
A1: 命名空间的主要作用是避免[命名冲突] (name collision),尤其是在大型项目或使用多个库时,它可以帮助组织代码,使同名但不同上下文的实体能够共存。

Q2: 什么是嵌套命名空间?
A2: 嵌套命名空间是指在一个命名空间内部再定义的另一个命名空间。它提供了更精细的代码组织结构。

Q3: 如果我在内层命名空间里,能否直接访问外层命名空间的成员?
A3: 可以。 内层命名空间对其外部的命名空间成员具有直接访问权限。

Q4: 如果我在外层命名空间里,能否直接访问内层命名空间的成员?
A4: 不能。 外层命名空间必须使用完整的命名空间路径(例如 Outer::Inner::member)来访问内层命名空间的成员。

Q5: 从 main 函数(全局命名空间)访问嵌套命名空间 Hello::World 中的 say_something 函数的语法是什么?
A5: 语法是 Hello::World::say_something();

6. 常见误解或错误

  1. 误解1: 认为外层命名空间可以直接访问内层命名空间成员。

    • 错误: namespace Outer { namespace Inner { int val; } void func() { val = 10; } } 这里的 val 会导致编译错误。
    • 正确做法: namespace Outer { namespace Inner { int val; } void func() { Inner::val = 10; } } 或者 Outer::Inner::val = 10;
  2. 误解2: 混淆了 using namespace 语句的作用。

    • using namespace Hello; 只能让你直接访问 Hello 命名空间中的成员(如 age, do_something())。
    • 错误:不会让你直接访问 Hello::World 命名空间中的成员(如 local_var, say_something())。你仍然需要写 World::say_something()Hello::World::say_something()
    • 如果你想直接访问 Hello::World 中的成员,你需要 using namespace Hello::World; 或者同时 using namespace Hello;using namespace Hello::World;。但对于初学者,建议始终使用完整的命名空间路径,这样代码更清晰,不易出错。
  3. 误解3: 忘记命名空间是用来组织代码的,而不是用来限制访问权限。

    • 命名空间的主要目的是避免命名冲突和逻辑分组,而不是实现像 privateprotected 那样的访问修饰符。命名空间中的所有成员默认都是[公共的] (public)。
  4. 错误: 拼写错误或遗漏 :: 作用域解析运算符。

    • 这是最常见的语法错误。记住,每次跨越命名空间边界访问其内部成员时,都需要使用 ::

编码练习

好的,现在轮到您动手实践了!请完成下面的代码,实现一个简单的嵌套命名空间。

任务:
您需要创建一个名为 Game 的外层命名空间,并在其中嵌套一个名为 Player 的内层命名空间。

  1. Game 命名空间中,声明一个整型变量 max_score 并初始化为 1000
  2. Player 命名空间中,声明一个字符串变量 name 并初始化为 "Hero"
  3. Player 命名空间中,定义一个无参数无返回值的函数 display_player_info()
    • 在这个函数内部,打印玩家的 name
    • 然后,直接访问并打印外层 Game 命名空间中的 max_score
  4. Game 命名空间中,定义一个无参数无返回值的函数 start_game()
    • 在这个函数内部,打印一条消息,例如 “Game started!”。
    • 然后,使用完整路径调用内层 Player 命名空间中的 display_player_info() 函数。
  5. main 函数中:
    • 使用完整路径调用 Game 命名空间中的 start_game() 函数。

请填写 _______ 部分的代码,使其能够正确运行并输出预期的结果。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
#include <string> // 用于使用 std::string

// 1. 定义外层命名空间 Game
namespace Game {
// 在 Game 命名空间中,声明一个整型变量 max_score 并初始化为 1000
int max_score = _______;

// 2. 在 Game 命名空间内部,嵌套定义 Player 命名空间
namespace Player {
// 在 Player 命名空间中,声明一个字符串变量 name 并初始化为 "Hero"
std::string name = _______;

// 3. 在 Player 命名空间中,定义 display_player_info() 函数
void display_player_info() {
std::cout << "Player Name: " << name << std::endl;
// 直接访问并打印外层 Game 命名空间中的 max_score
std::cout << "Max Score (from Game namespace): " << _______ << std::endl;
}
} // Player 命名空间结束

// 4. 在 Game 命名空间中,定义 start_game() 函数
void start_game() {
std::cout << "Game started!" << std::endl;
// 使用完整路径调用内层 Player 命名空间中的 display_player_info() 函数
_______;
}
} // Game 命名空间结束

int main() {
// 5. 在 main 函数中,使用完整路径调用 Game 命名空间中的 start_game() 函数
_______;

return 0;
}

评论