之前幾次娇豫,對(duì)C++中的一些核心話題進(jìn)行了一些梳理耀找,主要都是集中在關(guān)于面向?qū)ο蟮乃枷敕矫妗N彝ㄟ^部分故事的思路厂汗,結(jié)合生活來理解了關(guān)于面向?qū)ο蟮囊恍﹩栴}委粉。如果需要可以回看我文章列表的中的文章。今天的部分比較零散娶桦,我的思考可能也有限贾节,很多部分我的思考也不夠,不足以用故事來涵蓋趟紊,但是既然是個(gè)故事狗氮双,那么還是來說一下,類型到底是干什么用的霎匈。
但是C++的內(nèi)容非常龐大,之前也僅僅是冰山的一角送爸,還有非常豐富的知識(shí)有待梳理铛嘱。
在梳理今天內(nèi)容的之前,我想先簡(jiǎn)單說明一下關(guān)于類型的概念袭厂,為什么計(jì)算機(jī)系統(tǒng)中需要設(shè)計(jì)變量類型呢墨吓?如果你熟悉JavaScript,那么在其中聲明變量直接使用var xxx
就行了纹磺,而在Python中帖烘,甚至連具體的類型名稱都不用寫!豈不是快哉橄杨?秘症!省下了大量的力氣來討論變量類型照卦,如果在c++11 之前,auto關(guān)鍵字還不能用于自動(dòng)聲明變量類型的時(shí)候乡摹,C++對(duì)于變量類型的要求可謂是相當(dāng)?shù)膰?yán)格了役耕。
那么咱們就先來說說,為什么需要有變量類型這件事吧聪廉。
- 變量類型的作用
這個(gè)需要從非常原始的話題開始說起了~~~~原始到要存二進(jìn)制開始說起瞬痘。。板熊。
其實(shí)我們都知道框全,計(jì)算機(jī),之所以叫做計(jì)算機(jī)干签,是因?yàn)樗荒芡ㄟ^計(jì)算數(shù)字(真的就是計(jì)算數(shù)字竣况,連數(shù)學(xué)都算不上。筒严。丹泉。),而且為了方便計(jì)算機(jī)系統(tǒng)設(shè)計(jì)和實(shí)現(xiàn)鸭蛙,采用的是我們基本上很難讀懂的二進(jìn)制來計(jì)算我們想要的結(jié)果的摹恨。二進(jìn)制很方便的表明了電的狀態(tài)。但是娶视,對(duì)于二進(jìn)制來說晒哄,我們基本沒辦法直接讀懂到底其中在說啥(就算能讀懂,也記不住肪获,可以參考一個(gè)智力比賽記燈泡的開關(guān)(個(gè)人覺得這個(gè)比賽喪心病狂寝凌,在此不吐槽了)。孝赫。较木。。)青柄。他的最根本的優(yōu)點(diǎn)也十分突出伐债,** 那就是簡(jiǎn)單(簡(jiǎn)單到只有兩個(gè)數(shù)) 但隨之而來的缺點(diǎn)也很明顯,那就是他實(shí)在太長(zhǎng)了致开。比如一個(gè)很簡(jiǎn)單的236
使用二進(jìn)制表示卻成了1110 1100
(幸好我還很用心的添加了空格<實(shí)際怕自己寫錯(cuò)被打臉>)峰锁,而這些我們很難直接讀懂的東西會(huì)連續(xù)的寫在一起,像0011 1001 0110 1100 1010
這樣双戳,那么我們就很那區(qū)分出來虹蒋,這到底是我們?nèi)粘V惺煜さ?code>235210還是57 和 869
等等等等。因?yàn)檫@牽扯到我上篇文章講的那個(gè)鑰匙和箱子的故事**,也就是我們并不能通過數(shù)字來判斷魄衅,多少個(gè)單位能夠算作一組峭竣,這一組能夠表達(dá)一個(gè)想要的數(shù)字。那這個(gè)時(shí)候徐绑,再拿出之前的故事邪驮,想知道箱子里面放的啥,那在箱子上貼上名字就行啦傲茄。其實(shí)類型相當(dāng)于這個(gè)標(biāo)簽毅访,在連續(xù)的二進(jìn)制數(shù)字的序列中,就可以輕松知道盘榨,這個(gè)變量占有多大的內(nèi)存喻粹。
因此,類型聲明草巡,其實(shí)是為了告訴編一起守呜,我這個(gè)變量需要多大的內(nèi)存空間,比如int一般為4個(gè)字節(jié)山憨,編譯器可以非常方便的按照大小取出全部的數(shù)據(jù)來了查乒。那么為什么C/C++對(duì)于內(nèi)存這么關(guān)注,而Python這類語言不關(guān)注呢郁竟?其實(shí)玛迄,對(duì)于python這類解釋形語言來說,在他和內(nèi)存之間其實(shí)還有一個(gè)東西叫做虛擬機(jī)棚亩,他主要負(fù)責(zé)控制變量的類型計(jì)算蓖议,而不需要Python親力親為來控制變量的類型了。(其實(shí)也就是把類型控制的部分抽出來了讥蟆,不代表他沒有類型勒虾,不然Python中不會(huì)有type()內(nèi)置函數(shù)了)
那么C/C++的類型如此重要,會(huì)不會(huì)牽扯到一個(gè)問題呢瘸彤,那就是類型轉(zhuǎn)換的問題了修然。
轉(zhuǎn)換函數(shù)
- 轉(zhuǎn)為其它類型(Conversion function)
//定義
class Fraction
{
public:
Fraction(int num, int den = 1) :m_numerator(num), m_denminator(den){}
//定義轉(zhuǎn)換函數(shù)
//可以告訴編譯器,可以將Fraction轉(zhuǎn)換為double
operator deouble() const
{
if(m_denominator)
return (double) (m_numerator / m_denominator) ;
else
return 1.0;
}
private:
int m_numerator;
int m_denominator;
};
//使用
Fraction f(3, 5);
double d = 4 + f; //調(diào)用operator double() 將f轉(zhuǎn)為0.6钧栖,再進(jìn)行運(yùn)算
//編譯器會(huì)先查看是否存在`double operator+(int, Fraction)`的函數(shù)聲明低零,如果該函數(shù)存在,則使用操作符重載
//如果操作符重載不存在拯杠,編譯器會(huì)查找是否存在Fraction想double的轉(zhuǎn)換函數(shù)
-
語法:
operator targetType() const { return statement; }
-
轉(zhuǎn)換函數(shù)的功能
- 可以通過該函數(shù)告訴編譯器,可以將自己轉(zhuǎn)換為某一個(gè)類型
-
轉(zhuǎn)換函數(shù)的注意事項(xiàng)
- 不需要參數(shù)
- 不需要返回類型
- 返回值需要考慮是否為const的問題
其它類型轉(zhuǎn)為自己的類型(non-explicit-one-argument constructor)
//定義
class Fraction
{
public:
//non-explicit-one-argument constructor
//構(gòu)造函數(shù)中部分有默認(rèn)值啃奴,構(gòu)造時(shí)潭陪,不需要傳遞全部參數(shù),只要一個(gè)實(shí)參就可以
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;
};
//調(diào)用
Fraction f(3, 5);
Fraction d2 = f + 4;//調(diào)用non-explicit ctor 將4轉(zhuǎn)為Fraction,再調(diào)用operator+(該函數(shù)按照上方定義得知依溯,需要Fraction+Fraction)
-
語法
- 按照正常構(gòu)造方法老厌,但在形參列表處,需要對(duì)不需要的參數(shù)寫入默認(rèn)值黎炉,最后只留下一個(gè)形參需要外部傳入即可構(gòu)造對(duì)象枝秤,該構(gòu)造函數(shù),可以將其他類型轉(zhuǎn)化為需要的類型
-
explicit關(guān)鍵字
- 提出問題
//定義
class Fraction
{
public:
//non-explicit-one-argument constructor
//構(gòu)造函數(shù)中部分有默認(rèn)值,構(gòu)造時(shí),不需要傳遞全部參數(shù)逻炊,只要一個(gè)實(shí)參就可以
Fraction(int num, int den = 1)
: m_numerator(num), m_denominator(den) {}
Fraction operator+(const Fraction& f)
{
return Fraction(....);
}
operator double() const
{
if(m_denominator)
return (double) (m_numerator / m_denominator) ;
else
return 1.0;
}
private:
int m_numerator;
int m_denominator;
};
//調(diào)用
Fraction f(3, 5);
Fraction d2 = f + 4; // [ERROR]Ambiguous
//符合語法泊碑,但是此處存在二義性,運(yùn)行時(shí)異常
//語義一:轉(zhuǎn)換函數(shù)厌均,編譯器可以將f轉(zhuǎn)換為double,再執(zhí)行加法運(yùn)算
//語義二:存在non-explicit-one-argument constructor,所以編譯器也可以將常數(shù)4構(gòu)造為Fraction沐序,再進(jìn)行加法運(yùn)算。
//由于以上兩種方案堕绩,都可以實(shí)現(xiàn)該條語句的調(diào)用策幼,所以編譯器產(chǎn)生二義性,不能選擇奴紧,隨報(bào)錯(cuò)特姐。
- explicit的用法
僅使用在構(gòu)造函數(shù)處
-
告訴編譯器,該構(gòu)造函數(shù)绰寞,僅用于調(diào)用到逊,不能用于轉(zhuǎn)換構(gòu)造使用
explicit Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) { }
智能指針(pointer-like classes)
//定義
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;
long* pn;
};
//使用
struct Foo
{
....
void method(void){....}
};
shared_ptr<Foo> sp(new Foo);
Foo f(*sp);
sp->method();//實(shí)際通過智能指針返回了Foo的指針
//讓對(duì)象sp,也具有了指針一樣的使用方法
//關(guān)于->符號(hào)滤钱,有一個(gè)特殊行為觉壶,當(dāng)作用下去后,得到對(duì)象后件缸,不會(huì)被消耗掉
- 智能指針中一定持有一個(gè)真正的指針
仿函數(shù)(function-like classes):重載()操作符
/定義部分
template <class T>
struct identity
{
const T&;
operator() (const T& x) const { return x; }
};
template <class Pair>
struct select1st
{
const typename Pair::first_type& operator()(const Pair& x) const
{
return x.first;
}
};
template <class Pair>
struct select2nd
{
const typename Pair::second_type& operator()(const Pair& x) const
{
return x.second;
}
}
//pair部分
template <class T1, class T2>
struct pair
{
T1 first;
T2 second;
pair(): first(T1()), second(T2()){}
pair(const T1& a, const T2& b)
:first(a), second(b){}
}
//使用部分
select1st <pair>() ();
//第一個(gè)括號(hào):創(chuàng)建對(duì)象
//第二個(gè)括號(hào):調(diào)用操作符()重載
namespace
- 通過namespace可以將部分內(nèi)容區(qū)分開铜靶,不需要考慮多人協(xié)作時(shí),類他炊、函數(shù)名稱沖突的情況争剿,只需要自己將自己的代碼用namespace包起來即可
.....
//定義namespace
namespace storyDog
{
......
void function1(){....}
......
}
.....
......
//使用
storyDog::function1();
......
模版
- 類模版(class template)
//定義部分
template <typename T>
class complex
{
public:
complex(T r = 0, T i = 0):re(r), im(i){}
complex& operator += (const complex&);
T real() const { return re; }
T imag() const { return im; }
private:
T re, im;
}
{
//使用的時(shí)候指定T的類型
complex<double> c1(2.5, 1.5);
complex<int> c2(c2, 6);
}
- 函數(shù)模版(function template)
//定義
template <class T>
inline const T& min(const T& a, const T& b)
{
return b < a? b: a;
}
class stone
{
public:
stone(int w, int h, int weight):_w(w), _h(h), _weight(weight){}
bool operator< (const stont rhs) const
{
return _weight < rhs._weight;
}
private:
int _w, _h, _weight;
}
//使用
stone r1(2, 3), r2(3, 3);
r3 = min(r1, r2);
//編譯器會(huì)對(duì)function template進(jìn)行實(shí)參推倒(argument deduction),無需手動(dòng)指定具體類型是什么痊末。
- 成員模版(member template)
template <class T1, class T2>
struct pair{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair():first(T1()), second(T2()){}
pair(const T1& a, const T2& b):first(a),
second(b){}
//成員模版(在模版中的模版)
//可以更細(xì)微的使用模版蚕苇,不需要與類模版相同
template<class U1, class U2>
pair(const pair<U1, U2>& p):first(p.first), second(p. second){}
}
- 考慮帶有子父類模版的情況
template<typename _Tp>
class share_ptr:public __shared_ptr<_Tp>
{
...
template<typename _Tp1>
explicit shared_ptr(_Tp1* __p)
: __shared_ptr<_Tp>(__p){}
...
};
```
```
Base1* ptr = new Derived1; //up-cast
shared_ptr<Base1> sptr(new Derived1); //模版up-cast
```
- 模版特化(Specialization)
//泛化的版本
template<class key>
struct hash();
//特化的版本
template<>
struct hash<long>{
size_t operator()(long x) const { return x;}
};
cout << hash<long>()(1000);
- 模版的偏特化(partial specialization)
- 個(gè)數(shù)上的偏特化
template<typename T, typename Alloc=...>
class vector
{
....
}
....
template <typename Alloc = ....>
//綁定了其中的一個(gè),另外一個(gè)沒有被特化
class vector<bool, Alloc>
{
....
}
- 范圍的偏特化
//非指針用這套代碼
template<typename T>
class C{....};
//指針用這套代碼
template <typename T>
class C<T*>{....};
```
- 模版模版參數(shù)(template template parameter)
//定義
template<typename T,
template <typename T>
class Container>
//含義:第二個(gè)模版接受一個(gè)Container
//并且凿叠,這個(gè)Container的模版接受第一模版參數(shù)作為他的模版參數(shù)
class XCls
{
private:
Container<T> c;
public:
....
};
//使用
template<typename T>
using Lst = list<T, allocator<T>>;
XCls<string, Lst> mylst;
淺談C++11
- 可變數(shù)量的模版參數(shù)(variadic templates)
void print(){}
//定義
template<typename T, typename... Types>
void print(const T& firstArg, const Types&... args)
{
cout << firstArg << endl;
print(args...);//遞歸調(diào)用涩笤,來解析模版參數(shù)包
}
.......
{
//調(diào)用
print(7.5, "hello", bitset<16>(377), 42);
//想要知道后續(xù)的參數(shù)包的大小嚼吞,可以使用sizeof...(args)來獲得
}
- auto關(guān)鍵字
list<string> c;
auto ite = find(c.begin(), e.end(), target);
//通過auto來聲明變量類型
//原始的是list<string>::iterator ite;現(xiàn)在簡(jiǎn)化為auto即可。
注意:使用auto聲明變量蹬碧,必須初始化舱禽,否則會(huì)報(bào)錯(cuò)。
- ranged-base for
- 對(duì)于可以遍歷的對(duì)象恩沽,可以簡(jiǎn)化的來寫for循環(huán)
for(decl :coll){
statement
}
- pass by value(不會(huì)影響原始容器的值)
vector<double> vec;
for(auto elem: vec){
cout << elem << endl;
}
- pass by reference(可以改變?cè)既萜鞯膬?nèi)容)
vector<double> vec;
for(auto& elem: vec){
elem += 3;//可以改變?cè)萜鞯膬?nèi)容
}
reference
引用(reference):就是對(duì)象的另一個(gè)名字誊稚。(名字是名詞,所以此時(shí)我們把引用當(dāng)做一個(gè)名詞)罗心。引用主要用作函數(shù)的形式參數(shù)里伯。(作為參數(shù)的,那更是名詞了)协屡。到此為止俏脊,接下來就好理解了,因?yàn)樗莻€(gè)名詞肤晓,對(duì)于名詞的理解就比動(dòng)詞的理解方便多了爷贫。
進(jìn)一步理解:引用是一種復(fù)合類型(引用又是一種類型),通過在變量名前添加”&“符號(hào)來定義补憾。復(fù)合類型指的是用其他類型來定義的類型漫萄。
結(jié)論:其實(shí)引用只是一個(gè)別名,即只是他綁定的對(duì)象的另一個(gè)名字盈匾,作用在引用上的所有操作事實(shí)上都是作用在該引用綁定的對(duì)象上
關(guān)于面向?qū)ο蟮膯栴}
關(guān)于這個(gè)問題啊腾务,我之前的筆記寫的很詳細(xì),在這里也不想再贅述了削饵。鏈接:
http://www.reibang.com/p/34a30505176d