1.编译原理
2.语言可用性的强化
2.1常量
-
nullptr
nullptr 的出现主要是替换NULL,在C语言中NULL表示的是(void*(0)),而在C++ 中表示0,
C++ 不允许直接将void* 隐式转换到其他类型
// #define NULL 0
// 导致C++ 重载特性发生混乱
void test(char*);
void test(int);
test(NULL);
因此C++ 引入nullptr 区分0 和 空指针, nullptr的类型是nullptr_t 可以隐式转换成任意类型的指针
#include <iostream>
#include <type_traits>
using namespace std;
void test(int i){
cout <<__FUNCTION__ <<endl;
}
void test(char *){
cout <<__FUNCTION__ <<endl;
}
int main(){
if (is_same<decltype(NULL),decltype(0)>::value)
cout << "NULL == 0" <<endl;
if (is_same<decltype(NULL),decltype(void*(0))>::value)
cout << "NULL == void*(0)" <<endl;
if (is_same<decltype(NULL),decltype(nullptr_t)>::value)
cout << "NULL == nullptr_t" <<endl;
test(0);
test(nullptr);
// test(NULL);
return 0;
}
-
constexpr
const 作为常量, 而constexpr 是修饰常量表达式,即在编译前可以确定表达式的值;
constexpr int getNum(){
return 10;
}
const int num = 10;
constexpr int sum = num * 2;
int g_list[sum]={0};
const len = num + 2;
char arr_4 [len]; // 数组的长度必须是一个常量表达式
2.2 变量及其初始化
- if/switch 变量声明强化
if 和 switch 中无法声明一个变量
// C++ 17 中是允许的
if ( const auto itr = std::find( vec.begin (), vec.end() , 3); 3 itr != vec . end () ) {
* itr = 4;
}
- 初始化列表
std::initializer_list
内置类型可以直接使用;
class MagicFoo {
public:
vector<int> m_vec;
// 初始化列表构造函数
MagicFoo(initializer_list<int> list) {
for (auto it = list.begin(); it != list.end(); ++it) {
m_vec.emplace_back(*it);
}
}
};
int main()
{
MagicFoo magicFoo = { 1,2,3,4,5 };
MagicFoo magicFoo2{ 1,2,3,4,5 };
int data{10};
return 0;
}
- 结构化绑定
std::tuple
// C++17的支持
#include <tuple>
std::tuple<int, double, std::string> f() {
return std::make_tuple(1, 2.0, "test");
}
int main()
{
auto [x, y, z] = f();
cout << x << "-" << y << "-" << z << endl;
return 0;
}
2.3 类型推导
- auto
auto 作为静态类型推导,不可以作为函数的参数
// 在函数的para 有默认值时可以使用
int test(auto data = 0) {
return data;
}
test(10);
// auto 还不能用于推导数组类型
auto arr = new auto (10);
auto data[20] = arr;
- decltype 推导表达式的类型
auto x = 10;
decltype(x) y=20;
- 尾返回类型推导
//
template < typename R , typename T , typename U >
R add ( T x , U y )
reurn x+y;
/*
这样的写法并不能通过编译。这是因为在编译器读到 decltype(x+y) 时,x 和 y 尚未被定
义
*/
decltype ( x + y ) add(T x, U y )
// C++14 开始是可以直接让普通函数具备返回值推导
// 尾返回类型(trailing return type)
template <typename T , typename U >
auto add ( T x , U y ) -> decltype(x+y)
reurn x+y;
- decltype(auto)
decltype(auto) 主要用于对转发函数或封装的返回类型进行推导,无需显示的指定decltype的参数
string getString();
decltype(auto) get_string_test()
return getString();
2.4 控制流
- if constexpr
template <typename T>
auto print_type_info(const T& t) {
if constexpr (is_integral_v<T>) {
return t + 1;
} else {
return t + 0.01;
}
}
int main()
{
cout << print_type_info(1) << endl; // 2
cout << print_type_info(1.0) << endl; // 1.01
return 0;
}
- 区间for迭代
vector<string> t;
for(string temp : t);
for(auto temp : t);
2.5 模板
C++ 模板能够将一切能够在编译器处理的事情在编译器处理,并且模板和泛型编程息息相关
- 外部模板
模板在被使用的才会被编译器实例化,项目中每次使用到的模板在编译器被实例化,会造成代码的编译时间增加,
C++11 引入外部模板,可以显示的通知编译器进行模板的实例化
template class vector<bool>; // 强行实例化
extern template class vector<double>; // 不在当前编译文件中实例化模板
- 尖括号 “>”
位运算符>>
前期模板中的 vector<vector > 最后的>> 是需要程序员自己增加空格
C11 之后vector<vector> 变得合法化了
- 类型别名模板
// typedef 和 using
using callback = std::function<void(string,int)>;
typedef void(*callback)(string,int);
- 默认模板参数
template <class T = int, class TT = float>
auto add(T x, TT y) ->decltype(x+y)
return x+y;
- 变长参数模板
template < typename Require , typename ... Args >
class Magic {
public:
Magic(Args ...args){
std :: cout << sizeof ...( args ) << std :: endl ;
}
}
5.1 递归模板函数
template <typename T >
void printf(T value) {
std::cout << value << std::endl;
}
template < typename T, typename ... Args >
void printf(T value, Args ... args) {
std::cout << value << std::endl;
printf(args ...);
}
int main()
{
printf(1, 2, "123 ", 1.1);
return 0;
}
// C17
template < typename T0 , typename ... T >
void printf ( T0 t0 , T ... t ) {
std :: cout << t0 << std :: endl ;
if constexpr ( sizeof ...( t ) > 0) printf ( t ...) ;
}
5.2 初始化列表展开
template < typename T , typename ... Arg >
void printf ( T t , Arg... args ) {
std :: cout << value << std :: endl ;
return std :: initializer_list <T >{([&] {
std :: cout << args << std :: endl ;
}() , value ) ...};
}
print (1 , 2.1 , "123 ");
5.3 折叠表达式
// c17
template < typename ... T >
auto sum ( T ... t ) {
return ( t + ...) ;
}
sum (1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10)
2.6 面向对象
- 委托构造
// 委托构造 简化代码
class Base {
public:
int m_val;
int m_data;
Base() :m_val(m_val) {
}
Base(int data) : Base() {
m_data = data;
}
Base(int val, int data) : Base(data) {
m_val = val;
}
};
- 继承构造
// 继承构造
class Base {
public:
int m_val;
std::string m_data;
Base() {
m_val = 1;
}
Base(std::string data) :Base() {
m_data = data;
}
};
class Subclass :public Base {
public:
using Base::Base;
};
- 显式虚函数重载 override final virtual
struct Base {
virtual void foo();
};
struct SubClass : Base {
virtual void foo() override;
};
struct BaseFinal {
// 禁止被重载
virtual void foo() final;
};
// 禁止被继承
struct SubClassFinal final : BaseFinal {
};
- 显式禁用默认函数
class Magic {
public:
Magic() = default;
Magic& operator=(const Magic&) = delete;
Magic(Magic&) = default;
Magic(Magic&&) = default;
~Magic() = default;
};
- 强类型枚举
enum class new_enum : unsigned int {
value1,
value2,
value3 = 100,
value4 = 200
};
template <typename T>
std::ostream& operator<<(typename std::enable_if<std::is_enum<T>::value,
std::ostream>::type& stream, const T& e) {
return stream << static_cast<typename std::underlying_type<T>::type>(e);
}
int main()
{
cout << new_enum::value4 << endl;
return 0;
}
3 语言运行期的强化
3.1 Lambda表达式
捕获列表 mutable(可选)->返回值类型{ 函数体};
捕获列表 值捕获= 引用捕获& 变量捕获 空捕获列表
C14 表达式捕获
auto important = std::make_unique<int>(1);
auto add = [v1 = 1, v2 = std::move(important)](int x, int y)->int{
return x + y + v1 + (*v2);
};
cout << add(3, 4) << endl;
泛型 Lambda(C14)
auto add = [](auto x,auto y){ return x+y; }
3.2 函数对象包装器
lambda 表达式的本质是一个和函数对象类型相似的类类型的对象,当捕获列表为空的时候还可以转换为函数指针进行传递
- std::function
// C++11 std::function 是一种通用、多态的函数封装
using foo = std::function<void(int)>; // using foo = void(int);
void functional(foo f) {
return f(1);
}
int main()
{
auto f = [](int value) {
cout << value << endl;
};
functional(f);
f(1);
return 0;
}
- std::bind/std::placeholder
int foo(int a, int b) {
return a + b;
}
int main()
{
auto bindFoo = std::bind(foo, std::placeholders::_1, 2);
cout << bindFoo(1) << endl;
return 0;
}
- 右值引用
/*
左值 右值的纯右值 将亡值 右值
左值
表达式后依然存在的持久对象
右值
表达式结束后不存在的临时对象
纯右值
字面量和匿名的临时对象 lambda表达式都是纯右值
将亡值
即将被销毁,却能够被移动的值
*/
void reference(string& str) {
cout << "左值" << endl;
}
void reference(string&& str) {
cout << "右值" << endl;
}
int main()
{
string lv1 = "string";
string&& rv1 = std::move(lv1);
const string& lv2 = lv1 + lv1;
string&& rv2 = lv1 + lv1;
reference(rv2); // 左值
return 0;
}
4.移动语义
class A {
public:
int* m_ptr;
A() :m_ptr(new int(1)) {
cout << "构造" << " " << m_ptr << endl;
}
A(A& a) :m_ptr(new int(*a.m_ptr)) {
cout << "拷贝" << " " << m_ptr << endl;
}
A(A&& a) :m_ptr(a.m_ptr) {
cout << "移动" << " " << m_ptr << endl;
}
~A() {
cout << "析构" << " " << m_ptr << endl;
}
private:
};
// 防止编译器优化
A return_rvalue(bool test) {
A a, b;
if (test)
return a;
return b;
}
int main()
{
A obj = return_rvalue(false);
cout << obj.m_ptr << endl;
cout << *obj.m_ptr << endl;
return 0;
}
/*
* 首先会在 return_rvalue 内部构造两个 A 对象,于是获得两个构造函数的输出;
* 函数返回后,产生一个将亡值,被 A 的移动构造(A(A&&))引用,从而延长生命周期,并将这个右值
* 中的指针拿到,保存到了 obj 中,而将亡值的指针被设置为 nullptr,防止了这块内存区域被销毁
*/
5.完美转发
完美转发就是在传递参数的时候保持原来的参数类型
void reference(int& v) {
cout << "左值" << endl;
}
void reference(int&& v) {
cout << "右值" << endl;
}
template <typename T>
void pass(T&& v) {
reference(v);
reference(std::move(v));
reference(std::forward<T>(v));
cout << endl << endl;
}
int main()
{
pass(1);
int v = 10;
pass(v);
return 0;
}
4.容器
4.1 std::array 和 std::forward_list
引入array和forward_list 能够让代码变得更现代化,并且使代码更加安全,同时还能够友好的使用标准库中的容器算法, 使用array 的时候要兼顾C风格的接口
forward_list 底层是由单向链表进行实现;不提供size接口;
4.2 无序容器
有序容器map/set* 这些元素内部通过红黑树进行实现,
无序容器中的元素是不进行排序的,内部通过Hash 表实现,在不关心容器内部元素顺序时,能够获得显著的性能提升
unordered_map/unordered_mutimap/unordered_set/unordered_multiset
4.3 元组 tuple
元组的三个核心的函数
1.make_tuple: 构造元组
2.get : 获得元组某个位置的值
3.tie: 元组拆包
#include <tuple>
auto getStudent(int id) {
if (id == 0) {
return std::make_tuple(3.8, 'A', "张三");
}
if (id == 1) {
return std::make_tuple(4.8, 'B', "李四");
}
return std::make_tuple(0.0, 'N', "null");
};
int main()
{
auto student = getStudent(0);
cout << "data: "
<< " GPA: " << get<0>(student)
<< " 成绩: " << get<1>(student)
<< " 姓名: " << get<1>(student)
<< endl;
double gpa;
char grade;
string name;
std::tie(gpa, grade, name) = getStudent(1);
cout << "data: "
<< " GPA: " << gpa
<< " 成绩: " << grade
<< " 姓名: " << name
<< endl;
return 0;
}
// 合并元组
auto newTuple = tuple_cat(getStudent(1),move(t));
// 遍历元组
template <typename T>
auto tupleLen(T &tpl){
return std::tuple_size<T>::value;
}
for(int i=0;i!=tupleLen(newTuple);++i){
cout << tuple_index(i,newTuple)<<endl;
}
5.指针
5.1 RAII 与引用计数
- 引用计数是为了防止内存泄漏,
- 在构造函数中申请内存,在析构函数中释放,也就是RAII 资源获取即初始化
- C11引入智能指针,使用引用计数的想法,让程序中不在需要关注手动释放内存
5.2 shared_ptr
shared_ptr 内部使用引用计数,当引用计数为0的时候,就会将对象自动删除
// 初始化的两种方式
std::shared_ptrstd::string s1(new string(“123”));
std::shared_ptrstd::string s2 = std::make_sharedstd::string(“1234”);
// 获取对象的原始指针
string* origin = s2.get();
// 减少引用计数
s2.reset();
// 获取对象被引用的个数
s2.use_count();
5.3 unique_ptr
//std::unique_ptr是一种独占的智能指针,禁止其他智能指针与其共享同一个对象,从而保证代码的安全
std::unique_ptr<int> p = std::make_unique<int>(10); // c14
// 移动权限
auto data = std::move(p);
// make_unique_ptr 实现机制
template <typename T, typename ...Args>
std::unique_ptr<T> my_make_unique(Args&& ...args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
5.4 weak_ptr
// shared_ptr 循环引用问题
// 循环引用
struct A;
struct B;
struct B {
using Ptr = std::shared_ptr<B>;
A::Ptr _ptr;
~B() {
cout << "~B()" << endl;
}
};
struct A {
using Ptr = std::shared_ptr<A>;
B::Ptr _ptr;
~A() {
cout << "~A()" << endl;
}
};
int main()
{
A::Ptr a = std::make_shared<A>();
B::Ptr b = std::make_shared<B>();
a->_ptr = b;
b->_ptr = a;
return 0;
}
// 解决这个问题的办法就是使用弱引用指针std::weak_ptr,弱引用不会引起引用计数增加
6.正则表达式
6.1 正则表达式简介
普通字符
特殊字符
限定符
6.2 std::regex 及其相关
7.线程与并发
7.1 thread
#include <thread>
void foo() {
cout << "test thread" << endl;
}
int main()
{
std::thread t(foo);
t.join();
return 0;
}
7.2 std::mutex, std::unique_lock
#include <thread>
#include <mutex>
#define LOCK_GURAD(x) std::lock_guard<decltype<x>> lk(x)
// 发生异常 锁没有被释放
void someOperator(const string& message) {
static std::mutex mtx;
LOCK_GURAD(mtx);
}
// unique_lock
std::mutex mtx;
void block_area() {
std::unique_lock<std::mutex> lock(mtx);
lock.lock();
lock.unlock();
}
int main()
{
std::thread t(block_area);
t.join();
return 0;
}
7.3 std::future, std::packaged_task
/*
* std::future 提供了一个访问异步操作结果的途径;
* 问题: 试想我们的主线程A 希望开辟一个线程B 去执行某个我们预期的任务,并返回一个结果
* 解决方案: 线程A 中启动任务B,执行完成后发送一个事件执行结果保存在某个全局变量中,当线程A
* 想要执行结果时,调用一个线程等待的函数获取结果
*
* std::future简化此流程,可以用来获取异步任务的结果,是一种线程同步的方法
* std::packaged_task 可以用来封装任何可以调用的目标,从而实现异步调用
*
*/
int main()
{
std::packaged_task<int()> task([]() {return 7; });
std::future<int> result = task.get_future();
std::thread(std::move(task)).detach();
std::cout << "waiting...";
result.wait();
cout << "Done :" << result.get() << endl;
return 0;
}
7.4 std::condition_variable
#include <thread>
#include <mutex>
#include <future>
#include <condition_variable>
#include <queue>
#include <chrono>
/*
std::condition_variable 是为了解决死锁而生的,当互斥锁的引入而不够实现场景功能时
问题: 线程A 可能等待某个条件为真才能继续执行,而一个忙等待循环中可能会导致其他所有线程都
无法进入临界区使条件为真就会发送死锁,
解决方法: condition_variable实例被创建出现主要就是用于唤醒等待线程从而避免死锁
std::condition_variable的notify_one和notify_all 就是为了解决此问题
*/
//
int main()
{
// 生产者的数量
std::queue<int> producedNums;
std::mutex m;
std::condition_variable condVar;
// 结束标志
bool done = false;
// 通知标志
bool notified = false;
// 生产者线程
std::thread producer([&] {
for (size_t i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::unique_lock<std::mutex> lk(m);
cout << "producing " << i << endl;
producedNums.emplace(i);
notified = true;
condVar.notify_one();
}
done = true;
notified = true;
condVar.notify_one();
});
// 消费者线程
std::thread consumer([&]() {
std::unique_lock<std::mutex> lk(m);
while (!done) {
while (!notified) {
// 循环避免虚假唤醒
condVar.wait(lk);
}
while (!producedNums.empty()) {
cout << " cosuming " << producedNums.front() << endl;
producedNums.pop();
}
notified = false;
}
});
producer.join();
consumer.join();
return 0;
}
8. 文件系统
8.1 std::filesystem
9. 其他
9.1 新类型
cout << sizeof(long long int) << endl; // 8
9.2 noexcept 的修饰和操作
// noexcept 修饰完一个函数之后能够起到封锁异常扩散的功效,如果内部产生异常,外部也不会触发
void divid(int a,int b) {
if (b == 0)
throw std::invalid_argument("0");
}
void test() noexcept {
return;
}
9.3 字面量
- 原始字符串字面量 去除转义限制
std::string str = R"(C\User\Desktop)"; - 自定义字面量
/*
自定义字面量支持四种字面量
* 整形字面量:重载时必须使用 unsigned long long、const char *、模板字面量算符参数
* 浮点型字面量:重载时必须使用 long double、const char *、模板字面量算符
* 字符串字面量:必须使用 (const char *, size_t) 形式的参数表
* 字符字面量: 参数只能是 char, wchar_t, char16_t, char32_t 这几种类型
*/
std::string operator"" _wow1(const char* wow1, size_t len) {
return std::string(wow1) + "wooooooow, amazing";
}
std::string operator"" _wow2(unsigned long long i) {
return std::to_string(i) + "wooooooow, amazing";
}
int main()
{
auto str = "abc"_wow1;
auto num = 1_wow2;
cout << str << endl;
cout << num << endl;
return 0;
}