• [GeekBand] C++ 高级编程技术 (1)


    一、类型转换

    class Fraction

    {

    public:

     

    explicit Fraction(int num, int den=1)

    : m_numerator(num), m_denominator(den)

    { cout << m_numerator << ' ' << m_denominator << endl; }

            ......

    operator double() const {

    return (double)m_numerator / m_denominator;

    }

     

            ......

    private:

    int m_numerator; //

    int m_denominator; //

    };

        

    1. 转出去——利用转换函数(Conversion Function)

      从一个类中转出去的方法是对int(),doube()等"操作符"进行重载。

    值得注意的一点是,所有的转换函数都不需要声明返回参数类型,也不需要输入参数。可以通过(double) obj_of_Fraction 进行调用。

    除了显式使用转换函数以外,转换函数也会进行隐式调用,例如:

    Fraction f(3,5);

    double d = 4 + f;

    在这种情况下,首先,编译器会检查是否存在+运算符的合适的函数重载。如果没有合适的函数重载被定义,则会先将f隐式转换为double,再进行相加。

    1. 转进来——合理使用explicit关键字避免二义性

      构造函数可以实现"转进来"的目的。Fraction(4)可以创造一个分数,其分母为1,分子为4。

      同样地,"转进来"的构造函数也可以被隐式调用。

      例如对于语句 Fraction d2 = f + 4,第一步,编译器仍然会检查是否存在+运算符的合适的重载Fraction::operator +(double);如果没有合适的函数重载被定义,则会将4转换为分数,然后再进行相加。

      不过,这种隐式转换有时反而是不利的,可能会导致二义性。考虑下面这种情况,如果在类的定义中存在下面的函数:

    Fraction operator+(const Fraction& f) {

    cout << "operator+(): " << f.m_numerator << ' ' << f.m_denominator << endl;

    //... plus

    return f;

    }

    那么,对于d2 = f + 4;有以下两种路径,产生了二义性从而不能通过。

    1. f转double->double 相加->结果转回Fraction->赋值
    2. 4转Fraction->Fraction相加->赋值

    为了消除这一二义性,在构造函数前面加了explicit关键字。Explicit关键字的意义是,这个函数只可以被显示调用,而不能被任何形式地隐式调用。 (由于这种声明,Fraction a = 1也不能被调用了。)

    如果给拷贝构造函数加explicit关键字,则Fraction B(A) 可以使用,Fraction B = A也不能使用了。

     

    二、pointer-like class

    1) 智能指针

    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;

    ......

    }

    这里需要特别说明一下对于->操作符重载的调用,考虑如下代码:

    shared_ptr<Foo> sp(new Foo);

    Foo f(*sp);

    sp->method();

     

    将会自动转化为px->method();这里涉及到了'->'的特别行为。'->'运算符的一个特点是,它不会再运算过程中被消耗。'sp->'的运算结果是px,又由于'->'没有被消掉,因此其转换为px->method()。

    2)迭代器——一种特别的智能指针

    其和智能指针基本一致,只是还需要对++和—运算符进行重载。

    T& operator *() const{ return (*node).data; }

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

    需要注意的一点是,这里显式调用了operator*(),这种形式是非常方便的。

    三、Function-like class(仿函数)

    即重载()运算符(又名函数调用操作符,其重载时的默认调用位置就在中间),在标准库中被广泛使用。

    template <class pair>

    struct select1st: public uniary_func<.......>{

    const typename pair::first_type&

    operator()(const pair &x) const{

    return x.first;

    }

    }

    这样就能够像函数一样使用这个类,select1st(pair(1,2));

    四、泛型编程初步

    泛型编程和面向对象编程是C++的两个重要方面。

    • 类模板和函数模板的作用

      通常来讲,类模板可以被用来做容器、迭代器等;而函数模板则是被用来做泛型算法。

    只需要使用template <class/typename T>进行声明

    类模板在使用时需要指明参数类型,但函数模板可以自动进行实参推导。

    • 类的成员模板

       

      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) {}

      };

      注意其中的成员函数——拷贝构造函数。通过这种成员模板方式,可以实现拷贝构造。

    • 模板的特化

      模板是一种泛化操作,而特化则是一种逆向的操作,其语法是:

      template<>

      struct hash<int>{

      size_t operator() (int x) const {return x;}

      }

    通过这种形式,可以给int参数类型单独制定方法,同样的语法也可以使用与函数模板。

    cout<<hash<int>()(1000);

    其中的hash<int>() 是声明了一个无参临时对象。

    • 模板偏特化

    4.1个数上的偏特化

    与全特化类似,这种偏特化也是固定参数进行特化。

    泛化声明:

    template<class T1,class T2>

    class vector

    {......}

     

    偏特化声明:

    template<class T2>

    class vector <bool,T2>

    {......}

    注意偏特化声明中的T2虽然仍写为T2,但与全泛化的T2并没有关系。

    4.2范围上的偏特化

    这种偏特化是指对于 const * p类型、*p类型、p类型的限制,偏特化就是按照范围划分不同的方法,例如如下的代码:

    template <class T>

    class C{......}

     

    template <class T>

    class C<T*>{......}

    当仅有第一种定义时,会自动调用第一种类型的模板;然而当有上两种定义时,对于指针类型的模板参数,可以使用特别的方法定义,这种分类是十分必要的。

    • 模板模板参数

    template<typename T,

    template <typename T>

    class Container

    >

    class XCls

    {

    private:

    Container<T> c;

    public:

    XCls()

    {

    for(long i=0; i< 100; ++i)

    c.insert(c.end(), T());

    }

    };

     

    注意第二个模板参数template <typename T> class SmartPtr ,其本身又是一个模板

    使用方法:

    template <class T>

    using Lst = list<T, allocator<T>>;

    XCls<string, Lst> mylist;

     

    注意:下面的用法不是模板模板参数。

    template< class T ,class Sequence = deque<T>>

    class stack{

    protected:

    Sequence C;

    }

    其中第二个参数制定了stack要依靠什么基本容器类型进行重载,其具有默认值deque。

    使用方式为: stack<int,list<int>> s2; 这里的list已经不是模板了,它在具体化之前已经变成了普通的模板参数。

    • 数量不定的模板参数(Since C++ 11,使用包)

       

      void print(){}

      template< typename T , typename... Types >

      void print(const T& firstArg,const Types &... args){

      cout<<firstArg<<endl;

      print(args...);

      }

    …是C++11中的新类型,…代表包(package),也就是以容器存储的数量不定的参数。对于这种数量不定的模板参数,始终采取将参数分为一个和一包的办法,这个包可以作为模板参数,也可以作为函数参数。在print()中,其采用了递归调用方法。也可以将第二参数其作为容器使用。

    五、C++的两个常用语法糖

    1. auto类型

      list<string> C;

      auto ite = find(C.begin(),C.end(),target);

      其中的auto类型会自动被推导成 list<string>::iterator

    2. 新的for语法

      for(variable:container){

      ......

      }

    变量会在容器中遍历,直到变量结尾。

    e.g.

    for(auto i:{1,2,3,4}){

    ......

    }

    这种语法搭配引用可以修改容器中的值。

    for(auto &elem:vector_obj){

    elem *= 3;

    }

  • 相关阅读:
    django查询今天,昨天,一周,分组统计月,年
    Rancher2.4.3 Rest API修改镜像地址
    Django-filter实现动态过滤与排序
    Python实现随机生成头像
    Python项目目录结构
    Django继承AbstractUser扩展字段
    Celery 监控方案
    Django项目配置快速启动
    Docker镜像分析工具dive
    Ddjango 多个app的urls配置
  • 原文地址:https://www.cnblogs.com/shawnChi/p/5740625.html
Copyright © 2020-2023  润新知