boolan/C++面向对象高级编程 part4

C++面向对象高级编程 part4

2017-11-06 12:43:00


item1. 导读

将C++视为一个语言联邦,包含四个次语言:

  • C
  • Object - Oriented C++
  • Template C++
  • STL

1. 操作符重载的多种用途

  1. 转化函数/类型转化
  2. poniter-like class
  3. function-like class

item2. Conversion function

转换函数作用是将当前定义的类型转换为其他类型。

1. 语法

class Fraction {
public:
Fraction(int num, int den=1) : m_numerator(num), m_denominator(den){}
    operator double() const {  // 转换函数
        return double(m_numerator/m_denominator);
    }

private:
    int m_numerator;
    int m_denominator;
}

Fraction f(3,5);
double d = 4+f;  // f转换为0.6

注意⚠️:

  1. 转换函数没有参数。
  2. 转换函数不用声明返回值类型。
  3. 转换不应该改变被转化对象的数据。所以要加const声明。

2. 转换何时发生

  1. 显示转换
  2. 隐式转换:编译器在编译阶段自动查找匹配的转换形式。

item3. Non-explicit one argument constructor

one argument 是指一个实参。Non-explicit one argument constructor是指该构造函数可以接受一个实参,但不限形参的个数。

1. 语法

class Fraction {
public:
    Fraction(int num, int den=1) : m_numerator(num), m_denominator(den){}
    Fraction operator+(const Fraction& f) {
        return Fraction(...);
    }

private:
    int m_numerator;
    int m_denominator;
}

Fraction f(3,5);
double d = 4+f;  // 调用Non-explicit one argument constructor 将4 转换为Fraction(4,1)

Non-explicit one argument constructor 副作用

C++ primer 4th 393
可以用单个实参调用的构造函数,定义了从形参类型到该类类型的一个隐式转换

引出的问题

潜在的隐式转换引入ambiguous/歧义,造成编译器无法解释代码。

class Fraction {
public:
    Fraction(int num, int den=1) : m_numerator(num), m_denominator(den){}
    operator double() const {  // 转换函数
        return double(m_numerator/m_denominator);
    }
    Fraction operator+(const Fraction& f) {
        return Fraction(...);
    }

private:
    int m_numerator;
    int m_denominator;
}
Fraction f(3,5);
double d = 4+f;  //error, ambiguous

2. explicit关键字

  1. explict关键字限制编译器隐式的通过Non-explicit one argument constructor 将其他类型转换成被类型。
  2. explicit 常用于修饰构造函数,只需要在声明处使用,无需再定义处重复。

语法

class Fraction {
public:
    explicit Fraction(int num, int den=1) : m_numerator(num), m_denominator(den){}
    operator double() const {  // 转换函数
        return double(m_numerator/m_denominator);
    }
    Fraction operator+(const Fraction& f) {
        return Fraction(...);
    }

private:
    int m_numerator;
    int m_denominator;
}
Fraction f(3,5);
Fraction d = 4+f;  // error, conversion from double to fraction
                   // 首先f转换为double ,然后 4+f 为double,
                   // 由于explict限定,单个double值无法转化为fraction

explict 仅限制隐式转换,不限制构造函数参数个数

#include <iostream>
class Test {
    public:
    explicit Test(int a, int b =1){}
};

int main() {
    double a;
    Test t(a);  // ok
    Test t2 = 1;  // error
    return 0;
}

item4. Pointer-like classes

为什么要设计pointer-like class: 设计比普通指针功能更多的指针,智能指针。

1. 智能指针

智能指针是一种pointer-like class。

语法

template <class T>
class Shared_ptr {
public:
    T& operator *() {
        return *ptr_;
    }
    T* operator->() {
        return ptr_;
    }

    Shared_ptr(T* ptr) : ptr_(ptr) {}
private:
    T* ptr_;
};

如何使class pointer-like :

  1. 重载操作符->
  2. 重载操作符*

operator ->的问题

....
T* operator->() const {
    return px;
}
....

shared_ptr<Foo> sp (new Foo);
sp->method(); 

如果是直接调用形式:
sp->method();
等价于:
pxmethod()
但实际等价于:
px->method()
Why?

->操作符的作用在返回后会持续作用下去。

2. 迭代器

迭代器也是pointer-like class。

template <class T> 
struct __list_node{
    void* prev;
    void* next;
    T data;
};
template <class T, class Ref, class Ptr>
struct __list_itertor{
    typedef __list_itertor<T,Ref,Ptr> self;
    typedef Ptr pointer;
    typedef Ref reference;
    typedef __list_node<T>* link_type;
    
    link_type node;
    
    reference operator*() const {
        return (*node).data;
    }
    
    pointer operator->() const{
        return &(operator*());
    }
    
    self& operator++()const{...}
    self& operator--()const{...}
    
};

迭代器重载了更多的的操作符号。

1510468145684.png

item5. Function-like classes

1. 语法

template <class T1, class T2>
struct pair{
    T1 first;
    T2 second;
    pair():first(T1()),second(T2()){}
    pair(const T1& a, const T2& b) : first(a), second(b){}
};

template <class T> 
struct identity {
    const T& operator()(const T& x) const {return x;}
};

template <class Pair>
struct select1st{
    const typename Pair::first_type&
            operator()(const Pair&x) const{
        return x.first;
    }
};
template <class Pair>
struct select2nd{
    const typename Pair::first_type&
            operator()(const Pair& x) const{
        return x.second;
    }
};
  1. 需要重载operator();
  2. 重载函数可以有入参和返回值。

构造函数与函数对象/operator()的差别:构造函数调用时是类型名(),函数对象调用时是对象名()

不含数据成员的空类大小:理论是0,实现是1。


item6. Namespace

目的:防止名称冲突


item7. Class Template


item8. Function Template

1. 实参推导

编译器会在模版函数调用时,对实参进行参数推导。

  1. 类模版在创建对象时需要指明类型,函数模版不必。why?

声明类模版对象时,如果不提供模版参数类型,编译器无法获知该模版参数类型,所以不知道该创建什么对象。函数模版在调用时一定会指定参数(这时参数类型会被确认)。


item9. Member Template

1. 语法

template <class T1, class T2>
struct pair{
    typedef T1 first_type;
    typedef T2 second_type;

    T1 first;
    T2 second;
    pair():first(T1()),second(T2()){}
    pair(const T1& a, const T2& b) : first(a), second(b){}

    template <class U1, class U2>  
            pair(const pair<U1, U2>&p): first(p.first), second(p.second) {}  // member template
};

class Base1{};
class Derived1: public Base1{};

class Base2{};
class Derived2: public Base2{};
1510470869688.png
  1. 成员模版,模版参数作用在成员函数中定义。
  2. 类模版,模版参数作用在成员变量中。
  3. 成员模版在类模版的基础上增加了模版的灵活性。

成员模版常被应用在标准库的构造函数中。

理解:member template 是 class template 中的 function template

2. 标准库中的应用

注意⚠️
应用场景:如何给模版类中的模版参数类型的成员,通过函数传递给其不同于该成员模版参数类型的实参(一般模版类中的模版参数类型是base class,参数是derived class,但实际测试即使不使用member template base/derived直接赋值也是ok的)?

class base {};

class derived : public base {};

void func1(base * b){
    cout<<__func__<<endl;
}

template <class T>
class Ttype{
private:
    T* base_;
public:
    Ttype(T* base):base_(base) {
        cout<<"created"<<endl;
    }
};


int main() {
    func(int(4));

    derived* pderived;
    func1(pderived);

    Ttype<base> b(pderived);  // ok
    return 0;
}

3. 三种模版

  1. class template
  2. Function template
  3. member template

4. shared_ptr中的member template

template <typename _Tp>
class shared_ptr:public __shared_ptr<_Tp> {
    template <typename _Tp1>
            explicit shared_ptr(_Tp1* __p) :__shared_ptr(__p){}
};

Base1* ptr = new Derived1;  // up-cast
shared_ptr<Base1>stpr(new Derived);  // 模拟 up-cast

注意这里的up cast 和 模拟的up cast

5. 关于函数调用

#include <iostream>

using namespace std;

void func(double a) {
    std::cout <<"double"<<endl;
}
/*
void func(char a) {
    std::cout <<__func__<<" double"<<endl;
} */

/*
void func(int a){
    std::cout <<"int"<<endl;
}*/

class base {};

class derived : public base {};

void func1(base * b){
    cout<<__func__<<endl;
}

template <class T>
class Ttype{
private:
    T* base_;
public:
    Ttype(T* base):base_(base) {
        cout<<"created"<<endl;
    }
};


int main() {
    func(int(4));  //ok

    derived* pderived;
    func1(pderived);  // ok 

    Ttype<base> b(pderived);  // ok
    return 0;
}

// 打印
func double
func1
created
  1. 函数调用时,如果有实参-形参完全匹配的则直接调用完全匹配的函数。
  2. 函数调用时,如果参数找不到完全匹配的类型时,编译器会自动进行实参-形参的转换(up-cast也ok),如果可以转换则调用。
  3. 函数调用时,如果存在多个实参-形参转换后匹配的函数,则发生ambiguous 。

item10. Specialization/模版特化

模版是泛化。特化是将特定类型的模版特化。

理解 :模版特化的的作用
在已有模版的基础上,对某些类型的重新定义模版函数/类,差别于已有模版的实现。

1. 语法

template <class Key>
struct hash{};

template <>
struct hash<int>{
    size_t operator()(int x) const {return x;}
};
struct hash<long>{
    size_t operator()(long x) const {return x;}
};

item11. 模版偏特化

偏特化种类

  1. 个数上的偏
  2. 范围上的偏

1. 个数上的偏

语法

template <typename T,typename Alloc=...>
class vector{};

template <bool, typename Alloc=....>
class vector{};

注意⚠️:
指定被特话的模版参数的顺序要从左到右,不能跳过左边的参数特化。

2. 范围上的偏

将模版类型改变为模版类型的指针是一种范围上的缩小。

语法

template <class T>
class C{
public:
    C(){
        std::cout<<"cotr T"<<std::endl;
    }
};

template <T>
class C<T*>{
public:
    C(){
        std::cout<<"cotr T*"<<std::endl;
    }
};

C<int>c;
C<int*>d;

item12. 模版模版参数

1. 语法

template <typename T, template <typename T2> class container>
class XCLS{
    container<T>c;
};

当模版的参数类型是模版类型时,这就是模版模版参数。

2. 接受两个模版参数的模版模版参数

template <typename T, template <typename T2> class container>
class XCLS{
    container<T>c;
};

template <typename T>
using Lst = std::list<T>;  // C++11

XCLS<std::string, std::list> mylist1;  // error,list 需要指定多个模版参数 
XCLS<std::string, Lst> mylist2;

注意⚠️:
容器有多个模版参数,一般使用时只会指定一个模版参数,其他模版参数采用默认值。

3. 接受一个模版参数的模版模版参数

template <typename T, template <typename T2>
        class SmartPtr>
class XCLS1
{
    SmartPtr<T>sp;
public:
    XCLS1():sp(new T){}
};

XCLS1<string,shared_ptr>p1;

item13. C++标准库

1510473047180.png

动手实践标准库要优于看示例。


item14. 三个主题

1. variadic templates(C++11)

语法

void print(){}
template <typename T, typename ... Types>
void print(const T& firstArg, const Types& ... args){
    std::cout<<"size of args : " << sizeof...(args) << std::endl;
    std::cout<< firstArg <<std::endl;

    print(args...);
};

print(7,"this",4.2);


size of args : 2
7
size of args : 1
this
size of args : 0
4.2

  1. 一个和一包template 参数。
  2. ...就是一个所谓的包
  3. 如何求一包的大小?sizeof...(args)
  4. 一个和一包的使用方法很灵活,不必只是递归的使用

2. auto (C++11)

语法

auto是C++11的一个语法糖

std::list<std::string> c;
auto iter = c.begin();

auto iter_1;  // error
iter_1 = c.begin();

注意⚠️:
auto 仅能以“初始化”的方式使用,否则编译器无法推导auto的类型。

3. ranged-base for (C++11)

语法

for (int i :{12,2}){
    std::cout<<i<<std::endl;
}

std::list<int> li;
for(auto elem : li) {
    std::cout<<elem;
}

for(auto& elem : li) {
    std::cout<<elem;
}

item15. Reference

三种类型的变量

  1. 普通变量
  2. 指针变量
  3. 引用变量
1510473681016.png
  1. reference本质(编译器实现)是指针,但使用时不能当作指针分析问题。
  2. reference定义时一定要有初值,指示所代表的变量,不能作为其他变量的引用。
  3. reference是代表关系,使用reference 就是在使用被reference的变量。
  4. reference 与其代表的变量,大小和地址都相同,其实是假象。
1510473715040.png

reference的常见用途

作为参数类型(parameters type)和返回值(return type)类型使用

reference 通常不用于声明变量,而是作为参数类型(parameters type)和返回值(return type)类型使用。

对调用者来说,传引用比传指针一致性更好,更友好。

1510473791782.png

声明入参为const & 比& 好

如果不想在函数改变入参的值,声明入参为const& 比 &好。原理与const成员函数类似。

class A{};
void func(const A&){};
void func1(A&){}

func(aa);  // ok
func1(aa);  // error

& 不作为函数签名(signature)的一部分,const 作为函数签名的一部分

double imag(const double& a){};
double imag(const double a){};  // ambiguous;

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

推荐阅读更多精彩内容