avatar

目录
332 - cpp using声明命名空间冲突

332 - C++ Using声明命名空间冲突

🧑‍💻 User

In this lecture, we’re going to learn about using declarations.And this is a facility we have in the C plus plus programming language to be able to call things thatlive inside a namespace without explicitly specifying the namespace name.Let’s see how this works.We have seen that we can set up different classes in our namespace using the syntax here.For example, this is a header file for our point class.We could do the same thing for our line class.As you see here, it’s living inside our namespace and we could do the same thing for our cylinder classagain.It is living inside this namespace here.If we want to use these classes that live inside the namespaces we have seen that we had to prefix everythingwith the namespace name and if you are using these things a lot, typing the namespace multiple timescan really be annoying and sometimes you want to avoid that using declarations or a facility we havein the C plus plus programming language to avoid typing these namespaces.A lot of times if you don’t want to do that.So how does this work?Well, we have two syntaxes we can use.If you look here, we have two syntaxes and I’m going to tell you the difference between them.The first one is going to just bring in a name inside the namespace and the syntax to do that.You say using explicitly as you see here, you say the namespace name, you say Colon, Colon, andthen you say the name you want to import that you want to use without prefixing that with the namespace.If we do something like this, we will be able to create the point objects without specifying the namespace.And I want you to know that the second one we can use is what you see here and you say using namespace,you say the namespace name and this is going to bring in the entire namespace and you will be able touse things inside that namespace without adding the namespace name prefix.Okay.So if you go down you’re going to see another example.You can say using namespace math.This is going to bring the entire math namespace.And if you say using math colon, colon add, this is only going to bring in the Add name from the mathnamespace and we’re going to see a few examples of how this works.I realize this may be confusing at this point, but don’t worry, we’re going to see an example andyou’re going to see how all this comes together.Okay.Once you have brought in your namespace, you can then create things without using namespace prefixes.You see, we can create a point without adding colon.Colon.We can create line objects.As you see down here, we can call the add function without specifying the namespace where it lives.And but this also works for builtin namespaces.For example, if you say using stdcout and using Stdendl, you will be able to say cout hello and endlwithout specifying the STD namespace here.And this is going to work.You can even bring in the entire STD namespace using the syntax here.But this is not recommended because then your names are going to conflict with the things that liveinside the standard namespace and that’s going to be really bad.Okay.Now that you have an idea about this, let’s head over to Visual Studio code and play with this a littlemore.Okay, Here we are in our working folder.The current project is using declarations.We’re going to grab our template files or I think we’re going to be using the files we have createdin our geometry or geom project here, the cylinder, the line and the point.So we’re going to grab things from this project here.I think that’s going to be easier to handle.So we’re going to grab the files that we need.We’re going to grab the main CPP file, We’re going to grab the point CPP file, we’re going to leaveout the executable because we don’t really want that.We’re going to copy these things and then we’re going to go in our current project.I think this is going to be better.It’s going to save us a few seconds.Now that we have this, we’re going to open this little guy in Visual Studio code.Let’s do that.Okay.We have our project open here and you see that to be able to use things from the global namespace,we had to specify the geometry namespace here.And sometimes this may be annoying if you are typing these things maybe a hundred times or more.So we’re going to use using declarations to make those things simpler.We’re going to start with our point class here.We can go in and say we don’t want to specify the namespace name when we create the point objects likethis and this is what we want.We want the syntax here to work and.We’re going to use using declarations to do that.So we’re going to say using geom and we’re going to say point to mean that the point class name is whatwe want to bring in.And the moment we do that, you’re going to see that the problem here is going to go away and this isgoing to work.So this is just bringing in the name from the namespace.Okay.I want you to know that this is the first syntax we can use to do this.But the second syntax you can use is to bring in the entire namespace.And you do that by saying using namespace.And you’re going to say the namespace name.I don’t think we need these things.That Visual Studio code is autocompleting for us.We’re going to say Geom and this is going to bring the entire namespace.And if we do this, even the things we have below here, for example, the line and the cylinder aregoing to work without specifying the namespace name.You can see that this is going to make your code easier to read, but I think it is going to be confusingbecause people want to know this.Things are going to be living in namespaces.They have to haunt this line down to be able to understand your code.But I’m going to leave this decision to you If you want to use this using declarations, please usethem.If you don’t want them, you can specify your own namespaces like we did before, and either choiceis going to work.Let’s try and build this.We’re going to build with GCC, you’re going to see that the build is going to be good and we can bringup a terminal to play with this a little more so that we can run the application and show you that itis working.We have our point, we have our line and we have our volume printed out.And this is really cool to play with this further, we’re going to add a few functions that live ina header file called operations and the CPP file called operations that CPP.So we’re going to add those files here.Let’s go on top here and add operations dot H and we’re going to also add operations that CPP.We are going to jump into the header file and put in our code.We have the namespace here and we don’t have an include guard on this header file.So let’s grab an include guard from some other file and we’re going to adapt this a little bit.I think that’s going to work better.Saving us a few typos.Let’s grab the ending of this little guy.Let’s grab cylinder dot h and we’re going to see the ending of that thing.We can go to the end and do that, and then we’re going to say operations dot H here, let’s say operations.And we can say operations here and we’re going to say operations down here.Okay, we’re good here.We are also going to put in our CPP file.Again, if we look at our operations here, it’s just setting up a simple example we can play with.We have add sub multi dev functions to add, subtract, multiply and divide.And we have two sets of these functions here.One set is going to just do the operations.It’s not going to do any adjustment and another set is going to do the adjustment upon further investigation.Here I see that the functions from the math namespaces are doing the adjustment that they shouldn’tdo.So I’m going to fix this a little bit.Sorry for this if you find this confusing, but this is programming.We make mistakes and we fix them.That’s the important thing.So let’s do that.We’re going to remove our adjustment here and we’re going to make sure that the adjustment is done inour weighted namespace here and we’re going to see that we don’t need this minus here.Uh, what was I doing when I typed this code?But this is working.The important thing is that this functions are living in separate namespaces.We have a set of functions which is going to just do the operations.It’s just going to add, subtract, multiply and divide.And we have a set of functions which is not just going to do the operations, but it’s also going todo an adjustment.We’re going to subtract.And suppose this is something important for whatever operation you are doing in your C plus plus program.The important thing is that we have these namespaces and we can use using declarations to make thingssimpler.Another thing we’re going to do is to head over in our main function and put in another add function.So for example, if we go down here and put in our add function, it’s going to say Add.We’re going to say that it lives in the global namespace and it’s just going to do something silly.It’s going to add up the numbers and it’s going to add 0.555 and we’re just going to see that we cancall this function here.Now, what happens if we go in our main function and call the Add function?Let’s do that.We’re going to put a separator here and we’re going to go down here and say, add, okay, which oneis going to be called?Well, the C plus plus compiler is going to see that we have an Add function here.We need to specify the parameters, let’s say ten and 20.This is going to satisfy our compiler.The compiler is going to look in the global namespace.It’s going to say, Aha, I have an Add function, I am going to call that Let’s build and show thatthis is actually the case.Okay.The world should be good here.We’re going to clear and run, Rooster.Clear and run, Rooster.We’re going to see that we are calling the Add function from the global namespace here.So what happens if, for example, we go down here and say, using math?And we can save this the moment we do this.We’re going to see a squiggly line.And this is problematic because we didn’t include the operations header.So let’s do that operations.And now this is going to be valid syntax, but the ad name is going to be confusing to the compiler.Now the compiler is going to see the Add function call here and it’s going to look on top here.It’s going to say, Aha, I have an Add function from the global namespace, but I also have an Addfunction from the math namespace that I can call and this is going to be confusing to the compiler.I want you to know this.So we’re going to try and build this with GCC and we’re going to get an ambiguous compiler error.I think that’s what we’re going to get.We’re going to see double math, add double conflicts with a previous declaration of Add, which ishere.So the compiler is seeing two declarations for the Add functions and it doesn’t know which one to use.Okay.We’re going to see the same problem if we also bring in the entire math namespace.For example, if we say using namespace and say math, for example, we’re going to have the same problemwe’re going to build, you’re going to see that world finished with errors and we’re going to see thatwe have two functions that could service the same call.And this is really bad.So if you want to call the Add function, you have to be sure you know which one you are calling.So for example, we can take out all of these declarations and come here and for example, say mathand specify that we want to call the add function from the math namespace.If we’re trying to build this, this is going to work.Let’s bring this up a little bit so that you can see you’re going to see that the build is good.And if we run this, we’re going to see that we are calling the Add function from the math namespace.If we say math weighted, we can say that.And build and run this application.We’re going to see that the world is good.If we run Rooster, we’re going to see that.Now we are calling the Add function from the weighted namespace and this is really cool.You really have to know this.But the important message here is that we can use the using declarations to bring in namespaces anduse them without specifying the namespace like we do here.And this is really cool.We have two syntaxes.We can bring in specific names or we can bring in the entire namespace and you’re going to have to choosewhatever you think is better for you.The last thing I want you to see in this lecture is that we can also use using declarations on builtin namespaces.So let’s see that pretty quick.We can bring in C out and Endl and we’re going to say using STD C out and using Stdendl and we can sayout Hello world here.And this is going to work because we have brought in these names from the STD namespace.If we build and run this application, you’re going to see that it’s going to work.Okay, The world is good.We can clear and clear, not clear and run.Rooster.We’re going to see that we are saying Hello world, but we are not specifying the namespaces when weissue our C out statement here.And some people prefer to do things like this, but I prefer to specify the namespaces where my thingsare living.This is one syntax we can use, but we can also bring in the entire STD namespace.Now you know how to do that.We can say using namespace STD and this is going to bring in the entire STD namespace, but this isalso going to work.But I don’t really recommend this because this is going to bring in the names from the STD namespaceand they are going to conflict with things you do in your own code.This is basically going to be the same problem we saw when we were playing with the Add function.We had an Add function that we defined ourselves here in the global namespace and it was conflictingwith functions from the math and math weighted namespaces.So you need to be very careful, but the decision is going to be yours because you know the specificsof your project.So I’m going to leave that decision to you.This is really all I had to share in this lecture.I hope you found it interesting.We are going to stop here in this one and the next one we’re going to learn about anonymous namespaces.Go ahead and finish up here and meet me there.


🤖 Assistant

好的,很高兴能为您详细讲解 C++ 中的 using 声明 (using declarations)!作为您的 C++ 导师,我将尽力用最清晰、最易懂的方式来帮助您掌握这个重要的概念。


C++ using 声明:让代码更简洁,但要小心命名冲突!

简要总结: 本次课程将深入探讨 C++ 中的 using 声明,它允许我们更简洁地使用命名空间 (namespace) 中的名称,而无需重复书写命名空间 前缀 (prefix)。我们将学习两种主要的 语法 (syntax) —— 引入单个名称和引入整个命名空间,并着重分析它们带来的便利性以及可能导致的 二义性 (ambiguous) 命名冲突问题,以及如何安全地使用它们。


1. 为什么我们需要 using 声明?—— 命名空间的“冗长”困扰

在 C++ 中,命名空间 (namespace) 是一种用于组织代码的机制,它帮助我们避免在大型项目中出现命名冲突。想象一下,您的团队开发了一个庞大的几何库 (例如 geom 命名空间),里面定义了 Point (点)、Line (线) 和 Cylinder (圆柱) 等类。当您想使用这些类时,每次都需要这样写:

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 假设这是您的Point类头文件:geom/Point.h
namespace geom {
class Point {
public:
double x, y, z;
Point(double x = 0, double y = 0, double z = 0);
void print() const;
};
} // end namespace geom

// 在您的主文件 main.cpp 中使用
#include "geom/Point.h"

int main() {
geom::Point p1(1.0, 2.0, 3.0); // 每次都要写 geom::
p1.print();
return 0;
}

这看起来还行,但如果您在一个文件中频繁创建 PointLineCylinder 对象,或者调用 geom 命名空间下的各种函数,每次都敲 geom:: 会非常繁琐和恼人。这正是 using 声明要解决的“痛点”。

2. using 声明的两种“魔法”语法

using 声明有两种主要的 语法 (syntax),它们提供了不同的便利程度,但潜在的风险也不同。

2.1 语法一:引入单个名称 (using namespace_name::member_name;)

这种语法允许您从一个 命名空间 中精确地引入一个特定的名称(比如一个类名、函数名或变量名),使其可以直接使用,而无需再添加命名空间 前缀

如何工作:
您可以使用 using 命名空间名称::您想引入的名称; 来实现。

示例代码:

让我们以上面的 geom::Point 为例。假设您的 main.cpp 文件原本是这样使用 PointLineCylinder 的:

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
// geom/Point.h (保持不变)
namespace geom {
class Point { /* ... */ };
}

// geom/Line.h (假设存在)
namespace geom {
class Line { /* ... */ };
}

// geom/Cylinder.h (假设存在)
namespace geom {
class Cylinder { /* ... */ };
}

// main.cpp (未使用 using 声明之前)
#include "geom/Point.h"
#include "geom/Line.h"
#include "geom/Cylinder.h"
#include <iostream>

int main() {
geom::Point p1(1.0, 2.0, 3.0);
geom::Line l1(p1, geom::Point(4.0, 5.0, 6.0)); // 甚至在参数中也要写
geom::Cylinder c1(p1, 5.0, 10.0);

std::cout << "Point created." << std::endl;
// ... 其他操作
return 0;
}

现在,我们使用 using 声明来简化 main.cpp

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// main.cpp (使用 using 声明引入单个名称之后)
#include "geom/Point.h"
#include "geom/Line.h" // 假设这些头文件也定义了 Line 和 Cylinder 在 geom 命名空间内
#include "geom/Cylinder.h"
#include <iostream>

// 1. 在这里引入 geom::Point
using geom::Point; // 声明:我希望以后可以直接使用 Point,它指的是 geom::Point

int main() {
Point p1(1.0, 2.0, 3.0); // 此时可以直接写 Point,无需 geom::Point

// 对于 Line 和 Cylinder,如果没用 using 声明,仍然需要加前缀
geom::Line l1(p1, geom::Point(4.0, 5.0, 6.0));
geom::Cylinder c1(p1, 5.0, 10.0);

std::cout << "Point created." << std::endl;
// ... 其他操作
return 0;
}

通过 using geom::Point;,编译器知道当您写 Point 时,您指的是 geom::Point。这让代码看起来更简洁。如果您还需要使用 LineCylinder,可以逐个添加:

cpp
1
2
3
4
using geom::Point;
using geom::Line;
using geom::Cylinder;
// 这样在 main 函数内部就可以直接使用 Point, Line, Cylinder 了

2.2 语法二:引入整个命名空间 (using namespace namespace_name;)

这种语法更强大,它会把指定 命名空间 中的所有名称都引入到当前的作用域 (scope) 中。这意味着该命名空间中的所有类、函数、变量等都可以直接使用,无需任何 前缀

如何工作:
您可以使用 using namespace 命名空间名称; 来实现。

示例代码:

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// main.cpp (使用 using namespace 声明引入整个命名空间之后)
#include "geom/Point.h"
#include "geom/Line.h"
#include "geom/Cylinder.h"
#include <iostream>

// 引入整个 geom 命名空间
using namespace geom; // 声明:我希望以后可以直接使用 geom 命名空间下的所有名称

int main() {
Point p1(1.0, 2.0, 3.0); // 直接使用 Point
Line l1(p1, Point(4.0, 5.0, 6.0)); // 直接使用 Line,且参数中的 Point 也不需前缀
Cylinder c1(p1, 5.0, 10.0); // 直接使用 Cylinder

std::cout << "Point created." << std::endl;
// ... 其他操作
return 0;
}

这种方法无疑使得代码在书写上极其简洁。但是,它也带来了更大的风险,尤其是当引入的命名空间中包含与当前 作用域 或其他引入的命名空间中名称相同的实体时,就会出现 二义性 (ambiguous) 错误,这是本课程中一个非常重要的点。

3. 命名冲突:using 声明的“双刃剑”

正如讲师所强调的,虽然 using 声明能让代码更简洁,但它可能导致命名冲突。当多个地方存在同名的类或函数时,编译器会感到困惑,不知道您到底想调用哪一个。这被称为 二义性 (ambiguous) 错误。

让我们通过一个具体的例子来深入理解这个问题。

场景设定:

  1. 全局命名空间 (global namespace) 中的 Add 函数:
    cpp
    1
    2
    3
    4
    5
    // main.cpp (在 main 函数外部)
    double Add(double a, double b) {
    std::cout << "Calling global Add function." << std::endl;
    return a + b + 0.555; // 一个“奇怪”的全局 Add 函数
    }
  2. math 命名空间中的 Add 函数:
    cpp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // operations.h
    namespace math {
    double Add(double a, double b);
    // ... 其他函数如 Sub, Mul, Div
    }

    // operations.cpp
    #include <iostream>
    #include "operations.h"
    namespace math {
    double Add(double a, double b) {
    std::cout << "Calling math::Add function." << std::endl;
    return a + b; // 简单的加法
    }
    }
  3. math_weighted 命名空间中的 Add 函数:
    cpp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // operations.h (与上面 math 命名空间在同一个文件中,但定义在不同的命名空间里)
    namespace math_weighted {
    double Add(double a, double b);
    }

    // operations.cpp
    #include <iostream>
    #include "operations.h"
    namespace math_weighted {
    double Add(double a, double b) {
    std::cout << "Calling math_weighted::Add function." << std::endl;
    return a + b - 1.0; // 带调整的加法
    }
    }

问题复现:

现在,我们在 main.cpp 中尝试调用 Add(10, 20)

情况一:只存在全局 Add,没有 using 声明

cpp
1
2
3
4
5
6
7
8
9
10
11
12
// main.cpp
#include <iostream>
// 没有包含 operations.h,也没有 using 声明

double Add(double a, double b) { /* ... */ } // 全局 Add

int main() {
double result = Add(10, 20); // 明确调用的是全局 Add
std::cout << "Result: " << result << std::endl;
return 0;
}
// 输出:Calling global Add function. Result: 30.555

这很明确,编译器会找到 全局命名空间 中的 Add 函数并调用它。

情况二:引入 math::Add,同时存在全局 Add(使用 using math::Add;

cpp
1
2
3
4
5
6
7
8
9
10
11
12
// main.cpp
#include <iostream>
#include "operations.h" // 包含 math::Add 的声明

double Add(double a, double b) { /* ... */ } // 全局 Add

using math::Add; // 引入 math 命名空间中的 Add 函数

int main() {
double result = Add(10, 20); // !!这里会报错:二义性!!
return 0;
}

当您尝试编译这段代码时,编译器会发出 二义性 (ambiguous) 错误!它会说:“嘿,我看到两个 Add 函数可以匹配 Add(10, 20):一个是 全局命名空间 里的 Add,另一个是你通过 using math::Add; 引入的 math::Add。我不知道该用哪一个!”

情况三:引入整个 math 命名空间,同时存在全局 Add(使用 using namespace math;

cpp
1
2
3
4
5
6
7
8
9
10
11
12
// main.cpp
#include <iostream>
#include "operations.h"

double Add(double a, double b) { /* ... */ } // 全局 Add

using namespace math; // 引入整个 math 命名空间

int main() {
double result = Add(10, 20); // !!同样会报错:二义性!!
return 0;
}

结果和情况二一样,编译器仍然会因为无法区分 全局命名空间Addmath::Add 而报错。

如何解决 二义性 错误?

当出现 二义性 错误时,您必须明确告诉编译器您想使用哪个 Add 函数。方法很简单:加上完整的命名空间 前缀

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
// main.cpp (解决二义性问题)
#include <iostream>
#include "operations.h"

double Add(double a, double b) { /* ... */ } // 全局 Add

// 可以选择不使用 using 声明,或者即便使用了,遇到冲突也显式指定
// using math::Add; // 如果写了这行,下面必须显式指定

int main() {
// 明确调用全局命名空间的 Add 函数
double result1 = ::Add(10, 20); // :: 表示全局命名空间
std::cout << "Result from global Add: " << result1 << std::endl;

// 明确调用 math 命名空间的 Add 函数
double result2 = math::Add(10, 20);
std::cout << "Result from math::Add: " << result2 << std::endl;

// 明确调用 math_weighted 命名空间的 Add 函数
double result3 = math_weighted::Add(10, 20);
std::cout << "Result from math_weighted::Add: " << result3 << std::endl;

return 0;
}

通过 ::Add (表示 全局命名空间 中的 Add),math::Addmath_weighted::Add,我们明确地指出了要调用的函数,消除了 二义性

经验法则:

  • 不推荐在头文件 (.h.hpp) 中使用 using namespace ...;:这会将整个命名空间引入到所有包含该头文件的文件中,极大地增加了命名冲突的风险。
  • .cpp 文件中谨慎使用 using namespace ...;:如果您的 .cpp 文件很小,且您非常确定不会有命名冲突,可以考虑使用。但通常,最好只引入您需要使用的特定名称(using namespace_name::member_name;),或者干脆不使用 using 声明,每次都写完整 前缀
  • 对于 std 命名空间:
    • std::coutstd::endl 是 C++ 标准库 (standard library) 中最常用的输出流对象和换行符。您可以通过 using std::cout;using std::endl; 来单独引入它们,然后直接写 cout << "Hello";endl;
    • 强烈不推荐在任何地方使用 using namespace std;std 命名空间非常庞大,包含了几千个名称。引入整个 std 命名空间几乎必然会导致您的代码与 std 库中的某些名称发生冲突,或者在您不知情的情况下,调用了 std 库中某个与您自定义名称相同的函数。这被称为“命名污染”。最好的做法是显式地使用 std:: 前缀,或者只 using 您需要的少数几个 std 名称。

4. QA 闪存卡 (QA Flash Cards)

  1. Q1: using 声明的目的是什么?

    • A1: 允许我们更简洁地使用 命名空间 中的名称,避免重复书写命名空间 前缀
  2. Q2: using 声明有两种主要 语法,请分别举例说明。

    • A2:
      • 引入单个名称:using geom::Point; (只引入 Point 这个类名)
      • 引入整个命名空间:using namespace geom; (引入 geom 命名空间下的所有名称)
  3. Q3: 在什么情况下 using 声明会引发 二义性 错误?

    • A3: 当您尝试引入的名称与当前 作用域 或其他已引入的名称发生冲突(即存在两个或多个同名但来自不同 命名空间 的实体)时,编译器会因为无法判断您想使用哪个而报错。
  4. Q4: 为什么通常 不推荐 在头文件 (.h.hpp) 中使用 using namespace std;

    • A4: 因为这会将整个 std 命名空间引入到所有包含该头文件的源文件中,极大地增加了 命名冲突(即“命名污染”)的风险,使得代码难以维护和理解。

5. 常见误解与错误 (Common Misunderstandings and Mistakes)

  1. 误解:using namespace std; 是 C++ 编程的标准做法。

    • 纠正: 这是一个非常常见的错误。在 C++ 初学阶段,很多教程会使用 using namespace std; 来简化代码,但这在实际的专业项目中是 强烈不推荐 的做法。正如前面所说,它会导致 命名污染二义性 错误。应该养成显式使用 std:: 前缀的习惯,或者只 using 您实际需要的少数几个 std 名称。
  2. 错误:将 using 声明放在头文件 (.h) 中。

    • 纠正: 即使是 using std::cout; 这样的单个名称引入,也应尽量避免放在头文件中。因为任何包含该头文件的源文件都会继承这个 using 声明,可能导致意想不到的命名冲突。using 声明的最佳实践是放在 .cpp 源文件中,或者在函数内部,这样它们的作用域 (scope) 被限制在当前文件或当前函数中,减少了冲突的可能性。
  3. 误解:using 声明可以解决所有的命名冲突。

    • 纠正: 恰恰相反,using 声明本身就是命名冲突的一个潜在来源!它通过将名称引入当前 作用域 来简化代码,但如果引入的名称与现有名称冲突,就会导致 二义性 错误。它并不是解决冲突的通用方法,而是用来在特定情况下简化代码书写,同时需要谨慎处理潜在冲突。
  4. 错误:忘记 using 声明有 作用域 (scope)。

    • 纠正: using 声明和任何其他变量或函数声明一样,也有 作用域。如果您将 using 声明放在一个函数内部,那么它只在该函数中有效。如果您将它放在 .cpp 文件的 全局作用域 (在所有函数外部),那么它在该文件中的所有代码都有效。理解这个 作用域 对于避免或调试命名冲突非常重要。

6. 编程练习 (Coding Exercise)

为了巩固您对 using 声明的理解,请完成以下练习。

背景:
我们有一个简单的数学运算库,包含在 operations.hoperations.cpp 中。
operations.h:

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef OPERATIONS_H
#define OPERATIONS_H

#include <iostream>

namespace basic_math {
double add(double a, double b);
double subtract(double a, double b);
} // namespace basic_math

namespace advanced_math {
double add(double a, double b, double c); // 这是一个三参数的 add
double multiply(double a, double b);
} // namespace advanced_math

#endif // OPERATIONS_H

operations.cpp:

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "operations.h"

namespace basic_math {
double add(double a, double b) {
std::cout << "Basic Math: Adding " << a << " and " << b << std::endl;
return a + b;
}
double subtract(double a, double b) {
std::cout << "Basic Math: Subtracting " << a << " and " << b << std::endl;
return a - b;
}
} // namespace basic_math

namespace advanced_math {
double add(double a, double b, double c) { // 注意:这是一个三参数的 add
std::cout << "Advanced Math: Adding " << a << ", " << b << " and " << c << std::endl;
return a + b + c;
}
double multiply(double a, double b) {
std::cout << "Advanced Math: Multiplying " << a << " and " << b << std::endl;
return a * b;
}
} // namespace advanced_math

任务:
请修改下面的 main.cpp 文件,使其能够编译并运行,同时尽可能地使用 using 声明来简化代码书写,但要避免 二义性 错误。

  1. 直接调用 basic_math::add(5.0, 3.0)
  2. 通过 using 声明引入 basic_math::subtract,然后直接调用 subtract(10.0, 4.0)
  3. 通过 using namespace 声明引入 advanced_math 命名空间。
  4. 调用 advanced_math 命名空间中的 add 函数(注意其参数数量)。
  5. 调用 advanced_math 命名空间中的 multiply 函数。
  6. 使用 using std::cout;using std::endl; 来简化 std::coutstd::endl 的书写。

main.cpp (待填充部分):

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
#include <iostream>
#include "operations.h"

// TODO: 在这里添加必要的 using 声明

int main() {
// TODO 1: 调用 basic_math::add(5.0, 3.0),不要使用 using 声明来简化
// double result_add_basic = basic_math::add(5.0, 3.0);
// std::cout << "Result from basic_math::add: " << result_add_basic << std::endl;

// TODO 2: 通过 using 声明引入 basic_math::subtract,然后直接调用 subtract(10.0, 4.0)
// double result_subtract = subtract(10.0, 4.0);
// std::cout << "Result from subtract: " << result_subtract << std::endl;

// TODO 3: 引入 advanced_math 命名空间 (使用 using namespace 声明)

// TODO 4: 调用 advanced_math 命名空间中的 add 函数 (三参数版本)
// double result_add_adv = add(1.0, 2.0, 3.0);
// std::cout << "Result from advanced_math::add: " << result_add_adv << std::endl;

// TODO 5: 调用 advanced_math 命名空间中的 multiply 函数
// double result_multiply = multiply(6.0, 7.0);
// std::cout << "Result from multiply: " << result_multiply << std::endl;

// TODO 6: 使用 using std::cout 和 using std::endl 来简化输出
// cout << "Exercise finished!" << endl;

return 0;
}

请您尝试完成 main.cpp 中的 TODO 部分。完成后,请提供您的解决方案。我会在您的解决方案提交后进行评分,并提供正确答案和更深入的解释。加油!


评论