目录
1.预定义符号
__FILE__
编译的文件名__LINE__
文件当前的行号__DATE__
文件编译的日期__TIME__
文件编译的时间__STDC__
如果遵循ANSI C标准,值为1。否则未定义。
下面打印各个符号的值:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
printf("__FILE__ : %s\n", __FILE__);
printf("__LINE__ : %d\n", __LINE__);
printf("__DATE__ : %s\n", __DATE__);
printf("__TIME__ : %s\n", __TIME__);
return 0;
}
输出:
__FILE__ : H:\gitee\c_program_pratice\test_2023_2_20\test_2023_2_20\test.c
__LINE__ : 7
__DATE__ : Feb 20 2023
__TIME__ : 11:16:05
由于vs2022不遵循ANSI C标准,所以是未定义的。
2. #define和#undef
(1).define定义标识符
比如:
#define MAX_VAL 30 //定义常量
#define DataType int //类型替换
#define do_forever for(;;) //死循环
我们可以得出#define的语法就是:#define 标识符 表达式。
注意:
最好不要加;
这有可能让程序编译不了,举个例子:
#define MIN_VAL 10;
if (1)
min = MIN_VAL;
else
min = -1;
min = MIN_VAL后面已经有分号;
了,#define后面还有一个,
在这种情况下代码可能会编译不通过。
(2).#define定义宏
使用#define时,也可以把参数替换到文本中,这种实现就叫做宏(macro)。
语法结构为:#define name(para-list) stuff。
举个例子:
这里定义了SQUARE的宏,来计算某个数的平方。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define SQUARE(n) n * n
int main()
{
int num = 3;
int square = SQUARE(num);
printf("%d\n", square);
return 0;
}
输出:
9
这个宏还有一点问题,只要我们的输入一变,结果就可能出问题。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define SQUARE(n) n * n
int main()
{
int square = SQUARE(3 + 1);
printf("%d\n", square);
return 0;
}
输出:
7
出现这样的结果是因为在进行宏替换时,不会事先计算表达式的值,而是直接进行替换。
SQUARE(3 + 1) = 3 + 1 * 3 + 1 = 7
所以输出的才是7
正确的写法为:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define SQUARE(n) (n)*(n)
int main()
{
int square = SQUARE(3 + 1);
printf("%d\n", square);
return 0;
}
输出:
16
但是新的问题又产生了,如果int square = 32 / SURARE(3 + 1)
,我们期望的结果是2。
但是结果并不是,上述式子等价于int square = 32 / (3 + 1) * (3 + 1)
结果为32。
下面是代码和运行结果:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define SQUARE(n) (n)*(n)
int main()
{
int square = 32 / SQUARE(3 + 1);
printf("%d\n", square);
return 0;
}
输出
32
我们希望的是整个宏的计算结果是一个整体,那么还要整体加一个括号。
最终的写法为:#define SQUARE(n) ((n)*(n))
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define SQUARE(n) ((n)*(n))
int main()
{
int square = 32 / SQUARE(3 + 1);
printf("%d\n", square);
return 0;
}
输出:
2
注意:
为了使宏在进行数值运算的时候结果更加符合预期,我们要对每个参数加括号,当作一个表达式。还要对宏整体加括号,把宏的计算结果当作一个整体。
(3).宏的替换规则
如果宏嵌套了宏,会把最外层的宏替换掉,再作为参数,进行宏替换。
如:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define SQUARE(n) ((n)*(n))
#define SUM(a, b) ((a) + (b))
int main()
{
int val = SQUARE(SUM(1, 2));
printf("%d\n", val);
}
输出:
9
SQUARE(SUM(1, 2))
~~~~
替换成SQUARE( ((1)+(2)) )
SQUARE( ((1)+(2)) )
替换成( (((1)+(2))) * (((1)+(2))) )
(4).带副作用的宏参数
我们有时才函数传参时会传a++, sum++, b--之类的参数,可是作为宏参数时可能引起大麻烦。
比如,你写了一个宏:DOUBLE(n) ( (n) + (n) )
此时,正常传参DOUBLE(2)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
结果为: 4
但是,如果a = 1
把a++
作为参数DOUBLE(a++)
~~~~~~~~~~~~~
结果为: 5
结果为5并不是我们想要的,这是因为:DOUBLE(a++) = ((a++) + (a++))
a自增了两次,所以结果才为5;
所以在宏传参时,要注意会不会改变原来参数的结果,比如传a++时可以传a + 1;
(5).#undef
#undef的作用是移除一个宏, 其用法如下:
#undef name
比如:
#define MIN_VAL 3
#undef MIN_VAL
3. 条件编译
在写代码的时候,有一些代码有时需要,有时不需要,这很抽象我们举个例子。
写一个获得一个随机数组的函数,我们可以选择不打印这个数组,也可以选择打印这个数组。
代码如下:
int* GetArr(int size)
{
//设置种子
srand((unsigned int)time(NULL));
//开辟空间
int* arr = (int*)malloc(sizeof(int) * size);
//给数组里的元素赋值
for (int i = 0; i < size; i++)
{
arr[i] = (int)rand() % 100 + 1;
}
//如果定义了__print__宏,则编译下面的代码
#ifdef __print__
for (int i = 0; i < size; i++)
{
printf("%d ", arr[i]);
}
#endif
return arr;
}
在这里只需要关注条件编译的代码,其他的代码可以不关注
#ifdef __print__
for (int i = 0; i < size; i++)
{
printf("%d ", arr[i]);
}
#endif
这里用到了#ifdef __print__和#endif
夹在中间的代码,有时参与编译,有时不参与编译。这要看条件#ifdef __print__
意思是,如果定义了__print__这个宏,就编译下面的代码,否则下面的代码不参与编译。
下面完整运行一遍这个函数:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int* GetArr(int size)
{
//设置种子
srand((unsigned int)time(NULL));
//开辟空间
int* arr = (int*)malloc(sizeof(int) * size);
//给数组里的元素赋值
for (int i = 0; i < size; i++)
{
arr[i] = (int)rand() % 100 + 1;
}
//如果定义了__print__宏,则编译下面的代码
#ifdef __print__
for (int i = 0; i < size; i++)
{
printf("%d ", arr[i]);
}
#endif
return arr;
}
int main()
{
//得到范围从1-100的三个元素的数组
int* arr = GetArr(3);
return 0;
}
当我们没有定义宏的时候,运行main函数,没有任何输出。
但当我们定义__print__宏时,就会输出生成的数组。
即:
#define __print__
运行,输出:
92 53 30
因为是数据是随机的,每次运行都不一样,可以在自己跑一下代码。
这就是条件编译的妙处,你需要编译某段代码时,定义一个宏就可以解决。
而不是不需要的时候就不写,需要的时候又要花时间把代码重写一遍。
下面介绍几个条件编译中常用到的语句。
1.#if 和#endif
用法和平时用到的if语句差不多
下面是#if的语法说明:
#if 常量表达式
…
#endif
下面是一个简单示例:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
#if 1
printf("1\n");
#endif
return 0;
}
输出:
1
我们把1改成0,就不会输出了
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
#if 0
printf("1\n");
#endif
return 0;
}
2.多分支#if
多分支#if跟平常用的if
else if
else
语法结构差不多,非常类似。
其语法结构如下:
#if ~~~~ 常量表达式
…
#elif ~~ 常量表达式
…
#else
…
#endif
3.#ifdef和#if defined()
#ifdef
和#if defined()
要表达的都是一个意思:如果定义了xxx
基本语法:
#ifdef symbol
#if defined(symbol)
#ifndef symbol
#if !defined(symbol)
下面是这几个语句的使用:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define ADD
int main()
{
int val = 1;
//ifdef的使用
#ifdef ADD
val += 1;
#endif
//if defined()的使用
#if defined(ADD)
val += 1;
#endif
//ifndef的使用
#ifndef ADD
val -= 1;
#endif
//if !defined()的使用
#if !defined(ADD)
val -= 1;
#endif
printf("val = %d\n", val);
return 0;
}
输出:
3
这里只定义了ADD,val自增了两次,后面的自减代码是不参与编译的,所以val不会自减。
最终val = 3。
4.头文件包含
头文件包含这块,最经常见到的问题就是:
#include"filename.h"和#include<filename.h>的区别?
#include"filename.h"首先会从源代码的目录里开始找,如果找不到,再去标准库里找头文件
#include<filename.h> 会直接去标准库下找头文件
当然了,头文件包含还有很多要注意的,这里就先略过了。