0%

C++查缺补漏

基础语法

constexpr

C++11引入

编译时常量,编译时直接计算结果,不必把表达式带入运行时计算,所以满足条件:编译时可计算

1
2
3
constexpr int get_five() { return 5; }
int some_value[get_five() + 7];
constexpr int a = 3;

运算符优先级

  1. 作用域运算符与括号(::,->,.)
  2. 一元运算符 (++--,*,&
  3. 算数运算符
  4. 关系运算符(>,<,==,!=)
  5. 位运算符
  6. 逻辑运算符
  7. 赋值运算符

结合方向:多数运算符都是自左向右,自右向左的有:一元运算符、条件运算符、以及赋值运算符

赋值运算符的返回值

即为所附的值

1
2
if((a=3) == 3){}	//true;
int b = a = 3; b = 3

函数重载匹配的顺序

1.实参形参类型相同、数组类型和指针类型之间的转换

2.const转换实现的匹配。

3.通过类型提升实现的匹配。

4.算数类型转换或者指针转换实现的匹配。

5.类类型转换实现的匹配

返回参数尾置返回类型

C++11引入

传统方式:int (*func(int i))[10] 表示返回一个指针,该指针指向含有十个整数的数组

返回参数尾置:auto func(int i)->int(*)[10]

命名空间别名

1
namespace Long = AVeryVeryLongName::AnotherVeryVeryLongNameForTest;

匿名命名空间

匿名命名空间的内容只能在本文件使用,和static的作用相似,但是看起来更加简洁

1
2
3
4
5
6
7
namespace { // 没有名字
int i = 0;
}
int main()
{
std::cout << i << std::endl; //不需要添加name::
}

浮点数转整数

直接去掉小数点后面的数值,9.99 = int 9,9.34 = int 9

数值溢出,可能出现未定义的行为

面向对象特性

explicit、default、delete

  • default:要求编译器生成默认构造函数

  • explicit:拒绝对象被隐式构造(单参数的构造函数尤其需要注意),不会被继承

  • delete:删除默认构造函数避免对象不符合预期的被构造

1
2
3
4
5
6
7
class MyClass
{
public:
MyClass()=default;
MyClass(MyClass &otehr)=delete;
explicit MyClass(int i);
};

类成员访问限制

  • 友元关系不能被继承

  • 继承方式与基类成员访问权限,如下

基类成员访问属性 在派生类中的访问属性
public protected private
public public protected private
protected protected protected private
private inaccessible inaccessible inaccessible

override、final

  • override:重写基类虚函数,编译时检查
  • final:到此为止,子类不能重写此虚函数

析构函数的虚化目的

派生类通过基类指针释放,若无虚化,这调用基类析构函数,若虚化,则先调派生类,再调基类

继承但没有虚函数

dynamic_cast无法通过编译

typeid运算符会给出静态绑定结果

typeid

用途:获取类型,常用于类型比较,当用于多态类型的表达式时,运行时进行虚表查找,其他情况下 typeid 表达式都在编译时解决。

typeid(T).name() :返回T的类型名称,若存在多态,将返回真实类型(派生类)

typeid(i) == typeid(int):类型比较,用于泛型与多态

类型转换

xxx_cast <new_type> (expression)

目的

克服C语言强制类型转换的以下三个缺点

  • 没有从形式上体现转换功能和风险的不同

  • 将多态基类指针转换成派生类指针时不检查安全性(dynamic_cast)

  • 难以在程序中寻找到底什么地方进行了强制类型转换

static_cast

编译时尽可能检查

没有继承关系的指针(void* 除外)与引用不能转换

其他类型与指针不能转换

失败:编译失败

dynamic_cast

使用虚表,进行多态类型检查,主要用于父类转子类,运行时检查

失败:指针返回nullptr,引用抛出异常

const_cast

去除对象const或者volatile属性

reinterpret_cast

类似纯C风格的指针类型强转,不常见

异常

构造函数抛异常

对象构造在堆上时,如果构造函数抛出异常,那么对象的堆内存会被自动释放。

但是要注意:如果构造函数分配了其他关联的资源,则需要程序员保证资源可以被全部释放

不允许抛异常的成员函数

  • 析构函数
  • 移动构造函数
  • 移动赋值运算符
  • 交换函数(swap)

模板编程

命名修饰

定义:C++编译器后根据命名空间、类名等条件对,函数、变量等实体进行修饰(重命名),实现命名的唯一性

对于C语言兼容:

1
2
3
4
5
6
7
#ifdef __cplusplus
extern "C" {
#endif
/* ... */
#ifdef __cplusplus
}
#endif

目的:告诉链接器这是用C语言编译器编出来的,命名没修饰过,找的时候用没修饰的命名找

可变参模板

定义

1
2
3
4
5
template <typename T, typename... Args> //typename...标识可变参数
返回类型 函数名(const T& t, const Args& ...args)
{
函数体
}

参数展开

1
2
3
4
5
6
7
8
9
10
// 展开函数
template<typename Head, typename... Args>
void print(const Head& t, const Args&... args) //通过递归展开参数包,一层特化一个参数
{
std::cout << t << " ";
return print(args...);
}
// 递归终止函数
void print(){//最后一个没有参数,展开函数无法匹配,需要单独用一个终止函数,终结匹配
}

模板实例化

参数不同,编译器会在生成不同实例的函数

函数对象与函数不同,及时参数相同,也是不同实例的函数

lamda是匿名函数,每一个都会生成不同的实例函数

tuple

  • 定义

    • 多元组,固定大小,包含一组异质数据

    *

    1
    using my_tuple = tuple<int, string, string>;
  • 相关工具

    • get:获取元素
    • tuple_size_v:获取 tuple 的大小
    • tuple_cat:拼接多个 tuple

tie

  • 定义

    把变量绑定成为引用的 tuple

右值引用与移动

目的

通过实现成员的移动,避免拷贝所造成性能损耗,右值引用的目的是触发移动,C++的函数日常同名,通过参数触发不同函数,一般认为T&&的均为移动类型

左值与右值的定义

左值

  • 有标识符、可以取地址
  • 非常(non-const)左值可以放在赋值运算符的左侧
  • 常见情况
    • 变量、函数名字
    • 左值对象的成员
    • 返回左值引用的表达式,如++x、x = 1、cout << ‘ ‘
    • 字符串字面量,如”hello world”

纯右值

  • 没有标识符、不能取地址的“临时对象”
  • 不可以放在赋值运算符的左侧
  • 常见情况
    • 返回类型非引用的函数调用或运算符表达式,如x++、1 + 2
    • 除字符串字面量外的字面量,如true、42
    • lambda表达式

将亡值

  • C++11引入,和纯右值合称为“右值”
  • 不可以放在赋值运算符的左侧
  • 常见情况
    • 右值对象或数组的成员
    • 返回右值引用的表达式,如std::move(x)

std::move

std::move:输入左值,返回右值,常用于触发对象移动构造函数和移动赋值函数

1
2
std::string str = "Hello";
std::string str2 = std::move(str); //move返回一个右值引用作为string的构造函数的入参,从而触发移动构造,str2="Hello",str=""

禁止使用std::move操作const对象:退化成了对象拷贝而不是对象移动,带来了性能上的损失

不要使用std::move 返回函数局部变量:阻止了返回值优化

坍缩规则与std::forward

  • 坍缩规则:对引用类型进行引用,从而产生了引用坍缩规则

    • (T&)&->T&
    • (T&)&& ->T&
    • (T&&)& ->T&
    • (T&&)&& ->T&&
  • T&&是转发引用(当其出现在函数模板的参数或变量声明中时)

  • std::forward\(x)保持x的引用类型

智能指针

unique_ptr:指向对象地址的unique_ptr只有一个,只能支持移动,不支持拷贝。可以使用make_unique构造,但不支持类的构造

shared_ptr:引入引用计数,当引用计数为0时,释放对象。可以使用make_shared构造,优点:对象与引用值只进行一次内存分配,代码简洁

weak_ptr:解决循环指针问题

1
2
auto sp9 = std::make_shared<std::vector<int>[]>(4, {5, 6});
auto sp10 = std::make_shared<std::vector<int>[]>(new std::vector<int>(4, {5, 6}));

并发编程

atomic

operator=:store,memory_order_seq_cst

operator T:load,memory_order_seq_cst

store:写操作

load:读操作

exchange:交换

内存序:

  • memory_order_relaxed:保证原子操作
  • memory_order_acquire:禁止CPU与编译器乱序,针对读
  • memory_order_release:禁止CPU与编译器乱序,针对写
  • memory_order_acq_rel:acquire与release综合
  • memory_order_seq_cst:最严格

future

1
2
3
4
5
6
int work(){
this_thread::sleep_for(2);
return 0;
}
auto fut = async(launch::async,work); //在linux与mac上,直接启一个线程,windows,背后用了线程池
int i = fut.get(); //等待执行结束,获取返回值