挑战一个月基本掌握C++(第十二天)了解命名空间,模板,预处理器

news/2024/12/27 21:36:46/

一 命名空间

假设这样一种情况,当一个班上有两个名叫 Zara 的学生时,为了明确区分它们,我们在使用名字之外,不得不使用一些额外的信息,比如他们的家庭住址,或者他们父母的名字等等。

同样的情况也出现在 C++ 应用程序中。例如,您可能会写一个名为 xyz() 的函数,在另一个可用的库中也存在一个相同的函数 xyz()。这样,编译器就无法判断您所使用的是哪一个 xyz() 函数。

因此,引入了命名空间这个概念,专门用于解决上面的问题,它可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。

我们举一个计算机系统中的例子,一个文件夹(目录)中可以包含多个文件夹,每个文件夹中不能有相同的文件名,但不同文件夹中的文件可以重名。

 1.1 定义命名空间

命名空间的定义使用关键字 namespace,后跟命名空间的名称,如下所示:

namespace namespace_name {// 代码声明
}

为了调用带有命名空间的函数或变量,需要在前面加上命名空间的名称,如下所示:

name::code;  // code 可以是变量或函数

命名空间如何为变量或函数等实体定义范围:

#include <iostream>
using namespace std;// 第一个命名空间
namespace first_space{void func(){cout << "Inside first_space" << endl;}
}
// 第二个命名空间
namespace second_space{void func(){cout << "Inside second_space" << endl;}
}
int main ()
{// 调用第一个命名空间中的函数first_space::func();// 调用第二个命名空间中的函数second_space::func(); return 0;
}

输出:

Inside first_space
Inside second_space

1.2 using 指令

您可以使用 using namespace 指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称。这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。

#include <iostream>
using namespace std;// 第一个命名空间
namespace first_space{void func(){cout << "Inside first_space" << endl;}
}
// 第二个命名空间
namespace second_space{void func(){cout << "Inside second_space" << endl;}
}
using namespace first_space;
int main ()
{// 调用第一个命名空间中的函数func();return 0;
}

输出:

Inside first_space

using 指令也可以用来指定命名空间中的特定项目。例如,如果您只打算使用 std 命名空间中的 cout 部分:

using std::cout;

随后的代码中,在使用 cout 时就可以不用加上命名空间名称作为前缀,但是 std 命名空间中的其他项目仍然需要加上命名空间名称作为前缀,如下所示:

#include <iostream>
using std::cout;int main ()
{cout << "std::endl is used with std!" << std::endl;return 0;
}

输出:

std::endl is used with std!

using 指令引入的名称遵循正常的范围规则。名称从使用 using 指令开始是可见的,直到该范围结束。此时,在范围以外定义的同名实体是隐藏的。

1.3 不连续的命名空间

命名空间可以定义在几个不同的部分中,因此命名空间是由几个单独定义的部分组成的。一个命名空间的各个组成部分可以分散在多个文件中。

所以,如果命名空间中的某个组成部分需要请求定义在另一个文件中的名称,则仍然需要声明该名称。下面的命名空间定义可以是定义一个新的命名空间,也可以是为已有的命名空间增加新的元素:

namespace namespace_name {// 代码声明
}

1.4 嵌套的命名空间

命名空间可以嵌套,您可以在一个命名空间中定义另一个命名空间,如下所示:

namespace namespace_name1 {// 代码声明namespace namespace_name2 {// 代码声明}
}

可以通过使用 :: 运算符来访问嵌套的命名空间中的成员:

// 访问 namespace_name2 中的成员
using namespace namespace_name1::namespace_name2;// 访问 namespace_name1 中的成员
using namespace namespace_name1;

在上面的语句中,如果使用的是 namespace_name1,那么在该范围内 namespace_name2 中的元素也是可用的,如下所示:

#include <iostream>
using namespace std;// 第一个命名空间
namespace first_space{void func(){cout << "Inside first_space" << endl;}// 第二个命名空间namespace second_space{void func(){cout << "Inside second_space" << endl;}}
}
using namespace first_space::second_space;
int main ()
{// 调用第二个命名空间中的函数func();return 0;
}

输出:

Inside second_space

二 模板

模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。

模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。

每个容器都有一个单一的定义,比如 向量,我们可以定义许多不同类型的向量,比如 vector <int> 或 vector <string>

可以使用模板来定义函数和类,接下来让我们一起来看看如何使用。

2.1 函数模板

模板函数定义的一般形式如下所示:

template <typename type> ret-type func-name(parameter list)
{// 函数的主体
}

在这里,type 是函数所使用的数据类型的占位符名称。这个名称可以在函数定义中使用。

下面是函数模板的实例,返回两个数中的最大值:

#include <iostream>
#include <string>using namespace std;template <typename T>
inline T const& Max (T const& a, T const& b) 
{ return a < b ? b:a; 
} 
int main ()
{int i = 39;int j = 20;cout << "Max(i, j): " << Max(i, j) << endl; double f1 = 13.5; double f2 = 20.7; cout << "Max(f1, f2): " << Max(f1, f2) << endl; string s1 = "Hello"; string s2 = "World"; cout << "Max(s1, s2): " << Max(s1, s2) << endl; return 0;
}

输出:

Max(i, j): 39
Max(f1, f2): 20.7
Max(s1, s2): World

2.2 类模板

正如我们定义函数模板一样,我们也可以定义类模板。泛型类声明的一般形式如下所示:

template <class type> class class-name {
.
.
.
}

在这里,type 是占位符类型名称,可以在类被实例化的时候进行指定。您可以使用一个逗号分隔的列表来定义多个泛型数据类型。

下面的实例定义了类 Stack<>,并实现了泛型方法来对元素进行入栈出栈操作:

#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>using namespace std;template <class T>
class Stack { private: vector<T> elems;     // 元素 public: void push(T const&);  // 入栈void pop();               // 出栈T top() const;            // 返回栈顶元素bool empty() const{       // 如果为空则返回真。return elems.empty(); } 
}; template <class T>
void Stack<T>::push (T const& elem) 
{ // 追加传入元素的副本elems.push_back(elem);    
} template <class T>
void Stack<T>::pop () 
{ if (elems.empty()) { throw out_of_range("Stack<>::pop(): empty stack"); }// 删除最后一个元素elems.pop_back();         
} template <class T>
T Stack<T>::top () const 
{ if (elems.empty()) { throw out_of_range("Stack<>::top(): empty stack"); }// 返回最后一个元素的副本 return elems.back();      
} int main() 
{ try { Stack<int>         intStack;  // int 类型的栈 Stack<string> stringStack;    // string 类型的栈 // 操作 int 类型的栈 intStack.push(7); cout << intStack.top() <<endl; // 操作 string 类型的栈 stringStack.push("hello"); cout << stringStack.top() << std::endl; stringStack.pop(); stringStack.pop(); } catch (exception const& ex) { cerr << "Exception: " << ex.what() <<endl; return -1;} 
}

输出:

7
hello
Exception: Stack<>::pop(): empty stack

三 预处理器

预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。

所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。

我们已经看到,之前所有的实例中都有 #include 指令。这个宏用于把头文件包含到源文件中。

C++ 还支持很多预处理指令,比如 #include、#define、#if、#else、#line 等,让我们一起看看这些重要指令。

3.1 #define 预处理

#define 预处理指令用于创建符号常量。该符号常量通常称为,指令的一般形式是:

#define macro-name replacement-text 

当这一行代码出现在一个文件中时,在该文件中后续出现的所有宏都将会在程序编译之前被替换为 replacement-text。例如:

#include <iostream>
using namespace std;#define PI 3.14159int main ()
{cout << "Value of PI :" << PI << endl; return 0;
}

现在,让我们测试这段代码,看看预处理的结果。假设源代码文件已经存在,接下来使用 -E 选项进行编译,并把结果重定向到 test.p。现在,如果您查看 test.p 文件,将会看到它已经包含大量的信息,而且在文件底部的值被改为如下:

$ gcc -E test.cpp > test.p...
int main ()
{cout << "Value of PI :" << 3.14159 << endl; return 0;
}

3.2 参数宏

可以使用 #define 来定义一个带有参数的宏,如下所示:

#include <iostream>
using namespace std;#define MIN(a,b) (a<b ? a : b)int main ()
{int i, j;i = 100;j = 30;cout <<"较小的值为:" << MIN(i, j) << endl;return 0;
}

输出:

较小的值为:30

3.3 条件编译

有几个指令可以用来有选择地对部分程序源代码进行编译。这个过程被称为条件编译。

条件预处理器的结构与 if 选择结构很像。请看下面这段预处理器的代码:

#ifdef NULL#define NULL 0
#endif

可以只在调试时进行编译,调试开关可以使用一个宏来实现,如下所示:

#ifdef DEBUGcerr <<"Variable x = " << x << endl;
#endif

如果在指令 #ifdef DEBUG 之前已经定义了符号常量 DEBUG,则会对程序中的 cerr 语句进行编译。您可以使用 #if 0 语句注释掉程序的一部分,如下所示:

#if 0不进行编译的代码
#endif

实例:

#include <iostream>
using namespace std;
#define DEBUG#define MIN(a,b) (((a)<(b)) ? a : b)int main ()
{int i, j;i = 100;j = 30;
#ifdef DEBUGcerr <<"Trace: Inside main function" << endl;
#endif#if 0/* 这是注释部分 */cout << MKSTR(HELLO C++) << endl;
#endifcout <<"The minimum is " << MIN(i, j) << endl;#ifdef DEBUGcerr <<"Trace: Coming out of main function" << endl;
#endifreturn 0;
}

输出:

Trace: Inside main function
The minimum is 30
Trace: Coming out of main function

3.4 # 和 ## 运算符

# 和 ## 预处理运算符在 C++ 和 ANSI/ISO C 中都是可用的。# 运算符会把 replacement-text 令牌转换为用引号引起来的字符串。

请看下面的宏定义:

#include <iostream>
using namespace std;#define MKSTR( x ) #xint main ()
{cout << MKSTR(HELLO C++) << endl;return 0;
}

输出:

HELLO C++

让我们来看看它是如何工作的。不难理解,C++ 预处理器把下面这行:

cout << MKSTR(HELLO C++) << endl;

转换成了:

cout << "HELLO C++" << endl;

## 运算符用于连接两个令牌。下面是一个实例:

#define CONCAT( x, y )  x ## y

当 CONCAT 出现在程序中时,它的参数会被连接起来,并用来取代宏。例如,程序中 CONCAT(HELLO, C++) 会被替换为 "HELLO C++",如下面实例所示。

#include <iostream>
using namespace std;#define concat(a, b) a ## b
int main()
{int xy = 100;cout << concat(x, y);return 0;
}

输出:

100

让我们来看看它是如何工作的。不难理解,C++ 预处理器把下面这行:

cout << concat(x, y);

转换成了:

cout << xy;

3.5 C++中的预定义宏

描述
__LINE__这会在程序编译时包含当前行号。
__FILE__这会在程序编译时包含当前文件名。
__DATE__这会包含一个形式为 month/day/year 的字符串,它表示把源文件转换为目标代码的日期。
__TIME__这会包含一个形式为 hour:minute:second 的字符串,它表示程序被编译的时间。

实例:

#include <iostream>
using namespace std;int main ()
{cout << "Value of __LINE__ : " << __LINE__ << endl;cout << "Value of __FILE__ : " << __FILE__ << endl;cout << "Value of __DATE__ : " << __DATE__ << endl;cout << "Value of __TIME__ : " << __TIME__ << endl;return 0;
}

输出:

Value of __LINE__ : 6
Value of __FILE__ : test.cpp
Value of __DATE__ : Feb 28 2011
Value of __TIME__ : 18:52:48


http://www.ppmy.cn/news/1558612.html

相关文章

【可靠能用】springboot集成netty,在不同端口实现tcp、websocket服务器

概述 Netty可以同时创建TCP服务器和WebSocket服务器。 Netty是一个高性能、异步事件驱动的网络应用程序框架,它提供了对TCP、UDP和多种协议的支持,包括WebSocket。这意味着你可以在同一个Netty应用程序中同时实现TCP和WebSocket通信。 实现方式 同一端口。通过配置不同的Ch…

【LeetCode 面试经典150题】详细题解之哈希表篇

【LeetCode 面试经典150题】详细题解之哈希表篇 1 哈希表的基础1.1 基础概念及实现1.2.1 哈希表的工作原理1.2.2 705.设计哈希集合1.2.3 706.设计哈希映射 1.2 HashMap相关1.2.1 基本操作1.2.2 遍历 1.3 Hashtable1.4 LinkedHashMap1.5 HashSet**1.5.1基本特性**1.5.2 基本方法…

鸿蒙Next之包体积极限优化

鸿蒙应用包大小优化全解析 在鸿蒙应用开发中&#xff0c;减小应用包大小对于提升应用下载和安装体验起着关键作用。通过压缩、精简或复用应用中的代码与资源&#xff0c;能有效降低包体积&#xff0c;减少空间占用并加快下载与安装速度。下面详细介绍一下鸿蒙应用包大小优化的…

2020数字中国创新大赛-虎符网络安全赛道丨Web Writeup

本文是i春秋论坛作家「OPLV1H」表哥参加2020数字中国创新大赛-虎符网络安全赛道线上初赛的赛后总结&#xff0c;关于Web的Writeup记录&#xff0c;感兴趣的小伙伴快来学习吧。 1、hash_file — 是使用给定文件的内容生成哈希值&#xff0c;和文件名称无关。 2、jwt令牌结构和j…

Idea 将多个module显示在同一个project

Idea 将多个maven项目显示在同一个project下 1、选择菜单 File-》New -》Module from Existing Sources -> 2、在弹出的界面选中对应的Module的pom.xml,然后点击OK按钮就行了 (弹出框上面也提示了Eclipse 项目选中.project文件&#xff1b;Maven 项目选中pom.xml; ) 最终显…

数据结构经典算法总复习(下卷)

第五章:树和二叉树 先序遍历二叉树的非递归算法。 void PreOrderTraverse(BiTree T, void (*Visit)(TElemType)) {//表示用于查找的函数的指针Stack S; BiTree p T;InitStack(S);//S模拟工作栈while (p || !StackEmpty(S)) {//S为空且下一个结点为空&#xff0c;意味着结束遍…

农家乐系统|Java|SSM|VUE| 前后端分离

【技术栈】 1⃣️&#xff1a;架构: B/S、MVC 2⃣️&#xff1a;系统环境&#xff1a;Windowsh/Mac 3⃣️&#xff1a;开发环境&#xff1a;IDEA、JDK1.8、Maven、Mysql5.7 4⃣️&#xff1a;技术栈&#xff1a;Java、Mysql、SSM、Mybatis-Plus、VUE、jquery,html 5⃣️数据库可…

透视网络世界:计算机网络习题的深度解析与总结【前3章】

第一章 1-14 计算机网络有哪些常用的性能指标&#xff1f; 答&#xff1a;速率&#xff0c;带宽&#xff0c;吞吐量&#xff0c;时延&#xff0c;时延带宽积&#xff0c;往返时间RTT&#xff0c;利用率 1-17收发两端之间的传输距离为1000 km,信号在媒体上的传播速率为2108m/…