(GeekBand)C++面向对象高级编程(下)第一周笔记(1)

第一节 导读

知识清单:

首先来列一张清单,清点一下后面课程将会深入的细节:

  • operator type()const;
  • explicit complex(...):initialization list{}
  • pointer-like object
  • function-like object
  • Namespace
  • template specialization
  • Standard Library
  • variadic template(since C++11)
  • move ctor(since C++11)
  • Rvalue reference(since C++11)
  • auto(since C++11)
  • lambda(since C++11)
  • range-base for loop(since C++11)
  • unordered containers(cince C++11)
目标:
  • 在先前的基础课程所培养的正规、大气的编程素养上,继续探讨更多技术。
  • 泛型编程(Generic Programming)和面向对象编程(Object-Oriented Programming)虽然分属不同思维,但它们正是C++的技术主线,所以本课程也讨论template(模板)。
  • 深入探索面向对象之继承关系(inheritance)所形成的对象模型(Object Model),包括隐藏于底层的this指针,vptr(虚指针),vtbl(虚表),virtual mechanism(虚机制),以及虚函数(virtual functions)造成的polymorphism(多态)效果。
推荐书目
  • 《C++ Primer》
  • 《The C++ Programming Language》
  • 《Effective Modern C++》
  • 《Effective C++》
  • 《The C++ Standard Library》
  • 《STL源码剖析》

第二、三节 non-explicit one argument constructor & Conversion Function(转换构造与类型转换函数)

为了方便对照学习,记忆,决定把二三节内容放在一起讲解。

转换构造函数

定义

在CPP中,类的构造函数可以省略不写,这时CPP会为它自动创建一个隐式默认构造函数(implicit default constructor);也可以由用户定义带参数的构造函数,构造函数也是一个成员函数,他可以被重载;当一个构造函数只有一个参数,而且该参数又不是本类的const引用时,这种构造函数称为转换构造函数(non-explicit ont argument constructor)。(该段引自百度百科)

class Complex
{
private:
    double real,imag; //复数的实部和虚部
public:
    Complex(double x)
    { 
        real=x;
        imag=0;
    }
    //与下方等价
    /*
    Complex(double x,double y=0):real(x),imag(y)
    {
    
    }
    */
};

这个构造函数即 转换构造函数。
如上文。构造函数只有一个参数 double x,它也不是本类的const引用。

应用

通过转换构造函数可以将一个指定类型的数据转换为类的对象。

1.用于定义

转换构造函数一般由系统自动调用(当然代码里自己调用完全没问题),这点很利于编程。
例如:

  • Complex t=5.0;
  • Complex t(5.0);
  • Complex t=Complex(5.0);
  • Complex t=(Complex)5.0;

这时系统就自动调用了 Complex(double x)将 5.0转换成Complex类,再赋值给t。

2.用于计算

通常来讲,转换构造函数更多搭配运算符重载用来计算。

class Complex
{
public:
    Complex(double x,double y=0)//转换构造
    :real(x),imag(y){}
    Complex operator+(const Complex& f)//操作符重载
    {
        return Complex(......);
    }
private:
    double real;
    double imag;
};
Complex t=5.0;
Complex b=t + 4.8;

编译器会隐式调用转换构造函数将5.0转换为Complex成员并赋值给t。第二步同理,将4.8转换成Complex成员后调用'+'的重载函数完成计算。

类型转换函数

通过转换构造函数可以将一个指定类型的数据转换为类的对象。但是不能反过来将一个类的对象转换为一个其他类型的数据(例如将一个Complex类对象转换成double类型数据)。

C++提供类型转换函数(type conversion function)来解决这个问题。类型转换函数的作用是将一个类的对象转换成另一类型的数据。如果已声明了一个Complex类,可以在Complex类中这样定义类型转换函数:

operator double() const//类型转换函数
{
    return real;
}

从函数结构来看,与重载函数类似,都需要关键字operator,只不过这里的转换的是类型而已,double在Complex类中经过重载后,Complex就被赋予了一种新的含义,既可以当做Complex类型本身使用,也可以当做double类型来使用。

我们来举一个简单的例子:

class Complex
{
public:
    Complex():real(0),imag(0)
    {}
    Complex(double x,double y):real(x),imag(y)
    {}
    operator double() const
    {
        return real;
    }
private:
    double real;
    double imag;
};
Complex t(5,0);
Complex b=t + 4.8;

此时我们的b=t+4.8运算有了另一种解法,即将Complex对象t通过隐式调用类型转换函数转换为double对象完成计算。

小结:

  • 转换构造函数可以将一个指定类型的数据转换为类的对象。
  • 类型转换函数可以将一个类的对象转换为一个其他类型的数据。

我们了解了转换构造函数与类型转换函数可以为Complex b=t+4.8这样的运算提供两个不同角度的解法,那么如果Complex类同时拥有了两种函数,又会怎样呢?

class Complex
{
public:
    Complex():real(0),imag(0)
    {}
    Complex(double x,double y=0)//转换构造
    :real(x),imag(y){}
    Complex(double x,double y):real(x),imag(y)
    {}
    Complex operator+(const Complex& f)//操作符重载
    {
        return Complex(......);
    }
    operator double() const//
    {
        return real;
    }
private:
    double real;
    double imag;
};
Complex t(5,0);
Complex b=t + 4.8;

编译器会提示ambiguous(歧义),即有多重解。当编译器可选择的方案不止一种,会出现这种提示。在案例中,编译器既可以通过转换构造函数将4.8转换为Complex对象,与可以通过类型转换函数将t转换为double对象完成计算。

但是在实际使用的过程中,难免会遇到这种情况,好在CPP为我们提供解决的办法:explicit。

explicit关键字多用在转换构造函数之前,其作用是指定该构造函数只能被显示调用(即创建实例时的调用,如Complex a(5,0)),而不可以再被隐式调用,这样就解决了程序的ambiguous问题。

第四节 pointer-like classes(关于智能指针)

我们知道智能指针能够比原生指针做更多事情,例如处理线程安全,提供写时复制,确保协议,并且提供远程交互服务等等等等许许多多强大的功能。但其实不论是多牛的智能指针,在它的内部一定至少有一个原生指针在工作。现在就我们从语法的角度来初窥智能指针。

以shared_ptr为例:

template<class T>
class shared_ptr
{
public:
    T& operator*() const//重载*
    {return *px;}
    T* operator->() const//重载->
    {return px;}
    shared_ptr(T* p):px(p){}
private:
    T* px;
    long* pn;
}
struct Foo
{
    void method(){}
};
shared_ptr<Foo> sp(new Foo);
Foo f(*sp);
sp->method();
//px->method();
解析:

智能指针的本质,其实就是把一个原生指针包装在类中,再向类中写进各种对指针操作符的重载,使用户在使用类时可以完全按照指针的语法去使用。这样做的优势很明显,我们可以根据自己的需求向类中写入各种功能,相当于“组装一个无所不能的指针”。

语法其实不难理解,只是重载一下指针的操作符"*"与"->",但是其中有一个小细节很容易被忽略,及时是有多年经验的工程师也未必能解释清楚,在这里再次感谢侯老师。我举个例子:

我们已经对操作符“*”和“->”进行了重载,当编译器在执行*sp时,返回值是*px,这很好理解,可是在执行sp->时,返回值是sp,为什么能起到和sp->一样的效果呢?

原来,CPP为了支持这种做法,在这里进行了特殊的处理,使得->可以无限次的使用,即在sp之后自动补齐->。(注:只有在这种情况下)


看完了shared_ptr,我们再来看看迭代器。

迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。迭代器修改了常规指针的接口,所谓迭代器是一种概念上的抽象:那些行为上像迭代器的东西都可以叫做迭代器。然而迭代器有很多不同的能力,它可以把抽象容器和通用算法有机的统一起来。

迭代器提供一些基本操作符:*、++、==、!=、=。这些操作和C/C++“操作array元素”时的指针接口一致。不同之处在于,迭代器是个所谓的复杂的指针,具有遍历复杂数据结构的能力。其下层运行机制取决于其所遍历的数据结构。因此,每一种容器型都必须提供自己的迭代器。事实上每一种容器都将其迭代器以嵌套的方式定义于内部。因此各种迭代器的接口相同,型号却不同。这直接导出了泛型程序设计的概念:所有操作行为都使用相同接口,虽然它们的型别不同。(以上定义摘自百度百科)

下面我们来看一下迭代器的实现:

template<class T>
struct __list_node
{
    void* prev;
    void* next;
    T data;
}
template<class T,class Ref,class Ptr>
struct __list_iterator
{
    typedef __list_iterator<T,Ref,Ptr> self;
    typedef Ptr pointer;
    typedef Ref reference;
    typeder __list_node<T>* link_type;
    link_type node;
    bool operator==(const self& x)const{return node==x.node;}
    bool operator!=(const self& x)const{return node!=x.node;}
    
    reference operator*()const{return (*node).data;}
    pointer operator->()const{return &(operator*());}
    
    self& operator++(){node=(link_type)((*node).next);return *this;}
    self operator++(int){self tmp=*this;++*this;return tmp;}
    self& operator--(){node=(link_type)((*node).prev);return *this;}
    self& operator--(int){self tmp=*this;--*this;return tmp;}
};

我们把其中隔开的两个函数抽出,简单的分析一下。

从使用者的角度来讲,只会通过右上角的方式来调用,然而实际的处理过程如左侧所示:

  • 当执行*ite时,会获得(*node).data;其中*node为一个object,data为其中的成员。
  • 当执行ite->method()时,会调用上方的operator*()获得(*node).data,返回其地址。

这样一来就完美的将原生指针node包裹在了迭代器__list_iterator中。

第五节 function-like classes (仿函数)

仿函数在标准库中有着广泛的应用,这节课我们将从标准库中抽取一个案例来探讨仿函数的用法,对于为什么要让一个类模仿函数行为,这节我们不做讨论。

通常来讲,如果一个东西可以接收小括号这种操作符我们就叫它函数,或者像函数的东西。

上面是标准库中的一段代码(有省略)。

select1st与select2st分别通过对()的重载提取pair对象的第一个元素和第二个元素。

图片中灰色处省略了部分代码,展开如下:

再来看看标准库中其它的仿函数:

我们发现标准库中的仿函数通常要继承一些古怪的base,下面是base的原型:

在这里我们不对base做任何讨论,在后面有专门讲解STL的课程会深入讲解。

第六节 namespace 经验谈

#include<iostream>

namespace lalala
{
    int a=5;
}
namespace lalala1
{
    int a=10;
}


int main()
{
    std::cout<<lalala::a<<std::endl;
    std::cout<<lalala1::a<<std::endl;
    return 0;
}

很小的话题,给出一段示例代码,相信有一定C++基础的人都可以理解。

第七节 class template

template<typename T>
class complex
{
public:
    complex(T r=0,T i=0)
    :re(r),im(i)
    {}
    complex& operator +=(const complex&);
    T real () const { return re; }
    T imag () const { return im; }
private:
    T re,im;
    friend complex& _doapl(complex*,const complex&);
};
complex<double> c1(2.5,1.5);
complex<int> c2(2,6);
解析:

template的基本使用方法,不做赘述。

在定义object时指明模板类型,传入后与符号T绑定。

PS:在template<...>的尖括号中,class与typename是等价的。

第八节 Function Template(函数模板)

class stone
{
public:
    stone(int w,int h,int we)
    :_w(w),_h(h),_weight(we)
    {}
    bool operator< (const stone& rhs) const
    { return _weight < rhs._weight; }
private:
    int _w,_h,_weight;
}
template<class T>
inline
const T& min(const T& a,const T& b)
{
    return b<a?b:a;
}
stone r1(2,3),r2(3,3),r3;
r3=min(r1,r2);
解析:

stone两个object r1,r2,传入min函数。min函数在接收参数后将参数类型与模板类型T绑定(实参引导),确认类型后a,b进行'<'操作,编译器会进入T类(stone)类内寻找对应的重载函数来执行。

PS:该用法在“(GeekBand)C++面向对象高级编程(上)第二周笔记(1)”中有过介绍。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容