预编译命令
#include
#include<文件名> 或者 #include"文件名"
功能:
先由预处理器删除这条指令,然后把指定的文件插入该命令行位置,使指定文件和当前的源程序文件连成一个文件。
注意:
-
一个include命令只能指定一个被包含文件。
-
使用尖括号:在包含文件目录中查找(即由用户事先在设置环境时设置的);使用双引号:首先在当前的源文件的目录中查找,若未找到才去包含目录中查找。
-
文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。
-
若在file1.c文件中写入:(注意前后顺序!)
#include<file2.c> #include<file3.c>则file1.c和file3.c都可以使用file2.c的内容,而不必在file3.c中重复包含。为防止文件内容重复被包含就要合理使用条件编译。
-
被包含文件(file2.c)与其所在的文件(即用#include命令的源文件file1.c)在预编译后已成为同一个文件。
因此如果file2.c中有全局静态变量,它也在file1.c文件中有效,不必用extern声明。
重复包含的解决方案:
1.如果头文件首尾均加上条件编译语句,就不会出现重复定义的编译错误。
这三行语句也可用一行 #pragma once 语句替代。详情
"head1.h"
#ifndef _HEAD1_H_
#define _HEAD1_H_
struct Stu{string name;int age;
};
#endif
"main.cpp"
#include "head1.h"
#include "head1.h"
int main(){return 0;
}
2.给可能重复在不同头文件中定义的结构体使用条件编译,就不会出现重复定义的编译错误。
"head1.h"
#ifndef _HEAD1_H_
#define _HEAD1_H_#ifndef _Stu
#define _Stu
struct Stu{string name;int age;
};
#endif#endif
"head2.h"
#ifndef _HEAD2_H_
#define _HEAD2_H_#ifndef _Stu
#define _Stu
struct Stu{string name;int age;
};
#endif#endif
"main.cpp"
#include "head1.h"
#include "head2.h"
int main(){return 0;
}
#define
无参宏定义
形式:
#define 标识符 字符串
功能:
用于指定标识符(宏名)代替字符序列(宏体)
#表示这是一条预处理命令,define为宏定义命令,“标识符”为所定义的宏名,“字符串”可以是常数、表达式、格式串等。
对源程序进行编译时,先由预处理程序进行宏置换,即用表达式代换所有宏名,然后再进行编译
注意:
1.宏定义的位置为任意,但一般情况下放在函数外面。
2.习惯上宏名用大写字母表示,以便与变量区别。但也可以用小写。
3.宏名的有效范围为从定义命令开始到 #undef PI 语句 或者 本源文件结束(如果没有#undef语句)
4.宏定义是用宏名表示一个字符串,只是进行简单的代换,不做任何正确性检查。如有错误,只能在编译已被宏展开之后的源程序时发现。
5.宏定义不是说明或语句,行末不必加分号;如加,一起置换。例:#define P printf("\n");
6.宏名在源程序中被引号括起来,则预处理程序不对其进行宏置换。例:printf("PI"); 输出:PI
7.宏定义允许嵌套但不允许递归。#define max max+10 是错误的
#define width 40 //长方形的宽
#define length width+20 //长加宽,这里运用了嵌套
var = length*2; //计算周长
8.宏定义必要时要加圆括号,例如上例中宏展开为 var = 40+20 * 2 计算错误
应改为 #define length (width+20) 其宏展开为 var = (40+20) * 2 计算正确
9.宏定义可表示数据类型 #define LL long long ,但与用typedef定义数据说明符有区别:
宏定义只是简单的字符串代换,在预处理时完成;
typedef定义的数据说明符是在编译时处理的,它不是进行简单的代换,而是对类型说明符重新命名。
#define STU1 int *
typedef (int *) STU2
STU1 a, b;
宏代换后变成 int *a, b; a 是指向整型的指针变量,b 是整型变量。
STU2 a, b;
由于STU2是一个类型说明符,所以此时 a 和 b 都是指向整型的指针变量。
10.对“输出格式”进行宏定义可以减少书写工作量。
#define P printf#define D "%d\n"#define F "%f\n"int a = 3;float b = 2.1;P(D F,a,b);
含参宏定义
形式:
#define 宏名(形参表) 字符串
功能:
宏展开:形参用实参替换,其他字符保持不变。字符串中包含括号中的形参。
注意:
1.宏体及各形参外一般应加圆括号,避免宏展开后运算优先级发生改变。
#define POWER(x) ((x)*(x))
2.宏名和形参表间不能加空格。
3.宏定义可以实现某些函数的功能。
4.宏定义中的形参是标识符,而宏调用中的实参可以是表达式。
5.含参宏定义和函数的区别:
(1) 函数调用先求表达式的值,再带入形参。宏调用先进行宏展开,即字符串的替换。
如 POWER(i++) 会运行 (i++) * (i++) ,平方后 i 会自加 2 次而不是一次。
(2) 宏调用是通过宏展开完成的,在编译阶段进行,不占运行时间,只占编译时间。
函数调用是在程序运行时进行的,占运行时间(包括分配内存单元,保留现场,值传递和返回)
(3) 带参宏定义不存在类型问题,宏名无类型,参数也无类型,也不需要分配内存空间。
而函数却要求形参和实参类型必须保持一致,调用时也需要给形参分配临时的储存空间。
(4) 多次宏调用会使程序变长,而函数调用多次也不会使程序加长。
(5) 调用函数只能得到一个返回值,而用宏则可以设法得到多个结果。
连接符
字符串化操作符#
功能:
用于宏定义中,表示把参数字符串化,即把一个参数转换成字符串。
实例:
#include<iostream>
using namespace std;
#define PASTE(n) "abcdef"#n
int main() {printf("%s\n", PASTE(15)); //输出:abcdef15return 0;
}
符号连接操作符##
功能:
连接符,连接两个参数作为一个标识。
实例:
1.连接
#include<iostream>
using namespace std;
#define NUM(a,b,c) a##b##c //连接
#define STR(a,b,c) a##b##c
int main() {printf("%d\n", NUM(1, 2, 3)); //输出:123
// printf("%s\n", STR(aa, bb, cc)); //一些编译器会报错return 0;
}
2.变量的命名
#include <iostream>
using namespace std;
#define VAR(v) num##v //变量的命名
int main() {int VAR(First) = 1;int VAR(Tail) = 2;int VAR(Cur) = 3;printf("%d %d %d", numFirst, numTail, numCur); //输出:1 2 3return 0;
}
注意:
下面是一个错误示例:
#include <iostream>#define TRR1T 1
#define TRR2T 2#define MM(x) TRR##x##T * 10using namespace std;int main() {int i;cin >> i;cout << MM(2); //正确输出 20
// cout << MM(i); //[错误]'TRRiT' was not declared in this scope; did you mean 'TRR1T'?return 0;
}
宏定义的原理是原样替换,并不会根据 \(i\) 的值替换成 TRR1T ,而是直接替换成 TRRiT 。
一个可以参考的修改方案是:
#include <iostream>#define TRR1T 1
#define TRR2T 2#define MM(x) TRR##x##T * 10using namespace std;int main() {int i;cin >> i;switch (i) {case 1:cout << MM(1); break;case 2:cout << MM(2);break;default:cout << "ERROR";break;}return 0;
}
字符化操作符#@
功能:
只能用于有传入参数的宏定义中,且必须置于有宏体的参数名前。作用是将传入的单字符参数名转换成字符,以一对单引号括起来。
实例:
#include <iostream>
using namespace std;
#define ToChar(c) #@c
int main() {putchar(ToChar(2)); //输出:2return 0;
}
#if
功能:
条件编译在预编译中执行,可以实现某些语句正常编译或者完全忽略,而程序中的if语句依然会全文编译。
形式:
1.
#if 常数表达式1程序段1
#elif 常数表达式2 //(可不写)程序段2
#else //(可不写)程序段3
#endif
2.
#ifdef 标识符 //如果此宏名已经被定义(#define)过程序段1
#else //(可不写)程序段2
#endif
3.
#ifndef 标识符 //如果此宏名没有被定义(#define)过程序段1
#else //(可不写)程序段2
#endif
实例:
#include<iostream>
using namespace std;
int main() {
#ifndef ONLINE_JUDGE //在测评网站上一般都有定义的这个宏freopen("in.txt","r",stdin);freopen("out.txt","w",stdout);
#endifint a, b;cin >> a >> b;cout << a + b;return 0;
}
#pragma
1.#pragma message("消息文本")
功能:
当编译器遇到这条指令时,在编译输出窗口中将消息文本打印出来。
实例:
#include <iostream>
using namespace std;
#pragma message("This is a test.")
int main() {return 0;
}
点击“编译”后,编译器窗口中提示:[说明] '#pragma message: This is a test.'
2.#pragma once
功能:
在头文件的最开始加入这条指令就能够保证头文件被编译一次。详情
3.#pragma warning(command:错误代码)
功能:
设置警告信息状态。
disable:4066 //屏蔽代码为4066的警告信息
once:4066 //仅报告一次代码为4066的警告信息
error:4066 //将代码为4066的警告信息作为一个错误
4.#pragma comment(comment-type,"commentString")
说明:
comment-type 是一个预定义的标识符,指定注释的类型,一般用来加载静态库。
commentString 是一个为 comment-type 提供附加信息的字符串。
5.#pragma pack()
说明:
指定对齐长度:用于结构体、联合中。