预定义符号
C语言中含有一些预定义符号,我们可以直接使用。
•__FILE__ 当前文件路径
•__LINE__ 文件当前行号
•__DATE__ 文件编译时的日期
•__TIME__ 文件编译时的时间
•__STDC__ 如果编译器遵循ANSI C则为1,否则未定义
#include<stdio.h>
int main()
{
printf("%s\n", __FILE__);
printf("%d\n", __LINE__);
printf("%s\n", __TIME__);
printf("%s\n", __DATE__);
return 0;
}
运行结果为:
#define定义常量
基本语法:#define name stuff
源代码预编译后会将name全部替换成stuff。
常见用法
#define MAX 1000
#define reg register //为 register这个关键字,创建⼀个简短的名字
#define do_forever for(;;) //⽤更形象的符号来替换⼀种实现
#define CASE break;case //在写case语句的时候⾃动把 break写上。
// 如果定义的 stuff过⻓,可以分成⼏⾏写,除了最后⼀⾏外,每⾏的后⾯都加⼀个反斜杠(续⾏符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ , \
__DATE__,__TIME__)
#define定义宏
#define允许将参数替换到文本中,这种实现方式称为宏(macro)或定义宏(define macro)
基本语法:#define name( parament-list ) stuff
其中 parament-list为一个符号表,它的内容可能出现在stuff中,注意参数列表的“(”必须与name紧邻,不能有空格。
常见使用方式:
#define SQUARE(x) ((x)*(x))
#define DOUBLE(x) ((x)+(x))
#define ADD(x,y) ((x)+(y))
•值得注意的是,当宏里面参数出现了一次以上,那么宏参数可能带有副作用。
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);
此时x、y、z的值难以确定。
•宏替换规则
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上
述处理过程。
•注意:定义宏中可以出现其他#define定义的参数,但是宏不能实现递归;字符串内容不被翻译成宏参数。
•offsetof的模拟实现:
#include<stdio.h>
#define OFFSETOF(type,men) (size_t)&(((type*)0)->men)
宏和函数的比较
属性 | #define定义宏 | 函数 |
---|---|---|
代码长度 | 一般较长 | 较短 |
执行速度 | 更快 | 较慢 |
操作符优先级 | 可能出现错误 | 准确、可预测 |
带有副作用的参数 | 难以观察,易出现 | 易控制 |
参数类型 | 参数无关,可以是任意类型 | 类型固定 |
调试 | 不能调试 | 可以调试 |
递归 | 不能递归 | 可以递归 |
#和##
#运算符
#运算符可以将一个宏参数转换为字符串字面量,比如#a就会变成“a”。
示例:
#include<stdio.h>
#define PUTS(x,format) printf("the value of "#x" is "format"\n",x)
int main()
{
int a = 95;
PUTS(a, "%d");
double b = 3.1415;
PUTS(b, "%lf");
return 0;
}
运行结果为:
##运算符
##又称为记号粘合符,## 可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。
例如实现多个相似函数的定义:
#include<stdio.h>
#define GENERIC_MAX(type) \
type type##max(type x,type y)\
{\
return x>y?x:y;\
}
GENERIC_MAX(int)
int main()
{
int x = 10, y = 20;
int z = intmax(x, y);
printf("%d\n", z);
return 0;
}
运行结果为:
•命名约定:
一般来讲函数的宏的使用语法很相似。所以语言本⾝没法帮我们区分二者。
那我们平时的一个习惯是:把宏名全部大写,函数名不要全部大写
#undef
#undef NAME用于移除一个宏定义。
命令行定义
许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
这样,这个程序在不同机器执行时,能定义不同的值,更具灵活性。
条件编译
•条件编译是当满足条件就编译,不满足条件就不参与编译
•基本的语法为:
#if 常量表达式
语句
#enif
需要注意的是,#if后面的一定是常量表达式,而不能出现变量。
•其他的条件编译函数:
#elif
#ifdef//等价于#if defined(symbol)
#ifndef//等价于#if !defined(symbol)
•显然,当我们调试信息不想删除的时候可以适用条件编译
•条件编译还可以嵌套:
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
头文件包含
•头文件包含包括两种方式:include"“,include<>;”“包含时会优先检索当前文件夹是否有该头文件,如果没有再检索标准位置;<>包含则会直接检索标准位置。
•这告诉我们,我们可以使用”"包含库函数的头文件。
•我们自定义的头文件显然可以和库函数头文件同名
•嵌套文件包含:当我们多次包含同一个头文件时,就会浪费空间,甚至发生错误。所以我们需要限定头文件包含时copy的次数,可以加入如下代码:
#ifndef __NAME_H__
content
#enif
或者
#pragma once