【極客班】《c++面向?qū)ο蟾呒壘幊滔碌谝恢堋穼W(xué)習(xí)筆記

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

首先提到的類成員函數(shù)是轉(zhuǎn)換函數(shù)(conversion function)和隱式單參數(shù)構(gòu)造函數(shù)(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;

}

這個(gè)類中double()是conversion function,此處構(gòu)造函數(shù)是隱式單參數(shù)構(gòu)造函數(shù)甩卓。

這種情況下凝垛,有可能將將f轉(zhuǎn)換成double,然后將得到double與4相加,得到結(jié)果轉(zhuǎn)換成Fraction.也可能對4使用構(gòu)造函數(shù)轉(zhuǎn)換成Fraction,然后將f和構(gòu)造生成的Fraction對象相加恃锉,將最終結(jié)果賦給f2.這時(shí)候會產(chǎn)生二義性隔盛。編譯時(shí)有下面的錯(cuò)誤信息:

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

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

3/5 23/5

另一個(gè)有趣的類是智能指針(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對象調(diào)用*或者->醉锄,實(shí)際作用的是該對象內(nèi)的指針成員變量px.

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

還有一個(gè)比較有趣的是迭代器對象榆鼠,例如__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();

//相當(dāng)于調(diào)用(&(*node).data)->method();

//相當(dāng)于調(diào)用(*node).data.method()

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

#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;

}

在使用時(shí)可以創(chuàng)建對象亥鸠,然后像使用函數(shù)一樣給這個(gè)對象傳遞參數(shù)妆够,就會調(diào)用重載()的函數(shù)。

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

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

};

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

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;

};

這兩個(gè)基類參數(shù)分別為1個(gè)或者2個(gè)神妹。

我查看了STL網(wǎng)頁上關(guān)于Functors?的介紹,這里面提到家妆,所有的函數(shù)鸵荠、函數(shù)指針以及重載()操作符的類都可以被稱為functor(或者function object)。而在STL算法中functor只需要零個(gè)伤极、一個(gè)或者兩個(gè)參數(shù)蛹找,分別用generator、unary_function哨坪、binary_function庸疾,這三個(gè)functor對應(yīng)于函數(shù)f(), f(x)和f(x,y).

另外,有趣的是当编,c++11中認(rèn)為unary_function和binary_function已經(jīng)過時(shí)了届慈,不建議使用,貌似c++標(biāo)準(zhǔn)委員會建議c++17將它們刪除忿偷,stackoverflow上面有討論的頁面?金顿,不過我沒看懂。

課堂中還講到namespace,寫個(gè)測試小函數(shù):

#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;

}

還講到了類模板和函數(shù)模板鲤桥。

類模板定義如下:

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);

而函數(shù)模板定義如下:

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

{

? return b < a ? b : a;

}

在使用min函數(shù)時(shí)揍拆,會對T的類型進(jìn)行推導(dǎo)。

另外還有類成員函數(shù)模板茶凳,其用法跟函數(shù)模板類似礁凡。

對于模板高氮,還可以用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>

{ ... }

還有比較有趣的模板模板參數(shù)(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的值來判斷當(dāng)前使用的c++標(biāo)準(zhǔn)剪芍。

我當(dāng)前使用的是clang 3.4,如果使用默認(rèn)編譯選項(xiàng)窟蓝,那么輸出結(jié)果是199711 對應(yīng)c++0x

如果添加-std=c++11,那么輸出結(jié)果是201103? 對應(yīng)c++11

如果添加-std=c++1y罪裹,那么輸出結(jié)果是201305.

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

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

for(auto item: v)

cout << item << endl;

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

但實(shí)際上reference應(yīng)該是用指針來實(shí)現(xiàn)的匈挖。reference通常不直接定義碾牌,主要用于參數(shù)和返回類型。

類之間的關(guān)系有繼承(inheritance)和復(fù)合(composition)以及二者的結(jié)合儡循。

而其構(gòu)造函數(shù)是從內(nèi)到外執(zhí)行舶吗,析構(gòu)函數(shù)是從外到內(nèi)執(zhí)行。


本節(jié)有一個(gè)有趣的習(xí)題择膝,就是對象內(nèi)存布局誓琼,看下面例子:

#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)進(jìn)行編譯,:

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

nm 命令查看object_model,然后能找到其中幾個(gè)函數(shù)地址:

08048880 W _ZNK4Base2f1Ev

08048950 W _ZNK4Base2g1Ev

08048940 W _ZNK7Derived2g1Ev

08048900 W _ZNK7Derived2s1Ev

使用c++filt 對demange這幾個(gè)函數(shù)名稱肴捉,得到下面信息:

mangled name?????? demangled name

_ZNK4Base2f1Ev? Base::f1() const

_ZNK4Base2g1Ev Base::g1() const

_ZNK7Derived2g1Ev Derived::g1() const

_ZNK7Derived2s1Ev Derived::s1() const

使用gdb調(diào)試object_model,然后在main函數(shù)末尾處打斷點(diǎn):

(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也執(zhí)行類似的操作,結(jié)果如下:

(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這兩個(gè)變量:

(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內(nèi)存布局如下:??????????

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

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

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

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

__________________

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

___________________

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

___________________

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

使用gdb打印d1和d2相關(guān)信息:

(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內(nèi)存布局如下(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

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

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)

按照這個(gè)生成信息可以知道帖鸦,clang 3.4采用4字節(jié)對齊,并且虛函數(shù)表中有三項(xiàng):

第一項(xiàng)是0

第二項(xiàng)是其type info

第三項(xiàng)才是其中的虛函數(shù)

g++中也有類似命令:

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


在review其它同學(xué)的筆記時(shí)胚嘲,有提到new/delete和malloc/free的區(qū)別作儿。new/delete在自由存儲區(qū),而malloc/free是在堆上馋劈,new/delete是類型安全的攻锰,而malloc/free不是類型安全晾嘶。在c++中只應(yīng)當(dāng)使用new/delete,盡量不要使用malloc/free.雖然二者有free storage和堆的差別,但實(shí)際上跟編譯器實(shí)現(xiàn)有關(guān)娶吞,可能這兩個(gè)區(qū)域位于相同的區(qū)域垒迂,也可能位于不同的區(qū)域。關(guān)于二者的比較可以參看下面的文章:

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



最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末妒蛇,一起剝皮案震驚了整個(gè)濱河市机断,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绣夺,老刑警劉巖吏奸,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異陶耍,居然都是意外死亡奋蔚,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門烈钞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泊碑,“玉大人,你說我怎么就攤上這事棵磷。” “怎么了晋涣?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵仪媒,是天一觀的道長。 經(jīng)常有香客問我谢鹊,道長算吩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任佃扼,我火速辦了婚禮偎巢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘兼耀。我一直安慰自己压昼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布瘤运。 她就那樣靜靜地躺著窍霞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拯坟。 梳的紋絲不亂的頭發(fā)上但金,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機(jī)與錄音郁季,去河邊找鬼冷溃。 笑死钱磅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的似枕。 我是一名探鬼主播盖淡,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼菠净!你這毒婦竟也來了禁舷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤毅往,失蹤者是張志新(化名)和其女友劉穎牵咙,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體攀唯,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡洁桌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侯嘀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片另凌。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖戒幔,靈堂內(nèi)的尸體忽然破棺而出吠谢,到底是詐尸還是另有隱情,我是刑警寧澤诗茎,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布工坊,位于F島的核電站,受9級特大地震影響敢订,放射性物質(zhì)發(fā)生泄漏王污。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一楚午、第九天 我趴在偏房一處隱蔽的房頂上張望昭齐。 院中可真熱鬧,春花似錦矾柜、人聲如沸阱驾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啊易。三九已至,卻和暖如春饮睬,著一層夾襖步出監(jiān)牢的瞬間租谈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留割去,地道東北人窟却。 一個(gè)月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像呻逆,于是被迫代替她去往敵國和親夸赫。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容