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; // 无法通过编译,非虚函数重载};
如果没有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语言学习笔记三
- C语言学习笔记<三 >
- C/C++学习笔记(三)
- C++Primer学习笔记《三》
- Objective C学习笔记(三)
- 《C语言宝典》学习笔记(三):数据类型
- ASP.Net 3.5学习笔记(C#)三
- 嵌入式学习笔记-C语言(三)
- Object C NSArray (学习笔记三)
- 《C和指针》学习笔记(三)
- C++学习笔记(Thinking in c++) 三
- c++primer学习笔记(三)
- C++Primer学习笔记之三
- Object-C学习笔记三-----继承
- C Sharp与.net学习笔记(三)
- IOS之Objective-C学习笔记(三)
- 《C++Primer Plus》学习笔记(三)
- c++Templates学习笔记(三)
- java 代理设计模式
- 我的大学——学习生活总结
- 其它好文章
- C++11学习笔记(六)
- 工具条CToolBar与状态条CStatusBar的基本添加方法
- C++11学习笔记(三)
- C++11学习笔记(二)
- iOS 修改类名
- C++11学习笔记(一)
- 关于Linux命令ls的一道笔试题
- 多级指针
- [转载]分页查询
- android获取string.xml的值
- 39.Ugly Number II(动态规划)