目录
1. 预定义宏
- __cplusplus:C++语言的版本号。如:202002(c++20),201703(c++17),201402,201103
- __DATE__ :预处理时的日期
- __FILE__ :源文件名
- __LINE__ :源文件行号
- __has_include:是否存在某个可包含的文件
- __cpp_modules:是否支持模块机制
- __cpp_decltype:是否支持decltype特性
- __cppdecltype_auto:是否支持decltype(auto)特性
- __cpp_lib_make_unique:是否提供函数make_unique()
- __GNUC__:检查GCC的版本
- __SSE4_2__ 、__x86_x64:检查CPU指令集
2.属性
- C++11之前非官方的“编译指令”:__attribute__(GCC)、__declspec(VS)
- C++11支持的属性:noreturn、carries_dependency
语法:用“[[…]]”标识
示例:
[[noreturn]] //属性标签,函数绝不会返回任何值
int func(bool flag)
{
throw std::rutime_error("xxx");
}
-
C++14新增属性:deprecated(废弃某段代码,不鼓励使用)
-
C++17/20新增属性:
- nodiscard:显示声明不允许忽略函数返回值
- maybe_unused:显示标记某段代码暂时不用,但保留,因为将来可能会用
- fallthrough:仅用于switch语句
- likely/unlikely:标记某段代码路径更可能/更不可能,只是编译器优化
-
非标准扩展属性(GCC):
- const:标记函数时无副作用的常量函数,让编译器积极优化
- constructor:函数会在main()之前执行
- destructor:函数会在main()之后执行
- always_inline:要求编译器强制内敛函数,效果比inline关键字更强。
- hot:标记“热点”函数,要求编译器更积极地优化。
- 示例:
[[gnu::constructor]]
void first_func(){
printf("before main()\n");
}
3.断言
- 动态断言
assert:断言一个表达式必定为真,否则输出错误消息,然后调用abort()终止程序。只会在调试版本里生效(之前未定义NDEBUG)。 - 静态断言
static_assert:C++的关键字,在编译阶段检测各种条件的断言,在编译阶段计算表达式的值,如果是false就会报错,导致编译失败。
C++11中要求有两个参数,即static_assert(cond,msg),第二个参数是警告消息。C++14可以不提供参数。
智能化看到编译时的常量和类型,不能看到运行时的变量、指针、内存数据等。
类型检查用的模版元函数:
static_assert(is_integral_v<T>); //断言T是整数类型
static_assert(is_pointer_v<T>); //断言T时指针类型
static_assert(is_default_constructible_v<T>); //断言T有默认构造
4.面向对象编程
- 面向对象编程的关键是抽象和封装,而不是继承和多态。应当尽量少用继承和多态。
- 继承的深度不要超过3层。如果超过,考虑用组合关系替代继承,或者改用模版和泛型。
- 设计类接口的时候,尽量简单、短小精悍,只负责单一的功能。
- 避免嵌套类,应使用命名空间,把内部类提到外边,歼敌原来类的耦合度和复杂度。
5.转型操作符、委托构造及静态成员初始化
- 转型操作符
class DemoClass{
puublic:
operator bool(){...}//转型为bool
};
- 委托构造
在一个构造函数中直接调用另一个构造函数。 - 静态成员变量初始化
- const静态成员变量,C++允许直接在声明的时候初始化。非const静态成员变量,必须在实现文件里单独再初始化(因为需要非配唯一的存储空间)。
C++17中通过inline关键字声明内联变量可以初始化非const静态成员变量。
class DemoClass{
puublic:
static const int x = 10;
//static std::string prefix = "xxx"; //无法通过编译
inline static std::string prefix = “xxx”; //C++17编译正常
};
6.型别推导
- C++14新增了字面量后缀“s”来表示标准字符串,所以可以用
auto str = "xxx"s;
的形式直接推导出std::string类型。
auto str = "hello";
auto str1 = "hello"s;
- 类成员变量初始化不允许使用auto类型推导,但静态成员变量允许使用auto类型推导。
- C++17中的auto的结构化绑定
tuple x{1,"x"s,0.1};
auto [a,b,c] = x;//结构化绑定,x中的值分别赋值给a,b,c
std::map<int,std::string> map = {{1,"abc"s},{2,"qwe"s}};
for(auto& [k,v] : map){
std::cout << k << "=>" << v << std::endl;
}
7.const、mutable、volatile、consexptr
- std::as_const():无条件吧变量转为常量引用。
- mutable:mutable修饰的成员变量可以再const修饰的函数中修改
- volatile:告诉编译器不要优化
//如要获取实时温度时需要连续赋值
int a = 1;
a = 2;
a = 3; //编译器优化后为 a = 3;
volatile int a = 1;
a = 2;
a = 3; // 不优化
- consexptr:定义编译期常量,能够用于编译期计算。
8.异常
- 异常只能按照catch块在代码里的顺序依次匹配,而不会去寻找最佳匹配。因此最好只用一个catch块。
- catch块的捕获变量使用&,避免异常对象复制的代价。
- function-try:
void some_function()//函数名之后直接写try块
try{
//函数体
...
}
catch(...){
... //catch块与函数体同级并列
}
- 谨慎使用异常,以下情况请使用异常:
- 不允许被忽略的错误
- 极少情况下才会发生的错误
- 严重影响正常流程,很难恢复正常状态的错误
- 本地无法处理,必须“穿透”调用栈,传递到上层才能被处理的错误
比如构造函数、读写文件可以使用异常。socket通信失败频率太高,不要使用异常,使用错误码检查更好。
- noexcept 可以当作编译期运算符,指定在某个条件下才不会抛出异常,常用的noexcept其实相当于noexcept(true)。
- throw可以抛出任何类型的异常,但最好使用标准库里定义的exception类。
9.lambda式
- C++里每个lambda式都是一个独特的类型
- lambda式的泛型编程
auto f = []<typename T>(const T& x){
static_assert(is_integral_v<T>);
return x + x;
}
10.内联名字空间和嵌套名字空间
- 内联名字空间(C++11)
inline namespace tmp{
auto x = 0L;
auto str = "hello";
}
cout << x << endl. //可以直接使用内部成员,不需要空间限定
cout << tmp::str << end;//也可加名字空间
- 嵌套名字空间(C++17)
以“::”来分隔多个名字空间
namespace a::b::c::{
...
}
11. if/switch语句初始化(C++17)
if(init;cond){
}
vector<int> v{1,2,3};
if(auto pos = v.end();!v.empty()){
}
//多线程编程锁定互斥量
if(scoped_lock g;tasks.empty()){
}
12. 二进制字面值与数字分位符(c++14)
- 0b/0B 用来直接写二进制数字
auto x = 0b11010010;
auto x = 010; //八进制
- 数字分位符 用单引号" ’ "来分组
auto a = 0b1011'0101;
13. weak_ptr的另一个用途
weak_ptr的另一个用途——让类正确的自我创建shared_ptr:对象内部用weak_ptr来保管this指针,然后调用lock()获取shared_ptr();
- 辅助类 enable_shared_from_this
需要自我管理的类必须以继承的方式使用它,之后就可以用成员函数shared_from_this()创建shared_ptr,或者调用成员函数weak_from_this()创建weak_ptr。 - 示例
class SharedSelf final : public std::enable_shared_from_this<SharedSelf>{
};
auto ptr1 = make_shared<SharedSelf>(); //调用工厂函数创建智能指针
assert(ptr1.use_count() == 1);
auto ptr2 = ptr1->shared_from_this(); //正确获得共享指针
assert(ptr2.use_count() == 1);
auto ptr3 = ptr1->weak_from_this(); //也可以获得弱指针
assert(ptr3.use_count() == 1);
14 字符串
- std::string 中c_str()和data()的区别:两个函数都返回const char*指针,c_str()必定会在末尾添加一个“\0”,data()在c++11之后才等同于c_str()。
- 原始字符串(c++11)
原始字符串不会对字符串里的内容做任何转换,完全保持原始风貌。- 示例:
auto str = R"(nier:automata)";
- 引号+圆括号如何处理?
在圆括号的两边加上做多16个字符的特别界定符。
示例:auto str = R"==(R"(xxx)")=="; //原样输出R"(xxx)"
- 示例:
- 字符串转换函数
- 将字符串转换为整数:stoi() / stol() / stoll()
- 将字符串转换为浮点数:stof() / stod
- 把整数、浮点数转换成字符串:to_string()
- 字面值后缀
C++14新增了一个字面值后缀“s”,表示字符串类型。使用声明的时候可以利用自动类型推导,也可以省去声明临时字符串变量的麻烦,效率也会更高。需要打开名字空间using namespace std::literals;
- 字符串视图
C++17新增了新的字符串类string_view,内部只保存了一个指针和长度。- 内部使用了常量指针,所以是一个只读视图,只能查看字符串而无法修改,相当于“const string&”。
- 因为使用了字符指针,可以从C字符串构造,没有“const string&”的临时对象创建操作。
- 因为使用的是指针,要当心引用的内容可能会失效。
- string_view是一个只读视图,不能保证字符串末尾一定是NULL,无法提供成员函数c_str()。不能把它用于C函数传参。
- 也可以用后缀“sv”来表示string view,直接用auto来推导类型,也需要打开名字空间std::literals,如:
auto sv3 = "view it"sv;
- string_view提供了很多和string同名的函数,如:empty()/size()/front()/back()/find()等。string_view用在只读、弱引用场合。
- string_view不能修改字符串内容,但是可以调整它内部的指针和长度,如:
string_view sv {"god of war"s};
assert(sv.substr(4,2) == of); //取子串
sv.remove_prefix(3); //删除前缀
assert(sv == " of war");
sv.remove_suffix(4); //删除后缀
assert(sv == " of");
- 字符串格式化—— format(C++20)
- 格式占位符的基本形式:{序号:格式标志}
< : 数据左对齐 > : 数据右对齐 + : 为数字添加正负号标记 - : 为数字添加正“-”标记,正数无标记 空格:为数字添加正“-”标记,正数前加空格 //? b : 格式化二进制整数 d : 格式化十进制整数 o : 格式化八进制整数 x/X : 格式化十六进制整数 # : 非十进制数字显示“0b” “0o” “0x”前缀 数字: 格式化输出的宽度 format("{:>10}", "hello"); //右对齐,10个字符的宽度 format("{:04}, {:+04}", 100L,88); //指定填充和宽度,默认是十进制 format("{0:x}, {0:#X}",100L); //格式化为十六进制 format("{:04o}, {:04b}",7,5); //格式化为八进制/二进制,宽度是4 format("{{xxx}}"); //输出 hello 0100, +088 64, 0X64 0007, 0101 {xxx}
15 正则表达式
15.1 正则表达式对象
正则表达式主要用到两个类regex和smatch。
- regex 表示一个正则表达式,是basic_regex的特化形式。
- smatch 表示正则表达式的匹配结果,是match_results的特化形式
创建正则表达式的时候可以传递一些特殊标志,用于控制正则的处理过程。这些标志位于名字空间std::regex_constantes中,如:
- icase 匹配时忽略大小写
- optimize 要求尽量优化正则表达式,但会增加正则表达式对象的构造时间
- ECMAScript 使用ECMAScript兼容语法,这也是默认语法
- awk/grep/egrep 使用awk/grep/egrep语法
示例:
using namespace std::regex_constants;
regex reg1{"xyz",icase | oprimize}; //忽略大小写且尽量优化
15.2 正则表达式算法
- regex_match 完全匹配一个字符串
- regex_search 在字符串里查找一个正则匹配,只要找到一个符合的子串就行。
- regex_replace 先正则查找再替换,原字符串不改动,返回新字符串
15.3 正则匹配
regex_match的三个参数重载形式中,第二个参数存储匹配结果,匹配结果中第0号元素是整个匹配串,其他的则是子表达式匹配串。(子表达式就是正则表达式中”()“括起的部分)。regex_match匹配的时候需要注意,如果想要获取捕获结果,那么目标字符串不能是临时对象,因为匹配结果需要引用字符串,而临时变量在函数调用后就消失了,会导致引用无效。
$N 引用匹配的子表达式,N表示子表达式的序号
$& 引用整个匹配结果
示例:
std::cout << std::regex_replace("hello mike",std::regex(R"((\w+)\s(\w+))"),"$2-says-$1($&)"); //mike-says-hello(hello mike)
16 标准容器
16.1 有序容器
定义容器的时候必须要指定key的比较函数。这个函数通常默认为less,表示小于关系。
template<
class key,
class Compare = std::less<key>
> class set;
template<
class key,
class T,
class Compare = std::less<key>
> class map;
自定义类型不支持比较函数,作为容器的key时需要重载比较操作符“<”,或者自定义模板参数。
auto comp = [](auto&& a,auto&& b){
return a > b;
};
set<int,decltype(comp)> gs(copm);
应用场合:如果要求实时插入排序,选择set或map,否则选择vector,全部数据掺入完成后再一次性排序。