第一章 兼容
预处理操作:文件包含、宏、条件编译
1.1 预定义宏__func__
预定义宏__func__
返回所在函数的名字const char*
const char* hello()
{
return __func__;
}
__FILE__
、__LINE__
、__TIME__
、__DATA__
int main()
{
cout << hello() << endl;
cout << __FILE__ << endl; //返回所在文件的名字
cout << __LINE__ << endl; //返回当前行号
cout << __TIME__ << endl; //返回当前编译时间
cout << __DATE__ << endl; //返回编译日期
return 0;
}
1.2 #pragma
#pragma once
放在头文件最前面,表示只编译一次,防止头文件被多次包含,等价于下面的#ifdef那一系列。
C++11中增加了操作符_Pragma {字符串字面值} _Pragma {"once"}
相比预处理指令,操作符可以放在宏中。更灵活一些吧,具体用到再说!
#pragma pack (n)
用来改变编译器内存对齐方式,这个就涉及到struct内变量如何对齐了。比较复杂,这里先简单引入一下。
默认pack(8)也即是八字节对齐,可以是1, 2, 4, 8, 16, …
struct 对齐规则:
- 整个内存大小是最大成员类型的整数倍
- 每个成员开始时都是该成员类型大小的整数倍
struct a
{
int a1; // 4
char a2; // 5
char a3; // 6
}; // 8
struct b
{
char b1; // 1
int b2; // 5
char b3; // 9
}; // 12
1. 3 #ifdef
在头文件中加入,防止头文件被包含两次!!!乌鱼子,终于明白了
xxx.h
#ifndef xxx_H
#defind xxx_H
//一些头文件的定义
#endif
xxx.cpp
#inlcude<xxx.h> 第一次包含xxx.h时未定义xxx_H,进入代码段编译
#inlude<xxx.h> 第二次包含xxx.h时 #ifndef为假,不会再进入代码段编译,防止xxx.h被包含两次
1.4 [宏的高级应用](C语言宏高级用法 - AlanTu - 博客园 (cnblogs.com))
第一个是#x,#加参数表示将参数看作字符数组
#define LOG_INFO(x) printf("%s\n", #x);
LOG_INFO("something");
//ouput: "something" 一整个作为字符串
第二个是##,两个#表示先分割再连接,就是宏之前是使用空格进行分割的,空格前面的来进行匹配。
#define TYPE0(type, name) type name type type;
//比如这个我们想要type替换成参数里面的type但是最终还要和name合起来,这个空格就做不到,可以使用##
//先将其按照##分割成name_ type 和_type,只有中间者type匹配上了被替换成参数中的type,剩余两个不变,然后再把他们三个强制连接起来。
#define TYPE1(type,name) type name_##type##_type
第三个是do{}while(0),这个是构建一个代码块,成为一个独立的单元。因为while(0),所以只执行{}代码一次。为什么只用{}不行呢?
#define switch(x, y) {int tmp = x; x = y; y = tmp;}
if(x > y)
switch(x, y);
//这样替换会在花括号后面多加一个;不对!
//可以使用do {} while(0);
#define switch(x, y) do{int tmp = x; x = y; y = tmp;}while(0)
//正好while(0)缺一个分号,在switch(x, y)这多一个分号!合适!
1.5 变长参数__VA_ARGS__
#define LOG(...) printf(__VA_ARGS__)
LOG("%d\n", x);
//两个参数"%d\n"和x,传进...从__VA__ARGS__获取
1.6 long long
C++11引入了long long,长度不少于64位,可以在里面查看范围%lld
弱类型语言和强类型语言:强类型语言数据类型如果不强制转换,就不能自动随着环境转换,如Python、Java,但是C++数据类型会随着环境隐式发生转换。
静态语言和动态语言:静态语言是声明变量时必须给出类型,如C++,但是Python这种就不用,他是在变量第一次赋值时确定类型。
1.7 __cplusplus
#ifdef __cplusplus
extern "C" void display(); //单句话放在后面就行
#else
void display();
#endif
//如果是C++则使用extern C使用C编译器编译,否则就不用extern
#ifdef __cplusplus
extern "C" {
#endif
void display();
#ifdef __cplusplus
}
#endif
//多句话需要用{}括起来,也即是如果是C++会多第二行和第8行,这样就将其中的代码使用extern "C"括起来了,不是C++就不用extern 括起来
1.8 静态断言
运行时断言:assert(expr) 如果不为真,终止;
可以使用定义NDEBUG宏取消断言
#ifdef NDEBUG
#define assert(expr) static_cast<void>(0)
#endif
//如果定义了NDEBUG宏,assert就会展开为一个无意义的式子
预处理时断言:#ifndef 和#error
编译时断言:static_assert(constexpr, waring_string);可以放在任意位置
1.9 noexcept
不会抛出异常,在delete和析构函数中不应该抛出异常
1.10 sizeof 运算符
在类中只能对静态成员或者通过实例的非静态成员获得大小
class A
{
int a1;
static int b;
}
A a;
//sizeof a.a1 sizeof A::b 都可以 sizeof A::a1不可以
//现在C++11也支持sizeof A::a1了
1.11 扩展的friend语法
声明为friend的可以访问类中的所有成员(包括private),破坏了类的封装。
friend class A;声明A是B的友元类
C++11中声明一个类时可以不用加class了,这样有个好玩的好处是:可以为类模板声明友元类了
template<typename T>
class B
{
friend T;
}
//如果T是一个class类型,比如A那么可以为B声明A作为友元类了
比如说我想使用B中的成员进行测试,但又不想全部暴露给所有人。那我可以写一个TestClass,将其传入模板参数,这样这个类就是B的友元类了,就可以访问其中的成员了。但是普通类型int不可以。有趣有趣!
1.12 final / override
基类中不声明为虚函数,重写会变成重载。
我们无法阻止派生类重写父类的虚函数,C++11推出了final,可以阻止派生类重写父类的虚函数,得是在虚函数的基础上才有重写(???疑惑:声明虚函数的意义不是要重写吗,现在不想让他重写,不声明虚函数不就行了?)。哦是这样的,所以final一般用在派生类中,这样中途改变后面的派生类不能重写,因为派生类从基类继承来还是虚函数。
virtual void fun() final;//写在派生类中,后面的子类不能再重写fun()函数了
override表示这个函数是重写虚函数的,如果编译器判断不是重写的会报警!
加在派生类的重写函数后面
继承顶层声明了虚函数,表示这个可以被派生类重写,当然你也可以不重写。但是如果你不写virtual,后面在派生类中重写的话,就会报错,因为不是有效的重载。
而且顶层声明了virtual后面不用写virtual也都是虚函数,但是你可能但从派生类中看不出来,所以你可以加上一个override表示一下,这样但从派生类中也可以看出你是重写的父类的虚函数了。
1.13 模板的默认类型参数
template<typename T, tyename U = double> class A{};
A a(1);//就会被推断成A a(1, 0);
1.14 外部模板
一种编译器时间和空间的优化手段。
类似于全局变量和外部变量的引用
比如说,我们在a.cpp中定义了一个i变量,在b.cpp中想要使用这个i,可以extern int i;
,这样在两份文件中就只会保留一个i。a.o中保存i,b.o中保存i符号,链接时就不会出错,实现了数据共享。
同样数据存在这样的问题,代码也存在这样的问题,再比如说在test.h中写了一个模板类template<T> class A{};
,然后在a.cpp中使用A<int> a;
实例化了一个int类型的模板类,然后再在b.cpp中使用A<int> b
,又实例化了一个int类型的模板,看起来会冲突,但是代码和数据不一样,编译器会优化只保留一个类实例,因此不会出错,但是可以使用外部模板来优化这个,这样编译器就不需要再删掉一个冗余了。
模板显示实例化 template<int> A;
只需要在b.cpp中改为extern template<int> A;
这样在编译b.cpp时就不会再生成一份int类型的模板实例了。