首页 > 其他分享 >C语言预处理和宏

C语言预处理和宏

时间:2023-10-13 23:46:28浏览次数:100  
标签:__ int C语言 编译 printf 预处理 define

C语言预处理和宏

预处理命令

预处理是C语言的一个重要功能,由预处理程序完成。当对一个源文件进行编译时,系统将自动调用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。这些在编译之前对源文件进行简单加工的过程,就称为预处理

预处理阶段的工作:把代码当成普通文本,根据设定的条件进行一些简单的文本替换,将替换以后的结果再交给编译器处理。

预处理命令

  • #号开头
  • 放在所有函数之外,而且一般都放在源文件的前面
指令 描述
#define 定义宏
#include 包含一个源代码文件
#undef 取消已定义的宏
#ifdef 如果宏已经定义,则返回真
#ifndef 如果宏没有定义,则返回真
#if 如果给定条件为真,则编译下面代码
#else #if 的替代方案
#elif 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个 #if……#else 条件编译块
#error 当遇到标准错误时,输出错误消息
#pragma 使用标准化方法,向编译器发布特殊的命令到编译器中

预定义宏

ANSI C 定义了许多宏(预定义宏 | Microsoft Learn)。在编程中可以使用这些宏,但是不能直接修改这些预定义的宏。

描述
__DATE__ 当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量。
__TIME__ 当前时间,一个以 "HH:MM:SS" 格式表示的字符常量。
__FILE__ 当前文件名,一个字符串常量。
__LINE__ 当前行号,一个十进制常量。
__FUNCTION__ 当前所在的函数。
__STDC__ 当编译器以 ANSI 标准编译时定义为 1,否则未定义。

这些宏常用于调试和输出日志。

#include <stdio.h>
#ifdef _DEBUG
#define DEBUGMSG(msg, date) \
    printf(msg);            \
    printf("%d%d%d", date, __LINE__, __FILE__)
#else
#define DEBUGMSG(msg, date)
#endif
int main() {
    printf("%s\n", __FILE__);      // G:\Code\test.cpp
    printf("%d\n", __LINE__);      // 4
    printf("%s\n", __DATE__);      // Oct 13 2023
    printf("%s\n", __TIME__);      // 23:01:14
    printf("%s\n", __FUNCTION__);  // main
    printf("%d\n", __STDC__);      // 1
    return 0;
}

条件编译

条件编译可以嵌套。

注意:未满足条件编译指令的代码,在预处理阶段将被编译器自动删除,不参与后面的代码编译过程。

(1)分支判断

#if 表达式
	//TODO
#elif 表达式
	//TODO
#else 表达式
	//TODO
#endif

(2)判断是否有#define定义

//第一种的正面
#if defined(表达式)
	//TODO
#endif

//第一种的反面
#if !defined(表达式)
	//TODO
#endif

//第二种的正面
#ifdef 表达式
	//TODO
#endif

//第二种的反面
#ifndef 表达式
	//TODO
#endif

宏中#和##的用法

#把一个宏参数变成对应的字符串

#define print(val, fmt) \
    printf("The value of " #val " is " fmt "\n", val)
int main() {
    int age = 18;
    print(age, "%d"); //输出The value of age is 18
}

##把位于它两边的宏参数合成一个。 它允许宏定义从分离的文本片段创建标识符

#define CAT(x, y) x##y
int main() {
    int workhard = 100;
    printf("%d\n", CAT(work, hard));  // 输出100
}

注意:凡宏定义里有用###的地方,宏参数不会再展开。

#define A (2)
#define STR(s) #s
#define CONS(a, b) int(a##e##b)

printf("max: %sn", STR(INT_MAX)); // 展开为printf("max: %s\n", #INT_MAX);
printf("%s\n", CONS(A,A));       // 展开为printf("%s\n", int(AeA)); //编译错误

/宏跨行延续

一个宏通常写在一个单行上。但是如果宏太长,一个单行容纳不下(导致有些行没有#号),则使用宏延续运算符(\)。

#define getsymdo        \
    if (-1 == getsym()) \
    return -1

定义宏

参考:C语言宏定义、宏函数、内置宏与常用宏_宏定义多个函数-CSDN博客

#define机制包括了一个规定,允许把参数替换到文本中,实现类似函数的功能【相比于函数不需要建立栈帧】,这种实现通常称为宏(macro)或定义宏(define macro)。

#define MALLOC(num,type) (type*)malloc(num*sizeof(type)) // 一个简写malloc的宏

使用#undef来移除一个#define定义的宏。

在这里插入图片描述

避免边界效应

由于宏展开的特性,需要避免边界效应,常用的解决方案:

(1)为参数添加()包裹

#define MAX( x, y ) ( ((x) > (y)) ? (x) : (y) )
#define MIN( x, y ) ( ((x) < (y)) ? (x) : (y) )

(2)使用do {} while(0)来包裹整个内容

#define DO(a,b) do { a+b; a++; } while(0)

(3)谨慎使用宏,小心副作用(side effect)

#define MAX(x, y) ((x) > (y) ? (x) : (y))
int a = 10, b = 20;
int c = MAX(a++, b++);
/*实际上展开得到:
int c = ((a++)>(b++)?(a++):(b++));
显然是错误的,会产生副作用*/

(4)宏一般不写末尾的;号,以此让用户强制书写;

避免宏出现递归

宏不能递归地展开:

#define FAC(x) (x) * FAC(x - 1)  // error

可变参数宏

一、概念介绍:
1、...:表示可变参数列表。
2、__VA_ARGS__:表示是一个可变参数的宏。
3、args...:表示可变参数列表,表示后续的args可能会有多个。
4、args:表示是一个可变参数的宏。

二、基础应用:

#define LOG1(...)  func1(__VA_ARGS__)  
#define LOG2(args...) func1(args) 

__VA_ARGS__作用: 将左边宏中的...的内容原样抄到右边__VA_ARGS__所占用的位置。 以上两宏等价。

三、实现格式化输出:

#define LOG1(fmt, ...)  printf(fmt, ##__VA_ARGS__)
#define LOG2(fmt, args...) printf(fmt, ##args)

##的作用:当可变参数的个数为0时,##起到把前面多余的","去掉的作用,否则会编译出错。

一个内核日志输出的示例:

#define MODULENAME "helloworld"
#define LOG_INFO(fmt, ...)\
    printk (KERN INFO MODULENAME ":" fmt, ##__VA_ARGS__)

// 使用
LOG_INFO("This is a log message. Value of x is %d\n", x);
// 输出
[ 12.345678] INFO helloworld: This is a log message. Value of x is 123

宏的缺陷

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  2. 宏是没法调试的。
  3. 宏由于类型无关,没有参数检查。
  4. 宏可能会带来运算符优先级的问题。

标签:__,int,C语言,编译,printf,预处理,define
From: https://www.cnblogs.com/3to4/p/17763538.html

相关文章

  • 萌新学习c语言记录
    今天写的题要求使用while语句发现输出一直循环一直循环着最后一个数然后看别人的答案发现要这样做然后我搜索了一下发现while(scanf("%d",&a)!=EOF)可以检查输入是否成功如果不输入的话将不会进行下一步......
  • 学习C语言心得-自定义函数 输入两个数字求和
    输入两个数字求和#include<stdio.h>intsum(inta,intb){ returna+b;}intmain(){ inta=0; intb=0; printf("请输入ab的值:"); scanf("%d%d",&a,&b); intSum=sum(a,b); printf("Sum=%d",Sum); return0;}运行......
  • 学习C语言心得-传址调用
    运用传址调用来交换两个数#include<stdio.h>inttransform(int*a,int*b){ intx=0; x=*a; *a=*b; *b=x;}intmain(){ inta=10; intb=20; printf("交换前:%d%d\n",a,b); transform(&a,&b); printf("交换后:%d%d\n",......
  • 学习C语言心得-运用自定义函数求素数
    自定义函数求素数#include<stdio.h>intpanduan(inta){ inti=0; for(i=2;i<a;i++) { if(a%i==0) { returna; } } return0;}intmain(){ intnumber=0; printf("请输入一个数:"); scanf("%d",&number); int......
  • c语言代码练习(无符号整数)29
    #define_CRT_SECURE_NO_WARNINGS1#include<stdio.h>#include<windows.h>intmain(){unsignedinti;for(i=9;i>=0;i--){printf("%u\n",i);Sleep(100);}return0;}死循环,因为无符号整数,没有负数......
  • C语言 - 使用_beginthreadex()创建线程
    经过了解才知道,C++03之前,用的创建线程都是CreateThread与_beginthreadex。使用这个两个函数进行创建线程。然后C++11之后,就出现了新的线程函数thread,当然,这个创建线程比较方便!经过两三天的纠结,最终决定深入研究_beginthreadex此方式创建线程,具体为什么我也说不清楚,看到网上很多......
  • c语言代码练习(字节序列)-28
    需求:写一段代码高数我们当前机#define_CRT_SECURE_NO_WARNINGS1#include<stdio.h>intsheck_sys(){inta=1;return*(char*)&a;}intmain(){intret=sheck_sys();if(ret==1){printf("小端");}else......
  • C语言 - 内联函数
    在C语言中,内联函数(InlineFunction)是一种用于优化代码执行效率的机制。内联函数在编译时将函数的代码直接插入到调用它的地方,而不是通过函数调用的方式执行,从而减少了函数调用的开销,提高了代码的执行速度。C语言的内联函数使用inline关键字来声明。将函数声明为内联函数只是给......
  • 学习C语言心得--传址调用
    传址调用#include<stdio.h>voidswep(int*x,int*y){ intp=0; p=*x; *x=*y; *y=p;}intmain(){ inta=10; intb=20; printf("交换前:%d%d",a,b); swep(&a,&b); printf("交换后:%d%d",a,b); return0;}运行结果:......
  • C语言函数和指针的关系之三(完结)
     指针保存函数的地址(函数指针)1、函数指针的概念:咱们定义的函数,在运行程序的时候,会将函数的指令加载到内存的代码段。所以函数也有起始地址。c语言规定:函数的名字就是函数的首地址,即函数的入口地址咱们就可以定义一个指针变量,来存放函数的地址。这个指针变量就是函数指针变量......