標(biāo)題: 如何學(xué)習(xí)C++(面向過(guò)程編程) [打印本頁(yè)]

作者: hushao    時(shí)間: 2016-1-3 02:21
標(biāo)題: 如何學(xué)習(xí)C++(面向過(guò)程編程)
C++支持3種編程方法即“面向過(guò)程編程”、“泛型編程”和“面向?qū)ο缶幊獭。掌握這3中編程方法,就能夠?qū)W好C++。
這篇日志談?wù)勅绾斡肅++進(jìn)行面向過(guò)程編程。
C語(yǔ)言的編程方式就是面向過(guò)程編程,將C++與C對(duì)比起來(lái)學(xué)習(xí)是掌握C++面向過(guò)程編程的一種捷徑。
對(duì)比1:基本語(yǔ)法
在基本語(yǔ)法上C++和C語(yǔ)言是相同的,C語(yǔ)言有的語(yǔ)法C++都有,而C++有的語(yǔ)法C語(yǔ)言基本全有。如果不考慮泛型編程和面向?qū)ο缶幊蹋珻++在基本語(yǔ)法上與C語(yǔ)言無(wú)區(qū)別。因此學(xué)習(xí)過(guò)C語(yǔ)言的程序員,可以直接跳過(guò)C++基本語(yǔ)法的學(xué)習(xí)。
對(duì)比2:基本數(shù)據(jù)類(lèi)型
所謂基本數(shù)據(jù)類(lèi)型是指int、float、double等這些類(lèi)型。相對(duì)于C語(yǔ)言,C++增加了bool型。bool型只有兩個(gè)值true和false,分別表示真和假。
其通常使用的方式如下:
bool flag=(x>10);
if(flag)
   printf("x>10");
else
   printf("x<=10");
當(dāng)然這是一個(gè)示例,實(shí)際的情況可能要復(fù)雜得多。bool型可以賦值為整型。
例如:
bool flag=10;
bool flag=-10;
bool flag=0;
只要賦值的整型不是0,那么bool型的值就是true,反之是false。
對(duì)比3:static變量
C++允許將局部變量定義為static。例如:
int fun()
{
    static int x=0;
    x++;
   return x;
}
如果按照下列方式調(diào)用該函數(shù):
fun();
fun();
int x=fun();
問(wèn)x的值是多少?
答案是3。
其原因在于,一旦一個(gè)變量被聲明為static,則該變量一直存在于函數(shù)的局部,而不會(huì)在函數(shù)退出時(shí)銷(xiāo)毀。故此按照上述調(diào)用實(shí)際上執(zhí)行了三次x++。
static變量的引入是為了解決全局變量的過(guò)度使用。如果C語(yǔ)言沒(méi)有static變量,那么程序只能這么寫(xiě)。
int x;
int fun()
{
    x++;
    return x;
}
此時(shí)x是一個(gè)全局變量。
對(duì)比4:變量初始化
C++允許一種新的變量初始化方法,如下:
int a(10);
此語(yǔ)句與C語(yǔ)言中的:
int a=10;
等價(jià)。
引入這種新語(yǔ)法的原因是為了與對(duì)象的初始化統(tǒng)一。例如:
ifstream f("c:\\test.txt");
該語(yǔ)句定義了對(duì)象f,f是一個(gè)輸入文件流對(duì)象(關(guān)于對(duì)象的問(wèn)題后面再談)。由于對(duì)象的初始化只能采用上面的方式,因此索性引入了
int a(10);
這樣的語(yǔ)法。否則傻瓜編譯器可能就要出現(xiàn)二義性錯(cuò)誤了。
換言之,這種新語(yǔ)法完全可以不用,用也沒(méi)有壞處,反正編譯器認(rèn)識(shí)。
對(duì)比5:默認(rèn)函數(shù)參數(shù)
int fun(int x=0)
{
    x++;
    return x;
}
上述函數(shù)使用了默認(rèn)參數(shù),即給參數(shù)x一個(gè)默認(rèn)值0。調(diào)用函數(shù)時(shí)
int y=fun();
int y=fun(0);
兩者的結(jié)果完全等價(jià),y的值都是1。而
int y=fun(10);
調(diào)用的結(jié)果就是11。
對(duì)比6:引用
下列代碼中,b稱(chēng)為a的引用。
int a=10;
int &b=a;
此時(shí)若執(zhí)行如下代碼:
b=b+10;
請(qǐng)問(wèn)a的值是多少?答案是20。
為什么修改b,同時(shí)也會(huì)修改a呢?因?yàn)閎是a的引用。也就是b和a是同一個(gè)東西。
上述代碼可以換成指針形式,即:
int a=10;
int *b=&a;
*b=*b+10;
其結(jié)果仍然是把a(bǔ)修改為了20。
那么為什么要引入引用呢?因?yàn)?font color="#ff0000">引用比指針更加安全。如果我們使用指針,那么就可能出現(xiàn)下面的情況:
int a=10;
int *b=&a;
b=b+10;
這個(gè)程序員把代碼寫(xiě)錯(cuò)了,但是編譯器并不會(huì)發(fā)出任何警告。因?yàn)樵诰幾g器眼中,這個(gè)代碼是完全正確的。
b=b+10;
意味著b指向的地址后移10,此時(shí)b已經(jīng)不再指向a,而指向了一個(gè)我們不知道的位置。如果把這個(gè)代碼改成下面的形式,結(jié)果更是災(zāi)難性的。
int a=10;
int *b=&a;
b=b+10;  //本意是寫(xiě)*b=*b+10,因?yàn)槭韬鐾浟?
*b=100;
按照程序員的本意,此時(shí)a和b都應(yīng)該變成100。但很遺憾上述代碼不僅a仍然是10,并且還修改了一個(gè)不確定位置的值為100。若修改的位置正好是你的銀行存款,原本的數(shù)字是10000000,而現(xiàn)在修改為了100,你甚至都不知道是怎么修改的。你說(shuō)這不是災(zāi)難性的嗎?
如果將指針改為引用,情況就好得多了。
換言之指針類(lèi)型可以直接操作地址值,而引用是不行的
引用的另外一個(gè)特點(diǎn)是不允許空引用。例如
int&b;
這樣的聲明就是錯(cuò)誤的,而指針是可以為空的。
int *b;
是合法的。
扯一句題外話,java的引用和C++的引用也是不同的。java的引用是可以為空的。因此java的引用類(lèi)型等價(jià)于C++不允許修改地址的指針。
引用的一個(gè)重大用途是用于參數(shù)傳遞。先看下列代碼:
int fun(vector<int> v)
{
 v[0]++;
 return v[0];
}

int main()
{
 vector<int> v(3);
 v[0]=1;
 v[1]=2;
 v[2]=3;

 int y=fun(v);
 return 0;
}
其中vector是C++標(biāo)準(zhǔn)庫(kù)中的新類(lèi)型。大家可以先把它認(rèn)為是數(shù)組。
上述代碼,將v傳遞到fun中,并在其中將0號(hào)元素++后返回。程序看似沒(méi)有問(wèn)題,但實(shí)際上隱藏了巨大的隱患。
vector<int> v(300000000);
v[0]=1;
v[1]=2;
v[2]=3;
.....
v[300000000]=....;
 int y=fun(v);

把程序修改成上面就可以看出問(wèn)題了。問(wèn)題在于v的大小為300000000,這個(gè)長(zhǎng)度簡(jiǎn)直是太大了。但調(diào)用fun的時(shí)候,編譯器會(huì)將v拷貝一份,然后將這個(gè)拷貝傳遞到函數(shù)fun。可以想象一下這么大的一個(gè)數(shù)據(jù),拷貝是不是要花時(shí)間?拷貝是不是要消耗內(nèi)存?怎么避免這個(gè)問(wèn)題呢?
事實(shí)上C語(yǔ)言可以用指針解決這個(gè)問(wèn)題,代碼修改后如下
int fun(vector<int> *v)
{
 (*v)[0]++;
return (*v)[0];
}

int main()
{
vector<int> v(3);
v[0]=1;
v[1]=2;
v[2]=3;

int y=fun(&v);
return 0;
}
此時(shí)函數(shù)調(diào)用時(shí)傳遞的就是指針,不再是整個(gè)值,也就不存在拷貝的問(wèn)題。用引用同樣可以解決這個(gè)問(wèn)題。
int fun(vector<int> &v)
{
   v[0]++;
   return v[0];
}

int main()
{
vector<int> v(3);
v[0]=1;
v[1]=2;
v[2]=3;

int y=fun(v);
return 0;
}
從代碼上看是不是引用更友好一些呢?
但是我們也發(fā)現(xiàn)引用和指針有一個(gè)副作用,那就是v[0]的值會(huì)在fun中被修改。因此代碼應(yīng)該修改一下:
int fun(vector<int> &v)
{
   return v[0]+1;
}

但我們知道程序員是會(huì)犯錯(cuò)誤的,盡管我們知道應(yīng)該如此寫(xiě),但也許一不小心就犯錯(cuò)了。有解決的辦法嗎?那就是給引用加上const。
對(duì)比7:const變量
C++引入了const關(guān)鍵字,用于定義常量。例如:
const int a=10;
此時(shí)a稱(chēng)為一個(gè)整型常量,即a的值不能修改,若修改則編譯器會(huì)報(bào)錯(cuò)。
在參數(shù)傳遞時(shí)可以為參數(shù)加上const,以避免參數(shù)被修改。如下:
int fun(const vector<int> &v)
{
    v[0]++;    //此處將報(bào)錯(cuò)
   return v[0];
}

const的另一個(gè)有趣問(wèn)題在于const放的位置。
const int a=10;
int const a=10;
都是合法的。那么在使用上有區(qū)別嗎?也沒(méi)有區(qū)別,都是int常量。可是如果將int換成引用或者指針情況就大不一樣了。
cont int *a;  //常量指針,a指向的類(lèi)型為const int, a的類(lèi)型為*
int const *a; //常量指針,a指向的類(lèi)型為int const  , a的類(lèi)型為*
int* const a; //指針常量,a指向的類(lèi)型為int, a的類(lèi)型為*const
const int* const a; //指向常量的指針常量,a指向的類(lèi)型為const int, a的類(lèi)型為*const
有了*號(hào)之后組合數(shù)量猛增,那么引用情況也是類(lèi)似的
const int &a; //常量引用,a引用的類(lèi)型為const int, a的類(lèi)型為&
int const &a; //常量引用,a引用的類(lèi)型為int const, a的類(lèi)型為&
int& const a; //引用常量,a引用的類(lèi)型為int, a的類(lèi)型為&  const
const int& const a; //指向常量的引用常量,a引用的類(lèi)型為const int, a的類(lèi)型為& const
對(duì)初學(xué)者而言這實(shí)在是很復(fù)雜,不過(guò)了解一下編譯器的原理,就很容易搞清楚。
說(shuō)白了上面的代碼都是在定義變量a,a是引用或者指針,引用就是&,指針就是*。對(duì)于引用或者指針,那么都有引用或者指向的變量類(lèi)型。
以“&”和“*”為分隔符,“&”和“*”前面的就是引用或者指向的變量類(lèi)型。
而在“&”和“*”之后的符號(hào)則說(shuō)明,變量a本身是不是一個(gè)const。
搞明白了這樣的原則,那么下面的類(lèi)型就不是很變態(tài)了。
const int** &const a;
通常如此變態(tài)的用法只有在考試時(shí)出現(xiàn),如果是在實(shí)際項(xiàng)目中誰(shuí)敢這么用,那么就有開(kāi)除的風(fēng)險(xiǎn)了。
對(duì)比8:new和delete
new和delete是非常重要的操作符,用于動(dòng)態(tài)內(nèi)存管理。C語(yǔ)言里面有malloc和free兩個(gè)函數(shù)與之對(duì)應(yīng)。
但很多C語(yǔ)言課程講到malloc和free的時(shí)候都直接跳過(guò)不講,很多學(xué)習(xí)C語(yǔ)言的學(xué)生也通常沒(méi)有學(xué)習(xí)過(guò)內(nèi)存管理。
因此new和delete盡管很重要但很多學(xué)生在大學(xué)四年內(nèi)都無(wú)法掌握,盡管這是實(shí)際應(yīng)用中非常重要的。
這也難怪,如果我們總是做一些計(jì)算水仙花數(shù)的題目,是不需要用到內(nèi)存管理的。
假設(shè)現(xiàn)在有一個(gè)需求:將硬盤(pán)上的50MB的數(shù)據(jù)讀取到程序中,并保存在變量a里面。
我們不管怎么讀取硬盤(pán)數(shù)據(jù),僅考慮變量a應(yīng)該怎么定義。
  unsigned char  a[1024*1024*50];   //事實(shí)上這么寫(xiě)可能會(huì)出錯(cuò),因?yàn)榭赡懿辉试S定義這么大的數(shù)組哦
應(yīng)該是這樣吧,一個(gè)unsigned char是8位,也就是1個(gè)byte,1024*1024*50個(gè)byte剛好50MB。
現(xiàn)在考慮一個(gè)復(fù)雜的情況,如果文件的大小事不確定的,又應(yīng)該如何定義變量呢?
unsigned char a[SIZE];
其中SIZE表示文件的大小(字節(jié)數(shù)),但問(wèn)題在于我們根本不知道SIZE有多大。如果SIZE是一個(gè)變量,上述定義是無(wú)法編譯的。
因此就必須有一種動(dòng)態(tài)分配內(nèi)存的機(jī)制:
unsigned char *a=new unsigned char[SIZE];
此處的SIZE可以是一個(gè)變量。
我們看看下面代碼的執(zhí)行效果:
unsigned char *a=new unsigned char[1024*1024*50];
while(true);
動(dòng)態(tài)分配50MB的內(nèi)存,同時(shí)循環(huán)不退出,這是為了便于觀察系統(tǒng)內(nèi)存的變化?聪聢D

注意看Demos.exe*32這個(gè)進(jìn)程,它占用了51,748K內(nèi)存,也就是大約50MB內(nèi)存,換言之分配50MB是成功的。

程序修改為如下形式:

unsigned char *a=new unsigned char[1024*1024*100];
while(true);
 

 

沒(méi)錯(cuò)分配了100MB內(nèi)存。下面還有一個(gè)代碼:

while(true)

{

  unsigned char *a=new unsigned char[1024*1024*100];

}

感興趣的可以自己試試,此代碼可以測(cè)試你的系統(tǒng)在幾秒內(nèi)藍(lán)屏。我是不會(huì)測(cè)試這個(gè)代碼的。

這個(gè)代碼會(huì)在循環(huán)中不斷的分配100MB的內(nèi)存,很快內(nèi)存就會(huì)分配完,然后機(jī)器掛掉。

有人也許會(huì)有疑問(wèn):a不是一個(gè)局部變量嗎?再次循環(huán)的時(shí)候前一個(gè)a變量不是銷(xiāo)毀了嗎?

的確a會(huì)不斷的產(chǎn)生和銷(xiāo)毀,但那是變量a銷(xiāo)毀。a是一個(gè)指針,a所指的對(duì)象并沒(méi)有銷(xiāo)毀。

若要銷(xiāo)毀分配的內(nèi)存可以如下做:

while(true)

{

   unsigned char *a=new unsigned char[1024*1024*100];

   //do something

   delete a[];

}

這個(gè)程序的執(zhí)行結(jié)果如下圖:
時(shí)刻A

 時(shí)刻B

 

時(shí)刻A和B的內(nèi)存是不同的,這說(shuō)明程序在動(dòng)態(tài)分配內(nèi)存,同時(shí)內(nèi)存不會(huì)一直增加,由于有delete釋放內(nèi)存,因此程序的內(nèi)存在100MB以內(nèi)(為什么是變化的?自己考慮吧)
new和delete比較容易混淆的地方是它們的另外一種用法:
int *a=new int(10);
delete a;
=============
int *a=new int[10];
delete []a;
上述代碼有一個(gè)非常細(xì)微的差別
new int(10);和new int[10];
delete a;和delete []a;
解釋一下
int *a=new int(10);
表示動(dòng)態(tài)分配了1個(gè)int型的內(nèi)存,并將內(nèi)存的值初始化為10,然后將地址賦值給指針變量a。
delete a;
用于刪除指針變量a。
===========================
int *a=new int[10];
表示動(dòng)態(tài)分配了10個(gè)int型的內(nèi)存,并將內(nèi)存的首地址賦值給指針變量a。
delete []a;
用于刪除指針變量a。
============================
換言之
new 類(lèi)型(參數(shù))用于動(dòng)態(tài)生成單個(gè)變量,并賦初值
delete 變量 用于刪除
new 類(lèi)型[數(shù)量] 用于分配多個(gè)內(nèi)存空間
delete []變量 用于刪除
 
關(guān)于new和delete另一個(gè)需要記住的地方是new之后必須delete,否則就會(huì)內(nèi)存泄漏。
如果你的程序開(kāi)始需要new,那么很快你也會(huì)掌握delete。
但如果你的程序從來(lái)都不需要new,那么你也不會(huì)明白delete。
如果你的C++程序從來(lái)沒(méi)有new和delete,那么沒(méi)有哪個(gè)公司敢雇用你,除了讓你當(dāng)清潔工。
 
對(duì)比9:名字空間
在C++中如果想使用標(biāo)準(zhǔn)庫(kù)中的類(lèi)和函數(shù)就會(huì)用到名字空間。例如:
#include<iostream>
using namespace std;   //這就是名字空間
int main()
{
    cout<<"hello world"<<endl;
    return 0;
}
其中std是名字空間,using namespace表示使用std這個(gè)名字空間。
關(guān)于名字空間,一般正常的C++書(shū)籍都會(huì)介紹,這里就不說(shuō)了。如果你的C++書(shū)籍沒(méi)有解釋名字空間,那么建議換一本書(shū)(這是有可能的,特別是譚浩強(qiáng)之類(lèi)的書(shū))。
比較奇怪的一點(diǎn)是,盡管名字空間這個(gè)技術(shù)還算有用,并且標(biāo)準(zhǔn)庫(kù)中也用了,但在實(shí)際編程中,大家還很少用 。也許C++程序員現(xiàn)在多數(shù)都是些LIB和DLL,名字沖突的概率比直接寫(xiě)源碼低的原因吧。
對(duì)比10:inline
一個(gè)函數(shù)用inline修飾就成為內(nèi)聯(lián)函數(shù),如下:
inline void fun()
{
}
inline是給編譯器用的。如果一個(gè)函數(shù)聲明為inline,那么編譯器會(huì)在調(diào)用該函數(shù)的地方直接展開(kāi)函數(shù)(而不是函數(shù)調(diào)用)。這樣會(huì)增加函數(shù)調(diào)用的效率。內(nèi)聯(lián)函數(shù)可以部分替代C語(yǔ)言的宏定義。
以上只是官方的一種說(shuō)法。官方其實(shí)還有另外一個(gè)說(shuō)法,就是inline不是強(qiáng)制性的。換句話說(shuō),編譯器可以忽略掉inline。
個(gè)人看法,inline還是不inline真的無(wú)法提高什么性能,尤其是如果你想用C++寫(xiě)面向?qū)ο蟪绦颉?/div>
對(duì)比11:頭文件的使用
C++標(biāo)準(zhǔn)庫(kù)的頭文件都是不帶.h結(jié)尾的。例如
#include<iostream>
#include<string>
#include<vector>
事實(shí)上頭文件以.h結(jié)尾只是一個(gè)習(xí)慣,對(duì)于編譯器而言,就算你用.exe結(jié)尾,它也照樣不管。
對(duì)比12:編寫(xiě)自定義頭文件
如果你沒(méi)有編寫(xiě)過(guò)自定義頭文件,那么說(shuō)明你的程序還不夠大。不夠大的含義是:恐怕不到100行。。。。。
但凡大點(diǎn)的程序都是要寫(xiě)自定義頭文件的。這有幾個(gè)原因:1.程序分模塊;2.程序分工;3.編譯效率;4.編譯器限制太大的文件。
不詳細(xì)解釋了。自定義頭文件也是項(xiàng)目大到一定程度,自然會(huì)寫(xiě)的。
關(guān)鍵是怎么寫(xiě)才正確,這里有幾個(gè)原則。
原則1:只有函數(shù)聲明,不能有函數(shù)實(shí)現(xiàn)
以下代碼稱(chēng)為函數(shù)聲明
void fun();
以下代碼稱(chēng)為函數(shù)實(shí)現(xiàn)
void fun()
{
}
那么函數(shù)實(shí)現(xiàn)寫(xiě)在什么地方呢?寫(xiě)在一個(gè)cpp文件里面。
至于為什么要這么做,就要扯到編譯器的實(shí)現(xiàn)問(wèn)題了?傊贿@么做,遲早有一天會(huì)出錯(cuò)。而且錯(cuò)得讓人找不到北。
這里有一個(gè)例外情況inline函數(shù)的實(shí)現(xiàn)是可以放在頭文件里面的。但這只是例外。
原則2:變量聲明加extern
頭文件中聲明的變量一般都是全局變量。如果此變量是常量,那么可以不加extern,其他變量是加上的好。
原因也是編譯器的實(shí)現(xiàn)機(jī)制。
全局變量若定義在頭文件里面應(yīng)該加上extern,如下:
//a.h文件
extern int x;
全局變量的初始化則放到一個(gè)cpp文件中,例如:
//a.cpp
int x=10;
然后在b.cpp文件中就可以使用x了,如下:
#include"a.h"
int main()
{
     x=20;
}
上述代碼如果去掉extern會(huì)出錯(cuò)。
但如果去掉extern,并且把初始化放到頭文件中,則有可能正確。例如
//a.h文件
int x=10;
==========================
#include"a.h"
int main()
{
x=20;
}
這完全可能編譯正確,但也僅僅是由于運(yùn)氣比較好。
如果a.h在多個(gè)cpp文件中被引用就會(huì)出錯(cuò)的。
原則3:用宏定義避免同一個(gè)頭文件被多次#include
//a.h文件
int x=10;
==============================
#include"a.h"
#include"a.h"
int main()
{
x=20;
}
注意a.h被#include了兩次,x就被定義了兩次,不出錯(cuò)才怪呢。
用宏定義就可以避免這種錯(cuò)誤。
//a.h文件
#ifndef A_H
#define A_H
int x=10;
#endif
這樣無(wú)論#include幾次,x都只定義1次。
特別注意:如果不采用原則2的寫(xiě)法,即使加上了宏定義,下列代碼仍然是錯(cuò)誤的。
//b.h
#ifndef B_H
#define B_H
void fun();
#endif
==================
//b.cpp
#include"a.h"
void fun()
{
      x++;
}
===================
#include "a.h"
#include "b.h"
void main()
{
    x++;
}
 
 
 C++的面向過(guò)程編程基本上是對(duì)C語(yǔ)言的增強(qiáng),面向?qū)ο蟛攀荂++的核心內(nèi)容。如果你用面向?qū)ο缶幊,那么本文的很多?nèi)容其實(shí)可以忽略。
寫(xiě)完了,大家看看有沒(méi)有什么遺漏。
 
 
 
 





歡迎光臨 (http://www.torrancerestoration.com/bbs/) Powered by Discuz! X3.1