【第十一节】C/C++的预处理及命名空间

目录

一、预处理

1.1 文件包含(#include)

1.2 宏定义(#define)

1.2.1 常量宏定义

1.2.2 宏函数

1.2.3 宏取消

1.3 条件编译

1.4 预处理使用例子

1.4.1 extern "C" 块的应用

1.4.2 选择编译

1.4.3 注释大段代码

1.4.4 避免头文件被重复引用

二、命名空间

2.1 命名空间介绍

2.2 需要命名空间的原因

2.3 命名空间的定义

2.4 使用命名空间和别名

2.4.1 使用

2.4.2 别名


一、预处理

        在C/C++中,预处理是编译过程的第一阶段,主要由预处理器完成。预处理的主要功能是根据预处理指令对源代码进行相应的文本处理,然后输出到下一阶段。下面,我将详细介绍预处理中的三种主要操作:文件包含、宏定义和条件编译。

1.1 文件包含(#include)

        文件包含是预处理中最常用的指令之一,它允许程序员在一个源文件中包含另一个文件的内容。这通常用于包含头文件(.h.hpp),这些头文件通常包含函数声明、类定义、宏定义等。文件包含有两种形式:

  • 尖括号形式 (#include <filename>): 用于包含标准库头文件或系统头文件。预处理器会在标准库路径中查找这些文件。
  • 双引号形式 (#include "filename"): 用于包含用户自定义的头文件。预处理器首先在当前目录或指定的项目目录中查找这些文件,如果找不到,则可能回退到标准库路径中查找。

        文件包含的主要目的是实现代码的模块化,避免重复编写相同的代码,同时提高代码的可维护性和可重用性。

1.2 宏定义(#define)

        宏定义是预处理的另一个重要功能,它允许程序员定义常量或宏函数。宏定义在预处理阶段进行文本替换,不占用任何存储空间,也不分配内存地址。

1.2.1 常量宏定义

常量宏定义通常用于表示一个不会改变的值。例如:

#define PI 3.14159

在代码中,每当出现PI时,预处理器都会将其替换为3.14159

1.2.2 宏函数

宏函数类似于函数,但它实际上只是文本替换,没有函数调用的开销。例如:

#define MAX(a, b) ((a) > (b) ? (a) : (b))

使用宏函数时,要注意参数周围的括号,以避免出现运算优先级和结合性错误。

1.2.3 宏取消

宏取消使用#undef预处理指令。它用于取消之前定义的宏,使该宏在后续代码中不再可用。

例如:

#define DEBUG 1
// ... 一些使用DEBUG宏的代码 ...
#undef DEBUG

        在这个例子中,DEBUG宏最初被定义并赋值为1。然后,在代码中使用DEBUG宏进行条件编译或其他操作。最后,使用#undef DEBUG取消DEBUG宏的定义,以确保在后续代码中不会意外地使用到它。

        宏定义可以提高代码的可读性和可维护性,但也要注意避免过度使用宏定义,因为宏只是简单的文本替换,没有类型检查,可能会导致一些不易察觉的错误。

注意事项:

  1. 没有类型检查:宏只是简单的文本替换,因此预处理器不会检查类型是否正确。
  2. 多次求值:如果宏的参数在替换后的代码中多次出现,那么它会被多次求值,这可能导致不期望的副作用。
  3. 括号问题:在定义带参数的宏时,必须注意参数的括号问题,以确保宏替换后的表达式具有正确的优先级和结合性。
  4. 作用域:宏定义在定义它的文件以及包含该文件的任何文件中都可见,直到它被#undef取消或到达文件的末尾。因此,要小心避免在不同文件中定义同名宏而导致的冲突。

1.3 条件编译

        条件编译允许程序员根据特定的条件决定是否包含某段代码。这在跨平台编程、调试代码或根据不同的配置选项构建程序时非常有用。

条件编译的主要预处理指令包括:

  • #if: 判断后面的条件是否为真,如果为真则编译下面的代码,直到遇到#else#elif#endif
  • #ifdef: 判断是否定义了某个宏,如果已定义则编译下面的代码。
  • #ifndef: 与#ifdef相反,判断某个宏是否未定义。
  • #else: 与#if#ifdef#ifndef配对使用,当前面的条件不满足时,编译#else后面的代码。
  • #elif: 类似于#else if,用于提供额外的条件判断。
  • #endif: 结束条件编译块。

例如:

#ifdef DEBUG
// 这段代码只在定义了DEBUG宏时才会被编译
printf("Debug information\n");
#endif

条件编译使得程序员能够灵活地控制哪些代码应该被编译和执行,从而满足不同的需求和环境。

1.4 预处理使用例子

条件编译:应用在条件编译中的条件表达式不必加括号。


1.4.1 extern "C" 块的应用

    #ifdef __cplusplus
        extern "C" {
    #endif

        /* Assume C declarations for C++ */

    #ifdef __cplusplus
        }
    #endif

        __cplusplus是C++的预定义宏,表示当前开发环境是C++。在C++语言中,为了支持重载机制,在编译生成的汇编代码中,会对函数名字进行进行一些处理(通常称为函数名字改编),如加入函数的参数类型或返回类型等,而在C语言中,只是简单的函数名字而已,并不加入其它信息:
    void show(int demo);
    void show(double demo);
        C无法区分上面两个函数的不同,因为C编译器产生的函数名都是 _show,而C++编译器产生的名字则可能是 _show_Fi 和 _show_Fd,这样就很好地把函数区别开了。
        所以,在C/C++混合编程的环境下, extern "C" 块的作用就是告诉C++编译器这段代码要按C标准编译,以尽可能地保持C++与C的兼容。
        再比如说用C++开发了一个DLL库,为了能够让C语言也能够调用该DLL输出(Export)的函数,需要用extern "C"来强制编译器不要修改你的函数名。

1.4.2 选择编译

        有时候程序中的某些调试代码,只需要在调试的时候被编译,而不希望在程序的发行版中被编译,你可能会看到类似这样的代码段:

    #ifdef _DEBUG_
        printf("File: %s, Line: %d | x = %d\n", __FILE__, __LINE__, x);
    #else
        printf("x = %d\n", x);
    #endif

        当符号 _DEBUG_ 没有被定义的时候,仅编译 #else 与 #endif 之间的代码;定义了 _DEBUG_ 符号之后,只编译 #ifdef 与 #else 之间的代码。要想定义一个符号很简单,只需要在文件头部加上像这样的一条语句:
    #define _DEBUG_

1.4.3 注释大段代码

        注释大段代码可以使用/*--*/,但不支持嵌套注释,有时容易出错。通常忽略大段代码的正确方法是使用条件编译指令,示例代码如下:

    #if 0
      ......
      ......
    #endif

1.4.4 避免头文件被重复引用

        头文件通常包含有声明及定义语句,头文件还要以包含其它的头文件,即头文件嵌套,因此需要注意“重复引用”的情况,因为某些声明或定义如typedef,在一次编译中仅允许出现一次,重复包含会导致编译器提示出错信息。头文件嵌套如:
    //a.h
    #include "b.h"
    //main.cpp
    #include "a.h"//已经包含了b.h
    #include "b.h"
 
    技巧是定义一个与头文件相关的宏定义符号,如果预处理器已经见过这个符号,那么就不会重复嵌入文件的内容:
    // b.h
    #ifndef __B_H__
        #define __B_H__
      <定义或声明>
    #endif

二、命名空间

2.1 命名空间介绍

        在C++中,预处理器指令、静态变量和函数等特性可能会引起命名冲突,因为它们在编译时会直接插入到代码中,而不会考虑命名空间。然而,命名空间提供了一种机制来解决这种问题,通过将相关的标识符(如变量、函数、类等)组织在同一个命名空间中,可以避免命名冲突。

        命名空间是一个逻辑上的类型组织系统,它允许你对程序中的类型进行逻辑上的分组,并使定义在同一个命名空间中的类可以直接相互调用。标准的C++有命名空间机制,使用namespace关键字来定义命名空间。库或程序中的每一个C++定义集都被封装在一个命名空间中,如果其他的定义中有相同的名字,但它们在不同的命名空间,则不会产生命名冲突。

        命名空间的主要优点是它可以避免全局作用域中的命名冲突,并提供了一种组织和管理大型代码库的方法。通过使用命名空间,你可以将相关的代码和标识符封装在一个特定的上下文中,这样可以减少命名冲突的可能性,并使代码更易于理解和维护。

2.2 需要命名空间的原因

        在C++中,命名空间是ANSI C++引入的一种机制,它允许用户创建可以由用户命名的作用域,以处理程序中常见的同名冲突。这种机制在C语言中已经存在,但C++在此基础上进行了扩展,引入了类作用域。

        在C语言中,定义了三个层次的作用域:文件(编译单元)、函数和复合语句。在C++中,除了这三个作用域外,还引入了类作用域。类作用域是出现在文件内的,这意味着在一个文件中可以定义多个类,每个类都有自己的作用域。

        在不同的作用域中,可以定义相同名字的变量,这些变量在各自的作用域内是互不干扰的,系统能够区分它们。这就是命名空间的基本概念。通过使用命名空间,可以创建一个独立的命名空间,在这个命名空间中定义的变量、函数、类等在该命名空间内是唯一的,不会与其他命名空间中的同名标识符发生冲突。

2.3 命名空间的定义

格式:
namespace 命名空间名{
    //命名空间成员(其他命名空间或类的定义)
}

命名空间定义说明:

  1. 命名空间只能在全局范围内定义。

  2. “命名空间名”是C++合法的标识符,也可以是用作用域分解运算符(::)来构成命名空间的完全限定名。可以没有命名空间名(无名的命名空间),其成员的名称不需要限定就可以使用。

  3. 不允许在定义命名空间时使用任何访问修饰符,系统默认命名空间具有public访问属性。

  4. 命名空间的成员可以是另一个命名空间(嵌套)或类型(类、结构、接口、枚举等)的定义。

  5. 一个命名空间可以在多个头文件中用一个标识符来定义。

  6. 一个命名空间的名字可以用另一个名字来作它的别名。

  7. 不能像类那样去创建一个命名空间的实例。

  8. 可以在一个编译单元中包含一个未命名的命名空间,但只能包含一个未命名的命名空间。

  9. 命名空间在命名空间的结尾,右花括号的后面可以跟一个分号(;)。

代码示例:

#include<iostream>
using namespace std;

//命名空间Outer的定义
namespace Outer {
    int nNumA;
    //子命名空间Inner的内部定义
    namespace Inner {
        void fun_a() { nNumA++; } //Outer::nNumA
        int nNumA;
        void fun_b() { nNumA++; } //Inner::nNumA
        void fun_c();
    }
    void fun_c();
    //namespace Inner2;//错误,不能声明了命名空间
}
void Outer::fun_c() { nNumA--; }//命名空间outer成员fun_c()的外部定义

void Outer::Inner::fun_c() { nNumA--; }//命名空间Inner成员fun_c()的外部定义
//namespace Outer::Inner2 {/**/ }//错误,不能在外部定义子命名

int main() {
    cout << "hello" << endl;
    system("pause");
    return 0;
}

2.4 使用命名空间和别名

2.4.1 使用

访问一个命名空间中的成员可以采用下面三种方法:


A. 直接访问命名空间的成员(完全限定名):用作用域分解运算符(:),例如:
std::cout<<"hello world!"<<std::endl;
缺点:输入繁琐

B. 使用声明:用using声明一次性引用名字,例如:
using std::cout;
cout<<"hello world!"<<std: :endl;
缺点:缺乏类型信息,不能克服重载问题

C. 使用指令:用using指令把所有名字引入到命名空间中,例如:
using namespace std;
cout<< "hello world!"<<endl;
缺点:可能会产生命名冲突

2.4.2 别名

        标准C++引入命名空间,主要是为了避免成员的名称冲突。若果用户都给自己的命名空间取简短的名称,那么这些(往往同是全局级的)命名空间本身,也可能发生名称冲突。如果为了避免冲突,而为命名空间取很长的名称,则使用起来就会不方便。这是一个典型的两难问题。


标准C++为此提供了一种解决方案--命名空间别名,格式为
namespace 别名 = 命名空间名;

  • 28
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

攻城狮7号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值