C语言预处理

文章介绍了C语言中的预处理指令,包括#和##运算符的用途,以及宏定义的使用示例。内联函数作为提高效率的手段,其定义和限制也进行了说明。同时,文章讨论了可变参数宏__VA_ARGS__的应用,以及命令行参数argc和argv在程序中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

运算符 #和##的使用

在 C 语言凡是以#开头的均为预处理指令,预处理又叫预编译。
预编译而是编译前的处理,是在正式编译之前由系统自动完成的。

#运算符说明

#运算符可以将语言符号转化为字符串。
例如:
如果x是一个宏参量,那么#x可以把参数名转化为相应的字符串,该过程为字符串化。

#define PSQR(x)  printf("the square of "#x" is %d \n",(x*x))

宏定义使用展开后为

PSQR(2+4) ==> the square of 2+4 is 36

##运算符说明

##运算符可以将两个运算符号组合成单个运算符号

例如:

#define XNAME(n)  x##n

下面的宏定义调用形式展开后:

XNAME(4) ==> x4

使用示例

extern int partbl_save_json(const void*, const char*);\
extern int partbl_save_xml(const void*, const char*);\

#define DECLARE_PARTBL(_name, _type) \
    LNKTBL_DECLARE_NODE(ptbl_t, _name) = {\
        .name  = # _name,\
        .save  = partbl_save_ ## _type,\
    }
    
//对应宏定义经过预处理编译后结果为
#define DECLARE_PARTBL(sys, json)
    LNKTBL_DECLARE_NODE(ptbl_t, _name) = {\
        .name  = "sys",\
        .save  = partbl_save_json,\
         }

内联函数

什么是内联函数

官方定义:

在计算机科学中,内联函数(有时称作在线函数或编译时期展开函数)是一种编程语言结构,用来建议编译器对一些特殊函数进行内联扩展(有时称作在线扩展);也就是说建议编译器将指定的函数体插入并取代每一处调用该函数的地方(上下文),从而节省了每次调用函数带来的额外时间开支。但在选择使用内联函数时,必须在程序占用空间和程序执行效率之间进行权衡,因为过多的比较复杂的函数进行内联扩展将带来很大的存储资源开支

通俗说明

在C语言中,如果一些函数被频繁调用,不断地有函数入栈,即函数栈,会造成栈空间或栈内存的大量消耗。为避免消耗栈空间,将这一部分函数定义为内联函数,在预编译的时候,使用到这些函数的部分编译器会将函数复制到对应的位置,从而在编译的时候直接执行而不在进行入栈出栈操作。

内联函数怎么用

如何定义内联函数

普通函数使用关键词 inline修饰符,表示为内联函数

//函数定义为inline即:内联函数  
inline char* dbtest(int a) 
{  
	return (i % 2 > 0) ? "奇" : "偶";  
} 

注意:

  1. 关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用
  2. 最好将内联函数定义放在头文件中

内联函数局限

inline只适合涵数体内代码简单的函数数使用,不能包含复杂的结构控制语句例如while、switch,并且内联函数本身不能是直接递归函数(自己内部还调用自己的函数)。

内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。

可变参数

可变参数__VA_ARGS__说明

VA_ARGS 是一个C 语言可变参数的宏, C99 规范中新增内容。与其对应使用的是可变参数列表…
… 表示可变参数列表。
__VA_ARGS__作用: 将左边宏中的’…'的内容原样抄到右边__VA_ARGS__所占用的位置。 以上两宏等价
常用场景如:

#define LOG1(...)  func1(__VA_ARGS__)  
//使用示例为:
LOG1("231313")
//输出效果为
231313

可变参数__VA_ARGS__使用

使用方法1:直接使用可变参数宏

这种使用方法中,仅仅只支持字符串常量,__VA_ARGS__作用仅为在预编译时替换…对应的字符串。无法支持可变数量的参数。

#define LOGFUNC(...) (printf(__VA_ARGS__))

使用方法2:可变参数宏与参数格式化配合使用

增加格式化描述内容,可以达到输出可变参数目的。

#define LOGSTRINGS(fmt, ...) printf(fmt,__VA_ARGS__)
//使用方法
 LOGSTRINGS("0123456789,%d%s",1,"sd")
 //输出效果
 0123456789,1sd

但是这种使用方法无法解决一种场景,需要输出的内容只为一个字符串常量时。这种场景时会报错。
原因为,可变参数格式为0时,对应的__VA_ARGS__不存在,fmt后的符号“,”依然存在,不符合C语言规范。
如下所示:

    //输出字符串常量报错
    //LOGSTRINGS("0123456789");

    /*LOGSTRINGS("0123456789");报错如下:
    main.cpp: In function ‘int main()’:
main.cpp: error: expected primary-expression before ‘)’ token
    3 | #define LOGSTRINGS(fm, ...) printf(fm,__VA_ARGS__)

使用方法3:使用##VA_ARGS

如果可变参数被忽略或为空,## 操作将使预处理器(preprocessor)去除掉它前面的那个逗号。从而实现完美兼容可变参数和字符串常量输出。
这种使用方法也是在日志打印输出时经常使用的方法

#include <stdio.h>

#define LOGFUNC2(fmt,...) (printf(fmt" line:%d - %s/%s  \n",##__VA_ARGS__,__LINE__,__TIME__,__DATE__));

int main()
{
    //可变参数
    LOGFUNC2("i am C++ :%d name:%s age:%d",112,"C语言教程",18);// ok

    //字符串常量
    LOGFUNC2("i am C++ ");// ok

}
/*
输出结果:
i am C++ :112 name:C语言教程 age:18 line:7 - 08:40:32/Jul 11 2021
i am C++  line:8 - 08:40:32/Jul 11 2021
*/

常用打印输出定义

extern void log_msg(unsigned level, const char *tips,const char *format, ...)

#define ERROR(_format, ...)             log_msg(0,  __func__, _format, ##__VA_ARGS__)
#define WARNN(_format, ...)             log_msg(1,  __func__, _format, ##__VA_ARGS__)
#define INFOR(_format, ...)             log_msg(2,  __func__, _format, ##__VA_ARGS__)
#define DEBUG(_format, ...)             log_msg(3,  __func__, _format, ##__VA_ARGS__)

可变参数内容提取

指定参数格式的参数定义

此类定义中,第一个参数标识后续参数格式。后续为参数列表。此时参数一般用于后续参数类型均为已知固定类型,只是格式不定的情况。
如下所示:

int func_name(int arg1, ...);

arg1为可变参数格式
…为参数列表

#include <stdio.h>
#include <stdarg.h> //C库函数,必须包含头文件
 //求平均数函数
double average(int num,...)
{
 
    va_list valist;
    double sum = 0.0;
    int i;
 
    /* 为 num 个参数初始化 valist */
    va_start(valist, num);
 
    /* 访问所有赋给 valist 的参数按照int格式读取 */
    for (i = 0; i < num; i++)
    {
       sum += va_arg(valist, int);
    }
    /* 清理为 valist 保留的内存 */
    va_end(valist);
 
    return sum/num;
}
 
int main()
{
   printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
   printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}

va_start(ap, last_arg):初始化可变参数列表。ap 是一个 va_list 类型的变量,last_arg 是最后一个固定参数的名称(也就是可变参数列表之前的参数)。该宏将 ap 指向可变参数列表中的第一个参数。

va_arg(ap, type):获取可变参数列表中的下一个参数。ap 是一个 va_list 类型的变量,type 是下一个参数的类型。该宏返回类型为 type 的值,并将 ap 指向下一个参数。

va_end(ap):结束可变参数列表的访问。ap 是一个 va_list 类型的变量。该宏将 ap 置为 NULL。

格式化可变参数定义

这种一版定义方式为,其中fmt使用字符串格式化方式填充内容

LOGFUNC2(fmt,...)

LOGFUNC2("i am C++ :%d name:%s age:%d",112,"C语言教程",18);

可变参数格式话输出,参考代码:
C 库函数 int vsprintf(char *str, const char *format, va_list arg) 使用参数列表发送格式化输出到字符串
int vsprintf(char *str, const char *format, va_list arg)

char buffer[80];
int vspfunc(char *format, ...)
{
   va_list aptr;
   int ret;

   va_start(aptr, format);
   ret = vsprintf(buffer, format, aptr);
   va_end(aptr);

   return(ret);
}

命令行参数argc argv

执行程序时,可以从命令行传值给 C 程序。这些值被称为命令行参数,它们对程序很重要,特别是当您想从外部控制程序,而不是在代码内对这些值进行硬编码时,就显得尤为重要了.

在c语言编程中,经常可以看到如下的main函数声明

int main(int argc, char *argv[])

int main(int argc, char **argv)

argc 是argument count的缩写表示传入main函数中的参数个数
argv 是 argument vector的缩写表示传入main函数中的参数列表,是一个指针数组,里面存的是字符指针,即一个一个的字符串

如果main函数为启动函数,则参数包括程序自身。如下所示:
./argtest
argc is 1
argv[0] is: ./argtest

常用预编译指令与宏定义

宏定义含义
#ifdef如果宏已经定义,则返回真
#ifndef如果宏没有定义,则返回真
#if如果给定条件为真,则编译下面代码
#else#if 的替代方案
#elif如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif结束一个 #if……#else 条件编译块
#error当遇到标准错误时,停止编译,输出错误消息
_ DATE_编译时的当前日期,一个以 “MMM DD YYYY” 格式表示的字符常量
_ TIME_编译时的当前时间,一个以 “HH:MM:SS” 格式表示的字符常量。
_ FILE_这会包含当前文件名,一个字符串常量。
_ LINE_这会包含当前行号,一个十进制常量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值