(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 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 sp(new Foo);
Foo f(*sp);
sp->method();
//px->method();
解析:

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

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

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

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


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

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

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

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

template
struct __list_node
{
    void* prev;
    void* next;
    T data;
}
template
struct __list_iterator
{
    typedef __list_iterator self;
    typedef Ptr pointer;
    typedef Ref reference;
    typeder __list_node* 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;}
};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

第六节 namespace 经验谈

#include

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


int main()
{
    std::cout<

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

第七节 class template

template
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 c1(2.5,1.5);
complex 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
inline
const T& min(const T& a,const T& b)
{
    return b
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)”中有过介绍。

你可能感兴趣的:((GeekBand)C++面向对象高级编程(下)第一周笔记(1))