C++ 11(一)

注意:C++ 2.0新特性中关键字的源码都还没看

C++2.0新特性

语言+标准库(以头文件的形式呈现)

在这里插入图片描述

了解编译器对 C++ 2.0的支持程度
还有几个重要网站,见STL()的前面

全文检索工具——grep

在这里插入图片描述

在这里插入图片描述

Dev C++ | 如何设置C++11标准

在DEV C++中选择tool
选Compiler Options

在这里插入图片描述

上图选settings

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

Variadic Templates(可变模板参数)

在C++11之前,类模板和函数模板只能含有【固定数量的模板参数】。
C++11增强了模板功能,允许模板定义中包含【0到任意个模板参数】,这就是【可变参数模板】。
可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,
【声明可变参数模板】时需要在typenameclass后面带上省略号“…”:
template<class ... T> void func(T ... args)//T叫模板参数包,args叫函数参数包
{//可变参数模板函数

}

func();    // OK:args不含有任何实参
func(1);    // OK:args含有一个实参:int
func(2, 1.0);   // OK:args含有两个实参int和double
T叫模板参数包,args叫函数参数包。

省略号“…”的作用有两个:

1、声明一个参数包,这个参数包中可以包含0到任意个模板参数
2、在模板定义的右边,可以将参数包展开成一个一个独立的参数

可变参数模板函数

可变模板参数定义如下:

在这里插入图片描述
参数包的展开

1、递归方式展开
通过递归函数展开参数包,需要提供【一个参数包展开的函数】和【一个递归终止函数】。
#include <iostream>
using namespace std;

//递归终止函数
void debug()
{
    cout << "empty\n";
}

//展开函数
template <class T, class ... Args>
void debug(T first, Args ... last)
{
    cout << "parameter " << first << endl;
    debug(last...);
}

int main()
{
    debug(1, 2, 3, 4);

    return 0;
}

在这里插入图片描述
递归调用过程如下

    debug(1, 2, 3, 4);
    debug(2, 3, 4);
    debug(3, 4);
    debug(4);
    debug();

通过可变参数模板实现打印函数:(奇怪????)

#include <iostream>
#include <stdexcept>
using namespace std;

void Debug(const char* s)
{
    while (*s)
    {
        if (*s == '%' && *++s != '%')
        {
            throw runtime_error("invalid format string: missing arguments");
        }

        cout << *s++;
    }
}

template<typename T, typename... Args>
void Debug(const char* s, T value, Args... args)
{
    while (*s)
    {
        if (*s == '%' && *++s != '%')
        {
            cout << value;
            return Debug(++s, args...);
        }

        cout << *s++;
    }

    throw runtime_error("extra arguments provided to Debug");
}

int main()
{
    Debug("a = %d, b = %c, c = %s\n", 250, 'm', "mike");

    return 0;
}
2、非递归方式展开
#include <iostream>
using namespace std;

template <class T>
void print(T arg)
{
    cout << arg << endl;
}

template <class ... Args>
void expand(Args ... args)
{
    int a[] = { (print(args), 0)... };
}

int main()
{
    expand(1, 2, 3, 4);

    return 0;
}

在这里插入图片描述

expand函数的逗号表达式:(print(args), 0), 也是按照这个执行顺序,先执行print(args),再得到逗号表达式的结果0。

同时,通过初始化列表来初始化一个变长数组,{ (print(args), 0)}将会展开
成( (print(args1), 0), (print(args2), 0), (print(args3), 0), etc…),
最终会创建一个元素值都为0的数组int a[sizeof(args)]

可变参数模板

继承方式展开参数包:(后面这里牵扯到模板的偏特化????)

可变参数模板类的展开一般需要定义2 ~ 3个类,包含类声明和特化的模板类:
#include <iostream>
#include <typeinfo>
using namespace std;

template<typename... A> class BMW{};  // 变长模板的声明

template<typename Head, typename... Tail>  // 递归的偏特化定义
class BMW<Head, Tail...> : public BMW<Tail...>
{//当实例化对象时,则会引起基类的递归构造
public:
    BMW()
    {

        printf("type: %s\n", typeid(Head).name());
    }

    Head head;
};

template<> class BMW<>{};  // 边界条件

int main()
{
    BMW<int, char, float> car;

    return 0;
}

在这里插入图片描述
模板递归和特化方式展开参数包

#include <iostream>
using namespace std;

template <long... nums> struct Multiply;// 变长模板的声明

template <long first, long... last>
struct Multiply<first, last...> // 变长模板类
{
    static const long val = first * Multiply<last...>::val;
};

template<>
struct Multiply<> // 边界条件
{
    static const long val = 1;
};

int main()
{
    cout << Multiply<2, 3, 4, 5>::val << endl; // 120

    return 0;
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这个print()接受一个参数和一包参数(……表示的就是不知道多少个参数)【这里不仅个数随意,参数类型也随意】
template<typename T,typename… Types>【表示的1+其余】

当没有参数的时候会调用①的print()函数

可以帮助我们做递归,这递归的目的就是为了将参数个数一一分解
怎样分解,就是把n变成1+其他

sizeof(arg)可以获取参数的个数

下面这两块没看懂????

在这里插入图片描述
在这里插入图片描述

Space in Template Expressions

在这里插入图片描述

nullptr

nullptr是为了【解决原来C++NULL的二义性问题】而引进的一种新的类型,因为NULL实际上代表的是0
void func(int a)
{
    cout << __LINE__ << " a = " << a <<endl;
}

void func(int *p)
{
     cout << __LINE__ << " p = " << p <<endl;
}

int main()
{
    int *p1 = nullptr;
    int *p2 = NULL;

    if(p1 == p2)
    {
        cout << "equal\n";
    }

    //int a = nullptr; //err, 编译失败,nullptr不能转型为int

    func(0); //调用func(int), 就算写NULL,也是调用这个
    func(nullptr);

    return 0;
}

在这里插入图片描述

原生字符串字面值

原生字符串字面值(raw string literal)使用户书写的字符串“所见即所得”。
C++11中原生字符串的声明相当简单,【只需在字符串前加入前缀,即字母R,并在引号中使用括号左右标识,】
就可以声明该字符串字面量为原生字符串了。
#include <iostream>
#include <string>
using namespace std;

int main(void)
{
    cout << R"(hello, \n world)" << endl;
    string str = R"(helo \4 \r 
    abc, mike
    hello\n)";
    cout << endl;
    cout << str << endl;

    return 0;
}

在这里插入图片描述

auto

auto的自动类型推导,用于从初始化表达式中推断出变量的数据类型。
从这个意义上讲,auto并非一种“类型”声明,而是一个类型声明时的“占位符”,
编译器在【编译时期】会将auto替换为变量实际的类型。
不要依赖auto,要会类型推到

auto的使用是在类型很长或者很复杂的时候

在这里插入图片描述
重点看下面的注释部分

#include <iostream>
#include <vector>
#include <string>
using namespace std;

double foo() {}

void func(vector<string> & tmp)
{
    for (auto i = tmp.begin(); i < tmp.end(); i++)
    {
        // 一些代码
    }
}

int main()
{
    auto x = 1;      // x的类型为int
    auto y = foo();  // y的类型为double
    struct m { int i; }str;
    auto str1 = str;    // str1的类型是struct m

    auto z;     // err, 无法推导,无法通过编译
    z = x;

    return 0;
}

在这里插入图片描述
注意点(这几种无法编译的情况为啥呢????)

1: auto函数参数,有些编译器无法通过编译
2: auto非静态成员变量,无法通过编译
3: auto数组,无法通过编译
4: auto模板参数(实例化时),无法通过编译
void fun(auto x =1) {}  // 1: auto函数参数,有些编译器无法通过编译

struct str
{
    auto var = 10;   // 2: auto非静态成员变量,无法通过编译
};

int main()
{
    char x[3];
    auto y = x;
    auto z[3] = x; // 3: auto数组,无法通过编译

    // 4: auto模板参数(实例化时),无法通过编译
    vector<auto> x = {1};

    return 0;
}

在这里插入图片描述

标准库本身也有使用新的关键字

Uniform Initialization(统一初始化)

在这里插入图片描述
初始化
类内成员初始化

class Mem
{
public:
    Mem(int i): m(i){} //初始化列表给m初始化
    int m;
};
class Group
{
public:
    Group(){}

private:
    int data = 1;       // 使用"="初始化非静态普通成员,也可以 int data{1};
    Mem mem{2}; // 对象成员,创建对象时,可以使用{}来调用构造函数
    string name{"mike"};
};

初始化列表

初始化方式如下

int a[]{1, 3, 5};
int i = {1};  
int j{3}; 

下面是用于结构体类型的情况
struct Person  
{  
  std::string name;  
  int age;  
};  

int main()  
{  
    Person p = {"Frank", 25};  
    std::cout << p.name << " : " << p.age << std::endl;  
} 
其他一些不方便初始化的地方使用,
比如std的初始化,如果不使用这种方式,只能用构造函数来初始化,难以达到效果:
std::vector<int> ivec1(3, 5);  
std::vector<int> ivec2 = {5, 5, 5};  
std::vector<int> ivec3 = {1,2,3,4,5}; //不使用列表初始化用构造函数难以实现  

防止类型收窄

【类型收窄】指的是导致**数据内容发生变化**或者**精度丢失**的隐式类型转换。
使用列表初始化可以防止类型收窄。

关于类型收窄

为什么要防止类型收窄

struct Foo  
{  
   Foo(int i) { std::cout << i << std::endl; }  
};  
 
Foo foo(1.2); 
【以上代码在C++中能够正常通过编译】,但是传递之后的i却不能完整地保存一个浮点型的数据。

上面的示例让我们对类型收窄有了一个大概的了解。具体来说,类型收窄包括以下几种情况:
1)从一个浮点数隐式转换为一个整型数,如int i=2.22)从高精度浮点数隐式转换为低精度浮点数,如从long double隐式转换为doublefloat3)从一个整型数隐式转换为一个浮点数,并且超出了浮点数的表示范围,如float x=(unsigned long long)-14)从一个整型数隐式转换为一个长度较短的整型数,并且超出了长度较短的整型数的表示范围,如char x=65536。

在C++98/03中,像上面所示类型收窄的情况,编译器并不会报错(或报一个警告,如Microsoft Visual C++)。
这往往会导致一些隐藏的错误。【在C++11中,可以通过列表初始化来检查及防止类型收窄】
列表初始化防止类型收窄示例

在这里插入图片描述
请看上图的报错

int a = 1.1;                // OK  
int b = { 1.1 };                // error  
 
float fa = 1e40;                // OK  
float fb = { 1e40 };            // error  
 
float fc = (unsigned long long)-1;  // OK  
float fd = { (unsigned long long)-1 };  // error  
float fe = (unsigned long long)1;       // OK  
float ff = { (unsigned long long)1 };   // OK  
 
const int x = 1024, y = 1;  
char c = x;             // OK  
char d = { x };             // error  
char e = y;             // OK  
char f = { y };             // OK 
上面的各种隐式类型转换中,只要遇到了类型收窄的情况,初始化列表就不会允许这种转换发生。

其中需要注意的是x、y被定义成了const int。如果去掉const限定符,那么***一个变量f也会因为类型收窄而报错

Initializer Lists(不是很懂)

在这里插入图片描述

不接受窄的转化
Initializer Lists可以实现参数个数不定(但是类型有说法)

在这里插入图片描述

由源码可知,Initializer Lists的背后就是array

clion编译器中的源码和老师的一样

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

Initializer Lists的使用就可以使得max和min接受任意多的参数【这就是Initializer Lists的妙用】

explicit

在这里插入图片描述

explicit这个关键字主要是用在构造函数身上
下面是C++ 2.0之前的用法(单一实参)

在这里插入图片描述
在这里插入图片描述

下面是C++ 2.0【之后】的用法

在这里插入图片描述

for循环的一种规则

基于范围的for循环

    int a[] = { 1, 2, 3, 4, 5 };
    int n = sizeof(a) / sizeof(*a); //元素个数

    for (int i = 0; i < n; ++i)
    {
        int tmp = a[i];
        cout << tmp << ", ";
    }
    cout << endl;

    for (int tmp : a)
    {
        cout << tmp << ", ";
    }
    cout << endl;


    for (int i = 0; i < n; ++i)
    {
        int &tmp = a[i];
        tmp = 2 * tmp;
        cout << tmp << ", ";
    }
    cout << endl;

    for (int &tmp : a)
    {
        tmp = 2 * tmp;
        cout << tmp << ", ";
    }
    cout << endl;

使用基于范围的for循环,其【for循环迭代的范围必须是可确定的】:

int func(int a[])//形参中数组是指针变量,无法确定元素个数
{
    for(auto e: a) // err, 编译失败
    {
        cout << e;
    }
}

int main()
{
    int a[] = {1, 2, 3, 4, 5};
    func(a);

    return 0;
}

静态断言

C/C++提供了调试工具assert,这是一个宏,用于在【运行阶段】对断言进行检查,
如果条件为真,执行程序,否则调用abort()
int main()
{
    bool flag = false;

    //如果条件为真,程序正常执行,如果为假,终止程序,提示错误
    assert(flag == true); //#include <cassert>或#include <assert.h>
    cout << "Hello World!" << endl;

    return 0;
}

【C++ 11新增了关键字static_assert】,可用于在【编译阶段】对断言进行测试。

语法:

    static_assert(常量表达式,提示字符串)
    【注意】:只能是【常量表达式】,不能是变量
示例:

int main()
{
    //该static_assert用来确保编译仅在32位的平台上进行,不支持64位的平台
    static_assert( sizeof(void *)== 4, "64-bit code generation is not supported."); 
    cout << "Hello World!" << endl;

    return 0;
}
静态断言的好处:

更早的报告错误,我们知道构建是早于运行的,更早的错误报告意味着【开发成本的降低】
减少运行时开销,静态断言是【编译期】检测的,【减少了运行时开销】

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

=default,=delete

优秀博客

如果程序员【没有显式地为一个类定义】某个特殊成员函数,【而又需要用到】该特殊成员函数时,
则编译器会隐式的为这个类生成一个默认的特殊成员函数。

default针对的是【默认构造函数】、【析构函数】、【拷贝构造函数】和【拷贝赋值函数】

defaulted 函数特性仅适用于【类的特殊成员函数】(上面四种),且该【特殊成员函数没有默认参数】。

好处
1. 减轻程序员的编程工作量
2. 获得编译器自动生成的默认特殊成员函数的高的代码执行效率

default详解
在这里插入图片描述

delete

为了能够让程序员【显式的禁用某个函数】,
C++11 标准引入了一个新特性:deleted 函数。程序员只需在函数声明后上“=delete;”,就可将该函数禁用。
用法:
1、用于禁用类的某些转换构造函数,从而避免不期望的类型转换。
2、用来禁用某些用户自定义的类的 new 操作符,从而避免在自由存储区创建类的对象
3、必须在函数第一次声明的时候将其声明为 deleted 函数,否则编译器会报错。
(即对于类的成员函数而言,deleted 函数必须在类体里(inline)定义,而不能在类体外(out-of-line)定义。)
4、虽然 defaulted 函数特性规定了只有类的特殊成员函数才能被声明为 defaulted 函数,
【但是】 deleted 函数特性并没有此限制。非类的成员函数,即普通函数也可以被声明为 deleted 函数。
5、虽然delete让函数不能被调用。但是【函数标示符】仍是有效的,在名字查找和函数重载解析时仍会查找到该函数标示符。
如果编译器在解析重载函数时,解析结果为 deleted 函数,则会出现编译错误。
构造函数可以重载,但是拷贝构造函数不能

在这里插入图片描述

????

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

Alias Template

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

模板模板参数

在这里插入图片描述

在这里插入图片描述

Type Alias

在这里插入图片描述

using[用于模板的别名]

见下面的示例

#include <iostream>
#include <type_traits> //std::is_same
using namespace std;

using uint = unsigned int;
typedef unsigned int UINT;
using sint = int;

int main()
{
    //std::is_same 判断类型是否一致
    //这个结构体作用很简单,就是两个一样的类型会返回true
    cout << is_same<int, sint>::value << endl; // 1
    cout << is_same<uint, UINT>::value << endl; // 1

    return 0;
}

在这里插入图片描述

noexcept

void func3() throw(int, char) //只能够抛出 int 和char类型的异常
{//C++11已经弃用这个声明
     throw 0;
}

void BlockThrow() throw() //代表此函数不能抛出异常,如果抛出,就会异常
{
    throw 1;
}


//C++11 使用noexcept替代throw()
void BlockThrowPro() noexcept   //代表此函数不能抛出异常,如果抛出,就会异常
{
    throw 2;
}

参考这篇博客

下面是std::initializer_list的默认构造函数,其中使用了noexcept
constexpr initializer_list() noexcept
      : _M_array(0), _M_len(0) { }
该关键字告诉编译器,函数中不会发生异常,这有利于编译器对程序做更多的优化。
如果在运行时,noexecpt函数向外抛出了异常(如果函数内部捕捉了异常并完成处理,这种情况不算抛出异常),
程序会直接终止,调用std::terminate()函数,该函数内部会调用std::abort()终止程序。

C++的异常处理

C++中的异常处理是在运行时而不是编译时检测的。为了实现运行时检测,编译器创建额外的代码,然而这会妨碍程序优化。
在实践中,一般两种异常抛出方式是常用的:

一个操作或者函数可能会抛出一个异常;
一个操作或者函数不可能抛出任何异常。
后面这一种方式中在以往的C++版本中常用throw()表示,在C++ 11中已经被noexcept代替。
	void swap(Type& x, Type& y) throw()   //C++11之前
    {
        x.swap(y);
    }

//下面单独使用noexcept,表示其所限定的swap函数绝对不发生异常
    void swap(Type& x, Type& y) noexcept  //C++11
    {
        x.swap(y);
    }

有条件的noexcept

	void swap(Type& x, Type& y) noexcept(noexcept(x.swap(y)))    //C++11
    {
        x.swap(y);
    }
它表示,如果操作x.swap(y)不发生异常,那么函数swap(Type& x, Type& y)一定不发生异常。

什么时候该使用noexcept

使用noexcept表明函数或操作不会发生异常,会给编译器更大的优化空间。
然而,【并不是加上noexcept就能提高效率】。
以下情形鼓励使用noexcept1、移动构造函数(move constructor)
2、移动分配函数(move assignment)
3、析构函数(destructor)。

在新版本的编译器中,析构函数是默认加上关键字noexcept的。

在这里插入图片描述

在这里插入图片描述

强类型枚举

C++ 枚举类型详解

C++ 11引入了一种新的枚举类型,即“枚举类”,又称“强类型枚举”。
声明请类型枚举非常简单,只需要【在enum后加上使用classstruct】(声明实现)。如:
enum Old{Yes, No};          // old style
enum class New{Yes, No};    // new style
enum struct New2{Yes, No};  // new style
“传统”的C++枚举类型有一些【缺点】:
它会在一个代码区间中抛出枚举类型成员(如果在相同的代码域中的两个枚举类型具有相同名字的枚举成员,这会导致【命名冲突】),
【它们会被隐式转换为整型,并且不可以指定枚举的底层数据类型】。
int main()
{
    enum Status{Ok, Error};
    //enum Status2{Ok, Error};//err, 导致命名冲突, Status已经有成员叫Ok, Error
    
    return 0;
}

上诉的缺点在C++ 11中都获得了解决

int main()
{
    enum class Status {Ok, Error};
    enum struct Status2{Ok, Error};

    //Status flag1 = 10; // err,无法隐式转换为int类型
    //Status flag2 = Ok; // err,必须使用强类型名称
    Status flag3 = Status::Ok;

    enum class C : char { C1 = 1, C2 = 2};//指定枚举的底层数据类型
    enum class D : unsigned int { D1 = 1, D2 = 2, Dbig = 0xFFFFFFF0U };

    cout << sizeof(C::C1) << endl;   // 1
    cout << (unsigned int)D::Dbig << endl;   // 编译器输出一致,4294967280
    cout << sizeof(D::D1) << endl;     // 4
    cout << sizeof(D::Dbig) << endl;   // 4


    return 0;
}

常量表达式

参考博客

类的改造

1、继承构造

C++ 11【允许派生类继承基类的构造函数】,【默认构造函数、复制构造函数、移动构造函数除外】。
#include <iostream>
using namespace std;

//基类
class A
{
    public:
        A(int x, int y)
        {
            a = x;
            b = y;
        }
    protected:
        int a;
        int b;
};

//派生类
class B:public A
{
public:

    //继承构造
    using A::A;

    void display()
    {
        cout << "a = " << a << ", b = " << b << endl;
    }

    //没有增加新的成员变量
    int tmp;
};

int main()
{
    //派生类对象
    B obj(10, 20);
    obj.display();

    return 0;
}

上诉代码若是去掉 using A::A; 这一行,看报错信息
在这里插入图片描述

注意:
1、继承的构造函数【只能初始化基类中的成员变量】,不能初始化派生类的成员变量
2、如果1、基类的构造函数被声明为私有,或者2、派生类是从基类中虚继承,那么不能继承构造函数
3、一旦使用继承构造函数,编译器不会再为派生类生成默认构造函数

2、委托构造

和继承构造函数类似,委托构造函数也是C++11中对C++的构造函数的一项改进,其【目的也是为了减少程序员书写构造函数的时间】。

如果一个类包含多个构造函数,C++ 11允许在一个构造函数中的定义中使用另一个构造函数,但这必须通过初始化列表进行操作,如下:
#include <iostream>
using namespace std;

class Test
{
public:
    //委托构造,一定要通过初始化列表方式
    Test():Test(1, 'a')
    {

    }

    Test(int x): Test(x, 'b')
    {

    }

    Test(char x): Test(11, x)
    {

    }

    int a;
    char b;

private:
    Test(int x, char y): a(x), b(y)
    {

    }
};

int main()
{
    //Test obj; //    Test():Test(1, 'a')
    Test obj1();
    cout << obj1 << endl;

    Test obj2('z');
    cout << obj2.a << endl;
    cout << obj2.b << endl;


    Test obj3(10);
    cout << obj3.a << endl;
    cout << obj3.b << endl;
    return 0;
}

3、继承控制:final和override

C++11之前,一直没有继承控制关键字,禁用一个类的进一步衍生比较麻烦。

C++ 11添加了两个继承控制关键字:final和override。

用户定义字面量

参考

模板的改进

1、右尖括号>改进

在C++98/03的泛型编程中,模板实例化有一个很繁琐的地方,就是连续两个右尖括号(>>)会被编译解释成右移操作符,
【而不是模板参数表的形式】,需要一个空格进行分割,以避免发生编译时的错误。
template <int i> class X{};
template <class T> class Y{};

int main()
{
    Y<X<1> > x1;    // ok, 编译成功
    Y<X<2>> x2;     // err, 编译失败

    return 0;
};
在实例化模板时会出现连续两个右尖括号,同样static_castdynamic_castreinterpret_castconst_cast表达式转换时
也会遇到相同的情况。C++98标准是让程序员【在>>之间填上一个空格】,
在C++11标准中,要求编译器对模板的右尖括号做单独处理,使编译器能够正确判断出”>>”是一个右移操作符还是模板参数表的结束标记。

2、函数模板的默认模板参数

参考博客

C++11之前,类模板是支持默认的模板参数,却不支持函数模板的默认模板参数

//1、普通函数带默认参数,c++98 编译通过,c++11 编译通过
void DefParm(int m = 3) {}

//2、类模板是支持默认的模板参数,c++98 编译通过,c++11 编译通过
template <typename T = int>
class DefClass {};

//3、函数模板的默认模板参数, c++98 - 编译失败,c++11 - 编译通过
template <typename T = int> void DefTempParm() {}

【类模板的默认模板参数必须【从右往左】定义】,函数模板的默认模板参数则没这个限定

template<class T1, class T2 = int> class DefClass1;
template<class T1 = int, class T2> class DefClass2;   // 无法通过编译

template<class T, int i = 0> class DefClass3;
template<int i = 0, class T> class DefClass4;         // 无法通过编译

template<class T1 = int, class T2> void DefFunc1(T1 a, T2 b);
template<int i = 0, class T> void DefFunc2(T a);

override

override确保在派生类中声明的函数跟基类的虚函数有相同的签名

class A1
{
public:
    //这是第一个虚函数,没有重写,不能用override修饰
    virtual int func(int a)
    {

    }
};

class A2:public A1
{
public:
    //在重写虚函数地方,加上override, 要求重写的虚函数和基类一模一样
    virtual int func(int b) override
    {

    }
};
上诉代码如果将派生类中的函数改为
virtual int func(float b) override

在这里插入图片描述

在这里插入图片描述

final

final阻止类的进一步派生和虚函数的进一步重写

//final【阻止类】的进一步【派生】,【虚函数】的进一步【重写】
#if 0
class A1 final //加上final,指定A1不能派生
{
    int a;
};

class A2: public A1 //err, 基类不能再派生了
{

};
#endif

//基类
class B1
{
public:
    virtual void func() final {} //这是最终版本的虚函数,不能再重写

};

//派生类重写基类的虚函数
class B2: public B1
{
public:
    //virtual void func() {} //err, 基类中的虚函数是最终版本,不能再重写
};

在这里插入图片描述

decltype

decltype实际上有点像auto的反函数, auto可以让你声明一个变量,
而decltype则可以从一个变量或表达式中【得到其类型】,如下:
#include <typeinfo>
#include <iostream>
#include <vector>
using namespace std;

int main()
{
    int i;
    decltype(i) j = 0;
    cout << typeid(j).name() << endl;   // 打印出"i", g++表示integer

    float a;
    double b;
    decltype(a + b) c;
    cout << typeid(c).name() << endl;   // 打印出"d", g++表示double

    vector<int> vec;
    typedef decltype(vec.begin()) vectype; // decltype(vec.begin()) 改名为 vectype

    vectype k;  // 这是auto无法做到的
    //decltype(vec.begin()) k;  // 这是auto无法做到的
    for (k = vec.begin(); k < vec.end(); k++)
    {
        // 做一些事情
    }

    enum {Ok, Error, Warning}flag;   // 匿名的枚举变量
    decltype(flag) tmp = Ok;

    return 0;
}

追踪返回类型

返回类型后置:在函数名和参数列表后面指定返回类型。

基本用法

int getSize();int main(void)
{
    int tempA = 2;
    
    /*1.dclTempA为int.*/
    decltype(tempA) dclTempA;
    /*2.dclTempB为int,对于getSize根本没有定义,但是程序依旧正常,因为decltype只做分析,并不调用getSize().*/
    decltype(getSize()) dclTempB;return 0;
}

与const结合

	double tempA = 3.0;
    const double ctempA = 5.0;
    const double ctempB = 6.0const double *const cptrTempA = &ctempA;
    
    /*1.dclTempA推断为const double(保留顶层const,此处与auto不同)*/
    decltype(ctempA) dclTempA = 4.1;
    /*2.dclTempA为const double,不能对其赋值,编译不过*/
    dclTempA = 5;
    /*3.dclTempB推断为const double * const*/
    decltype(cptrTempA) dclTempB = &ctempA;
    /*4.输出为4(32位计算机)和5*/
    cout<<sizeof(dclTempB)<<"    "<<*dclTempB<<endl;
    /*5.保留顶层const,不能修改指针指向的对象,编译不过*/
    dclTempB = &ctempB;
    /*6.保留底层const,不能修改指针指向的对象的值,编译不过*/
    *dclTempB = 7.0;

与引用结合

	int tempA = 0, &refTempA = tempA;/*1.dclTempA为引用,绑定到tempA*/
    decltype(refTempA) dclTempA = tempA;
    /*2.dclTempB为引用,必须绑定到变量,编译不过*/
    decltype(refTempA) dclTempB = 0;
    /*3.dclTempC为引用,必须初始化,编译不过*/
    decltype(refTempA) dclTempC;
    /*4.双层括号表示引用,dclTempD为引用,绑定到tempA*/
    decltype((tempA)) dclTempD = tempA;
    
    const int ctempA = 1, &crefTempA = ctempA;
    
    /*5.dclTempE为常量引用,可以绑定到普通变量tempA*/
    decltype(crefTempA) dclTempE = tempA;
    /*6.dclTempF为常量引用,可以绑定到常量ctempA*/
    decltype(crefTempA) dclTempF = ctempA;
    /*7.dclTempG为常量引用,绑定到一个临时变量*/
    decltype(crefTempA) dclTempG = 0;
    /*8.dclTempH为常量引用,必须初始化,编译不过*/
    decltype(crefTempA) dclTempH;
    /*9.双层括号表示引用,dclTempI为常量引用,可以绑定到普通变量tempA*/
    decltype((ctempA))  dclTempI = ctempA;

与指针结合

int tempA = 2;
    int *ptrTempA = &tempA;
    /*1.常规使用dclTempA为一个int *的指针*/
    decltype(ptrTempA) dclTempA;
    /*2.需要特别注意,表达式内容为解引用操作,dclTempB为一个引用,引用必须初始化,故编译不过*/
    decltype(*ptrTempA) dclTempB;

decltype和auto的区别

decltypeauto都可以用来推断类型,但是二者有几处明显的差异:

1auto忽略顶层constdecltype保留顶层const2、对引用操作,auto推断出原有类型,decltype推断出引用;

3、对解引用操作,auto推断出原有类型,decltype推断出引用;

4auto推断时会实际执行,decltype不会执行,只做分析。总之在使用中过程中和const、引用和指针结合时需要特别小心。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

Lambdas

在这里插入图片描述

参考

C++ 11新特性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值