C++11学习笔记(三)

来源:互联网 发布:大数据就业前景 编辑:程序博客网 时间:2024/05/23 16:56

【final和override】

final和override在其它的一些OOP中盛行,如今C++11也加入了这2个关键字。

通过下面这段代码来了解final的使用方式

struct Object{    virtual void fun() = 0;};struct Base : public Object {    void fun() final;   // 声明为final};struct Derived : public Base {    void fun();     // 无法通过编译};

由此可见,final关键字可以很方便的在继承关系中 终止  虚函数的可重载性。


先说一个C++中重载的一个特点,然后再说override关键字

在C++中,若基类中一个成员函数声明为virtual,则继承类中,无论是否使用virtual关键字修饰成员函数,它都依然是虚函数。这虽然方便了书写,却使得代码不够直观、明确。


因而引入了override关键字,下面看一个例子


struct Base {    virtual void Turing() = 0;    virtual void Dijkstra() = 0;    virtual void VNeumann(int g) = 0;    virtual void DKnuth() const;    void Print();};struct DerivedMid: public Base {    // void VNeumann(double g);    // 接口被隔离了,曾想多一个版本的VNeumann函数};struct DerivedTop : public DerivedMid {    void Turing() override;    void Dikjstra() override;           // 无法通过编译,拼写错误,并非重载    void VNeumann(double g) override;   // 无法通过编译,参数不一致,并非重载    void DKnuth() override;             // 无法通过编译,常量性不一致,并非重载    void Print() override;              // 无法通过编译,非虚函数重载};


继承类中,Dijkstra()被误写成Dikjstra(),VNeumann(double g)与VNeumann(int g)函数原型 不匹配,并且错误的重写了废墟函数Print()。


如果没有override,那么上面代码中的错误就不会被发现。


【继承构造函数】

C++98中,继承构造函数的方式并不合用,尤其是类中具有多个构造函数的时候

struct A {     A(int i) {}    A(double d, int i) {}    A(float f, int i, const char* c) {}    // ...};struct B : A {     B(int i): A(i) {}     B(double d, int i) : A(d, i) {}    B(float f, int i, const char* c) : A(f, i, c){}    // ...    virtual void ExtraInterface(){}};

类B中只有 ExtraInterface()这一个函数,却继承了类A的所有构造函数,这无疑是非常不方便的。


但是,若要继承基类的其它函数,可以使用using声明

#include <iostream>using namespace std;struct Base {    void f(double i){ cout << "Base:" << i << endl; }};struct Derived : Base {    using Base::f;    void f(int i) { cout << "Derived:" << i << endl; }};int main() {    Base b;    b.f(4.5);  // Base:4.5    Derived d;    d.f(4.5);  // Base:4.5}

派生类声明了和基类同名的函数f(),同时通过using声明也使用了基类的f(),在main()中,同时声明了b和d,并传入参数4.5,结果,b和d都使用了基类的f()函数。


C++11将这一特性也用在了构造函数上

struct A {     A(int i) {}    A(double d, int i) {}    A(float f, int i, const char* c) {}    // ...};struct B : A {     using A::A;     // 继承构造函数    // ...    virtual void ExtraInterface(){}};


不过,继承的构造函数无法初始化子类的成员,所以,我们需要使用C++11中的成员初始化来完成子类成员的初始化。

struct A {    A(int i) {}    A(double d, int i) {}    A(float f, int i, const char* c) {}    // ...};struct B : A {     using A::A;    int d {0};};int main() {    B b(356);   // b.d被初始化为0}

有的时候,基类的构造函数会有默认参数,子类通过using声明继承基类的构造函数时,并不能继承其默认参数。如此一来,基类实际上会产生多个版本的构造函数,它们都会被子类继承。

struct A {    A (int a = 3, double = 2.4){}};struct B : A{    using A::A;};

B可能从A中继承而来的候选继承构造函数如下

A(int=3,double=2.4);//使用2个参数A(int=3);//减掉一个参数A(const A&);//默认的复制构造函数A();//不使用参数的情况

相应的,B的构造函数会包含以下一些

B(int,double);//一个继承构造函数B(int);//减掉一个参数的继承构造函数B(const B&);//复制构造函数-不是继承来的B();//不使用参数的默认构造函数

在使用有默认参数值的构造函数的基类时,要特别小心

而有的时候,会遇到继承冲突的情况,这多发生在子类拥有多个基类的时候。

struct A { A(int) {} };struct B { B(int) {} };struct C: A, B {    using A::A;    using B::B; }; 

通过继承A、B,C拥有2个函数签名相同的构造函数,这根本无法通过编译。

解决方案是,通过显式声明C的构造函数

struct C: A, B {    using A::A;    using B::B;     C(int){}}; 

此外,一旦使用了继承构造函数,则编译器不再为子类生成 默认构造函数。那么,下面的代码是不能通过编译的。

struct A { A (int){} };struct B : A{ using A::A; };B b;    // B没有默认构造函数

【委派构造函数】

委派构造函数的作用也是为了减少代码量,下面看一个构造函数代码冗余的例子

class Info {public:    Info() : type(1), name('a') { InitRest(); }    Info(int i) : type(i), name('a') { InitRest(); }    Info(char e): type(1), name(e) { InitRest(); }private:    void InitRest() { /* 其它初始化 */ }    int  type;    char name;    // ...};

可以看出,3个构造函数基本相似,需要优化。

首先,试试使用成员初始化来优化代码

class Info {public:    Info() { InitRest(); }    Info(int i) : type(i) { InitRest(); }    Info(char e): name(e) { InitRest(); }private:    void InitRest() { /* 其它初始化 */ }    int  type {1};    char name {'a'};    // ...};

代码确实优化了不少,但是,每个构造函数中都使用了 InitRest() 这个函数。

下面看看C++11中的解决方案

class Info {public:    Info() { InitRest(); }    Info(int i) : Info() { type = i; }    Info(char e): Info() { name = e; }private:    void InitRest() { /* 其它初始化 */ }    int  type {1};    char name {'a'};    // ...};

这里,基准函数 Info()被称为 目标构造函数,在初始化列表中 调用 基准函数的,则被称为 委派构造函数。委派构造函数不能使用初始化列表,只能在函数体内给变量赋初值。


上面的代码并不方便,我们可以改动一下,使得委派构造函数可以使用初始化列表

class Info {public:    Info() : Info(1, 'a') { }    Info(int i) : Info(i, 'a') { }    Info(char e): Info(1, e) { }private:    Info(int i, char e): type(i), name(e) { /* 其它初始化 */ }    int  type;    char name;    // ...};

这样一来,InitRest()函数也省了。


当构造函数比较多时,可能有不止一个委派构造函数,有时候,目标构造函数本身也是委派构造函数,这样一来,就可能形成链状关系。

class Info {public:    Info() : Info(1) { }    // 委托构造函数    Info(int i) : Info(i, 'a') { } // 既是目标构造函数,也是委托构造函数    Info(char e): Info(1, e) { }private:    Info(int i, char e): type(i), name(e) { /* 其它初始化 */ } // 目标构造函数    int  type;    char name;    // ...};

这样的情况下是无法通过编译的,应该尽量避免。


委派构造函数一个很实际的应用就是,使用构造模板函数产生目标构造函数。

#include <list>#include <vector>#include <deque>using namespace std;class TDConstructed {    template<class T> TDConstructed(T first, T last) :         l(first, last) {}    list<int> l;public:    TDConstructed(vector<short> & v):         TDConstructed(v.begin(), v.end()) {}    TDConstructed(deque<int> & d):         TDConstructed(d.begin(), d.end()) {}};


【列表初始化】

在C++98中,可以通过'{'、‘}’来对数组进行初始化,但是对于自定义类型,却无法使用这一特性。像STL中的vector,总是要先声明,再循环初始化,这无疑很不利于泛型编程。

C++11中使用了一种叫做 “初始化列表” 的方法

#include <vector>#include <map>using namespace std;int a[] = {1, 3, 5};        // C++98 - 通过, C++11 - 通过int b[] {2, 4, 6};          // C++98 - 失败, C++11 - 通过vector<int> c{1, 3, 5};     // C++98 - 失败, C++11 - 通过map<int, float> d = {{1, 1.0f}, {2, 2.0f} , {5, 3.2f}}; // C++98 - 失败, C++11 - 通过 

即便是自己定义的类,也可以通过#include<initializer_list>头文件,并且声明一个以initialize_list<T>模板类为参数的构造函数。

#include <vector>#include <string>using namespace std;enum Gender {boy, girl};class People {public:    People(initializer_list<pair<string, Gender>> l) {  // initializer_list的构造函数        auto i = l.begin();        for (;i != l.end(); ++i)            data.push_back(*i);    }private:    vector<pair<string, Gender>> data;};People ship2012 = {{"Garfield", boy}, {"HelloKitty", girl}};

同样,函数的参数列表一样可以使用初始化列表

#include <initializer_list>using namespace std;void Fun(initializer_list<int> iv){ }int main() {    Fun({1, 2});    Fun({}); // 空列表}

下面这个例子,重载了operator [] 和 operator =,以及使用辅助的数组。
#include <iostream>#include <vector>using namespace std;class Mydata {public:    Mydata & operator [] (initializer_list<int> l)    {        for (auto i = l.begin(); i != l.end(); ++i)            idx.push_back(*i);        return *this;    }    Mydata & operator = (int v)     {        if (idx.empty() != true) {            for (auto i = idx.begin(); i != idx.end(); ++i) {                d.resize((*i > d.size()) ? *i : d.size());                d[*i - 1] = v;            }            idx.clear();        }        return *this;    }    void Print() {        for (auto i = d.begin(); i != d.end(); ++i)             cout << *i << " ";        cout << endl;    }private:    vector<int> idx;    // 辅助数组,用于记录index    vector<int> d;};int main() {    Mydata d;    d[{2, 3, 5}] = 7;    d[{1, 4, 5, 8}] = 4;    d.Print();  // 4 7 7 4 4 0 0 4}

使用初始化列表的另一个优势就是——可以防止类型收窄

const int x = 1024;const int y = 10;char a = x;                     // 收窄,但可以通过编译char* b = new char(1024);       // 收窄,但可以通过编译char c = {x};                   // 收窄,无法通过编译char d = {y};                   // 可以通过编译unsigned char e {-1};           // 收窄,无法通过编译float f { 7 };                  // 可以通过编译int g { 2.0f };                 // 收窄,无法通过编译float * h = new float{1e48};    // 收窄,无法通过编译float i = 1.2l;                 // 可以通过编译

在C++11中,类型初始化是唯一一种可以防止类型收窄的初始化方式。



1 0
原创粉丝点击