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 
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 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 sp (new Foo);
sp->method(); 

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

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

2. 迭代器

迭代器也是pointer-like class。

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

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

boolan/C++面向对象高级编程 part4_第1张图片
1510468145684.png

item5. Function-like classes

1. 语法

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

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

template 
struct select1st{
    const typename Pair::first_type&
            operator()(const Pair&x) const{
        return x.first;
    }
};
template 
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 
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   
            pair(const pair&p): first(p.first), second(p.second) {}  // member template
};

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

class Base2{};
class Derived2: public Base2{};
boolan/C++面向对象高级编程 part4_第2张图片
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__<
class Ttype{
private:
    T* base_;
public:
    Ttype(T* base):base_(base) {
        cout<<"created"< b(pderived);  // ok
    return 0;
}

3. 三种模版

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

4. shared_ptr中的member template

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

Base1* ptr = new Derived1;  // up-cast
shared_ptrstpr(new Derived);  // 模拟 up-cast

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

5. 关于函数调用

#include 

using namespace std;

void func(double a) {
    std::cout <<"double"<
class Ttype{
private:
    T* base_;
public:
    Ttype(T* base):base_(base) {
        cout<<"created"< b(pderived);  // ok
    return 0;
}

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

item10. Specialization/模版特化

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

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

1. 语法

template 
struct hash{};

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

item11. 模版偏特化

偏特化种类

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

1. 个数上的偏

语法

template 
class vector{};

template 
class vector{};

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

2. 范围上的偏

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

语法

template 
class C{
public:
    C(){
        std::cout<<"cotr T"<
class C{
public:
    C(){
        std::cout<<"cotr T*"<c;
Cd;

item12. 模版模版参数

1. 语法

template  class container>
class XCLS{
    containerc;
};

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

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

template  class container>
class XCLS{
    containerc;
};

template 
using Lst = std::list;  // C++11

XCLS mylist1;  // error,list 需要指定多个模版参数 
XCLS mylist2;

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

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

template 
        class SmartPtr>
class XCLS1
{
    SmartPtrsp;
public:
    XCLS1():sp(new T){}
};

XCLS1p1;

item13. C++标准库

boolan/C++面向对象高级编程 part4_第3张图片
1510473047180.png

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


item14. 三个主题

1. variadic templates(C++11)

语法

void print(){}
template 
void print(const T& firstArg, const Types& ... args){
    std::cout<<"size of args : " << sizeof...(args) << std::endl;
    std::cout<< firstArg <
  1. 一个和一包template 参数。
  2. ...就是一个所谓的包
  3. 如何求一包的大小?sizeof...(args)
  4. 一个和一包的使用方法很灵活,不必只是递归的使用

2. auto (C++11)

语法

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

std::list 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< li;
for(auto elem : li) {
    std::cout<

item15. Reference

三种类型的变量

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

reference的常见用途

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

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

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

boolan/C++面向对象高级编程 part4_第6张图片
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;

你可能感兴趣的:(boolan/C++面向对象高级编程 part4)