以上内容总结至:微软官网https://docs.microsoft.com/zh-cn/cpp/preprocessor/preprocessor-directives?view=msvc-170
预处理器指令:(如 #define
和 #ifdef
)通常用于使源程序在不同的执行环境中易于更改和编译预处理器语句使用的字符集与源文件语句相同,但转义序列不受支持。 预处理器语句中使用的字符集与执行字符集相同。 预处理器还可识别负字符值。
常见的预处理关键词:
- (
#
) 必须是包含指令的行上的第一个非空白字符- 指令后面的任何文本都必须以单行注释分隔符开头 (
//
) 或括在注释分隔符 (/* */
)- 预处理器指令的行可以通过紧靠在行尾标记前面,使用反斜杠 (
\
)
#define :
创建一个宏,该宏是标识符或参数化标识符与标记字符串的关联。 在定义宏之后,编译器可用标记字符串替换源文件中标识符的每个匹配项
#define I 30 //把 I 定义为整形常量
//这两种的区别
#define P (x+y)
#define P(x,y) x+y
int x=20;int y=30
求 k=x*P(x,y)*y
//第一种
k=x*(x+y)*y=20*(20+30)*30=30000
//第二种
k=x*x+y*y=20*20+30*30=1300
#undef :
移除(取消定义)之前使用
#define
创建的名称
#define P (x+y) //创建
#define P1 x+y //创建
...
...
#undef P //删除
#undef P1 //删除
#error:
在编译时发出用户指定的错误消息,然后终止编译
#if、#elif、#else 和 #endif :
控制源文件部分编译:
#if 每个命令指令都必须由结束的 #endif 指令匹配 。 在 #elif 和 #if 指令之间#endif任意数目的 #else 指令,但最多允许一#else指令。 如果存在 #else ,则此指令必须是最后一个指令,然后才能 #endif。#if、#elif、#else 和 #endif 指令可以嵌套在其他 #if 指令的文本部分中。 每个 嵌套#else、 #elif 或 #endif 指令都属于 最近的前一 个#if指令。
格式:
#if ......
#elif......//可以没有这一行
#else.....
#endif....
defined:预处理运算符
用法:
已 (标识符)
定义标识符
#if defined(CREDIT)
credit();
#elif defined(DEBIT)
debit();
#else
printerror();
#endif
//如果定义了 CREDIT ,则编译对credit()调用
//如果定义了标识符 DEBIT,则编译对 debit 的函数调用。
//如果两个标识符都未定义,则编译对 printerror 的调用。
假设 p是一个已定义的量
#if p>5
#define G 1
#elif p=5
#define G 2
#else
#define G 3
#endif
//当p大于5时 G 为 1
//当p等于5时 G 为 2
//两个都不满足时 G 为 1
#ifdef 和#ifndef
相当于:#if defined ... #if !defined ...
提供这些指令只是为了实现与该语言的早期版本的兼容性。
首选
defined(
identifier
)
与 指令一起使用的#if
常量表达式。
#import :(C++专用)
过去一直合并类型库中的信息。 类型库的内容将转换为 C++ 类,主要描述 COM 接口
#import "filename" [attributes]
<#import文件名>[属性]
#include :头文件
告知预处理器在 指令出现时包含指定文件的内容。
#include "
path-spec" //用户自定义的文件
#include <
path-spec>//系统文件
#include<iostream>//系统文件
#include"Person.h"//用户自定义文件
这两个包含的区别:
- #include <> 编译器直接从系统类库目录里查找头文件,如果类库目录下查找失败,编译器会终止查找。
- #include "" 编译器默认从当前文件所在目录下查找头文件,如果查找失败,再从项目工程中设置的头文件引用目录查找,在 Linux GCC 编译环境下,则一般通过使用 -L 参数指定引用目录,如果项目配置的头文件引用目录中仍然查找失败,再从系统类库目录里查找头文件。
- #include <> 一般用于包含系统头文件,诸如 stdlib.h、stdio.h、iostream 等;#include "" 一般用于包含自定义头文件,比如我们自定义的 test.h、declare.h 等。
C++ 中的预定义宏
C++ 提供了下表所示的一些预定义宏:
宏 | 描述 |
---|---|
LINE | 这会在程序编译时包含当前行号。 |
FILE | 这会在程序编译时包含当前文件名。 |
DATE | 这会包含一个形式为 month/day/year 的字符串,它表示把源文件转换为目标代码的日期。 |
TIME | 这会包含一个形式为 hour:minute:second 的字符串,它表示程序被编译的时间。 |
#include <iostream>
using namespace std;
int main ()
{
cout << " __LINE__ : " << __LINE__ << endl;
cout << "__FILE__ : " << __FILE__ << endl;
cout << " __DATE__ : " << __DATE__ << endl;
cout << " __TIME__ : " << __TIME__ << endl;
return 0;
}
结果为:
__LINE__ : 6 //行号
__FILE__ : test.cpp //文件
__DATE__ : Feb 28 2011 //日期
__TIME__ : 18:52:48 //时间
预处理器运算符:
操作员 | 操作 |
---|---|
字符串化运算符 (#) | 使相应的实际参数用双引号引起来 |
字符化运算符 (#@) | 使相应的参数括在单引号中,并被视为特定于 Microsoft (字符) |
标记粘贴运算符 (##) | 允许连接用作实际参数的令牌以形成其他标记 |
defined 运算符 | 简化了在某些宏指令中编写复合表达式 |
运算符用于连接两个令牌:
#define CONCAT( x, y ) x ## y
#include <iostream>
using namespace std;
#define concat(a, b) a ## b
int main()
{
int xy = 100;
cout << concat(x, y); //结果为100 concat 把(x,y)拼接成 xy
return 0;
}
宏和 C++ :
- 在 C++ 中,声明为
const
的对象可用于常量表达式。 它允许程序声明具有类型和值信息的常量。 它们可以声明可以使用调试器以符号方式查看的枚举。- 使用预处理器指令
#define
定义常量时,它并不精确,也不为类型安全。 不会为 对象分配任何存储const
,除非程序包含采用其地址的表达式。
C++ 内联函数功能取代了函数类型宏。 使用内联函数取代宏的好处如下:
类型安全。 内联函数需要接受与常规函数相同的类型检查。 宏不是类型安全的。
纠正具有副作用的参数的处理。 内联函数在输入函数体之前计算作为参数提供的表达式。 因此,具有副作用的表达式不会不安全。
四大强制类型转换:
参考资料:C++ 四种强制类型转换 - 静悟生慧 - 博客园 (cnblogs.com)
C++ static_cast、dynamic_cast、const_cast和reinterpret_cast(四种类型转换运算符) (biancheng.net)
static_cast (静态类型转换)
主要用于普通对象,static_cast没有动态类型检查,所以是不安全的,在编译期间转换
常见的用法:
- 向上转型(类和普通变量都可以)
- 向下转型(普通变量),类向下转型会报错
- void *转换为各类指针(其他指针转换为void无需强制转换)
注意:static_cast不能转换掉expression的const、volitale或者__unaligned属性。
向上向下转型
class person
{
};
class son:public person
{
};
int main()
{
int a = 10;
double b = static_cast<double>(a);//int -> double 普通变量向上转型
int a1 = static_cast<int>(b);//普通变量可以向下转型
son s;
person p = static_cast<person>(s);//son -> person 类向上转型
son s1 = static_cast<son>(p);//报错无法强制转换(类向下转换)
return 0;
}
各类指针和void *的转换
class person
{
};
int main()
{
int a = 10;
int *p = &a;
void *v = p;//其他指针转void *可以不用强制类型转换
int *p1 = v;//报错无法转换,需要用强制转换
int *p2 = static_cast<int *>(v);
person per;
person* per1= &per;
void *son = per1;//类的指针可以转换为void *
person *per2 = static_cast<person*>(son);
return 0;
}
const_cast(常量类型转换)
从const转换为非const,从volatile转换为非volatile(并不是真正的去除const),一般不会使用
注意事项:这两种初始化的区别
- const int a=10;
- int p=10; const int a=p;
void text() {
const int a = 10;
int *p;
p = const_cast<int*>(&a);
(*p)++;
cout << a << endl;//结果为10
cout << *p << endl;//结果为11
cout << "-----------" << endl;
}
void text1() {
int i = 10;
const int a = i;
int *r = const_cast<int *>(&a);
(*r)++;
cout << i << endl;//结果为10
cout << a << endl;//结果为11
cout << *r << endl;//结果为11
}
int main() {
text();
text1();
return 0;
}
解释:
当const int a=10;时,编译器会进行优化,直接将10代替a,所以a是不可以改变的
当int p=10; const int a=p;时,编译器不会优化,不会直接用p代替a,所以当运行(*r)++;会改变a中的值
reinterpret_cast(重解释转换)
是一种不安全的转换,用于指针和引用,是一种位操作。
主要用途:
- 改变指针或引用的类型
- 将指针或引用转换为一个足够长度的整形
- 将整型转换为指针或引用类型
void text1() {
int *m = new int(10);
double *n = reinterpret_cast<double*>(m);//指针和指针转换
int * a = new int(5);
int b = reinterpret_cast<int>(a);//指针转化为整型
int c = 10;
int *d = reinterpret_cast<int *>(c);//整型转化为指针
}
int main() {
text1();
return 0;
}
以上三种都是编译时进行类型转换
dynamic_cast(动态转换)
dynamic_cast的注意事项:
- dynamic_cast是运行的时候借助 RTTI 进行类型转换(必须包含虚函数)
- dynamic_cast转换如果成功的话返回的是指向类的指针或引用,转换失败的话则会返回NULL
- dynamic_cast作用与类的指针和引用或void*,不能用于常规类型强制转换
不包含虚函数使用不了(动态多态)
- 向上转型:条件 (有继承关系,有虚函数)向上转型的话一般是安全的,不会进行任何运行期间的检查,这时相当于static_cast
- 向下转型:运行的时候借助 RTTI 进行类型转换,向下转换的成功与否还与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,否则转换失败(向下转型看本质)
向上转型:
class person{
public:
virtual void show() { cout << "person类" << endl; }
};
class son:public person
{
public:
void show() { cout << "son类" << endl; }
};
void text1() {
//向上转型,static_cast和dynamic_cast等价
son *s = new son;
person * p = dynamic_cast<person *>(s);
son *s1 = new son;
person * p1 = static_cast<person *>(s1);
}
int main() {
text1();
return 0;
}
向下转型:
注意:向下转型其实也是(向上转型)
例一:失败的案例
class person{
public:
virtual void show() { cout << "person类" << endl; }
};
class son:public person
{
public:
virtual void show() { cout << "son类" << endl; }
};
class man :public son
{
public:
virtual void show() { cout << "man类" << endl; }
};
int main() {
person *p = new person;
son *s = dynamic_cast<son*>(p);
if (s == NULL)
{
cout << "s转换失败" << endl;
}
man *m = dynamic_cast<man*>(p);
if (m == NULL)
{
cout << "m转换失败" << endl;
}
return 0;
}
转化失败的原因: dynamic_cast 会沿着继承链寻找想要转化的目标,没有找到返回null
例二:转化成功的案例
int main() {
person *p = new man;
son *s = dynamic_cast<son*>(p);
if (s != NULL)
{
cout << "s转换成功" << endl;
}
man *m = dynamic_cast<man*>(p);
if (m != NULL)
{
cout << "m转换成功" << endl;
}
return 0;
}