C++
C++
目录
C++新标准C++11/14
新标准简概
简概
- 学习参考
- 【B站】C++新标准-C++11/14,侯捷老师的课(侯捷C++系列课程,多人推荐)(主要参考) 【CSDN】别人的听课笔记 (挺深入,但说话讲得有点不清晰,有时我真的听不清他说的什么,像在学校听课一样,如果有字幕就好了)
- 【C语言中文网】C++11(主要参考)
- 《高速上手C++11 14 17.pdf》
- 网站参考
- 了解编译器对C++2.0的支持(百度或谷歌:Compiler support for C++11 and C++14)
- CppReference.com
- gcc.gnu.org
- 《The C++ Standard Library》Apr9,2012
- 《C++ Primer》Aug 16,2012
- 《The C++ Programming Language》May 20,2013
- 《Effective Modern C++》Nov,2014
- 工具
- Windows Grep 2.3。是全文检索工具,方便去看标准库的源代码
- C++11简概
- C++ 2.0 New Features(新特性),即C++11/14
- 从以下两个层面来学习:
- 语言
- 标准库
- C++ Standard 演化
- C++ 98(1.0)
- C++ 03(TR1,Technical Report 1)
- C++ 11(2.0)
- C++ 14
- C++ 17
- C++ 20
确认支持版本
cout << __cplusplus << endl;
// 输出:199711,表示支持 C++98 and C++03
// 输出:201103,表示支持 C++11
如果输出过低,可能需要在编译器平台中打开一个Compiler的设置中,设置支持新版本的C++特性
数据
空指针 nullptr and std::nullptr_t
C++11 lets you use nullptr instead of 0 or NULL.
不会出现以下歧义
// 函数重载
void f(int);
void f(void*);
// 调用
f(O); // calls f(int)
f(NULL); // calls f(int) if NULL is 0, ambiguous otherwise【歧义】
f(nullptr); // calls f(void*)
nullptr is a new keyword. 而 nullptr_t是一个常量,用于不支持nullptr新关键字的程序
4.9.2\includel\stddefh
#if defined(_cplusplus)8&_cplusplus >= 201103L
#ifndef _GXX_NULLPTR_T
#define _6XXX_NULLPTR_T
typedef decltype(nullptr) nullptr_t;
自动类型推导 Automatic Type Deduction with auto
不同于在C语言中的auto
,那个表示的是局部变量
auto是编译时类型推导,和 js 的 var/let 或 python 的不标类型不同
auto i = 42;
double f();
auto d = f();
Using auto is especially useful where the type is a pretty long and/or complicatedexpression.
即主要用于类型很长很复杂的情况下,如容器迭代器和lambda函数,不要滥用
vector<string> v;
// ...
auto pos = v.begin();
auto l = [](int x) -> bool { // 返回bool类型的lambda表达式
// ...
};
类型推导 decltype 新关键字
作用
decltype
实现了typeof
语法,可以推断出表达式的类型.
// 新写法
map<string, float> coll;
decltype(coll)::value_type elem; // elem的类型能随着coll而变,修改程序时只需修改一处
// 旧写法
map<string, float> coll;
map<string, float>::value_type elem;
应用
常用于声明返回值类型、元编程和代指lambda函数的类型上
template <typename T1, typename T2>
decltype(x+y) add(Tl x, T2 y); // error: 'x' and 'y' was not declared in this scope
从语法上来说,上述程序是错误的,因为变量x
和y
在函数外访问不到,因此需要使用C++11声明返回值类型的新语法:返回类型后置
返回类型后置
template <typename T1, typename T2>
auto add(Tl x, T2 y) -> decltype(x+y);
补充:Lambda表达式中的写法就用到了这个返回类型后置。且对于 lambda,返回类型必须后置
后置返回类型杂谈:
参考:https://www.zhihu.com/question/334039589/answer/763159374
后置类型语法优于前置类型语法
模板中的应用
该语法进一步增强了模板语法的灵活性
例程1
template <typename T>
void test_decltype(T obj) {
map<string, float>::value_type elem1;
typedef typename decltype(0bj)::iterator iType; // decltype
typedef typename T::iterator iType;
decltype(obj) anotherObj(obj); // decltype
}
例程2
auto cmp = [](const Person& p1, const Person& p2){
return pl.lastname()<p2.lastname() ||
(p1.lastname()—p2.lastname() &&
pl.firstname() p2.firstname());
};
// ...
std::set<Person,decltype(cmp)> coll(cmp); // decltype
交互
(无)
优化
for 基于范围循环 range-based for statement
for (decl:coll) {
statement
}
代码举例:
for(Type VarName : Array){
//每个元素的值会依次赋给 VarName
}
for (std::vector<int>::const_iterator iter = ints.begin(); iter != ints.end(); ++iter) // 通用for
for(auto iter:ints){}; // C++11
for_each(ints.begin(), ints.end(), [](auto iter)->void{/**/}); // STL方法(函数模板)
volatile 禁止reorder
C++11之后的跨平台实现(volatile关键字):原理主要是让编译器不reorder
封装
Lambda 匿名函数
基本用法
lambda函数既可以用作变量,也可以立即执行
// Lambda对象
[] {
std::cout << "hello lambda" << std::endl;
};
// 直接执行
[] {
std::cout << "hello lambda" << std::endl;
}();
// 用作变量
auto l = [] {
std::cout << "hello lambda" << std::endl;
};
l();
原型
[]{}
本质是lambda对象- 原型:
- 参数: ()、mutable、throwSpec、retType 都是可选的
[]
:是定义时传参[=]
表示使用值传递变量,里面直接写变量名也是[&]
表示使用引用传递变量
()
:调用时传参mutable
:使[]
传入的变量可以被修改(值传递时)throwSpec
:???->retType
:返回值类型
底层原理 - mutable原理
编译等价代码
auto f = [id]() mutable {
std::cout << "id:" << id << std::endl;
++d;
};
// 会编译等价于以下代码
class Functor { // 类的定义只有一次
private:
int id;
public:
void operator()(){
std::cout << "id:" << id << std::endl;
++d;
}
};
Functor f;
// 或用函数来理解(Lambda不是这个原理!仅作理解参考!)
function(int id){
const m_id = id; // 静态变量只会初始化一次
//...
}
坑,定义时传参
int id = 0;
// 注意这里如果不加mutable会报错,说变量是只读的
auto f = [id]() mutable { // 这里已经传入了id参数
std::cout << "id:" << id << std::endl;
++d;
};
id = 42;
f(); // id:0
f(); // id:1
f(); // id:2
std::cout << "id:" << id << std::endl; // id:42
底层原理 - 引用传参
引用传参
int id = 0;
auto f = [&id](int param) { // 定义时引用传参
std::cout << "id:" << id << std::endl;
++d; ++param;
};
id = 42;
f(7); // id:42
f(7); // id:43
f(7); // id:44
std::cout << "id:" << id << std::endl; // id:45
Qt的坑
// 错误写法
int count = 0; // 如果这里被销毁了,Lambda内部的指针变为悬垂指针,打印出来的值会是错的
connect(button, &QAction::triggered, [&count]{
qDebug()<<"qDebug: " << ++count; // -858993460、-858993459、...
});
// 正确写法(mutable原理)
int count = 0;
connect(button, &QAction::triggered, [count]()mutable{
qDebug()<<"qDebug: " << ++count; // 1、2、3、...
});
模板中的使用
(视频没太听懂)
auto cmp = [](const Person& p1,const Person& p2){ // 比较大小
return p1.lastname() < p2.lastname() ||
(p1.lastname() == p2.lastname() &&
p1.firstname() < p2.firstname());
};
// ...
std::set<Person,decltype(cmp)> coll(cmp); // 模板使用。用了下decltype类型推导得到Lambda对象类型
lambda函数充当predict判定
vector<int> vi{5, 28, 50, 83, 70, 590, 245, 59, 24};
int x = 30;
int y = 100;
vi.erase(remove_if(vi.begin(), vi.end(),
[x, y](int n) { return x < n && n < y; }) // 如果数字在30~100之间就去掉。这里的n是erase传进去的
vi.end()
);
for(auto i:vi)
cout << i << ' '; // 输出:5 28 590 245 24
cout << endl;
其他
- 比较
- 函数指针、函数符、Lambda函数:
- (略)
- 补充
- Lambda 函数的返回值必须后置
错误记录 - 运行时崩溃
调试模式下报错:
The inferior stopped because it triggered an exception.
Stopped in thread 0 by: Exception at 0x7ffd9b7f9aeb, code: 0xc0000005: read access violation at: 0x28, flags=0x0 (first chance).
次等的停止是因为它触发了一个异常。
在线程0中被:Exception at 0x7ffd9b7f9aeb停止,code: 0xc0000005:读访问冲突在:0x28, flags=0x0(第一次机会)。
搜索得知是指针问题,然后发现是在lambda中用引用的方式传递了一个指针参数
QWebEngineView *view = new QWebEngineView(wid);
connect(btn, &QPushButton::clicked, this, [&view](){};
// 应该改为
connect(btn, &QPushButton::clicked, this, [view](){};
// 主要是我的view本来不是指针而是对象,后来改成指针后忘记该lmabda函数了
noexcept 保证无异常
(视频没太听懂)
保证函数不会出异常
void foo() noexcept; // 保证不出异常
void swap(Type& x, Type& y) noexcept(noexcept(x.swap(y))){} // 条件不出异常
面向对象
=default,=delete
(视频没太听懂)
如果你自行定义了一个ctor,那么编译器就不会再给你一个 default ctor
=default
使得编译给类加上默认的构造函数、析构函数、拷贝构造函数、拷贝赋值函数、移动构造函数等.
在C++11之前,我们都是手动给类添加空的构造函数等函数,但是这样手动添加的函数与编译器生成的默认构造函数是不同的,一个影响就是使类不再是POD类型,减少了编译器对其优化的可能性.
如果你强制加上
=default
,就可以重新获得并使用default ctor
class Zoo
{
public:
Zoo(int i1, int i2) : d1(il), d2(i2){ }
Zoo(const Zoo&) = delete; // 拷贝构造函数 删除
Zoo(Zoo&&) = default; // 右值引用 默认
Zoo& operator=(const Zoo&)= default; // 赋值构造函数 默认
Zoo& operator=(const Zoo&&)= delete; // 移动构造函数 删除
virtual ~Zoo(){}
private:
int d1, d2;
};
QA
complex<T>
(复数)有所谓 Big-Three 吗- 没有
operator=(const complex<...>&)
- 没有
~complex()
- 没有
string
有所谓 Big-Three吗- 有,而且有Bug-Five
=delete
表示删除该函数,使得该类不具有对应的构造、析构、拷贝构造、拷贝赋值、析构等功能
explicit 针对一个以上参数的构造函数
(这个讲得有点不是很清晰)
explicit
for ctors taking more than one argument.在以前这个关键字是只能支持一个参数的构造函数的
class P{
public :
P(int a, int b){
cout << "P(int a,in t b) in";
}
P(initializer_list<int>) {
cout << "P(initializer__list<int> ) in";
}
explicit P(int a, int b, int c) {
cout <<"explicit P(int a,int b,int c) in";
}
};
void fp(const P&){ };
作用
struct Complex
{
int real, imag;
Complex(int re, int im=0) : real(re), imag(im){}
Complex operator+(const Complex& x){
return Complex((real + x.real),
(imag + x.imag));
};
}
Complex cl(12,5);
Complex c2 = cl + 5; // 此时前者可能发生类型转换,Complex -> int
/* explicit Complex(int re, int im=0) : real(re), imag(im){}
* 则不会出现以上情况,而会直接报错
*/
override 复写
复写、改写,应用在虚函数中
应用举例:
struct Base {
virtual void vfunc(float){}
};
struct Derived1:Base{
virtual void vfunc(int){} // 此处不加override不会报错,编译器以为你要重写定义一个虚函数而不是重写
};
struct Derived2:Base{
virtual void vfunc(int) override{} // 此处会报错,编译器认为你要重写,但找不到对应的基类方法
};
final 防止继承或复写
作用1:不允许类被继承
struct Basel final{}; // 不让别人继承自己 struct Derived1:Base1 {}; // 此时会报错
作用2:不允许类方法被复写
struct Base2{ virtual void f() final; // 不允许被复写 }; struct Derived2:Base2{ void f(); }
泛型
模板表达式的空格 Spaces in Template Expressions
旧版本可能是为了避免和>>
运算符混淆,现在能区分开了
vector<list<int> >; // OK in each C++ version
vector<list<int>>; // OK since C++11
模板别名(Alias Template),using的三个作用
(视频没太听懂)
using的三个作用
- 命名空间的使用
- 子类中引用基类的成员
- 别名指定(有类似功能的还有:#define预编译、typedef)
使用
(这里主要讲的是using的第三个作用——别名)
template <typename T>
using Vec = std::vector<T, MyAlloc<T>>; // 指定别名
Vec<int> coll; // 这里表示上面两个T都是int
上述功能使用宏定义或**typedef
**都不能实现
要想使用宏定义实现该功能,从语义上来说,应该这样实现:
#define Vec<T> std::vector<T, MyAlloc<T>> // 理想情况下应该这样写,但不能通过编译 Vec<int> container;
但是define不支持以小括号定义参数,要想符合语法,需要这样写
#define Vec(T) std::vector<T, MyAlloc<T>> // 能通过编译,但是使用小括号失去了泛型的语义。typedef不能接受参数 Vec(int) container;
这样可以通过编译,但是Vec(int)这种指定泛型的方式与原生指定泛型的方式不一致.
其他补充:模板模板参数也需要通过alias template指定其它模板参数的初值(这里没懂)
template tamplate parameter 模板模板参数
(视频没太听懂)
模板模板参数也需要通过alias template指定其它模板参数的初值
高端模板技巧
template<typename T,
template <class> class Container
>
比较 typedef
using 类型别名(Type Alias)(类似typedef)
typedef void (*func)(int,int); // typedef 写法
using func = void(*) (int,int); // using 写法,更清晰
可变类型模板(此处略)
(此处略)
C++ Standard Library 新特性
其他不是特别常用但见过的函数
- std::call_once():只调用一次