C++11学习笔记(三)
2015-11-29 19:54
435 查看
【final和override】
final和override在其它的一些OOP中盛行,如今C++11也加入了这2个关键字。
通过下面这段代码来了解final的使用方式
由此可见,final关键字可以很方便的在继承关系中 终止 虚函数的可重载性。
先说一个C++中重载的一个特点,然后再说override关键字
在C++中,若基类中一个成员函数声明为virtual,则继承类中,无论是否使用virtual关键字修饰成员函数,它都依然是虚函数。这虽然方便了书写,却使得代码不够直观、明确。
因而引入了override关键字,下面看一个例子
继承类中,Dijkstra()被误写成Dikjstra(),VNeumann(double g)与VNeumann(int g)函数原型 不匹配,并且错误的重写了废墟函数Print()。
如果没有override,那么上面代码中的错误就不会被发现。
【继承构造函数】
C++98中,继承构造函数的方式并不合用,尤其是类中具有多个构造函数的时候
类B中只有 ExtraInterface()这一个函数,却继承了类A的所有构造函数,这无疑是非常不方便的。
但是,若要继承基类的其它函数,可以使用using声明
派生类声明了和基类同名的函数f(),同时通过using声明也使用了基类的f(),在main()中,同时声明了b和d,并传入参数4.5,结果,b和d都使用了基类的f()函数。
C++11将这一特性也用在了构造函数上
不过,继承的构造函数无法初始化子类的成员,所以,我们需要使用C++11中的成员初始化来完成子类成员的初始化。
有的时候,基类的构造函数会有默认参数,子类通过using声明继承基类的构造函数时,并不能继承其默认参数。如此一来,基类实际上会产生多个版本的构造函数,它们都会被子类继承。
B可能从A中继承而来的候选继承构造函数如下
相应的,B的构造函数会包含以下一些
在使用有默认参数值的构造函数的基类时,要特别小心
而有的时候,会遇到继承冲突的情况,这多发生在子类拥有多个基类的时候。
通过继承A、B,C拥有2个函数签名相同的构造函数,这根本无法通过编译。
解决方案是,通过显式声明C的构造函数
此外,一旦使用了继承构造函数,则编译器不再为子类生成 默认构造函数。那么,下面的代码是不能通过编译的。
【委派构造函数】
委派构造函数的作用也是为了减少代码量,下面看一个构造函数代码冗余的例子
可以看出,3个构造函数基本相似,需要优化。
首先,试试使用成员初始化来优化代码
代码确实优化了不少,但是,每个构造函数中都使用了 InitRest() 这个函数。
下面看看C++11中的解决方案
这里,基准函数 Info()被称为 目标构造函数,在初始化列表中 调用 基准函数的,则被称为 委派构造函数。委派构造函数不能使用初始化列表,只能在函数体内给变量赋初值。
上面的代码并不方便,我们可以改动一下,使得委派构造函数可以使用初始化列表
这样一来,InitRest()函数也省了。
当构造函数比较多时,可能有不止一个委派构造函数,有时候,目标构造函数本身也是委派构造函数,这样一来,就可能形成链状关系。
这样的情况下是无法通过编译的,应该尽量避免。
委派构造函数一个很实际的应用就是,使用构造模板函数产生目标构造函数。
【列表初始化】
在C++98中,可以通过'{'、‘}’来对数组进行初始化,但是对于自定义类型,却无法使用这一特性。像STL中的vector,总是要先声明,再循环初始化,这无疑很不利于泛型编程。
C++11中使用了一种叫做 “初始化列表” 的方法
即便是自己定义的类,也可以通过#include<initializer_list>头文件,并且声明一个以initialize_list<T>模板类为参数的构造函数。
同样,函数的参数列表一样可以使用初始化列表
下面这个例子,重载了operator [] 和 operator =,以及使用辅助的数组。
使用初始化列表的另一个优势就是——可以防止类型收窄
在C++11中,类型初始化是唯一一种可以防止类型收窄的初始化方式。
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中,类型初始化是唯一一种可以防止类型收窄的初始化方式。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- 关于指针的一些事情
- c++ primer 第五版 笔记前言
- share_ptr的几个注意点
- 局域网与广域网接口标准
- Lua中调用C++函数示例
- Lua教程(一):在C++中嵌入Lua脚本
- Lua教程(二):C++和Lua相互传递数据示例
- web标准知识——从p开始,循序渐进
- C++联合体转换成C#结构的实现方法
- 网页打开新窗口target=_blank不符合标准
- C++编写简单的打靶游戏
- C++ 自定义控件的移植问题
- C++变位词问题分析
- C/C++数据对齐详细解析
- C++基于栈实现铁轨问题
- C++中引用的使用总结
- 使用Lua来扩展C++程序的方法
- C++中调用Lua函数实例