c++学习笔记(2)-宏的用途

1、概述

宏是一种预处理指令,其作用是在源代码编译之前,将宏定义的标识符或表达式自动替换为指定的文本,相当于一个简单的代码替换器。具体来说,C++ 宏有以下几个常见用途:

  • 定义常量:使用常量宏可以定义一个常量;
  • 定义函数:通过宏函数提高程序执行效率;
  • 条件编译:使用条件编译宏可以根据条件编译一段代码,方便在不同平台或者不同环境下编译程序,或进行调试;
  • 简化代码:减少代码量,用宏定义需要频繁利用的代码;·
  • 提高可读性:使用宏可以帮助简化代码,避免代码冗余,提供代码的可读性和可维护性;
  • #拼接符:模板字符串;
  • ##拼接符:实现变量、函数动态命名,泛型。

2、定义常量

C++ 中可以使用 #define 语句来定义宏。以下是一个示例演示如何使用宏定义常量:

#include 
using namespace std;
// 定义常量
#define PI 3.1415926
int main() {
    double r=2.0;
    double area = PI * r * r;
    cout << "PI = " << PI << endl;
    cout << "The area of the circle is " << area << endl;
    return 0;
}

在上述代码中,通过 #define PI 3.1415926 定义了一个常量 PI,然后在程序中可以直接使用这个常量,而无需重复输入3.1415926。在程序执行时,编译器会将 PI 替换成实际值,从而计算出圆的面积,并输出到控制台。

3、定义函数

宏函数是将代码块封装成宏的形式,使用时会将宏展开成代码块。宏函数的使用示例如下:

#include
#define SQUARE(x) x * x  // 定义一个宏函数
int main() {
    int x = 3;
    int square_x = SQUARE(x);  // 使用宏函数求平方
    std::cout << "Square of " << x << " is " << square_x << std::endl;
    return 0;
}

上述示例中,我们定义了一个宏函数SQUARE(x),将一个数的平方封装成宏。在程序中,我们使用宏函数求出了一个数的平方。宏函数在宏展开时被直接替换,而不需要额外的函数调用开销和参数传递开销,所以效率更高。

4、条件编译

使用条件编译宏可以根据条件编译一段代码,方便在不同平台或者不同环境下编译程序。以下是一个简单的条件编译的代码:

/* 条件编译 */
#define DEBUG
#if defined(DEBUG)
    #define LOG_TAG "demo"
    #define LOGD(...) printf("["LOG_TAG"] "__VA_ARGS__)
#else
    #define LOGD(...)
#endif

int main(){
    /* 使用条件编译 */
    LOGD("Hello World!\n");
    return 0;
}

在上述代码中,宏定义使用了 #define 关键字来定义常量和带参函数,而条件编译使用了 #if defined() 和 #else 关键字来选择性的编译不同的代码。在 main 函数中,通过调用这些宏来输出结果或者打印日志。

5、减少代码量

举个例子,在这个示例中,我们有两个类Teacher和Student,它们都需要声明一个getAget()和setAge()两个成员函数,以及m_age成员变量,我们可以把相同的部分提取出来,使用以下宏定义:

#define DECLARE_AGE \
public:\
int getAge(){return m_age;}\
void setAge(int age){m_age=age;}\
int m_age;

// 使用宏定义
class Teacher {
    DECLARE_AGE
public:
	std::string m_name;
};

class Student {
    DECLARE_AGE
public:
	int m_weight;
};

宏定义DECLARE_AGE将需要重复声明的部分提取出来,这样就省略了部分代码。Qt中的Q_OBJECT宏是比较典型的应用。

6、增加可读性

6.1、空宏

定义一个空宏作为代码注释可以让注释看起来更加显眼、规范,示例代码如下:

#define ADDNUM
int main()
{
    int a = 10;
    ADDNUM a++;
    return 0;
}

上述代码中,我们定义了一个空宏ADDNUM,将其作为代码注释使用,在实际开发中,我们可以使用这种方式规范注释格式,使得代码更加易读易懂。最典型的例子就是Qt中的emit宏,用于注释某个函数是信号函数。

6.2、命名空间

使用宏定义名称空间的好处是可以在代码中减少重复的命名前缀,同时提高代码的可读性和易维护性。下面是一个使用宏定义名称空间的例子:

#define MY_NAMESPACE_BEGIN namespace MyNamespace {
#define MY_NAMESPACE_END }

MY_NAMESPACE_BEGIN

print() {
    cout << "Hello, MyNamespace!" << endl;
}

MY_NAMESPACE_END

7、拼接符#

#操作符可以将宏的参数转换为一个字符串常量,方便我们在调试程序、输出日志、代码生成等场景中使用。比如可以利用#操作符将一些重要变量的值输出到日志中,以便于调试。

#define DEBUG_PRINT(x) printf("%s = %d\n", #x, x);
int main(){
    int a = 10;
    DEBUG_PRINT(a); // 输出 "a = 10"
    return 0;
}

8、连接符##

宏连接符 ##,可以用于将两个宏参数拼接成一个新的标识符。这种方法在一些代码生成、框架、库等开发中经常使用,能够有效地减少代码的重复,提高代码的复用性和可扩展性。下面是一些使用 ## 操作符的例子。

8.1、动态生成函数名

在一些代码生成、框架开发中,经常需要定义一些具有相似功能的函数。利用 ## 操作符,可以方便地定义一些具有相似功能的函数,提高代码的复用性和可扩展性。

#define FUNC(name, type) name##_##type
void FUNC(add, int)() {}//add_int()
void FUNC(add, float)() {}//add_float()

8.2、泛型

可以通过宏定义和 ## 操作符来实现类似泛型的功能。下面是一个简单的例子。

#define DEFINE_TYPE(name, type) \
    typedef struct { \
        type value; \
    } name##_t;

DEFINE_TYPE(Int, int);
DEFINE_TYPE(Double, double);

int main() {
    Int_t i = {10};
    Double_t d = {3.1415926};
    printf("%d %lf\n", i.value, d.value);
    return 0;
}

你可能感兴趣的:(C++笔记,c++,学习,笔记)