zoukankan      html  css  js  c++  java
  • C++学习的小Tips

    Classes的两个经典分类
    Class without pointer member(s)
      complex
    Class with pointer member(s)
      string


    Header中的防卫式声明
    complex.h
    #ifndef __COMPLEX__
    #define __COMPLEX__
    //code
    #endif

    inline function
    函数若在class body内定义完成,便自动成为inline function的候选人
    class body之外定义的函数,需要加上inline关键字,以此建议编译器将其编译为inline function


    constructor(ctor, 构造函数)
    class complex
    {
      public:
        complex (double r = 0, double i = 0) // 默认实参
          : re (r), im (i)  // 初值列
        { }
    };
    构造函数中用初值列初始化变量,而最好不在函数体中用"="来赋值
    一个变量的构造有两个阶段:初始化,赋值;所以使用初值列的机制效率更好(省去了一个赋值阶段)
    ctor可以有多个重载


    ctor放在private区
    Singleton单例模式
    class A
    {
      public A& getInstance();
        setup() {...};
      private:
        A();
        A(const A& rhs);
        ...
    };

    A& A::getInstance()
    {
      static A a;
      return A;
    }

    A::getInstance().setup();
    外界不能直接创建对象的实例,只能通过class内的函数来创建


    const member function(常量成员函数)
    double real() const { return re; }
    这里的const关键字声明将一定不改变类内的成员变量re

    例如:若不写上述的const,以下代码块将会编译错误
    {
      const complex c1(1, 2)
      cout << c1.real() << c1.imag();
    }
    因为使用者创建了一个const的对象c1,即规定c1是一个常量,其中的成员变量也将是const的,没有可能被改变;而在调用成员函数real()时,并未声明其为一个常量成员函数,即类内的成员变量re将"有可能"被改变,这是矛盾的


    参数传递:pass by value VS. pass by reference(to const)
    按值传递将原封不动的复制参数,按引用传递相当于传递参数的地址(底层是指针);所以通常传引用效率更好
    例如:complex& operator += (const complex&);
    目的是按reference传入一个complex对象(的引用),并声明传入的对象本身禁止被函数修改,[pass by reference(to const)]
    参数的传递尽量by reference


    返回值传递:pass by value VS. pass by reference
    什么情况下可以pass by reference(to const)?
    什么情况下可以return by reference?
    例如后面的"__doapl"函数,它的第一参数将会被改动,第二参数不会被改动
    此时第二参数可以pass by reference(to const),第一参数可以return by reference

    下面是一个return by reference的正确例子:
    inline complex& __doapl(complex* ths, const complex& r)
    {
      ths->re += r.re;
      ths->im += r.im;
      return *ths;
    }
    inline complex& complex::operator += (const complex& r)
    {
      return __doapl(this, r);
    }
    注意第一参数传入的是一个指针,指向的对象不是函数体内临时创建的local变量,而是函数外已经存在的某个变量,因而可对其return by reference

    什么情况下不能return by reference?
    函数体内的local变量不能return by reference。因为当函数结束,其local变量也消亡了,若return by reference传出的一个"地址"指向的内容已经坏掉了
    例如:
    inline complex& __doapl(complex* ths, const complex& r)
    {
      return (ths->re + r.re);
    }


    friend(友元)
    类中的private成员re与im不能被外界直接取用(可通过类内函数取用),但类内的友元函数可以直接取得其"朋友"的私有成员
    class complex
    {
      private:
        double re, im;
        friend complex& __doapl(complex*, const complex&);
    };

    inline complex& __doapl(complex* ths, const complex& r)
    {
      ths->re += r.re; //自由取得friend的private成员
      ths->im += r.im;
    }

    相同class的各个objects互为friends(友元)
    class complex
    {
      public:
        int func(const complex& param)
        { return param.re + param.im; }
        // 这里直接取用了"朋友"的私有成员
      private:
        double re, im;
    };

    {
      complex c1(2,1);
      complex c2;
      // c1与c2互为friends
      c2.func(c1);
    }


    编写一个class的小总结
    数据一定放private中
    参数传递与返回值传递尽可能by reference
    在class body中的成员函数,需要加const的一定要加(常量成员函数)
    ctor尽量用它的初值列机制


    Big Three 三个特殊函数
    拷贝构造,拷贝赋值,析构函数
    一定要在拷贝赋值中检查是否self assignment
    inline String& String::operator=(const String& str)
    {
      if(this == &str)
        return *this;
      //...
    }


    内存管理
    new:先分配内存,再调用ctor
    Complex* pc = new Complex(1,2);
    编译器转化为:
    Complex *pc;
    void* mem = operator new(sizeof(Complex)); //分配内存
    pc = static_cast<Complex*>(mem); //强制类型转换
    pc->Complex::Compelex(1,2); //调用构造函数
    //Complex::Compelex(pc相当于隐藏在此的一个this指针,1,2);

    delete:先调用dtor,再释放内存
    delete pc;
    编译器转化为:
    Complex::~Complex(pc); //析构函数
    operator delete(pc); //释放内存

    注:"operator new()"和"operator delete()"是特殊的C++系统函数;前者用于分配内存,其内部调用malloc();后者释放内存,其内部调用free()


    静态数据成员与静态成员函数
    class内静态的数据只存在一份(存在于"全局/静态存储区")
    静态函数没有this指针,所以静态函数只能处理位于全局/静态存储区的静态数据,而不能访问class内的非静态数据成员
    例如:设计一个银行账户
    class Account
    {
    public:
      static double m_rate; //利率
      static void set_rate(const double& x)
      { m_rate = x; }
    };

    double Account::m_rate = 0.01; //静态数据成员的赋值方式1(静态数据成员的初始化)

    int main()
    {
      Account::set_rate(0.02); //静态数据成员的赋值方式2(通过class name调用静态函数)
     
      Account a; //静态数据成员的赋值方式3(通过object调用静态函数)
      a.set_rate(0.03);
    }


    class template类模板
    示例:
    template<typename T>
    class complex
    {
    public:
      complex(T r = 0, T i = 0)
        :re (r), im (i)
      { }
    private:
      T re, im;
    };

    {
      complex<double> c1(2.0, 1.0); //class中的"T"会全部替换成"double"
      complex<int> c2(2, 1); //class中的"T"会全部替换成"int"
    }

    补充一个类模板的例子:
    const std::size_t DefaultStackSize = 1024;
    template<typename T, std::size_t n = DefaultStackSize>
    class Stack
    {
    public:
      void Push(const T const& element);
      ...
    private:
      std::vector<T> m_Members;
      std::size_t m_nMaxSize = n;
    };

    类模板参数可以是任意类型名或常量(常量仅限int或enum类型);
    模板实参可以是一个int型或enum型的常量(此处是size_t,其实质是无符号整型;size_t是标准C库中定义的,在32位系统中为unsigned int,占4个字节;在64位系统中为 long unsigned int,占8个字节);
    n是编译时定义的常量,n可以有默认值;
    成员变量m_nMaxSize用n进行了初始化;


    function template函数模板
    例如有一个stone类:
    class
    {
    public:
      stone(int w, int h, int we)
        :width(w), height(h), weight(we)
        { }
      bool operator < (const stone& rhs) const
      { return weight < rhs.weight; }
    private:
      int width, height, weight;
    };

    要创建两个stone对象,并且比较stone的重量:
    stone r1(1,2,3), r2(2,3,4);
    r3 = min(r1, r2);

    若有函数模板:
    template<class T>
    inline const T& min(const T& a, const T& b)
    {
      return b < a ? b : a;
    }
    则函数模板中的"T"将会全部替换成类名"stone"
    因为r1和r2都是stone类,编译器会对function template进行argument deduction参数推导

    而在进行对象的大小比较时,因为"T"为"stone",于是调用stone::operator<

    补充一个函数模板的例子:
    template <typename T> T Max(T a, T b)
    {
      return a>b?a:b;
    }

    1.对于不同的实参类型,模板函数定义了一族函数;
    2.当传递模板实参时,函数模板依据实参的类型进行实例化;
    3.可以显式指定模板的实参类型,例如:
    对函数模板

    template <typename T>
    inline T const& Max(const T const& a, const T const& b)
    {...}

    以下代码

    Max<double>(1,2)
    函数模板会实例化一个函数
    inline double const& Max(const double const& a, const double const& b)
    {...}
    此时参数1,2也将被转换成double型;
    4.函数模板可以重载;
    5.当重载函数模板时,将改变限制在:显式指定模板参数;
    6.所有的重载版本的声明必须位于它们被调用的位置之前;


    组合与继承

    Composition(复合) 表示”has-a”


    复合关系下的构造与析构
    构造由内而外
    Container的构造函数首先调用Component的default构造函数,然后再执行自己

    析构由外而内
    Container的析构函数首先执行自己,然后再调用Component的析构函数


    Delegation(委托) or Composition by reference
    桥接模式(Handle/Body模式) or pImpl(Pointer to Implementation)
    有一个String类,除了包含class必要的声明之外,实际的实现方式定义在另一个类StringRep中,在String类中设置一个指针指向具体实现的类StringRep

    //file String.hpp
    class StringRep; //声明
    class String
    {
    public:
      String(); //默认构造
      String(const char* s); //拷贝构造
      String(const String& s); //拷贝构造
      String& operator=(const String& s); //拷贝赋值
      ~String(); //析构
    private:
      StringRep* rep; //Handle/body(pImpl)
    }

    //file String.cpp
    #include "String.hpp"
    namespace {
      class StringRep {
        friend class String;
        StringRep(const char* s);
        ~StringRep();
        int count;
        char* rep;
      };
    }

    Inheritance(继承) 表示"is-a"

    构造由内而外
    Derived(派生类/子类)的构造函数首先调用Base(基类/父类)的default构造函数,然后执行自己

    析构由外而内
    Derived的析构函数先执行自己,然后调用Base的在析构函数

    base class的dtor必须是virtual,否则会出现undefined behavior


    虚函数与多态
    Inheritance(继承) with virtual functions(虚函数)
    non-virtual函数:你不希望derived class重新定义(override/复写)它;
    virtual函数:你希望derived class重新定义(override/复写)它,并且你对它也有默认定义;
    pure virtual函数:你希望derived class一定要重新定义(override/复写)它,你对它没有默认的定义。


    conversion function 转换函数
    class Fraction
    {
    public:
      Fraction(int num, int den=1)
        :m_numerator(num), m_denominator(den) {}
      operator double() const
      {
        return (double)(m_numerator*1.0 / m_denominator);
      }
    private:
      int m_numerator; //分子
      int m_denominator; //分母
    };

    在执行以下代码块时:
    {
    Fraction f(3,5);
    double d=4+f;
    }
    先构造一个Fraction 3/5,然后尝试double与Fraction相加,并得到一个double值
    编译器会先检查是否定义了double+Fraction的operator+,若是即调用operator+函数,否则编译器会检查class Fraction是否定义了Fraction to double的转换函数,若是则调用该转换函数,否则编译错误


    non-explicit-one-argument ctor
    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);
      Fraction d2=f+4;
    }
    左操作数是Fraction、右操作数是double,而operator+函数左操作数是Fraction(隐含的this指针)、右操作数也是Fraction;因此编译器会尝试调用non-explicit ctor将"4"转"Fraction(4,1)",然后调用operator+


    conversion function 与 non-explicit-one-argument ctor并存
    class Fraction
    {
    public:
      Fraction(int num, int den=1)
        :m_numerator(num), m_denominator(den) {}

      //conversion function
      operator double() const
      { return (double)(m_numerator*1.0 / m_denominator); }
      //non-explicit-one-argument ctor
      Fraction operator+(const Fraction& f)
      { return Fraction(...); }

    private:
      int m_numerator; //分子
      int m_denominator; //分母
    };

    在执行以下代码块时:
    {
      Fraction f(3,5);
      Fraction d2=f+4;
    }
    因为二者任一均可调用,产生二义性,编译出错


    explicit-one-argument ctor
    class Fraction
    {
    public:
      explicit 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);
      Fraction d2=f+4; //[error] 改成"Fraction d2=f+Fraction(4);"可通过
    }
    将会报错,因在"explicit"关键字的限定下,operator+函数的实参一定要是Fraction类型,就算仅需要一个int型参数也能构造Fraction(例如Fraction(4)也就是4/1),但explicit仍不允许此行为发生


    pointer-like class 关于智能指针
    智能指针将一个一般指针封装到一个class中,并且重载*与->,使得智能指针不仅能实现一般指针的操作,而且能在class内扩展其它的操作
    智能指针的一般框架:
    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; //指向class T类型的指针
      //...  
    };

    相当于有一个"智能指针" shared_ptr(实际是一个class),其中包含一个"真正指针" px(私有变量;是一个指向class T类型的指针)

    使用示例:
    struct Foo
    {
      //...
      void method(void) {...}
    };

    shared_ptr<Foo> sp(new Foo);
    Foo f(*sp);
    sp->method();
    首先,声明一个指向class Foo类型的智能指针sp;
    然后,*sp会返回*px(class Foo对象)的reference,以此通过class Foo的拷贝构造函数创建class Foo对象f
    最后,sp->会返回px(指向class Foo类型的一般指针),所以sp->method()也就相当于px->method()


    pointer-like class 关于迭代器
    struct __list_node //链表元素
    {
      void* prev;
      void* next;
      T data; //这里T假定为struct Foo
    }

    struct __list_iterator //链表迭代器
    {
      //...
      typedef __list_node<T>* link_type;
      link_type node; //指向__list_node object的指针

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

      //typedef T* pointer
      pointer operator->() const
      {
        return &(operator*()); //会调用class内的operator*函数,返回一个T对象(的reference)
        //然后再用&取地址,返回一个T*类型的指针
      }
      //此外,迭代器不仅需要处理*和->,一般还需要处理++ --操作等,这里不扩展了...
    };

    使用示例:
    list<Foo>::iterator ite;
    ite->method();
    首先,申请一个迭代器ite,该迭代器是元素类型为class Foo的链表
    然后,有一个操作:ite->method(),通过迭代器ite调用class Foo的成员函数method()
    意思是调用Foo::method();
    相当于(*ite).method();因为*ite会获得一个Foo object;
    相当于(&(*ite))->method();因为&(*ite)会获得Foo object的指针,最后即通过该指针调用method();

    因为"ite->method()"最终相当于"(&(*ite))->method()",为了响应这种操作需求,这也就是为何struct __list_iterator内的operator->函数写法的原因 ["&(operator*())"响应"&(*ite)"]


    partial specialization,模板偏特化 - 个数的偏
    template<typename T, typename Alloc=...>
    (1)
    class vector
    {
      ...
    };

    绑定第一个模板参数为bool类型:
    (2)
    class vector<bool, Alloc=...>
    {
      ...
    };

    这样,当模板的第一个参数限定为bool类型时,将会使用代码片(2),而不是(1)
    注:只能从第一个模板参数开始绑定一个或连续的多个参数


    partial specialization,模板偏特化 - 范围的偏
    template <typename T>
    (1)
    class C
    {
      ...
    };

    当模板参数范围限定为指向T类型的指针:
    (2)
    class C<T*>
    {
      ...
    };

    这样,当以T类型创建class C时将使用(1);而以T*类型创建class C时将使用(2)
    例如:
    C<string> obj1; //使用(1)创建obj1
    C<string*> obj2; //使用(2)创建obj2

    reference与pointer的一些对比
    指针在声明时可以不初始化,而引用在声明时就必须对其初始化,并且在对引用初始化之后,不能再给它赋值
    例如:
    int x=0;

    int* px = &x; //ok

    int* px;
    px = &x; //ok

    int& rx = x; //ok

    int& rx; //no;必须在声明时初始化

    int y;
    rx = y; //no;在初始化后不能再对其赋值

    object与其reference的大小相同,地址也相同:
    sizeof(rx) == sizeof(x)
    &rx == &r
    (虽然编译器会这么告诉你,但实际这是假象)

    reference通常不用于声明变量,而通常用于参数类型(parameters type)和返回类型(return type)的描述

    以下被视为"same signature",二者不能同时存在:
    double imag(const double& im) ... {...}
    double imag(const double  im) ... {...}

    例如:
    double d=1.0;
    imag(d); //ambiguity
    若以上两个函数并存,imag(d)即产生二义性,因为二者均可调用,编译器无法决定选择哪一个

    扩展:
    问:const是不是函数签名的一部分?
    答:是
    例如:
    int f(...) const {...}
    int f(...) {...}
    它们被认为是两个不同的函数(函数签名不同)

  • 相关阅读:
    C#中,对Equals()、ReferenceEquals()、==的理解
    C#语言中的Main()可以返回int值
    C#中支持的运算符
    C#中,对象格式化的理解
    正则表达式
    .NET三年
    C#中,可重载的运算符
    c#中,const成员和readonly成员的区别
    c#中,struct和class的区别
    jQuery制作图片旋转效果
  • 原文地址:https://www.cnblogs.com/junjie_x/p/7531853.html
Copyright © 2011-2022 走看看