標籤:地址 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