【極客班】《c++面向對象高級編程下第一周》學習筆記

這門課主要偏重于泛型編程(generic programming)以及底層對象模型(this,vptr,vtbl,多態(polymorphism)等)。

首先提到的類成員函數是轉換函數(conversion function)和隱式單參數構造函數(non-explicit one argument constructor).如講義中提到的下面例子:

#include <iostream>

using namespace std;

class Fraction

{

public:

Fraction(int num, int den = 1)

: m_numerator(num), m_denominator(den) {

if(m_denominator != 0)

{

int gcd_val = gcd(m_numerator, m_denominator);

if(gcd_val != 1)

{

m_numerator = m_numerator / gcd_val;

m_denominator = m_denominator / gcd_val;

}

}

}

#if 1

operator double() const {

return (double)(m_numerator/m_denominator);

}

#endif

static int gcd(int a, int b)

{

if(b == 0)

return a;

else

return gcd(b, a%b);

}

Fraction operator+(const Fraction& f) {

return Fraction(m_numerator * f.m_denominator + m_denominator * f.m_numerator, m_denominator * f.m_denominator);

}

friend ostream& operator<<(ostream& os, const Fraction& f);

private

int m_numerator; //分子

int m_denominator; //分母

};

ostream& operator<<(ostream& os, const Fraction& f)

{

os << f.m_numerator << '/' << f.m_denominator;

return os;

}

int main(void)

{

Fraction f1(3,5);

Fraction f2 = f1 + 4;

cout << f1 << " " << f2 << endl;

return 0;

}

這個類中double()是conversion function,此處構造函數是隱式單參數構造函數。

這種情況下,有可能將將f轉換成double,然后將得到double與4相加,得到結果轉換成Fraction.也可能對4使用構造函數轉換成Fraction,然后將f和構造生成的Fraction對象相加,將最終結果賦給f2.這時候會產生二義性。編譯時有下面的錯誤信息:

error: use of overloaded operator '+' is ambiguous (with operand types 'Fraction' and 'int')

如果將double()函數的定義注釋掉就可以正常編譯執行,并能得到下面輸出信息:

3/5 23/5

另一個有趣的類是智能指針(shared_ptr、unique_ptr等),智能指針是像指針的類,其定義如下:

template <class T>

{

public:

??? T& operator*() const { return *px; }

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

??? shared_ptr(T* p) : px(p) {}

private:

??? T *px;

....

}

struct Foo

{

...

??? void method(void) {...}

};

shared_ptr<Foo> sp(new Foo);

Foo f(*sp);

sp->method();

對于這里的sp對象調用*或者->,實際作用的是該對象內的指針成員變量px.

另外sp->method()中sp->對應于px,那么貌似px后直接跟隨method(),感覺會有奇怪。而事實上sp后會一直帶有隱式的->,所以仍可以調用px->method函數。

還有一個比較有趣的是迭代器對象,例如__list_iterator對象如下:

template<class T, class Ref, class Ptr>

struct __list__iterator {

? typedef __list_iterator<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++ () { node = (link_type)((*node).next); return *this; } //后置++操作符

? self operator++(int) { self tmp = *this; ++*this; return tmp; } //前置++操作符

};

template<class T>

struct __list__node{

? void *prev;

? void *next;

? T data;

};

list<Foo>::iterator ite;

*ite;

ite->method();

//相當于調用(&(*node).data)->method();

//相當于調用(*node).data.method()

另外有意思的一種類是類似于函數的類(function-like class),一個簡單的小例子,代碼如下:

#includeusing namespace std;

class A

{

public:

A(int m=0,int n=0):x(m), y(n) {}

int operator()()

{

return x + y;

}

int operator()(int z)

{

return x + y + z;

}

int operator() (int z, int r)

{

return x + y + z + r;

}

private:

int x;

int y;

};

int main(void)

{

A a(3,4);

cout << a() << endl;

cout << a(2) << endl;

cout << a(2,3) << endl;

return 0;

}

在使用時可以創建對象,然后像使用函數一樣給這個對象傳遞參數,就會調用重載()的函數。

這節課中提到模板類重載()操作符的情況,課堂中提到下面例子:

template <class T>

struct identity {

const T& operator()(const T& x) const { return x; }

};

template <class T>

{

struct plus T operator() (const T&x, const T& y) const { return x + y; }

};

這兩個模板類在重載()操作符時會隱含的用到下面這兩個特殊的基類:

template <class Arg, class Result> struct unary_function{

? typedef Arg_argument_type;

? typedef Result result_type;

};

template <class Arg1, class Arg2, class Result> struct binary_function {

? typedef Arg1 first_argument_type;

? typedef Arg2 second_argument_type;

? typedef Result result_type;

};

這兩個基類參數分別為1個或者2個。

我查看了STL網頁上關于Functors?的介紹,這里面提到,所有的函數、函數指針以及重載()操作符的類都可以被稱為functor(或者function object)。而在STL算法中functor只需要零個、一個或者兩個參數,分別用generator、unary_function、binary_function,這三個functor對應于函數f(), f(x)和f(x,y).

另外,有趣的是,c++11中認為unary_function和binary_function已經過時了,不建議使用,貌似c++標準委員會建議c++17將它們刪除,stackoverflow上面有討論的頁面?,不過我沒看懂。

課堂中還講到namespace,寫個測試小函數:

#includeusing namespace std;

namespace yy1

{

int sum(const int& x,const int& y)

{

return x + y;

}

}

int main(void)

{

cout << yy1::sum(4,5) << endl;

return 0;

}

還講到了類模板和函數模板。

類模板定義如下:

template <typename T>

class Point

{

? public:

??? Point(T m = 0, T n = 0):x(m), y(n) {}

??? T DistanceFromOrigin() {return sqrt(x * x + y * y); }

? private:

??? int x;

??? int y;

};

使用方式如下:

Point<double> p(3,4);

而函數模板定義如下:

template <class T> inline const T& min(const T& a, const T&b)

{

? return b < a ? b : a;

}

在使用min函數時,會對T的類型進行推導。

另外還有類成員函數模板,其用法跟函數模板類似。

對于模板,還可以用template specialization,講義中提到下面的例子:

template <class key>

struct hash {};

template<> struct hash<char> {

? size_t operator()(char x) const { return x; }

};

template<> struct hash<int> {

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

};

還有部分特例化(partial specialization),講義中例子如下;

template <typename T, typename Alloc=...> class vector

{

? ...

};

template <typename Alloc = ...>? class vector<bool, Alloc>

{ ... }

還有比較有趣的模板模板參數(template template parameter), 看下面例子:

template<typename T, template<typename T> class SmartPtr>

class XCls

{

? private:

??? SmartPtr<T> sp;

? public:

??? XCls:sp(new T) {}

};

使用上面定義的例子如下:

XCls<string, shared_ptr> p1;


可以編寫在程序中輸出__cplusplus的值來判斷當前使用的c++標準。

我當前使用的是clang 3.4,如果使用默認編譯選項,那么輸出結果是199711 對應c++0x

如果添加-std=c++11,那么輸出結果是201103? 對應c++11

如果添加-std=c++1y,那么輸出結果是201305.

c++11中支持auto,可以自動推斷變量類型,并且c++11支持范圍操作,看下面例子:

vector v = {1,2,3,4,5};

for(auto item: v)

cout << item << endl;

另外,c++中,如果輸出對象和reference的大小和地址,其值是相等的。

但實際上reference應該是用指針來實現的。reference通常不直接定義,主要用于參數和返回類型。

類之間的關系有繼承(inheritance)和復合(composition)以及二者的結合。

而其構造函數是從內到外執行,析構函數是從外到內執行。


本節有一個有趣的習題,就是對象內存布局,看下面例子:

#include <iostream>

using namespace std;

class Base

{

int x;

char y;

public:

void f1() const{

cout << "f1" << endl;

}

virtual void g1() const {}

};

class Derived:public Base

{

char z;

public:

void s1() const{

cout << "s1" << endl;

}

virtual void g1() const{}

};

int main(void)

{

Base b1,b2;

b1.f1();

Derived d1,d2;

d1.s1();

return 0;

}

對上面程序(名稱為object_model.cpp)進行編譯,:

clang++ object_model.cpp? -g -o object_model

nm 命令查看object_model,然后能找到其中幾個函數地址:

08048880 W _ZNK4Base2f1Ev

08048950 W _ZNK4Base2g1Ev

08048940 W _ZNK7Derived2g1Ev

08048900 W _ZNK7Derived2s1Ev

使用c++filt 對demange這幾個函數名稱,得到下面信息:

mangled name?????? demangled name

_ZNK4Base2f1Ev? Base::f1() const

_ZNK4Base2g1Ev Base::g1() const

_ZNK7Derived2g1Ev Derived::g1() const

_ZNK7Derived2s1Ev Derived::s1() const

使用gdb調試object_model,然后在main函數末尾處打斷點:

(gdb) p &b1

$1 = (Base *) 0xffffced8

(gdb) p &(b1.x)

$2 = (int *) 0xffffcedc

(gdb) p &(b1.y)

$3 = 0xffffcee0 "p\211\004\b"

(gdb) p b1.f1

$4 = {void (const Base * const)} 0x8048880

(gdb) p b1.g1

$5 = {void (const Base * const)} 0x8048950

(gdb) info vtbl b1

vtable for 'Base' @ 0x8048a64 (subobject @ 0xffffced8):

[0]: 0x8048950

類似地,對b2也執行類似的操作,結果如下:

(gdb) p &b2

$6 = (Base *) 0xffffcec8

(gdb) p &(b2.x)

$7 = (int *) 0xffffcecc

(gdb) p &(b2.y)$8 = 0xffffced0 "p\211\004\b\200\206\004\bd\212\004\b\364\277\200Ap\211\004\b"

(gdb) p b2.f1

$9 = {void (const Base * const)} 0x8048880

(gdb) p b2.g1

$10 = {void (const Base * const)} 0x8048950

(gdb) info vtbl b2

vtable for 'Base' @ 0x8048a64 (subobject @ 0xffffcec8):

[0]: 0x8048950

再分別打印b1和b2這兩個變量:

(gdb) p b1

$12 = {_vptr$Base = 0x8048a64, x = 1098956788, y = 112 'p'}

(gdb) p b2

$13 = {_vptr$Base = 0x8048a64, x = 1098956788, y = 112 'p'}

(gdb) p sizeof(b1)

$14 = 12

(gdb) p sizeof(b2)

$15 = 12

可以畫出b1內存布局如下:??????????

_______________??????????????????? ---------------------------(vtbl)????????

|? vptr(0x8048a64)? ? ? ? ? | --------> |? ? ? ? 0x8048950?? () ? ?? |? ? ? ? ? ?

________________????????????????? __________________??????

|? x (4 byte) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? |

__________________

| y (1 byte)? ? ? ? ? ? ? ? ? ? ? ? ? ? |

___________________

|? padding(3 bytes)????????? ? ?? |

___________________

b2的vptr和vtbl中保存的值跟b1完全相等。但是x和y的地址完全不同。

使用gdb打印d1和d2相關信息:

(gdb) p &d1

$16 = (Derived *) 0xffffceb8

(gdb) p &(d1.x)

$17 = (int *) 0xffffcebc

(gdb) p &(d1.y)

$18 = 0xffffcec0 "\001"

(gdb) p d1.f1

$19 = {void (const Base * const)} 0x8048880

(gdb) p d1.s1

$20 = {void (const Derived * const)} 0x8048900

(gdb) p d1.g1

$21 = {void (const Derived * const)} 0x8048940

(gdb) info vtbl d1

vtable for 'Derived' @ 0x8048a30 (subobject @ 0xffffceb8):

[0]: 0x8048940

(gdb) p &d2

$22 = (Derived *) 0xffffcea8

(gdb) p &(d2.x)

$23 = (int *) 0xffffceac

(gdb) p &(d2.y)

$24 = 0xffffceb0 "\b\206\377\367`\235\004\b0\212\004\b\273\211\004\b\001"
(gdb) p d2.f1

$25 = {void (const Base * const)} 0x8048880

(gdb) p d2.s1

$26 = {void (const Derived * const)} 0x8048900

(gdb) p d2.g1

$27 = {void (const Derived * const)} 0x8048940

(gdb) info vtbl d2

vtable for 'Derived' @ 0x8048a30 (subobject @ 0xffffcea8):

[0]: 0x8048940

d1內存布局如下(12 bytes):

_______________? ? ? ? ? ? ? ? ? ? ---------------------------(vtbl)

|? vptr(0x8048a30)? ? ? ? | --------> |? ? ? ? 0x8048940? (Derived::g1())? ? ? |

________________????????????????? __________________

|? x ?? (4 bytes)? ? ? ? ? ? ? ? ? ?? |

__________________

| y???????? (1 bytes) ????? ? ? ? ? ? ?? |

___________________

|? z?????? (1 byte)??????????????????? |

____________________

|? padding(2 bytes)? ? ? ? ? ? ? |

___________________

d2中vptr和vtbl的值也是完全相等的,但x、y和z的地址不相等。

可以使用clang打印出對象的布局信息,使用下面命令(可能需要先將std以及cout注釋掉):

clang -cc1? -fdump-record-layouts object_model.cpp

生成一個object_model.cpp.002t.class文件,里面有類似下面的信息(要先用c++filt對符號進行轉換得到下面信息):

Vtable for Base

Base::vtable for Base: 3u entries

0? ? (int (*)(...))0

4? ? (int (*)(...))(& typeinfo for Base)

8? ? Base::g1


Class Base

size=12 align=4

base size=9 base align=4

Base (0x7f23ee353a80) 0

vptr=((& Base::vtable for Base) + 8u)


Vtable for Derived

Derived::vtable for Derived: 3u entries

0? ? (int (*)(...))0

4? ? (int (*)(...))(& typeinfo for Derived)

8? ? Derived::g1


Class Derived

size=12 align=4

base size=10 base align=4

Derived (0x7f23ee353e70) 0

vptr=((& Derived::vtable for Derived) + 8u)

Base (0x7f23ee353ee0) 0

primary-for Derived (0x7f23ee353e70)

按照這個生成信息可以知道,clang 3.4采用4字節對齊,并且虛函數表中有三項:

第一項是0

第二項是其type info

第三項才是其中的虛函數

g++中也有類似命令:

g++ -fdump-class-hierarchy object_model.cpp


在review其它同學的筆記時,有提到new/delete和malloc/free的區別。new/delete在自由存儲區,而malloc/free是在堆上,new/delete是類型安全的,而malloc/free不是類型安全。在c++中只應當使用new/delete,盡量不要使用malloc/free.雖然二者有free storage和堆的差別,但實際上跟編譯器實現有關,可能這兩個區域位于相同的區域,也可能位于不同的區域。關于二者的比較可以參看下面的文章:

http://www.cnblogs.com/jiayouwyhit/archive/2013/08/06/3242124.html

http://stackoverflow.com/questions/240212/what-is-the-difference-between-new-delete-and-malloc-free



最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容