美文网首页
boolan/C++面向对象高级编程 part4

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

作者: 我才是helo | 来源:发表于2017-11-12 16:20 被阅读0次

    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;
    

    相关文章

      网友评论

          本文标题:boolan/C++面向对象高级编程 part4

          本文链接:https://www.haomeiwen.com/subject/bkfomxtx.html