之前寫到了關(guān)于不帶有指針的class的設(shè)計(jì)思路和注意事項(xiàng)弄屡,但是對(duì)于C/C++語言來說寨闹,還有一個(gè)非常重要的概念就是指針础嫡,為什么將指針作為class設(shè)計(jì)的分界點(diǎn)呢氛谜?那么掏觉,之前我講了一個(gè)關(guān)于貓ヽ(=?ω?=)丿的故事,那么今天我再來講一個(gè)關(guān)于國(guó)王和寶箱的故事吧 <( ‵□′)───C<─___-)||值漫。
關(guān)于 指 針(鑰匙)的一個(gè)小故事
語文老師教導(dǎo)澳腹,講故事需要先說明時(shí)間地點(diǎn)人物發(fā)生了啥:
話說很久很久以前(發(fā)現(xiàn)詞語好匱乏、杨何、酱塔、),在遙遠(yuǎn)的國(guó)度有一個(gè)國(guó)王(突然想起了和尚)危虱,他有一件十分心愛的具備魔法的寶物羊娃,每次祭奠、儀式都會(huì)用到這個(gè)寶物的魔力埃跷。
而這個(gè)寶物由十個(gè)部件組成蕊玷,每次只有將十個(gè)部件全部拼接在一起才會(huì)發(fā)揮他的魔力(少了任何一塊都不行(¬_¬))。由于國(guó)王愛這個(gè)寶物愛的死去活來的弥雹,為了防止他過度沉溺在寶物的喜愛中無法自拔而耽誤朝政((=垃帅。=)其實(shí)我想說,到底啥寶物這么有吸引力剪勿。贸诚。。故事設(shè)定。酱固。械念。(▼皿▼#) ....就這樣吧)。
國(guó)王的爹爹(居然沒死╮(╯_╰)╭)將這個(gè)的碎片分別鎖在了十個(gè)寶箱里面(ヾ(???ゞ)這個(gè)故事背景我實(shí)在快編不下去了.......)运悲。龄减。。而每個(gè)箱子都對(duì)應(yīng)一把獨(dú)特的鑰匙(廢話扇苞。欺殿。寄纵。鳖敷。。一把鑰匙開一把鎖━━( ̄ー ̄*|||━━)程拭,特殊之處在于
***這 個(gè) 鑰 匙 不 但 可 以 打 開 箱 子 還 可 以 記 錄 箱 子 在 哪 里 , ****定踱。而國(guó)王的爹爹把這十把鑰匙分別交給了十個(gè)大臣來保管,也就是只有當(dāng)國(guó)王召集了十個(gè)大臣以后才能夠一睹寶物的真面目恃鞋。
ps: (● ̄(?) ̄●)背景就交代在這吧崖媚,雖然不合邏輯的地方很多,但是為了說清楚這個(gè)事恤浪,也就當(dāng)作合理吧畅哑。
**** 那 么 問 題 來 了 !K伞荠呐!****( ⊙ o ⊙ )
等到國(guó)王的爹爹死了,國(guó)王也長(zhǎng)大了不少砂客,決心要做一個(gè)好的國(guó)王泥张,已經(jīng)不那么迷戀那個(gè)寶物了。但是每次在使用寶物的時(shí)候鞠值,國(guó)王發(fā)現(xiàn)實(shí)在太麻煩了媚创,每次都要十個(gè)大臣全來了才能取出寶物的每個(gè)部件。有好幾次急用的時(shí)候(,,#?Д?)彤恶,大臣由于各種原因不能來把要是交給國(guó)王钞钙。還有的時(shí)候,十個(gè)大臣都來了声离,但是誰也不知道該開哪個(gè)箱子芒炼,每次都要一個(gè)個(gè)是試也是很大無時(shí)間的。
***有 沒 有 辦 法 能 幫 助 國(guó) 王 來 管 理 他 這 些 箱 子 呢 ( ▔?з?▔ )抵恋?****
這個(gè) 精彩絕倫(漏洞百出)的故事講完了焕议,但是這里面其實(shí)引出來的問題值得我們探討一下的。方法太多了,我來簡(jiǎn)單說說幾個(gè)拙見吧盅安。
方法一:
將十個(gè)寶箱和每個(gè)大臣手里面的鑰匙按照對(duì)應(yīng)關(guān)系編號(hào)[0-9]唤锉,至于擺放順序其實(shí)無所謂了,每個(gè)箱子上都有自己的編號(hào)别瞭,每次大臣來齊了就能很快的打開所有的箱子了窿祥。(好像和之前比只是加上了序號(hào))總結(jié):
1 能夠方便快速的打開箱子(真的快速嗎?蝙寨!難道箱子是亂放的晒衩,難道不需要找嗎)
2 但是每次打開箱子都必須十個(gè)大臣到場(chǎng),如果少了其中某個(gè)就不能打開全部箱子了(而且每次去開箱子的時(shí)候都需要所有大臣在場(chǎng)墙歪,否則听系,去了五號(hào)箱子那,發(fā)現(xiàn)其余大臣都在虹菲,唯獨(dú)五號(hào)大臣不在靠胜,就抓瞎了)-
方法二:
還是先將箱子和鑰匙進(jìn)行編號(hào),然后將9號(hào)箱子的鑰匙以及他所在位置放在8號(hào)箱子中毕源,將8號(hào)箱子的鑰匙以及他所在位置放在7號(hào)箱子中......將1號(hào)箱子的鑰匙以及他所在位置放在0號(hào)箱子中浪漠。
完成了鑰匙和位置的分配,現(xiàn)在只需要一個(gè)大臣拿著鑰匙就能開啟所有的箱子啦~ ~ ~ ~ ~ ~- 總結(jié):
1 只需保存一個(gè)箱子的鑰匙就可以管理所有的10個(gè)箱子啦(聽起來不錯(cuò)霎褐,但真就這么簡(jiǎn)單嗎址愿?!)
2 不需要擔(dān)心鑰匙和箱子位置具體在哪的問題(真的嗎冻璃?O煳健)
3 拿著鑰匙的那個(gè)大臣權(quán)利集中,一旦他丟了鑰匙俱饿,所有箱子都無法打開
4 如果中間某個(gè)箱子的位置或者鑰匙放錯(cuò)了或者忘記了放進(jìn)去~~~~那么后續(xù)的箱子就等于丟了(拿到了第一把鑰匙也打不開后面的鎖了歌粥、、拍埠、失驶、)
5 假設(shè)某一天國(guó)王想看看第十個(gè)箱子里面的東西的時(shí)候~ ~ ~那就麻煩了,得把前面幾個(gè)箱子都打開才行(十個(gè)箱子好像好像還好枣购,那假設(shè)有十萬個(gè)或者十億個(gè)箱子呢嬉探?他也許就只為了看看最后一個(gè)箱子就變成了一個(gè)開了一輩子箱子的國(guó)王(為啥突然想起來做木匠活的皇帝....))
6 如果某天國(guó)王想要在這群箱子中插入個(gè)箱子,只需要管理需要插入位置前后的箱子就行了(這個(gè)優(yōu)點(diǎn)今天可能用不到(  ̄ー ̄)(  ̄ー ̄)就先放著吧)
- 總結(jié):
-
方法三:
將十個(gè)箱子并排放在一起棉圈,[ 0 ][ 1 ][ 2 ][ 3 ]....[ 9 ]涩堤,箱子之間會(huì)形成自然順序。將下個(gè)箱子的鑰匙放在上個(gè)箱子上(這時(shí)候不需要保存位置啦分瘾,因?yàn)榫驮谒赃吢铮┨ノАR簿褪菍?號(hào)箱子的鑰匙以及他所在位置放在8號(hào)箱子上,將8號(hào)箱子的鑰匙以及他所在位置放在7號(hào)箱子上......將1號(hào)箱子的鑰匙以及他所在位置放在0號(hào)箱子上(注意沒有放在箱子里面!0谆辍汽纤!)。- 總結(jié)
1 箱子的位置不用擔(dān)心了福荸,都在這一排擺著呢(但是需要考慮的是萬一蕴坪、、敬锐、沒有地方放下這么多連續(xù)的箱子可怎么辦背传,也就是對(duì)空間的要求更高了,十個(gè)箱子好像還好~~但是加入十萬個(gè)台夺、十億個(gè)箱子呢径玖?)
2 保存一個(gè)鑰匙就可以管理全部的箱子了,也存在權(quán)利過分集中的問題谒养,如果挺狰,外面的那把鑰匙丟了、买窟、、薯定、箱子全都報(bào)廢了
3 如果這時(shí)候國(guó)王想要看看其中某個(gè)箱子始绍,那么只需要按照序號(hào)去看看就行了,畢竟鑰匙就放在之前的箱子上面(如果這里是十萬個(gè)箱子话侄,國(guó)王也可以不費(fèi)勁的從里面找出他想要的那個(gè)~ ~ ~ ~)
4 突然某一天國(guó)王想插入一個(gè)箱子亏推,如果箱子是插在最后沒啥問題,很容易年堆,但是吞杭、、变丧、芽狗、如果國(guó)王想要插入的位置是第一個(gè),那么國(guó)王得把所有的箱子往后諾一個(gè)位置才行痒蓬,如果是十億個(gè)箱子呢童擎?又出現(xiàn)了個(gè)挪了一輩子箱子的國(guó)王
- 總結(jié)
-
你TM弄了這么久箱子!攻晒!(# ?Д?)到底想干嘛(# ?Д?)顾复?!
其實(shí)這個(gè)故事雖然存在很多不合理的地方鲁捏,但實(shí)際是想做一個(gè)關(guān)于*** 數(shù) 據(jù) 結(jié) 構(gòu)*** 的比喻芯砸。
- 每個(gè)箱子里面保存的“寶藏”,就相當(dāng)于——數(shù)據(jù)
- 箱子以及箱子所處的位置,就相當(dāng)于——內(nèi)存
C/C++畢竟是*** 直 接 面 對(duì) 內(nèi) 存 ***的語言假丧,所以程序員難免要考慮這些“箱子們和鑰匙們” 的擺放和打開的問題###
***還 有 就 是 末购,這 些 管 理 方 法 后 面 肯 定 是 有 用 的 ~ ~ ~ ~ ~ ~, 就 先 放 在 這 里 虎谢, 我 之 后 再 提 ***
回到之前的一個(gè)問題盟榴,為什么類要用帶有指針和不帶指針來進(jìn)行區(qū)分呢?婴噩?擎场?
那么假設(shè)你是國(guó)王,你希望自己的箱子被打不開或者被找不到嗎几莽?
對(duì)于帶指針和不帶指針的class最大的區(qū)別就是是否需要管理這些放在箱子里面的“寶藏”Q赴臁!章蚣!如果管理不好站欺,那么會(huì)出現(xiàn)丟失找不到的情況,但是寶藏其實(shí)就在那里只是我們?cè)僖舱也坏搅讼舜梗@時(shí)候矾策,被丟失的寶藏就會(huì)出現(xiàn)一個(gè)問題,它會(huì)占用那些被我們找不到的空間峭沦。就像一個(gè)箱子的鑰匙丟了贾虽,箱子里面全是好東西,但是我們拿不到吼鱼,也砸不開那個(gè)箱子蓬豁,但是他還在你家里面占了很多空間。菇肃。地粪。。所以對(duì)于持有指針的class來說琐谤,手上拿到的指針蟆技,其實(shí)就相當(dāng)于那把鑰匙。管理鑰匙的本質(zhì)是為了管理箱子里的東西
class中帶不帶指針的問題關(guān)系著背后的數(shù)據(jù)(寶藏)該如何管理的問題
今天會(huì)用簡(jiǎn)化版本的string.h作為例子來說明帶有指針的class應(yīng)該如何設(shè)計(jì)笑跛,其實(shí)string相當(dāng)于之前的方法三的管理方法付魔,將所有的字符(char)在內(nèi)存中有一排連續(xù)的空間(數(shù)組),將每個(gè)字母放在連續(xù)的空間中飞蹂。就相當(dāng)于國(guó)王的十個(gè)箱子里面分別一個(gè)字母組成[ h ][ e ][ l ][ l ][ o ][ w ][ o ][ r ][ l ][ d ]的一串字符串几苍。這些“箱子”(內(nèi)存單元)是連續(xù)的排列的方式組合在一起。這時(shí)候陈哑,我們只需要拿著[ h ]這個(gè)箱子的鑰匙妻坝,就可以打開后面全部的箱子了伸眶,同時(shí)也可以根據(jù)每個(gè)箱子的位置來打開其中的箱子,取出其中的數(shù)據(jù)了(比如取出hello的[ o ]刽宪,他位于這一排箱子的第五個(gè)位置厘贼,如果從0號(hào)開始排序,那么他就是相當(dāng)于這串箱子的4號(hào)了圣拄,我們只需要拿著這個(gè)4號(hào)就能去走這個(gè)o了)嘴秸。
(后面該適當(dāng)嚴(yán)肅點(diǎn)了,但還會(huì)呼應(yīng)之前的故事庇谆,來解釋 我想解釋的內(nèi)容 (′?ω?`) )
帶有指針的class需要解決的Big Three問題
class String
{
public:
//構(gòu)造函數(shù)岳掐,接收一個(gè)字符串?dāng)?shù)組,通過字符串?dāng)?shù)組生成一個(gè)字符串(cstr相當(dāng)于保管起來的那個(gè)鑰匙)
String(const char* cstr=0);
//構(gòu)造函數(shù)(拷貝構(gòu)造饭耳,對(duì)于含有指針的class尤其要注意的)
//接收參數(shù)為一個(gè)String本身串述,也就是通過接收String本身來構(gòu)造一個(gè)新的String對(duì)象
String(const String& str);
//操作符重載,(拷貝賦值寞肖,對(duì)于含有指針的class尤其要注意的)
//操作符左右兩側(cè)均為String對(duì)象纲酗,也就是將一個(gè)String對(duì)象賦值給另外一個(gè)已有的String對(duì)象
String& operator=(const String& str);
//析構(gòu)函數(shù),對(duì)象死亡之前會(huì)默認(rèn)調(diào)用的函數(shù)
//防止指針失效了新蟆,但實(shí)際的數(shù)據(jù)還存在在內(nèi)存中觅赊,無法拿到,也無法改變和刪除
//會(huì)造成內(nèi)存泄漏栅葡,在對(duì)象死亡前需要再次管理那塊內(nèi)存空間
~String();
//獲取私有屬性茉兰,獲取時(shí)沒有改變內(nèi)容,需要注意const的修飾(找鑰匙的方法)
char* get_c_str() const { return m_data; }
private:
char* m_data; //相當(dāng)于管理這一串寶箱的第一把鑰匙
};
-
析構(gòu)函數(shù)
-
析構(gòu)函數(shù)是在對(duì)象生命周期的最末尾也就是對(duì)象即將被銷毀的時(shí)候進(jìn)行調(diào)用的函數(shù)欣簇,主要是為了方便清楚帶有指針變量的對(duì)象,能夠在對(duì)象清楚之前坯约,清理其指針?biāo)赶虻哪遣糠謨?nèi)存空間熊咽,以避免造成必要的內(nèi)存泄漏。
inline String::~String() { //刪除字符串的內(nèi)存空間中保存的內(nèi)容闹丐,釋放內(nèi)存 //字符串本質(zhì)上為字符數(shù)組横殴,所以再刪除的時(shí)候,需要使用delete[] delete[] m_data;
}
-
-
復(fù)制拷貝問題
- 與不帶指針的對(duì)比
- 對(duì)于不帶指針的class
- 編譯器會(huì)自動(dòng)幫我們實(shí)現(xiàn)拷貝構(gòu)造和拷貝復(fù)制兩種方法
- 并且編譯器會(huì)依照內(nèi)存中所保存的數(shù)據(jù)卿拴,依照每個(gè)byte進(jìn)行拷貝衫仑,相當(dāng)于兩份完全相同的數(shù)據(jù)
- 如果能夠滿足需求不需要重寫這兩種方法
- 對(duì)于帶有指針的class
- 編譯器會(huì)默認(rèn)拷貝指針變量(配一把鑰匙),但實(shí)際指針?biāo)赶虻臄?shù)據(jù)并未被復(fù)制一份堕花,導(dǎo)致兩個(gè)指針同時(shí)指向一個(gè)內(nèi)存空間(兩把一樣的鑰匙開的同一個(gè)箱子文狱,但是實(shí)際箱子里面的內(nèi)容依然只有一份,不要假裝騙自己說配了兩把鑰匙就擁有了兩份數(shù)據(jù)的道理相同)
- 如果使用了賦值符(A = B)的時(shí)候缘挽,相當(dāng)于將鑰匙A的鑰匙改造成鑰匙B瞄崇,但是對(duì)于A原始可以管理的那個(gè)箱子就再也沒辦法打開了呻粹,而A和B可以同時(shí)管理同一個(gè)箱子,沒辦法實(shí)現(xiàn)通過賦值符來實(shí)現(xiàn)賦值一個(gè)和B相同的箱子苏研。為了避免這個(gè)問題等浊,實(shí)現(xiàn)deep copy,必須重寫賦值符左右相同都是自己本身類型的操作符
- class中持有指針變量摹蘑,那么對(duì)于賦值過程需要考慮重寫拷貝構(gòu)造和拷貝賦值
- 對(duì)于不帶指針的class
- 拷貝構(gòu)造(Copy Constructor)
-
通過傳入和自己本身類型相同的對(duì)象筹燕,來構(gòu)造一個(gè)新的自己類型的對(duì)象
String s1("hello"); String s(s1); //第一種寫法,構(gòu)造一個(gè)新的String對(duì)象s衅鹿,初值為s1中的內(nèi)容 String s = s1; //第二種寫法撒踪,構(gòu)造一個(gè)新的String對(duì)象s,初值為s1中的內(nèi)容
拷貝過后會(huì)生成一個(gè)新的對(duì)象(s)
-
- 與不帶指針的對(duì)比
inline
String::String(const String& str) //參數(shù)就是自己類型本身塘安,可以使用引用糠涛,因?yàn)閭魅氲臄?shù)據(jù)不會(huì)被改動(dòng),需要使用const修飾
{
//因?yàn)槭切聞?chuàng)建的String對(duì)象兼犯,所以直接申請(qǐng)足夠的內(nèi)存空間忍捡,將內(nèi)容拷貝過去即可
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data); //str.m_data:相同類型的對(duì)象互為友元,可以直接讀取內(nèi)部的private屬性
}
- 拷貝賦值(Copy Assignment Operator)
將一個(gè)對(duì)象切黔,復(fù)制給另外一個(gè)** 已 有 **的對(duì)象
-
思路:先將原對(duì)象中的內(nèi)容清空砸脊,再將內(nèi)容進(jìn)行復(fù)制
{ String s1("hello"); String s2("hello"); s2 = s1; //將s1的內(nèi)容復(fù)制給s2,s2在復(fù)制之前實(shí)際是一個(gè)已存在的對(duì)象 }
-
函數(shù)定義
inline
String& String::operator=(const String& str)
{
//自我賦值檢查
//作用:
//1. 如果將s賦值給s纬霞,則無需操作凌埂,那么可以直接通過return來跳出該函數(shù),減少多余操作诗芜,提高效率
//2. 如果此處不做判斷瞳抓,直接執(zhí)行后續(xù)代碼,會(huì)在復(fù)制之前先釋放等待接受內(nèi)容的指針?biāo)赶虻目臻g伏恐,
// 因?yàn)椴僮鞣笥蚁嗤⒀疲瑫?huì)造成將被復(fù)制的對(duì)象那么部分給釋放,最終破壞原始的數(shù)據(jù)
if (this == &str)
return *this;
delete[] m_data; //清空原有對(duì)象的內(nèi)存翠桦,防止造成內(nèi)存泄漏
m_data = new char[ strlen(str.m_data) + 1 ]; //在堆上重新申請(qǐng)對(duì)應(yīng)長(zhǎng)度的空間
strcpy(m_data, str.m_data); //復(fù)制內(nèi)容到新申請(qǐng)的空間
return *this; //返回賦值符左側(cè)的指針變量所指向的內(nèi)容
}
string.h的完整代碼
#ifndef __MYSTRING__
#define __MYSTRING__
class String
{
public:
String(const char* cstr=0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
#include <cstring>
//通過字符串?dāng)?shù)組來構(gòu)造一個(gè)字符串對(duì)象的構(gòu)造函數(shù)
inline
String::String(const char* cstr)
{
//需要對(duì)傳入的參數(shù)進(jìn)行檢查是否有內(nèi)容
if (cstr) {
//需要針對(duì)傳入的字符串?dāng)?shù)組的長(zhǎng)度(strlen(cstr)來獲得),但字符串后需要放置一個(gè)結(jié)束符/0横蜒,所以長(zhǎng)度需要+1
m_data = new char[strlen(cstr)+1];//在堆內(nèi)存上,申請(qǐng)一塊長(zhǎng)度比傳入字符數(shù)組長(zhǎng)1的字符數(shù)組的內(nèi)存空間
strcpy(m_data, cstr);//使用strcpy函數(shù)將傳入的參數(shù)cstr的所指的內(nèi)存中的數(shù)據(jù)销凑,賦值到新申請(qǐng)的內(nèi)存空間上去丛晌,使兩塊內(nèi)存空間都同時(shí)具備相同的數(shù)據(jù)
}
else { //未指定初值的情況
//雖然傳入的部分為空,但是由于在C/C++中斗幼,管理字符串是以\0作為結(jié)尾的澎蛛,即使是空字符串實(shí)際并不完全為空
//所以在需要構(gòu)造長(zhǎng)度為一個(gè)單位的字符串?dāng)?shù)組,并在其中放入結(jié)束符/0
m_data = new char[1]; //申請(qǐng)空間
*m_data = '\0'; //保存結(jié)束符
}
}
inline
String::~String()
{
delete[] m_data;
}
inline
String& String::operator=(const String& str)
{
if (this == &str)
return *this;
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
//輸出孟岛,<<符號(hào)重載瓶竭,不能為成員函數(shù)督勺,因?yàn)椴僮鞣髠?cè)為ostream
#include <iostream>
using namespace std;
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
#endif
內(nèi)存管理初探
-
堆(heap)和棧(stack)
- 棧
- 棧,是存在于某作用域(scope)的一塊內(nèi)存空間斤贰。例如智哀,當(dāng)你調(diào)用的函數(shù),函數(shù)本身就會(huì)形成一個(gè)棧荧恍,用來存放所接收的參數(shù)瓷叫,以及返回地址。在函數(shù)本體內(nèi)聲明的任何變量送巡,其所使用的內(nèi)存塊都在棧上摹菠。
- 棧,其實(shí)是一種特殊的數(shù)據(jù)結(jié)構(gòu)骗爆,他的特性是先入后出(后入先出)次氨,就像一摞放在桌子上面的碟子,我們清洗好是依次往上摞在一起的摘投,而最先洗好的盤子位于最底下煮寡,我們?nèi)”P子的時(shí)候一般都是先從頂部直接拿就行了,所以造成了后洗好的盤子最先被取出來犀呼,也就是先入棧的幸撕,后出棧。對(duì)于函數(shù)來說無論接收的參數(shù)還是創(chuàng)建的參數(shù)外臂,都是最先得到的最后才出棧(被釋放坐儿,也就是對(duì)象死掉),后得到或創(chuàng)建的先出棧宋光。
- 特點(diǎn):
1 對(duì)于操作系統(tǒng)來說貌矿,程序員不需要管理?xiàng)I系淖兞浚拷唤o系統(tǒng)調(diào)度和處理罪佳,效率高
2 使用簡(jiǎn)單遂黍,但空間較小池摧,每個(gè)程序、函數(shù)都有自己的棧持钉,相對(duì)安全性高
- 堆
- 堆投储,是指有操作系統(tǒng)所提供的一塊全局的內(nèi)存空間第练,程序可以動(dòng)態(tài)分配(dynamic allocated)從中獲得若干區(qū)塊(blocks)。
- 特點(diǎn)
1 系統(tǒng)共用區(qū)域玛荞,不同的程序都可以訪問同一塊區(qū)域娇掏,不釋放內(nèi)存空間會(huì)造成內(nèi)存泄漏
2 需要自己手動(dòng)來申請(qǐng),同時(shí)也需要手動(dòng)來釋放勋眯,如果不釋放婴梧,當(dāng)處在棧中的俄變量被彈出下梢,則這部分空間丟失,會(huì)造成內(nèi)存泄漏(鑰匙丟了找不到箱子的情況)
- 棧
-
對(duì)象的創(chuàng)建(TypeName代表類型名稱塞蹭,比如自定義類型String等等孽江,params...代表傳入的參數(shù))
- 方式一:
TypeName t(params...);
- 所占用的空間來自于棧
- 方式二:
TypeName *pt = new TypeName(params...);
- 實(shí)際該語句所需要的空間來自棧和堆兩部分,其中番电,實(shí)際存儲(chǔ)的數(shù)據(jù)的內(nèi)存空間來自于堆岗屏,而變量pt的保存空間實(shí)際是在棧的,而存在棧里面pt由于是一個(gè)指針漱办,它記錄了所申請(qǐng)的堆中空間的地址(也就是箱子的位置)这刷。由于pt的存儲(chǔ)空間為棧,所以當(dāng)t出了所在的作用域之后娩井,t會(huì)被棧彈出暇屋,而堆上的內(nèi)容并不會(huì)被釋放,并且永遠(yuǎn)再?zèng)]有機(jī)會(huì)被delete了洞辣。如果在彈出前咐刨,未釋放堆上的那部分空間,則就造成了內(nèi)存泄漏了(其實(shí)內(nèi)存泄漏就相當(dāng)丟了鑰匙的寶箱屋彪,知道他的存在所宰,但是再也沒辦法拿到了,占用了不必要的系統(tǒng)資源畜挥,寶箱的花就相當(dāng)于丟了自己的錢W兄唷!但明知這部分錢是自己的蟹但,但是你就是沒辦法找到和證明了G!;恰)麦向。
- 方式一:
靜態(tài)對(duì)象(static object):這個(gè)對(duì)象其實(shí)就是一個(gè)"超級(jí)宅男 "。一直被保存在內(nèi)存中的靜態(tài)區(qū)(用于保存全局(global)和靜態(tài)(static)變量的內(nèi)存區(qū)域)客叉,并且被static所修飾的對(duì)象诵竭,聲明將會(huì)超過整個(gè)作用域(scope),畢竟他不在棧中兼搏。他的生命周期會(huì)貫穿程序的全生命周期卵慰。
全局對(duì)象(global object):定義在***任何 *** 作用域({ ... })之外的變量。他和靜態(tài)對(duì)象一樣保存在內(nèi)存的靜態(tài)區(qū)部分佛呻,他的生命周期也是在整個(gè)程序結(jié)束后才會(huì)結(jié)束裳朋。
-
new關(guān)鍵字:先分配memory,再調(diào)用Constructor
-
TypeName *pt = new TypeName(params...);
的編譯器處理過程
1void *mem = operator new (sizeof(TypeName)); //分配所需的內(nèi)存吓著,operator new的內(nèi)部調(diào)用了malloc(n)來分配空間
2pt = static_cast<TypeName *>(mem); //類型強(qiáng)轉(zhuǎn)
3pt->TypeName::TypeName(params...); //調(diào)用class所對(duì)應(yīng)的構(gòu)造函數(shù)
-
-
delete關(guān)鍵字:先分配disconstructor鲤嫡,再釋放內(nèi)存
-
delete pt;
的編譯器處理過程
1TypeName::~TypeName(pt); //調(diào)用析構(gòu)函數(shù)
2operator delete(pt); //釋放內(nèi)存送挑,operator delete內(nèi)部調(diào)用了free(pt)
-
-
動(dòng)態(tài)分配內(nèi)存的結(jié)構(gòu):所分配空間的大小都是16byte的倍數(shù)
- debug模式
- cookie (4byte)
- debuger header(4*8byte=32byte)
- 對(duì)象所需的空間(n byte)
- no man land (4byte)
- pad區(qū)域(填補(bǔ)區(qū)域,當(dāng)總內(nèi)存不為16byte倍數(shù)時(shí)暖眼,不足為最近16byte倍數(shù)所占用的空間)
- cookie(4byte)
- release模式
- cookie (4byte)
- 對(duì)象所需的空間(n byte)
- pad區(qū)域(填補(bǔ)區(qū)域惕耕,當(dāng)總內(nèi)存不為16byte倍數(shù)時(shí),不足為最近16byte倍數(shù)所占用的空間)
- cookie(4byte)
- cookie部分保存參數(shù)的意義
- 例如 cookie中保存為0x00000041:64byte的十六進(jìn)制為0x40罢荡,則0x00000040表示這部?jī)?nèi)存空間的大小赡突,而最低位1表示程序獲得內(nèi)存(系統(tǒng)給出的內(nèi)存),以此來作為標(biāo)識(shí)告訴系統(tǒng)這塊空間的大小和狀態(tài)区赵,同樣cookie也就變成了0x00000041惭缰。為了方便cookie能夠使用十六進(jìn)制最低位表示狀態(tài),高位表示控件大小笼才,則內(nèi)存控件需要時(shí)16byte的的倍數(shù)漱受,因?yàn)橹挥?6byte的倍數(shù)十六進(jìn)制數(shù)打的最低位才為0,也就是16(DEC) = 0x10(HEX)
- 對(duì)于數(shù)組的動(dòng)態(tài)分配的內(nèi)存結(jié)構(gòu)
- debug模式
- cookie (4byte)
- debuger header(4*8byte=32byte)
- 記錄保存的對(duì)象個(gè)數(shù)(4byte)
- 對(duì)象1所需的空間(n byte)
- 對(duì)象2所需的空間(n byte)
- .....
- 對(duì)象n所需的空間(n byte)
- no man land (4byte)
- pad區(qū)域(填補(bǔ)區(qū)域骡送,當(dāng)總內(nèi)存不為16byte倍數(shù)時(shí)昂羡,不足為最近16byte倍數(shù)所占用的空間)
- cookie(4byte)
- release模式
- cookie (4byte)
- 記錄保存的對(duì)象個(gè)數(shù)(4byte)
- 對(duì)象1所需的空間(n byte)
- 對(duì)象2所需的空間(n byte)
- .....
- 對(duì)象n所需的空間(n byte)
- pad區(qū)域(填補(bǔ)區(qū)域,當(dāng)總內(nèi)存不為16byte倍數(shù)時(shí)摔踱,不足為最近16byte倍數(shù)所占用的空間)
- cookie(4byte)
- debug模式
- 由于分配的內(nèi)存的結(jié)構(gòu)虐先,所以導(dǎo)致new [],要搭配對(duì)應(yīng)delete[]來釋放空間,否則會(huì)造成內(nèi)存泄漏派敷,對(duì)應(yīng)的delete[]會(huì)調(diào)用析構(gòu)函數(shù)多次蛹批,可以將需要釋放的內(nèi)存都釋放掉
- debug模式
補(bǔ)充部分:
- static關(guān)鍵字
- static 和non-static
非靜態(tài)對(duì)象:在內(nèi)存中會(huì)創(chuàng)建多份
靜態(tài)對(duì)象:僅在靜態(tài)區(qū)創(chuàng)建一份
-
非靜態(tài)成員函數(shù)
complex c1; cout << c1.real(); //調(diào)用complex中成員函數(shù)real()返回對(duì)應(yīng)的實(shí)部 //實(shí)際相當(dāng)于cout << complex::real(&c1); //調(diào)用該函數(shù)的對(duì)象c1的地址相當(dāng)于成員函數(shù)中的this,傳入到該函數(shù)中
-
靜態(tài)的函數(shù)(無this)
- 由于靜態(tài)的函數(shù)中沒有this篮愉,所以只能被處理靜態(tài)的屬性
- static 和non-static
class Account{
public:
static double m_rate;
static void set_rate(const dounle& x){m_rate = x;};
};
double Account::m_rate = 8.0; //對(duì)于靜態(tài)數(shù)據(jù)必須在函數(shù)外進(jìn)行定義腐芍,設(shè)置不設(shè)置初始值都可以
int main()
{
Account::set_rate(5.0); //通過class name調(diào)用
Account a;
a.set_rate(7.0); //通過object調(diào)用,對(duì)象的地址不會(huì)將a的地址作為this傳入函數(shù)中
}
-
Singleto的設(shè)計(jì)模式(將構(gòu)造函數(shù)放在private中)
-
懶漢模式:先創(chuàng)建好對(duì)象试躏,需要時(shí)直接返回該對(duì)象即可
class A { public: static A& getInstance(){ return a; } setup(){....} private: A(); A(const A& rhs); static A a; ... }
-
餓漢模式(Meyers Singleton):在調(diào)用的時(shí)候才創(chuàng)建所需要的對(duì)象猪勇,由于是靜態(tài),則內(nèi)存中式中只有一份所需要的對(duì)象
class A { public: static A& getInstance(){ return a; } setup(){....} private: A(); A(const A& rhs); ... }; A& A::getInstance() { static A a; return a; }
-
-
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; friend complex& __doapl(complex *, const complex *); };
{
complex<double> c1(2.5, 1.5);
complex<int> c2 (2, 6);
} -
function template泣刹,函數(shù)模版
class stone { public: stone(int w, int h, int we): _w(w), _h(h), _weight(we){} bool operator< (const stone & rhs) const {return _weight < rhs._weight; } private: int _w, _h, _weight; }; template <class T> inline const T& min(const T& a, const T& b) { return b<a? b: a; } ..... { stone r1(2, 3), r2(3, 3), r3; //無需明確指出,編譯器對(duì)function template進(jìn)行參數(shù)推到(argument deduction) //參數(shù)推倒結(jié)果犀被,T為stone项玛,于是替換所有的T為stone,調(diào)用stone::operator< r3 = min(r1, r2); }
-
namespace:相當(dāng)于一塊封閉的空間
namespace std { .... }
-
using directive:相當(dāng)于打開全部的封鎖
#include <iostream.h> using namespace std; int main() { cin << ...; return 0; }
-
using declaration:只是用某一條的聲明
#include <iostream.h> using namespace std::cout; int main() { std::cin << ...; cout<<...; return 0; }
-
-
不使用using namespace
#include <iostream.h> using namespace std::cout; int main() { std::cin << ...; std::cout<<...; return 0; }