跳至主要內容

C++

LincZero大约 10 分钟

C++

目录

C++新标准C++11/14

新标准简概

简概

  • 学习参考
  • 网站参考
    • 了解编译器对C++2.0的支持(百度或谷歌:Compiler support for C++11 and C++14)
    • CppReference.com
    • gcc.gnu.orgopen in new window
    • 《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

从语法上来说,上述程序是错误的,因为变量xy在函数外访问不到,因此需要使用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

参考:【博客园】C++11基于范围的for循环open in new window

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对象
  • 原型:[...](...)mutableopt throwSpecopt > retTypeopt{...}[...](...)mutable_{opt}~throwSpec_{opt}~->~retType_{opt}\{...\}
  • 参数: ()、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():只调用一次

Standard Template Library 新特性