🚀 优质资源分享 🚀
学习路线指引(点击解锁) | 知识定位 | 人群定位 |
---|---|---|
🧡 Python实战微信订餐小程序 🧡 | 进阶级 | 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。 |
💛Python量化交易实战💛 | 入门级 | 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统 |
0. 文章目的
面向C#新学者,介绍命名空间(namespace)的概念以及C#中的命名空间的相关内容。
1. 阅读基础
理解C与C#语言的基础语法。
理解作用域概念。
2. 名称冲突与命名空间
2.1 一个生活例子
假设猫猫头在北京有一个叫AAA的朋友,在上海有两个叫AAA的朋友,上海的两个AAA一个喜欢咸粽子,一个喜欢甜粽子。有一天猫猫找朋友玩,朋友问道:
“AAA最近过得怎么样”,
然而猫猫头有三个叫AAA的朋友,因此猫猫头不确定朋友问的是哪个AAA,于是朋友改问:
“上海的那个AAA最近过得怎么样”
精确了一点,但这还不够,因为猫猫头在上海认识两个叫AAA的朋友,于是朋友再次改问:
“上海的那个喜欢咸粽子的AAA最近过得怎么样。
到这里,猫猫头就确定了朋友问的是哪个小明。也就是说,通过地域+喜好+姓名,猫猫头可以确定朋友指的具体的人。
这个例子中,通过一层一层的限定修饰,我们从逐渐精确定位到了指定的AAA。在现实中,通过各种各样的限定修饰,我们可以区分具有相似名称的人或物,而对于程序来说也是如此。
2.2 从C语言的缺陷到命名空间
(1)函数命名冲突
在谈论什么是命名空间之前,我们先来看一看C语言中存在的一些问题。假设你和你的小伙伴同时开发一个C程序,并且你们很巧地定义了两个函数名相同的函数:
void Init() { } // 初始化控制台
void Init() { } // 初始化打印机
假设这两个函数做的事完全不同(一个用来初始化控制台(Console),一个用来初始化打印机(Printer))而无法合并,那么显然此时需要用一个办法来区分两个函数。经过简单讨论,你和你的小伙伴决定在每个函数名前添加函数的作用对象名字加以区分,于是你们把函数名改成了如下:
void ConsoleInit() { } // 用于初始化控制台的Init
void PrinterInit() { } // 用于初始化打印机的Init
随着开发进度的推进,你们创建的同名函数可能会越来越多,最后函数名看起来很可能像下面这样:
void ConsoleInit() { }
void ConsoleFoo() { }
void ConsoleWhatever() { }
void ConsolePrint(const char* s) { }
...
void PrinterInit() { }
void PrinterFoo() { }
void PrinterWhatever() { }
void PrinterPrint(const char* s) { }
...
当然这样的函数名并不是不行,但是函数名中含有不必要的冗余信息,使用这种函数名会使代码可读性下降,更重要的是,这还会使得编写代码时所需要输入的字符量大大增加:
ConsoleInit();
ConsoleFoo();
ConsoleWhatever();
ConsolePrint("...");
在上述代码中,你使用的函数前都添加了Console前缀,哪怕这时其实你可以明确自己大部分时候都是在操作控制台,此时,无论是使用还是阅读,这些前缀对你来说只是多余的。另一方面,假设有办法让编译器为某个范围内所有使用的函数名都自动添加‘Console’前缀,例如像下面这样:
// 告诉编译器为下面代码块中所有的函数名都添加Console前缀
{
Init(); // 在编译器看来是ConsoleInit
Foo(); // 在编译器看来是ConsoleFoo
Whatever(); // 在编译器看来是ConsoleWhatever
Print("..."); // 在编译器看来是ConsolePrint
}
此时就可以不用输入很多不必要的Console前缀,使用函数就方便了许多。
(2)让编译器代劳
基于上述理由,可以定义一种语法来告诉编译器为接下来使用的函数名都添加指定前缀,例如:
// 使用namespace关键字告诉编译器为其后代码块中所有的函数名都添加Console前缀
namespace Console
{
Init();
Foo();
Whatever();
Print("...");
}
在这里,我们设定使用namespace关键字来告诉编译器为后面代码块中所有的函数都添加其后面指定的Console前缀,这样在编译器看来,上述实际代码就如下:
ConsoleInit();
ConsoleFoo();
ConsoleWhatever();
ConsolePrint("...");
显然此时程序依然可以准确地调用合适的函数。同样,既然可以让编译器在调用函数时自动为其添加前缀,那么自然也可以让其在定义函数时也为函数名自动添加前缀:
namespace Console // 为其后代码块中的成员自动添加Console前缀
{
void Init() { ... }
void Foo() { ... }
void Whatever() { ... }
void Print(const char* s) { ... }
}
这样,在编译器进行自动转换后,上述的代码就会像下面这样:
void ConsoleInit() { }
void ConsoleFoo() { }
void ConsoleWhatever() { }
void ConsolePrint(const char* s) { }
有了这种自动添加前缀的语法后,那么在对控制台进行相关的操作时,就可以像下面这样操作了:
// 使用namespace关键字告诉编译器为其后代码块中所有的函数名都添加Console前缀
namespace Console
{
void Init() // 定义init函数(函数全名是ConsoleInit)
{
...
}
void Launch() // 定义Launch函数(函数全名是ConsoleLaunch),并在函数中调用前面定义的Init方法
{
Init(); // 该Init即ConsoleInit
...
}
}
而对打印机进行相关操作时,也只需要:
// 使用namespace关键字告诉编译器为其后代码块中所有的函数名都添加Printer前缀
namespace Printer
{
v