【C++要笑着学】关键字 | 命名空间 | 输入和输出(一)

简介: 本章将正式开始 C++ 的学习,将深入浅出地讲解。本篇将以 HelloWorld 开始,以 HelloWorld 结束。通过这段代码,讲解C++中的命名空间和输入与输出。话不多说,让我们开始吧

💭 写在前面



本章将正式开始 C++ 的学习,将深入浅出地讲解。本篇将以 HelloWorld 开始,以 HelloWorld 结束。通过这段代码,讲解C++中的命名空间和输入与输出。话不多说,让我们开始吧!


Ⅰ.  初探 HelloWorld


0x00 引入:“深”入浅出,你好世界!

所有的伟大,都源于一个勇敢的开始。学习一门语言当然是要从 HelloWorld 开始!


① 首先打开我们的编译器,我用的是 VS2077 VS2022:

c4df1f14ed86b4e35ad1603ead3dbf49_b824932639dd49198918084476a36627.png


打开编译器创建完新项目后,右键源文件,点击 "添加新建项" ,


名称我们就取为 test.cpp,这样我们的代码就能很好地创建出来了:

a441dd8ba86c10031de8e2d058c1cec5_024023b73ae546f592a370fd50c469c9.png


💬 创建完毕后,我们就可以开始打代码了:


#include <iostream>
using namespace std;
int main()
{
  cout << "Hello,World!" << endl;
  return 0;
}

如果你不知道 using namespace、cout、endl 这些是什么,


这都没有关系,我们下面会慢慢讲解,不用着急。


🚩 完成后运行一下代码:

7bf24aeaa8603a17b6aace8ac1d2d9ea_b3d03d3c2a8f4da699fccb897626c5b9.png

 成功打印出来了。


💬 我们再试一试C语言的 HelloWorld:


#include <stdio.h>
int main()
{
  printf("Hello, World!");
  return 0;
}

🚩 运行结果如下:

9bede12ed84d65f2027262a9ccf5b022_b419cf0507884f8da3b9c97e695f9a34.png


💡 我们发现,也是可以输出结果的。因为 C++ 是 C语言的超集。


 好了,我们已经精通C++ 了,本专栏完结撒花!  


 

Ⅱ.  关键字


0x00 关键字

 C++ 共计 63 个关键字,其中包括 C 语言的 32 个关键字。


这里我们只是看一下 C++ 有多少关键字,不会对关键字进行具体的讲解,


我们后续都慢慢会学到的。这里把关键字表列出来,只是为了能够混个眼熟,


我们 命名时要避开这些关键字!


C++98 关键字

eff632c3384785b2da526750ae4975b4_dd3d48a03d4c4111830cac06520c8d66.png


Ⅲ.  命名空间


0x00 问题引入

在C/C++中,变量、函数和类都是大量存在的,


这些变量、函数和类的名称都会存在于全局作用域中,这么一来就会导致命名的冲突。


使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染。


我们刚才 HelloWorld 代码中的 namespace 的出现,就是针对这个问题的。


💬 为了能够让大伙理解命名空间的存在是多么的合理,我们来故意踩一下命名冲突的坑。


在 stdlib 库中有一个生成随机数的函数 rand() ,


相信大家都认识,但是我们假装某个人不知道 stdlib 库中有一个叫 rand 的函数存在,


因此在定义变量时给变量取名为 rand 。


#include <stdio.h>
#include <stdlib.h>
int main(void)
{
  int rand = 233;
  printf("%d\n", rand);  // 这里到底是打印我们自己定义的rand,还是stdlib里的?
  return 0;
}

 我们知道,#include 包含头文件,头文件里的内容是会被展开来的。


当展开头文件时,stdlib 库中有一个叫 rand 的函数,我这里又定义了一个叫 rand 的变量,


此时就冲突了!


冲突了,那么问题来了,我们这里 printf 打印出来的会是什么呢?


编译器的寻找规则: 局部找 → 全局找 → 找不到(报错)。


或许他打印出什么并不重要,但是大家应该能够体会到命名冲突多是一件倒霉事了。


😂 问题是在C语言里几乎没有办法很好地解决这种问题。


所以为了很好地解决这种冲突的问题,C++ 就加入了命名空间的特性!


在 C++ 里,我们就可以利用 "命名空间" 来解决这个问题,


所以 C++ 提出了一个新语法 —— 命名空间 namespace!


0x01 命名空间的定义

 定义命名空间,肯定得用到我们刚才提到的 namespace 关键字,


namespace 后面可以取一个空间名,然后再接上一对大括号就可以了,上图!

464fe58a93d91fef04a39362cf33faa5_8a26c958038c43f3bbe37d79947715e0.png

那我们该如何使用它呢?


💬 我们来拿上面的代码来举例子,试试用 namespace 来解决命名冲突的问题:


namespace nb {  
  int rand = 233;
}
int main(void)
{
  // int rand = 233;
  printf("%d\n", nb::rand);    // 这样我们就能看出rand是nb这块命名空间里的变量了
  return 0;
}

🔑 画图详解:

356e8b2a71bfb4764663c0297776ca4d_89ee72208ba9457fbf32f5aaa2ad7b1a.png


💡 这里的 : :  叫做 "作用域限定符" 。这么一来,就不怕冲突了,问题就这么轻松地解决了。


命名空间能够达到一种类似于 "隔离" 的效果。


📌 注意事项:


注意!!!


① 命名空间必须在全局作用域下定义!


其次,正是因为命名空间是全局的,所以这个 rand 变量也自然而然地变成了全局变量。

f97351042077020c472b2b23450f2813_7adedc77541648ed8743a1043dd75540.png


② 命名空间长得有点像结构体,但是他和结构体不是一个东西,结构体是定义一个类型,


它们的性质是完全不一样的。还有,命名空间大括号外不用加分号,手残党要注意了。

cb40353add5e09d1d7d9375d12105db5_f041e605d3b845c7b37d040e02a9707f.png

0x02 命名空间里的内容

📚 命名空间里的内容,不仅仅可以存放变量,


还可以在里面放函数,结构体,甚至是 类(我们后面会讲)。

cab614b2aeec104b3872a6bc14e81dad_134dba9b58114172b97ea83f438dfe8e.png


💬 代码演示:


#include <stdio.h>
namespace N1 {
  int a = 10;
  int b = 20;
  int Add(int x, int y) {
  return x + y;
  }
}
namespace N2 {
  int c = 0;
  struct Node {
  struct Node* next;
  int val;
  };
}
int main(void)
{
  int res = N1::Add(N1::a, N1::b);
  printf("result = %d", res);
  struct N2::Node node1;
  return 0;
}

0x03 命名空间的嵌套

01203adf6e8ca614b58e8eb7671a29d5_3758301516e345a48dd51b409bdf71f6.png



📚 没想到吧,命名空间是可以套娃的,命名空间可以嵌套命名空间。


💬 嵌套方法演示:


// 国家5A级景区...
namespace AAAAA {
  int a5 = 10000;
  namespace AAAA {
  int a4 = 1000;
  namespace AAA {
    int a3 = 100;
    namespace AA {
    int a2 = 10;
    namespace A {
      int a1 = 1;
    }
    }
  }
  }
}
int main(void)
{
  // 取出 a1 
  int ret = AAAAA::AAAA::AAA::AA::A::a1;
  return 0;
}


📌 注意事项:虽然套了这么多层,但是它们仍然都是全局的,只是套在了一个命名空间内而已。


0x04 空间名重名问题

❓ 命名空间是用来解决命名冲突问题的,


那我项目中定义的某个命名空间的名字和其他命名空间的名字冲突了怎么办?


这话可能有点绕,不过没有关系,让我品一品82年的拉菲先。


其实, 在同一个工程中是允许存在多个相同名称的命名空间的。


编译器最后会将他们合成到同一个命名空间中的。


有两个相同名字的命名空间,就会合二为一。有三个相同名字的命名空间,就会三合一……


就算是有七颗龙珠,也召唤不出神龙,只会变成一个更大的 "龙珠"


💬 不信?这就上号给你们证明一波!


(证明:编译器会将重名的命名空间融合)

bd37dede1519242401c59cddd9ab3b82_231ac67b043243ee81319995e6486d63.png


🔺 总结:同一个工程中允许存在多个相同的命名空间,编译器最后会将它们合成到一起。


0x05 展开的方式

💬 假设有这样的一种情况,一个命名空间中的某个成员是我们经常要使用的:


#include <stdio.h>
namespace N1 {
  int a = 10;  // 假设a经常需要使用
  int b = 20;
  int c = 30;
}
void func(int n) {
  printf("HI, %d\n", n);
}
int main(void)
{
  printf("%d\n", N1::a);
  int res = N1::a;
  func(N1::a);
  printf("hello, %d\n", N1::a);
  return 0;
}

 指定的作用域,能够做到最好的命名隔离,但是使用起来好像不是很方便。


每次使用都要调 : :  ,好捏🐎烦!这也太难受了,有办法能解决吗?


这位同学请不要激动!命名空间是可以展开的。


💡 我们可以用 using namespace 将整个命名空间展开,因为命名空间是在全局土生土长的,所以展开后,里面的东西自然会被展开到全局。


using namespace 空间名;

💬 我们试一试:


#include <stdio.h>
namespace N1 {
  int a = 10;  // 假设a经常需要使用
  int b = 20;
  int c = 30;
}
void func(int n) {
  printf("HI, %d\n", n);
}
using namespace N1;  // 将N1这个命名空间展开
int main(void)
{
  printf("%d\n", a); // 这样我们就可以直接使用了,就不需要 "::" 了
  int res = a;
  func(a);
  printf("hello, %d\n", a);
  return 0;
}


但是!!! 全部展开,虽然使用起来方便,但是隔离的效果失效了!


❓ 那我们岂不是白写命名空间了?


所以一定要慎用!我们还是不建议大家把命名空间都展开的。


🔑 解决方法:我既要用起来方便,又要保持隔离的效果!小孩子才做选择!我全都要!


📚 指定展开某一个,直接使用 using 将命名空间中某个成员引入。


可以用来展开命名空间中最常用的成员:


using 空间名::成员

💬 演示如何做到全都要:


#include <stdio.h>
namespace N1 {
  int a = 10;  // 假设a经常需要使用
  int b = 20;
  int c = 30;
}
void func(int n) {
  printf("HI, %d\n", n);
}
// using namespace N1;
using N1::a;  // 单独展开一个,其他的不展开
int main(void)
{
  printf("%d\n", a); // 这样我们就可以直接使用了,就不需要 "::" 了
  int res = a;
  func(a);
  printf("hello, %d\n", a);
  return 0;
}

 (既保持了方便,又保持了隔离,岂不美哉)


0x06 匿名命名空间

 为了能够安心享受极致的嘴臭,祖安人是非常喜欢匿名喷人的。


📚 我们在C语言学习结构体的时候,我们就提到过匿名结构体。


命名空间这里也可以匿名!如果一个命名空间没有名称,我们就称它为匿名结构体。


// 匿名命名空间
namespace {
  char c;
  int i;
  double d;
}

这种情况,编译器会在内部给这个没有名字的 "匿名命名空间" 生成一个惟一的名字。


并且还会为该匿名命名空间生成一条 using 指令,所以上面的代码会等同于:


namespace _UNIQUE_NAME {
  char c;
  int i;
  double d;
}

using namespace _UNIQUE_NAME;

这里我们只需要知道有这么一个东西就可以了,如果感兴趣可以深究下去。


0x07 使用方式总结

学到这里,想必大伙已经对命名空间了解的差不多了,


我们来总结一下命名空间使用的三种方式。


💬 方式一:


空间名 + 作用域限定符


namespace N {
    int a = 10;
}
int main()
{
    printf("%d\n", N::a);
    return 0;
}

💬 方式二:


使用 using namespace 命名空间名称引入 (会破坏隔离效果)


namespace N {
    int a = 10;
}
using namespace N;
int main()
{
    printf("%d\n", a);
    return 0;
}

💬 方式三:


使用 using 将命名空间中成员引入


namespace N {
    int a = 10;
}
using N::a;
int main()
{
    printf("%d\n", a);
    return 0;
}


相关文章
|
15天前
|
安全 编译器 C++
C++ `noexcept` 关键字的深入解析
`noexcept` 关键字在 C++ 中用于指示函数不会抛出异常,有助于编译器优化和提高程序的可靠性。它可以减少代码大小、提高执行效率,并增强程序的稳定性和可预测性。`noexcept` 还可以影响函数重载和模板特化的决策。使用时需谨慎,确保函数确实不会抛出异常,否则可能导致程序崩溃。通过合理使用 `noexcept`,开发者可以编写出更高效、更可靠的 C++ 代码。
21 0
|
3月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
3月前
|
安全 程序员 编译器
【C++】如何巧妙运用C++命名空间:初学者必备指南
【C++】如何巧妙运用C++命名空间:初学者必备指南
|
3月前
|
存储 编译器 程序员
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(二)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
5月前
|
程序员 C++ 开发者
C++命名空间揭秘:一招解决全局冲突,让你的代码模块化战斗值飙升!
【8月更文挑战第22天】在C++中,命名空间是解决命名冲突的关键机制,它帮助开发者组织代码并提升可维护性。本文通过一个图形库开发案例,展示了如何利用命名空间避免圆形和矩形类间的命名冲突。通过定义和实现这些类,并在主函数中使用命名空间创建对象及调用方法,我们不仅解决了冲突问题,还提高了代码的模块化程度和组织结构。这为实际项目开发提供了宝贵的参考经验。
77 2
|
4月前
|
程序员 C++ 容器
C++编程基础:命名空间、输入输出与默认参数
命名空间、输入输出和函数默认参数是C++编程中的基础概念。合理地使用这些特性能够使代码更加清晰、模块化和易于管理。理解并掌握这些基础知识,对于每一个C++程序员来说都是非常重要的。通过上述介绍和示例,希望能够帮助你更好地理解和运用这些C++的基础特性。
51 0
|
5月前
|
存储 安全 编译器
C++入门 | auto关键字、范围for、指针空值nullptr
C++入门 | auto关键字、范围for、指针空值nullptr
81 4
|
5月前
|
编译器 C语言 C++
C++入门 | 命名空间、输入输出、缺省参数
C++入门 | 命名空间、输入输出、缺省参数
53 4
|
4月前
|
C语言 C++
C++(六)Namespace 命名空间
命名空间(Namespace)是为了解决大型项目中命名冲突而引入的机制。在多库集成时,不同类库可能包含同名函数或变量,导致冲突。C++通过语法形式定义了全局无名命名空间,并允许对全局函数和变量进行作用域划分。命名空间支持嵌套与合并,便于协同开发。其使用需谨慎处理同名冲突。
|
6月前
|
安全 编译器 C++
C++一分钟之-C++中的属性命名空间
【7月更文挑战第22天】C++11引入属性作为元数据,虽无内置属性命名空间,但可通过自定义属性与命名空间组合实现类似效果。例如,创建`perf`命名空间存放`slow`和`fast`属性来标记函数性能。正确使用属性需注意位置、避免重复和确保与实现一致,以提升代码可读性和编译器理解。通过模拟属性命名空间,可以更有效地管理和使用属性。
57 1