C++學習的小Tips

來源:互聯網
上載者:User

標籤:地址   action   inter   返回   explicit   使用   virtual   osi   namespace   

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"
}


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<


組合與繼承

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)"]


(待續)

C++學習的小Tips

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.