C++11语言特性梳理

关于C++11

C++11 is the second major version of C++ and the most important update since C++98. A large number of changes were introduced to both standardize existing practices and improve the abstractions available to the C++ programmers.

Before it was finally approved by ISO on 12 August 2011, the name ‘C++0x’ was used because it was expected to be published before 2010. It took 8 years between C++03 and C++11, so it has become the longest interval between versions so far. Since then, currently, C++ updates every 3 years regularly.

本文大纲

  • auto
  • decltype
  • final and override
  • trailing return type
  • rvalue reference
  • Move constructor
  • Move assign constructor
  • constexpr
  • brace-or-equal initializers
  • nullptr
  • long long
  • delegating and inherited constructors
  • using
  • variadic templates
  • lambda

Core Language Features

1. auto

auto: placeholder type specifiers 占位限定符
作用为:在初始化的时候进行类型推导, 但是其遵循模板参数推导
因此在推导的类似的时候只是基本类型,所以需要const 或者 & 等modier一起参与推导。

auto x = expr;
auto& x = expr;
const auto& x = expr

注意: auto有一点与template模板推导不同

auto x = {1,2,3};  

这个auto会进行二次推导,首先推导成initiallizer_list, 然后在对initialize的T进行推导。而模板无法对{}的表达式进行直接推导

关于返回值推导: C++11不支持返回值推导

auto func(auto a, auto b)

这个在C++11是不被允许的,auto作为参数和返回值不能进行推导
但是注意:在C++14里,该语句将被允许

2. decltype

跟auto不同,auto是由通过初始化的值进行类型推导,decltype则通过entity或者表达式进行推导。主要是用于在模板编程中,需要通过表达式进行类型推导。
还有一点不同就是auto是按照模板进行推导,需要搭配&,const这类modifier进行推导,但是decltype则不需要,他会根据参数类型进行推导 不同的类型

decltype(entity)
decltype(expression)

关于这个推导的参数分为两种。

  • 没有小括号
    • 如果参数表达式类型为 xvalue, 则推导的类型为T&&
    • 如果参数表达式类型为 lvalue,则推导的类型为T&
    • 如果参数表达式类型为 prvalue,则推导类型为T
  • 带有小括号
  •  带有小括号,则一概将参数看作常规的lvalue
    

3. final and override

这两个限定符一定要直接加载virt-specifier-seq的后面

final

final限定符,表示这个虚函数/或者这个类不能被派生类进行override

struct Base
{
    virtual void foo();
};
 
struct A : Base
{
    void foo() final; // Base::foo is overridden and A::foo is the final override
    void bar() final; // Error: bar cannot be final as it is non-virtual
};
 
struct B final : A // struct B is final
{
    void foo() override; // Error: foo cannot be overridden as it is final in A
};
 
struct C : B {}; // Error: B is final
override

就是表明该函数显示的重写了基类的虚函数。
加上关键字,一旦出现函数名写错,并没有对基类函数进行重载,编译器会报错。作为编程让编译器检查的手段

class A
{
public:
	virtual void print(){
		std::cout << "hello" << std::endl;
	}
};

class B final: A
{
public:
	void print(int a) override{   //error: 'void B::print(int)' marked 'override', but does not override
		std::cout << "hi "<< a << std::endl;
	}
};

4 trailing return type

尾置返回类型(trailing return type) 跟在形参后面并以 -> 符号开头,
用来解决返回类型依赖于参数的问题。

cpp reference 给出了两个例子

//由于是模板传参,返回值不定,因此需要进行推导, 尾后置类型->来简化
template<class T, class U> auto add(T t, U u) -> decltype(t + u);
// 简化返回类型
auto fpif(int)->int(*)(int)

5 右值引用

Rvalue References: 右值引用是用来扩展临时变量的生命周期
(const的左值引用也可以扩展临时变量的声明周期)

&& attr(optional) declarator

这里对比左值引用:
Lvalue References: 左值引用是为了对现存对象进行一个别名操作。
注意,只要是引用就一定要进行初始化

#include <iostream>
#include <string>
 
int main() {
    std::string s1 = "Test";
//  std::string&& r1 = s1;           // error: can't bind to lvalue
 
    const std::string& r2 = s1 + s1; // okay: lvalue reference to const extends lifetime
//  r2 += "Test";                    // error: can't modify through reference to const
 
    std::string&& r3 = s1 + s1;      // okay: rvalue reference extends lifetime
    r3 += "Test";                    // okay: can modify through reference to non-const
    std::cout << r3 << '\n';
}

附:
简单介绍: 左值,纯右值,将亡值
左值(lvalue):可以取地址的,有名字的变量
纯右值(prvalue):字面量,无名字的临时变量
将亡值(xvalue):其实属于 泛左值 和 右值 两个概念。
用左值去初始化对象或为对象赋值时,调用拷贝构造函数或赋值构造函数。
用右值去初始化对象或赋值时,调用的是移动构造函数和移动赋值值函数。
将亡值可以理解为通过移动构造其他变量内存空间的方式的值。

6 Move constructors 移动构造

//1) Typical declaration of a move constructor.
class-name ( class-name && );  
// 2) Forcing a move constructor to be generated by the compiler.
class-name ( class-name && ) = default;
class-name ( class-name && ) = delete;

与拷贝构造函数,传参为&&右值引用,移动构造一般是用右值(xvalue or prvalue)进行初始化的时候进行调用。移动构造函数会直接使用临时变量的资源,从而延长其生命周期,减少进行拷贝的开销。
三种初始化的方法
1 初始化: T a = std::move(b);
2 函数传参: f(std::move(a))
3 函数返回: T() 函数中 return a; 但a存在移动构造函数的时候,会进行调用

7. Move assign operator

移动赋值操作

class-name & class-name :: operator= ( class-name && )

可以发现与移动构造相同,都是重载操作符=,移动赋值与拷贝赋值的关系即是参数为右值引用进行传参,从而减少拷贝时间。

8. constexpr specifier

constexpr 关键字用来声明 函数或者变量可以在编译期进行执行

  1. constexpr修饰变量,则表明该变量为const,与const关键字相同
  2. constexpr修饰函数,则表明该函数为inline,同时在C++11标准中,该函数传参和返回值必须为字面量,而且C++11中constexpr修饰的函数只能使用递归而非循环,C++14中可以使用循环但是必须使用局部变量。
    关于使用costexpr: 来看一个例子
//由于constexpr修饰,则复杂度只为O(n),相当于迭代计算,在编译过程中会进行将数据进行存储,但递归复杂度过高时,会在编译耗时较长,也有可能出现栈溢出
constexpr long int fib1(int n) { 
	return (n <= 1)? n : fib1(n-1) + fib1(n-2); //只能包含一个retrun语句
} 
//在运行时进行递归,复杂度为O(2^n)
long int fib2(int n){
        return (n <= 1)? n : fib2(n-1) + fib2(n-2); 
}

9 brace-or-equal initializers

C++11统一了初始化,列表初始化即用大括号{ }进行初始化,分为直接初始化 和 列表拷贝初始化

// 列表初始化
T object{arg1, arg2, ...}
// 拷贝列表初始化
T object = {arg1, arg2, ....}

10. nullptr

nullptr类型用来规范NULL 这个宏定义。但使用空指针的时候,尽量使用nullptr.
NULL在有些编译器直接 #define NULL 0, 有些是#define NULL (void*) 0
在重载的时候,会造成混乱,比如下面的例子

void print(char* ptr){
   std::cout << "char* parser" << std::endl;
}

void print(int val){
   std::cout << "int parser" << std::endl;
}

上述代码但调用
print(NULL)的时候,会出现混乱的重载。我在g++ 8.1, c++14中测试发现该语句会直接保持,显示error: call of overloaded ‘print(NULL)’ is ambiguous

11. long long

C++ 11增加了longlong的表示类型,定义64bit 宽的数据类型。

12. 委托构造和继承构造 delegating and inherited constructors

委托构造:
比如有下面的例子:

class Foo
{
public: 
    Foo(char x, int y) {}
    Foo(int y) : Foo('a', y) {} // Foo(int) delegates to Foo(char, int)
};

F(int y) 要先调用Foo(char x, int y), 用列表初始化的方式减少重复代码。

继承构造:
如果使用了 using Base:Base 这句,那么所以基类的构造函数都会被在派生类重载
If the using-declaration refers to a constructor of a direct base of the class being defined (e.g. using Base::Base;), all constructors of that base (ignoring member access) are made visible to overload resolution when initializing the derived class.

class Base
{
public:
    Base(int i) :m_i(i) {}
    Base(int i, double j) :m_i(i), m_j(j) {}
    Base(int i, double j, string k) :m_i(i), m_j(j), m_k(k) {}
    ...
};

class Child : public Base
{
public:
    using Base::Base; // 将会复用基类所有的构造函数
};
13. 类型别名,using

C++11增加了用using表示别名的方法,本质上与typedef 关键字非常像,但是using支持定义模板的别名,而typedef则不支持。对于复杂的名称,using会比typedef更简洁。
函数指针

// 为函数指针取别名
typedef void(*fun)(int x, int y) //定义了一个函数指针,别名为fun
using fun = void(*)(int x, int y) //using 表示
// 为模板取别名
template<typename T>
using m = map<int, T> 
14. 变长模板

变长模板是由打包参数进行实现,
符号为"…"表示

15. lambda 表达式

在C++11中,lambda的规范如下。

[ captures ] ( params ) specs requires(optional) { body }

其中[captures] 为捕获列表,params为参数列表,opt为函数选项

lambda capture
capture列表中可以包括零个或者多个外部的捕获变量,capture list定义外部变量可以被匿名函数体内所访问

  • & (隐式的捕获外部所有变量的引用, 引用传递)
  • = (隐式的捕获外部所有变量的值, 值传递)

关于其他捕获形式,可在cppreference中查询https://en.cppreference.com/w/cpp/language/lambda

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值