久久久久一级精品亚洲国产成人综合AV区,久久精品aⅴ无码中文字字幕不卡,国产成人精品久久一区二区三区http://www.shnenglu.com/SpringSnow/category/8493.html雪化了,花開了,春天來了zh-cnTue, 22 Sep 2009 08:08:48 GMTTue, 22 Sep 2009 08:08:48 GMT60c++學習——More Effective C++ 基礎議題三http://www.shnenglu.com/SpringSnow/archive/2009/09/22/96936.htmlSandySandyTue, 22 Sep 2009 05:33:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2009/09/22/96936.htmlhttp://www.shnenglu.com/SpringSnow/comments/96936.htmlhttp://www.shnenglu.com/SpringSnow/archive/2009/09/22/96936.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/96936.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/96936.html
默認構造函數(指沒有參數的構造函數)是指C++語言中,你不用傳參數就可以調用的構造函數。構造函數用于初始化對象,而默認構造函數可以在不依賴任何外部信息的情況下創建對象。

在一個完美的世界里,無需任何外部信息即可創建對象的類可以包含默認構造函數,而需要額外信息來創建對象的類則不包含默認構造函數。可是我們的世界是不完美的,所以我們必將一些額外的因素考慮在內。特別地,如果一個類沒有默認構造函數,使用這個類的時候就會有一些限制。

沒有默認構造函數,在三種情況下它的應用可能會出現問題:
第一個問題是創建數組的時候,通常沒有很好的辦法可以指定數組元素的構造函數的參數。
第二個問題是沒有默認構造函數的類他們無法作為許多基于模板的容器類的類型參數使用。因為通常用于實例化模板的哪些類型需要提供默認構造函數。
第三個問題是在有虛基類的時候應該提供默認構造函數還是不提供默認構造函數。沒有默認構造函數的虛基類使用起來很痛苦,這是因為虛基類的構造函數所要求的參數必須由創建對象所屬的最遠的派生類所提供。

正因為這些強加于沒有默認構造函數的類上的重中限制,一些人認為所有的類都應該有默認構造函數,即使默認構造函數沒有足夠的信息來完全初始化一個對象。

但是默認構造函數會影響類的運行效率,有時會使其他成員函數變得復雜。

如果一個類的構造函數能夠確保所有的數據成員被正確初始化,就能避免付出一些代價。通常默認構造函數不提供這些保證。如果默認構造函數對于某些類沒有太大意義,最好避免使用他們。這給使用這種類加了一些限制,但是當你使用它時,它可以向你保證你能很放心地相信這個類被正確得初始化,并且具有高效的效率。

說實話,這一章節我看得不是很明白。
作者在一開始,列舉了一些沒有默認構造函數我們可能遇到的問題,在這些問題下,進而得出默認構造函數所帶來的一些效率和代價困擾。看來還需要在實踐中仔細揣摩揣摩。





Sandy 2009-09-22 13:33 發表評論
]]>
c++學習——More Effective C++ 基礎議題二http://www.shnenglu.com/SpringSnow/archive/2009/09/22/96922.htmlSandySandyTue, 22 Sep 2009 03:57:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2009/09/22/96922.htmlhttp://www.shnenglu.com/SpringSnow/comments/96922.htmlhttp://www.shnenglu.com/SpringSnow/archive/2009/09/22/96922.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/96922.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/96922.html條款三 絕不要把多態應用于數組

繼承的一大特性是,允許你通過指向基類的指針和引用來操縱派生類對象。也允許通過基類指針和引用來操縱派生類數組。

但是用基類指針操縱一個包含派生類對象的數組,就會發生各種個樣的問題,其結果往往是不確定的。

我根據書中的例子,寫了一個小程序:

#include <iostream>

using namespace std;

class BST
{
public:
    BST()
    
{
        i 
= 0;
    }

    
int i;
}
;

class BalancedBST: public BST
{
private:
    
int j;
}
;

void printBSTArray(ostream& outconst BST arr[], int numElements)
{
    
for (int i = 0; i < numElements; i++)
    
{
        
out << arr[i].i << endl;
    }

}


int main()
{
    cout 
<< "BST.\n";
    BST BSTArray[
10];
    printBSTArray(cout, BSTArray, 
10);

    cout 
<< "BalancedBST.\n";
    BalancedBST bBSTArray[
10];
    printBSTArray(cout, bBSTArray, 
10);

    system(
"pause");

    
return 0;
}


其結果如下:

 

 可以看到程序并不如我們所期望的那樣,這說明什么呢?
 arr[i],表示的是*(arr+i),但是arr+i所指向的地址偏離arr所指向的地址是i*(an object in the array)。
因為參數被聲明為BST數組類型,那么數組的每個元素必須是BST,那么它們的間隔也畢定是i*sizeof(BST)。如果傳入BalancedBST數組,編譯器可能就會犯錯誤,在這種情況下,編譯器就會假定數組里每個對象的大小都和BST的大小一樣。而通常派生類要比基類有更多的成員變量,所以派生類一般都比基類對象大。所以我們就看到了如上的結果。

 試圖通過一個基類指針刪除一個包含派生類對象的數組,也會有同樣的問題。

所以不要把多臺應用到數組上,還是很有好處的。



Sandy 2009-09-22 11:57 發表評論
]]>
c++學習——More Effective C++ 基礎議題http://www.shnenglu.com/SpringSnow/archive/2009/09/15/96194.htmlSandySandyTue, 15 Sep 2009 02:27:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2009/09/15/96194.htmlhttp://www.shnenglu.com/SpringSnow/comments/96194.htmlhttp://www.shnenglu.com/SpringSnow/archive/2009/09/15/96194.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/96194.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/96194.html最近忙著看書,但發覺記憶不是很好。看時明明白白的東西,一會就忘了。覺得還是記錄下來,好記性不如爛筆頭,減慢閱讀的速度,增加思考的時間。

More Effective C++之一 基礎議題

條款1:區分指針和引用
這個可以理解為:指針和引用有什么區別。
一是指針可以為空,而引用不能為空。
引用必須要指代某個對象。由于引用總是要指代一個對象,C++要求引用必須初始化。不存在空引用,則意味著引用比使用指針更高效。因為使用引用之前不需要測試它是否有效,而指針通常需要檢查其是否為空。
二是指針可以被重新賦值用以指向另外一個不同的對象,而引用總是指向初始化時它所指代的對象。

條款2:優先考慮C++風格的類型轉換
這個可以有兩個問題需要明白:一是C風格的類型轉換有什么缺點;二是C++風格的類型轉換的優點。
首先我們來看C風格類型轉換的缺點:
其一是可以通過它們在任意類型之間進行轉換。有些轉換差別很大,但C風格的類型沒有做區分,行為有些粗魯。
其二是C風格的類型轉換很難進行查找。

c++風格的類型轉換:有4種類型轉換,分別是static_cast, const_cast, dynamic_cast和reinterpret_cast。
使用時應寫成
static_cast<typde> (expression),其他同理。

static_cast 針對一種不涉及繼承的類型實施轉換,也不涉及const轉換的時候,就可以使用static_cast轉換。
const_cast用來去除掉一個表達式的const屬性或volatile屬性。強調的是通過這個轉換你要做的唯一一件事情就是改變某些東西的const屬性或者volatile屬性。目前為止,最通常的用法是去除掉一個對象的const屬性。
dynamic_cast,用來針對一個繼承體系做向下或者橫向的安全轉換。用dynamic_cast 把指向基類的指針(或引用)轉換成指向派生類或者基類的兄弟類的指針(或引用),而且同時可以知道轉換是否成功。空指針或者異常意味著失敗。
reinterpret_cast最常見的用法是用來在函數指針之間進行類型轉換。這種轉換常常是由(編譯器的)實現所定義的,致使reinterpret_cast幾乎不可移植。對函數指針實施類型轉換是不可移植的,而且在某些情況下會產生不正確的。

C++風格的類型轉換的優點:有確切的含義并容易識別,他們允許編譯器診斷類型轉換所產生的錯誤,不會使這些錯誤就在不知不覺的情況下被漏掉。

該上班了,其他待續。。。





Sandy 2009-09-15 10:27 發表評論
]]>
判斷計算機的大小尾數的方法http://www.shnenglu.com/SpringSnow/archive/2009/07/28/91477.htmlSandySandyTue, 28 Jul 2009 07:04:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2009/07/28/91477.htmlhttp://www.shnenglu.com/SpringSnow/comments/91477.htmlhttp://www.shnenglu.com/SpringSnow/archive/2009/07/28/91477.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/91477.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/91477.html
書中介紹了兩種方法
第一種是這樣的利用強制類型轉換
bool endianness()
{
      int testNum;
     char*  ptr;

     testNum = 1;
     ptr = (char*)&testNum;
     return ptr;
}

這個方法還比較好理解。

第二種方法就是利用union。
bool endianness()
{
    union{
         int theInteger;
         char singleChar;
   }endianTesg;

   endianTest.theInteger = 1;
   return endianTest.singleChar;
}

這種方法很巧妙。帶著困意就是沒有看懂,一個個問號就蹦出來了?這是為什么呢?

說實話,union在學習和工作中用的還真的不是很多。其用法還真是不記得。
所以趕快到網上去搜了一下。有篇文章還不錯,我看懂了。
共用體union用法講解
鏈接地址:http://blog.ednchina.com/likee/20666/message.aspx

Union表示幾個變量公用一個內存位置, 在不同的時間保存不同的數據類型和不同長度的變量。其長度為Union中最大的變量長度。

這樣,我們就不難理解上面的程序,theInteger和singleChar是共用一個內存位置的,如果是小尾數法的話,那么singleChar為1,對應theInteger的低八位;如果是大尾數法的話,那么singleChar為0, 對應theInteger的低八位。


繼續努力學習!
朝著夢想加油前進。


Sandy 2009-07-28 15:04 發表評論
]]>
類設計者的核查表http://www.shnenglu.com/SpringSnow/archive/2009/07/25/91098.htmlSandySandySat, 25 Jul 2009 00:32:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2009/07/25/91098.htmlhttp://www.shnenglu.com/SpringSnow/comments/91098.htmlhttp://www.shnenglu.com/SpringSnow/archive/2009/07/25/91098.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/91098.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/91098.html

去同學那玩,看到這么一本書《C++沉思錄》。這本書很早聽過,但是沒有讀過。于是捧起書讀了幾章,感覺很是不錯。其中第四章就是講“類設計者的核查表”。雖然用c++有幾年,但是有一些東西還是需要銘記于心的。

 

類設計者的核查表

一、        您的類需要一個構造函數么?

有些類太簡單,無需構造函數,但有些類太復雜,他們需要構造函數來隱藏它們的內部工作方式。

二、           您的數據成員是私有的么?

通常使用公有的數據成員不是什么好事,因為類設計者無法控制何時訪問這些成員。

三、           您的類需要一個無參的構造函數么?

如果一個類已經有了構造函數,想聲明該類的對象可以不必顯示地初始化它們,則必須顯示地寫一個無參的構造函數。

四、           是不是每一個構造函數初始化所有的數據成員?

構造函數的用途就是用一種明確定義的狀態來設置對象。對象的狀態由對象的數據成員進行反映。每個構造函數都要負責為所有的數據成員設置經過明確定義的值。

有時這種說法也未必總是正確的。有時,類會有一些數據成員,它們只在它們的對象存在了一定時間之后才有意義。提這個問題,只是激勵你進行思考。

五、           類需要構造函數么?

不是所有有構造函數的類都需要構造函數。如果深入考慮一個類要做些什么,那么該類是否需要析構函數的問題就十分明顯了。應該問一問該類是否分配了資源,而這些資源又不會有成員函數自動釋放,這就足夠了。特別是那些構造函數里包含了new表達式的類,通常要在析構函數中加上相應的delete表達式,所以需要一個虛析構函數。

六、        類需要一個虛析構函數么?

有些類需要虛析構函數只是為了聲明他們的析構函數是虛的。當然,決不會用做基類的類是不需要虛析構函數的:任何虛函數只在繼承的情況下才有用。

虛析構函數通常是空的。

七、           你的類需要復制構造函數么?

很多時候答案都是“不”,但是有時候答案是“是”。關鍵在于復制該類對象是否就相當于復制其數據成員和基類對象。如果并不相當,就需要復制構造函數。

如果你的類在構造函數內分配資源,則可能需要一個顯示的復制構造函數來管理資源。有析構函數的類通常是析構函數來釋放構造函數分配的資源,這通常說明需要一個復制構造函數。(空的虛析構函數除外)

如果不想用戶能夠復制該類的對象,就聲明復制構造函數為私有的。如果其他的成員不會使用這些成員函數,聲明就足夠了,沒有必要定義它們。

八、           你的類需要一個賦值操作么?

如果需要復制構造函數,同理多半也會需要一個賦值操作。

九、           你的賦值操作符能正確地將對象賦給對象本身么?

賦值總是用新值取代目標對象的舊值。如果原對象和目標對象是同一個,而我們又奉行“先釋放舊值,再復制”的行事規程,那么就可能在還沒有實施復制之前就把原對象銷毀了。

十、           你的類需要定義關系操作符么?

如果你的類邏輯上支持相等操作,那么提供operate== operate!=可能會有好處。類似的,如果你的類的值有某種排序關系,那就可能會想提供余下的關系操作符。只要它們想創建你的類型的有序集合,你就必須提供關系操作符。

十一  刪除數組時你記住了用delete[]么?

這個形式的存在,是C++希望在保持與C的兼容性的同時關注效率。C++要求用戶告知要被刪除的是不是數組。如果是,該實現就可能會提供另一個地方來存儲長度,因為與數組所需的內存量相比,這個常數的開銷會小很多。

十二   記得在復制構造函數和賦值操作符的參數類型中加上了const么?

復制構造函數應該是像X::X(const X&)這樣,畢竟復制對象不會改變原對象。實際上,由于綁定一個非const引用到一個臨時對象是非法的,使用X::X(X&)作為復制構造函數不會允許復制任何特殊表達式的結果。同樣道理適用于賦值。

十三   如果函數有引用參數,它們應該是const引用么?

只有當函數想改變參數時,它才應該有不用const聲明的引用參數。

 

   其中很多作者提到,提這些問題并不是希望去尋求答案,只是希望能夠激勵你進行思考。所以當我們設計一個類的時候,多思考一下,有沒有什么地方需要注意的,我們設計的類將會更合理,更健壯一些。

Sandy 2009-07-25 08:32 發表評論
]]>
int 與char的轉換http://www.shnenglu.com/SpringSnow/archive/2009/07/24/91058.htmlSandySandyFri, 24 Jul 2009 09:45:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2009/07/24/91058.htmlhttp://www.shnenglu.com/SpringSnow/comments/91058.htmlhttp://www.shnenglu.com/SpringSnow/archive/2009/07/24/91058.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/91058.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/91058.html今天抱著書在做這么一道題:
整數/字符串轉換
編寫兩個轉換例程。第一個例程將一個字符串轉換成帶符號的整數。您可以假定這個字符串只包含數字和符號字符('-'),是一個格式正確的整數,而且這個數字在int類型的范圍之內。第二個例程將Int類型中存儲的有符號整數轉換回字符串。

其中碰到了int與char的轉換問題。這個還真的把我難住了。我先用最笨的方法switch進行了轉換。你也知道這肯定不是最優的方法。直接轉換,值也肯定不對。

后來發現竟然是這么使用的,趕快記錄下來。
1、int 轉換成char
      例如:
                int  n = 1;
                char ch = char(n + '0');
                不過需要注意,此處的n只能是0-9之間的字符
2、char轉換成Int
                char ch = '9';
                 int n = int(ch) - int('0');
                  此處ch也是‘0’至‘9’的數字字符

多多學習,抓住機遇。



Sandy 2009-07-24 17:45 發表評論
]]>
轉載: .NET中,接口與類的區別 http://www.shnenglu.com/SpringSnow/archive/2009/06/26/88588.htmlSandySandyFri, 26 Jun 2009 09:32:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2009/06/26/88588.htmlhttp://www.shnenglu.com/SpringSnow/comments/88588.htmlhttp://www.shnenglu.com/SpringSnow/archive/2009/06/26/88588.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/88588.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/88588.html2、不能實例化一個接口,接口只包括成員的簽名;而類可以實例化(abstract類除外)。
3、接口沒有構造函數,類有構造函數。
4、接口不能進行運算符的重載,類可以進行運算符重載。
5、接口的成員沒有任何修飾符,其成員總是公共的,而類的成員則可以有修飾符(如:虛擬或者靜態)。
6、派生于接口的類必須實現接口中所有成員的執行方式,而從類派生則不然。

那么為什么還要有接口呢?
    主要原因是它是一種有效的契約。類有一些成員,不考慮把這些成員組合在一起,類只是一個擁有各種方法、字段和屬性的列表,但為了能以某種方式使用類,必須知道類能執行那些功能,具體的操作就是聲明執行一個或多個接口的類,類執行接口的方式是從接口中派生,再提供這個接口中定義的所有成員的執行方式。

摘自: http://www.cnblogs.com/ajayumi/archive/2008/06/10/1216746.html

Sandy 2009-06-26 17:32 發表評論
]]>
成員初始化列表 http://www.shnenglu.com/SpringSnow/archive/2009/06/18/87959.htmlSandySandyThu, 18 Jun 2009 05:33:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2009/06/18/87959.htmlhttp://www.shnenglu.com/SpringSnow/comments/87959.htmlhttp://www.shnenglu.com/SpringSnow/archive/2009/06/18/87959.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/87959.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/87959.html
學習是王道,搜索是方法。有了網絡就是好。

摘自:http://www.cnblogs.com/heyutao/archive/2009/05/22/1487081.html

成員初始化列表

類對象的構造順序是這樣的:
1.分配內存,調用構造函數時,隱式/顯示的初始化各數據成員
        初始化階段可以是顯式的或隱式的,取決于是否存在成員初始化表。隱式初始化階段按照聲明的順序依次調用所有基類的缺省構造函數,然后是所有成員類對象的缺省構造函數。
2.進入構造函數后在構造函數中執行一般計算

        計算階段由構造函數體內的所有語句構成。在計算階段中,數據成員的設置被認為是賦值,而不是初始化。

使用初始化列表有兩個原因:

1.必須這樣做:

        三種情況下需要使用初始化成員列表
        1)對象成員;
        2)const修飾的成員;
        3)引用成員數據;

(1)如果有一個類成員,它本身是一個類或者是一個結構,而且這個成員它只有一個帶參數的構造函數,而沒有默認構造函數,這時要對這個類成員進行初始化,就必須調用這個類成員的帶參數的構造函數,如果沒有初始化列表,那么他將無法完成第一步,就會報錯。

using namespace std;
class ABC
{
public:
    ABC(
int x,int y,int z):a(x),b(y),c(z){};
private:
  
int a;
    
int b;
    
int c;
}
;
class MyClass
{
public:
    MyClass(
int a,int b,int c):abc(a,b,c){}
private:
    ABC abc;
}
;

int main()
{
    MyClass o(
1,2,3);
    
return 0;
}

(2)當類成員中含有一個const成員時

(3)當類成員中含有一個引用時

#include<iostream>
using namespace std;

class ConstRef {
public:
    ConstRef(
int i);
    
void print();
private:
    
int a;
    
const int b;//const成員
    int &c;//引用
}
;

ConstRef::ConstRef(
int i):b(i),c(a)//含有一個const對象時,或者是一個引用時使用初始化成員列表
{
    a 
= i;       // ok
    
//b = i;         // 錯誤
    
//c = a;       // 錯誤
}

void ConstRef::print()
{
    cout
<<a<<endl;
    cout
<<b<<endl;
    cout
<<c<<endl;
}

int main()
{
    ConstRef o(
1);
    o.print();
    
return 0;
}


 

2.效率要求這樣做:

類對象的構造順序顯示,進入構造函數體后,進行的是計算,是對他們的賦值操作,顯然,賦值和初始化是不同的,這樣就體現出了效率差異,如果不用成員初始化列表,那么類對自己的類成員分別進行的是一次隱式的默認構造函數的調用,和一次復制操作符的調用,如果是類對象,這樣做效率就得不到保障。

注意:構造函數需要初始化的數據成員,不論是否顯式的出現在構造函數的成員初始化列表中,都會在該處完成初始化,并且初始化的順序和其在聲明時的順序是一致的,與列表的先后順序無關,所以要特別注意,保證兩者順序一致才能真正保證其效率。

現在明白為什么要使用成員初始化列表了。

這里再強調一下類的初始化的順序,應該是類成員變量的初始化不是按照初始化表的順序被初始化的,而是按照在類中聲明的順序被初始化的。
這是摘自:Effective C++學習筆記:初始化列表中成員列出的順序和它們在類中聲明的順序相同 http://www.shnenglu.com/xczhang/archive/2008/01/22/41613.html

為什么會這樣呢?我們知道,對一個對象的所有成員來說,它們的析構函數被調用的順序總是和它們在構造函數里被創建的順序相反。那么,如果允許上面的情況(即,成員按它們在初始化列表上出現的順序被初始化)發生,編譯器就要為每一個對象跟蹤其成員初始化的順序,以保證它們的析構函數以正確的順序被調用。這會帶來昂貴的開銷。所以,為了避免這一開銷,同一種類型的所有對象在創建(構造)和摧毀(析構)過程中對成員的處理順序都是相同的,而不管成員在初始化列表中的順序如何。

注意:上述內容不適用于static變量,static變量應該在類的構造函數前被初始化。



好文章拿來學習,請作者見諒哈!



Sandy 2009-06-18 13:33 發表評論
]]>
為什么拷貝構造函數必須為引用傳遞,不能是值傳遞? http://www.shnenglu.com/SpringSnow/archive/2009/06/18/87945.htmlSandySandyThu, 18 Jun 2009 03:13:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2009/06/18/87945.htmlhttp://www.shnenglu.com/SpringSnow/comments/87945.htmlhttp://www.shnenglu.com/SpringSnow/archive/2009/06/18/87945.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/87945.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/87945.html
去網上搜索了一下,的確有很多這方面的知識講解。

我們先看一下CSDN上的一個帖子的回答:
簡單的回答是為了防止遞歸引用。
具體一些可以這么講:
 當一個對象需要以值方式傳遞時,編譯器會生成代碼調用它的拷貝構造函數以生成一個復本。如果類A的拷貝構造函數是以值方式傳遞一個類A對象作為參數的話,當需要調用類A的拷貝構造函數時,需要以值方式傳進一個A的對象作為實參; 而以值方式傳遞需要調用類A的拷貝構造函數;結果就是調用類A的拷貝構造函數導致又一次調用類A的拷貝構造函數,這就是一個無限遞歸。

這個解釋還是蠻具體的。
利用值傳遞的話,會導致遞歸引用。

還有一片文章也談到了這個問題, 我覺得寫得也非常好!

為什么拷貝構造函數必須為引用傳遞,不能是值傳遞?
鏈接地址:http://www.cnblogs.com/chio/archive/2007/09/14/893299.html

其中講到了3個問題
1是拷貝構造函數的作用。
      作用就是用來復制對象的,在使用這個對象的實例來初始化這個對象的一個新的實例。
2是參數傳遞過程到底發生了什么?
      將地址傳遞和值傳遞統一起來,歸根結底還是傳遞的是"值"(地址也是值,只不過通過它可以找到另一個值)!
i)值傳遞:
    對于內置數據類型的傳遞時,直接賦值拷貝給形參(注意形參是函數內局部變量);
    對于類類型的傳遞時,需要首先調用該類的拷貝構造函數來初始化形參(局部對象);如void foo(class_type obj_local){}, 如果調用foo(obj);  首先class_type obj_local(obj) ,這樣就定義了局部變量obj_local供函數內部使用
ii)引用傳遞:
    無論對內置類型還是類類型,傳遞引用或指針最終都是傳遞的地址值!而地址總是指針類型(屬于簡單類型), 顯然參數傳遞時,按簡單類型的賦值拷貝,而不會有拷貝構造函數的調用(對于類類型).
3是在類中有指針數據成員時,拷貝構造函數的使用?
        如果不顯式聲明拷貝構造函數的時候,編譯器也會生成一個默認的拷貝構造函數,而且在一般的情況下運行的也很好。但是在遇到類有指針數據成員時就出現問題了:因為默認的拷貝構造函數是按成員拷貝構造,這導致了兩個不同的指針(如ptr1=ptr2)指向了相同的內存。當一個實例銷毀時,調用析構函數free(ptr1)釋放了這段內存,那么剩下的一個實例的指針ptr2就無效了,在被銷毀的時候free(ptr2)就會出現錯誤了, 這相當于重復釋放一塊內存兩次。這種情況必須顯式聲明并實現自己的拷貝構造函數,來為新的實例的指針分配新的內存。

問題1和2回答了為什么拷貝構造函數使用值傳遞會產生無限遞歸調用的問題;
問題3回答了回答了在類中有指針數據成員時,拷貝構造函數使用值傳遞等于白顯式定義了拷貝構造函數,因為默認的拷貝構造函數就是這么干的。

這樣我終于看明白了。作者寫得的確好。





Sandy 2009-06-18 11:13 發表評論
]]>
轉: 淺談C++的智能指針http://www.shnenglu.com/SpringSnow/archive/2009/06/18/87941.htmlSandySandyThu, 18 Jun 2009 02:47:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2009/06/18/87941.htmlhttp://www.shnenglu.com/SpringSnow/comments/87941.htmlhttp://www.shnenglu.com/SpringSnow/archive/2009/06/18/87941.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/87941.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/87941.htmlhttp://www.shnenglu.com/yearner/archive/2008/11/09/66447.html

對于智能指針,也只是聽說而已,對深層的東西也不甚了解。昨日又重聽這一字眼,想想多少也該了解一些。

淺談C++的智能指針

        內存泄露是C++程序員都頭疼的大問題。C++缺乏像JAVA、C#一樣,擁有GC這么一項有利的武器,它將內存管理的部分權限交給了程序員。雖然GC的存在節約了開發、排錯的時間與成本,但是C++為了追求運行速度而20年來堅決不予補充進其標準。(題外話:C++通過加大開發難度去換取執行速度的做法,在現在看來不知是否能給與正面的評價,還是留給將來再說吧。)

       從此,在堆上申請了內存忘了釋放、所造成的內存泄露的問題就一直困擾著C++程序員。也許為了稍許彌補沒有垃圾回收器所造成的開發門檻高,各大廠商開發的C++庫中都像COM學習引入智能指針試圖解決部分目前存在的問題。

       智能指針是存儲指向動態分配(堆)對象指針的類, 用于生存期控制, 能夠確保自動正確的銷毀動態分配的對象,防止內存泄露。它的一種通用實現技術是使用引用計數(reference count)。智能指針類將一個計數器與類指向的對象相關聯,引用計數跟蹤該類有多少個對象共享同一指針。每次創建類的新對象時,初始化指針并將引用計數置為1;當對象作為另一對象的副本而創建時,拷貝構造函數拷貝指針并增加與之相應的引用計數;對一個對象進行賦值時,賦值操作符減少左操作數所指對象的引用計數(如果引用計數為減至0,則刪除對象),并增加右操作數所指對象的引用計數;調用析構函數時,構造函數減少引用計數(如果引用計數減至0,則刪除基礎對象)。

        說到智能指針,我們一定要看看標準C++庫提供的“搞笑的”智能指針:auto_ptr。

       標準庫中提供了C++程序的基本設施。雖然C++標準庫隨著C++標準折騰了許多年,直到標準的出臺才正式定型,網上評論C++標準庫時都說:“在標準庫的實現上卻很令人欣慰得看到多種實現,并且已被實踐證明為有工業級別強度的佳作。”但目前的標準C++中,只有一種獨苗智能指針:std::auto_ptr。

        auto_ptr指針是一個RAII對象,它初始化時獲得資源,析構時自動釋放資源(生命期結束).它的缺點數不勝數:
1、auto_ptr要求一個對象只能有一個擁有者,嚴禁一物二主
2、缺少對引用數和數組的支持。
3、不可將auto_ptr對象作為STL容器的元素。C++標準明確禁止這樣做,否則可能會碰到不可預見的結果。(這一條暈死一大片)。
4、auto_ptr在被復制的時候會傳輸所有權

        反正由此可見:標準庫的智能指針就是無甚大用。

       在這樣的情況下,C++標準委員會自然需要考慮引入新的智能指針。目前由C++標準委員會庫工作組發起的Boost 組織開發了Boost系列智能指針。

        在Boost中的智能指針有五種: scoped_ptr,scoped_array,shared_ptr,shared_array,weak_ptr.

        前4種完全是針對標準庫中的auto_ptr提出解決方案,如:scope_ptr是針對“auto_ptr在被復制的時候會傳輸所有權”這一弱點提出的。最后一種沒見過,看名字像是弱引用智能指針,我懷疑是不是類似于JAVA中弱引用一樣,有待進一步學習。

        還查到一篇:
 
       C++深度探索系列:智能指針(Smart Pointer) [一] 
        http://dev.csdn.net/develop/article/17/17530.shtm

        有空可以看看!




Sandy 2009-06-18 10:47 發表評論
]]>
如何輸出“hello, world”http://www.shnenglu.com/SpringSnow/archive/2009/05/05/81930.htmlSandySandyTue, 05 May 2009 05:01:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2009/05/05/81930.htmlhttp://www.shnenglu.com/SpringSnow/comments/81930.htmlhttp://www.shnenglu.com/SpringSnow/archive/2009/05/05/81930.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/81930.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/81930.html
的確, 我們簡單的這樣寫,如下,就可以輸出一個“hello, world”.
#include <iostream>

using namespace std;

int main()
{
  cout 
<< "Hello, World!" << endl;
  
return 0;
}

但現在的條件是你需要在main函數里什么也不做,就輸出“hello, world”。

可能你很快就想到了。但是我很笨,沒有想到。不過我現在會了。

我們可以這樣做,
#include <iostream>

using namespace std;

class A
{
public:
  A()
  
{
    cout 
<< "Hello, World!" << endl;
  }

}
;

A a;

int main()
{
  
return 0;
}

聲明一個全局的對象a, 這樣的話,它會調用它的構造函數,也就會打印出“Hello,world”.
還有一種方法也是定義一個全局對象,只是利用方法,如下:

#include <iostream>

using namespace std;

int Func()
{
  cout 
<< "Hello, World!" << endl;
  
return 1;
}


int i = Func();

int main()
{
  
return 0;
}


其實這主要是一個全局對象的初始化順序的問題。
全局對象在調用 main之前初始化, 在退出main之后析構。

理解了這個,輸出“Hello,World”便很簡單了。

Sandy 2009-05-05 13:01 發表評論
]]>
計算機計算浮點數時的舍入誤差http://www.shnenglu.com/SpringSnow/archive/2009/04/15/79994.htmlSandySandyWed, 15 Apr 2009 05:38:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2009/04/15/79994.htmlhttp://www.shnenglu.com/SpringSnow/comments/79994.htmlhttp://www.shnenglu.com/SpringSnow/archive/2009/04/15/79994.html#Feedback1http://www.shnenglu.com/SpringSnow/comments/commentRss/79994.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/79994.htmlhttp://radovi.javaeye.com/blog/322096這篇博客里談到計算機計算浮點數時的舍入誤差,我也在VS2005下進行了相應的實驗,如下代碼:

#include <iostream>

using namespace std;

int main()
{
    
double f = 4.35;

    
int a = int(f*100);

    cout 
<< a << endl;

    system(
"pause");
}

 的確打印出來的結果是434.

老男孩給出的解釋是這樣的:
這個問題和二進制的表示有很大關系
簡單地說
計算機不能準確表示諸如1/10等一類分數

我查到了一篇文章:http://support.microsoft.com/kb/214118/zh-cn,如下,
       IEEE 754 標準是一種方法很容易操作的壓縮方式存儲浮點數。 Intel coprocessors 和實現浮點數學的大多數基于 PC 的程序使用此標準。
       IEEE 754 指定編號,以減少存儲要求,并允許該內置二進制算法指令來處理數據以相對較快速的方式的所有微處理器上可用的二進制格式存儲。 但是,是簡單的、 非重復的十進制數字的某些數字轉換為重復不能存儲的完美的準確性的二進制數字。
      例如數 1 / 10 可以表示簡單小數的十進制數字系統中:
      .1
     但是,二進制格式中的數目將十進制重復的二進制文件:
     0001100011000111000111 (和這樣上)
     此數字無法表示按有限數量的空間。 因此,此數字向下舍入的大約-2.78E-17 存儲。
      如果獲取給定的結果執行多個的算術運算,這些舍入誤差可能具有累積性。

      看來是和二進制的表示有很大關系。

Sandy 2009-04-15 13:38 發表評論
]]>
讀《小P成長記》http://www.shnenglu.com/SpringSnow/archive/2009/03/12/76330.htmlSandySandyThu, 12 Mar 2009 05:59:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2009/03/12/76330.htmlhttp://www.shnenglu.com/SpringSnow/comments/76330.htmlhttp://www.shnenglu.com/SpringSnow/archive/2009/03/12/76330.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/76330.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/76330.html
學習這么久,才發現思考方法的轉變還是很重要的。我們往往缺少一位向老C一樣的好老師,能夠慢慢引導我們學習。

機會難得,我們與小P一樣快快成長吧。

讀到第一桶第九碗了,
對里面的一些思想和方法很贊同,轉過來:

思想:
1. 以數據為中心思考問題的解決之道。
2. 將對同類數據的操作放在相同的編譯單元中。
3. 信息隱藏,而不是暴露,以將問題的規模控制在可理解的范圍。


方法:
1. 首先關注整體,而不是細節。
2. 先考慮測試,更先于編碼。
3. 先用偽代碼編程,以體現程序的意圖,再用具體代碼完善之。
4. 迅速構建可編譯通過的,并且可執行的框架程序,使用測試代碼測試之。
5. 將以上方法反復應用于子模塊,直到問題被解決。
6. 在上下文環境中提取對其他模塊的需求。
7. 先寫.h文件,后寫.c文件。
8. 先快速實現一個可行的解法,再根據反饋信息優化之。

如果大家也關心小P,就一同到那看看,鏈接地址:http://www.shnenglu.com/enderson/archive/2009/02/18/74215.html


Sandy 2009-03-12 13:59 發表評論
]]>
vector的一點疏忽http://www.shnenglu.com/SpringSnow/archive/2008/12/29/70647.htmlSandySandyMon, 29 Dec 2008 03:37:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2008/12/29/70647.htmlhttp://www.shnenglu.com/SpringSnow/comments/70647.htmlhttp://www.shnenglu.com/SpringSnow/archive/2008/12/29/70647.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/70647.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/70647.html今天遇到一個奇怪的問題,是有關于vector的.
他一直提示: subcript  out of range.這是指下標越界的問題,怎么也沒有理解如何下標越界了.
代碼我這樣寫的:
for (size_t   i= vc.size()-1; i >= 0; i--)
{
     ....
}
覺得這個問題很蹊蹺,在什么地方越界的呢?

跟蹤了一下i值的變化,原來i值的變化并非按照我想像的那樣進行,當i為零后,再減一,并不是變為一個負數,而是變成了一個非常大的正數,所以此時提醒越界了.

原來是這個原因.

對于vector的用法不是很熟,我暫時改為
int size = vc.size()-1;
for (int i = size; i>=0; i--)
{
 ......
}

這樣就解決了越界的問題.

那么發生這個問題的原因是什么呢?

我們需要清楚size_t的類型是什么?
size_t   
 有時就是unsigned   int;   
有時就是unsigned   long;

通過這個我們就知道size_t是無符號整數,所以這個問題也就清楚了.

那有沒有很好的方法來解決這個問題呢?
不像我那么笨的輸出呢?

有:
for (std::vector<int>::reverse_iterator i = vc.rbegin(); i < vc.rend(); i++)
 {
    ......
 }

這樣就把vctor中的元素顛倒了一個順序輸出了.

歡迎大家指教.



Sandy 2008-12-29 11:37 發表評論
]]>
轉:臨界區(Critical section)與互斥體(Mutex)的區別http://www.shnenglu.com/SpringSnow/archive/2008/12/26/70464.htmlSandySandyFri, 26 Dec 2008 10:39:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2008/12/26/70464.htmlhttp://www.shnenglu.com/SpringSnow/comments/70464.htmlhttp://www.shnenglu.com/SpringSnow/archive/2008/12/26/70464.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/70464.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/70464.html2、臨界區是非內核對象,只在用戶態進行鎖操作,速度快;互斥體是內核對象,在核心態進行鎖操作,速度慢。
3、臨界區和互斥體在Windows平臺都下可用;Linux下只有互斥體可用。
文章出處:http://www.diybl.com/course/3_program/c++/cppsl/2008525/117880.html

Sandy 2008-12-26 18:39 發表評論
]]>
轉:Win32 臨界區實現原理淺析http://www.shnenglu.com/SpringSnow/archive/2008/12/01/68288.htmlSandySandyMon, 01 Dec 2008 06:05:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2008/12/01/68288.htmlhttp://www.shnenglu.com/SpringSnow/comments/68288.htmlhttp://www.shnenglu.com/SpringSnow/archive/2008/12/01/68288.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/68288.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/68288.html摘自:http://www.diybl.com/course/3_program/c++/cppjs/2008426/111619.html
  
      去年11月的MSDN雜志曾刊登過一篇文章 Break Free of Code Deadlocks in Critical Sections Under Windows ,Matt Pietrek 和 Russ Osterlund 兩位對臨界區(Critical Section)的內部實現做了一次簡短的介紹,但點到為止,沒有繼續深入下去,當時給我的感覺就是癢癢的,呵呵,于是用IDA和SoftIce大致分析了一下臨界區的實現,大致弄明白了原理后也就沒有深究。現在乘著Win2k源碼的東風,重新分析一下這塊的內容,做個小小的總結吧 :P
     臨界區(Critical Section)是Win32中提供的一種輕量級的同步機制,與互斥(Mutex)和事件(Event)等內核同步對象相比,臨界區是完全在用戶態維護的,所以僅能在同一進程內供線程同步使用,但也因此無需在使用時進行用戶態和核心態之間的切換,工作效率大大高于其它同步機制。
     臨界區的使用方法非常簡單,使用 InitializeCriticalSection 或 InitializeCriticalSectionAndSpinCount 函數初始化一個 CRITICAL_SECTION 結構;使用 SetCriticalSectionSpinCount 函數設置臨界區的Spin計數器;然后使用 EnterCriticalSection 或 TryEnterCriticalSection 獲取臨界區的所有權;完成需要同步的操作后,使用 LeaveCriticalSection 函數釋放臨界區;最后使用 DeleteCriticalSection 函數析構臨界區結構。
     以下是MSDN中提供的一個簡單的例子:

    以下為引用:

 // Global variable
 CRITICAL_SECTION CriticalSection;

 void main()
 {
     ...

     // Initialize the critical section one time only.
     if (!InitializeCriticalSectionAndSpinCount(&CriticalSection, 0x80000400) )
         return;
     ...

     // Release resources used by the critical section object.
     DeleteCriticalSection(&CriticalSection)
 }

 DWORD WINAPI ThreadProc( LPVOID lpParameter )
 {
     ...

     // Request ownership of the critical section.
     EnterCriticalSection(&CriticalSection);

     // Access the shared resource.

     // Release ownership of the critical section.
     LeaveCriticalSection(&CriticalSection);

     ...
 }

     首先看看構造和析構臨界區結構的函數。
     InitializeCriticalSection 函數(ntosdll esource.c:1210)實際上是調用 InitializeCriticalSectionAndSpinCount 函數(resource.c:1266)完成功能的,只不過傳入一個值為0的初始Spin計數器;InitializeCriticalSectionAndSpinCount 函數主要完成兩部分工作:初始化 RTL_CRITICAL_SECTION 結構和 RTL_CRITICAL_SECTION_DEBUG 結構。前者是臨界區的核心結構,下面將著重討論;后者是調試用結構,Matt 那篇文章里面分析的很清楚了,我這兒就不羅嗦了 :P
     RTL_CRITICAL_SECTION結構在winnt.h中定義如下:

以下為引用:

 typedef struct _RTL_CRITICAL_SECTION {
     PRTL_CRITICAL_SECTION_DEBUG DebugInfo;

     //
     //  The following three fields control entering and exiting the critical
     //  section for the resource
     //

     LONG LockCount;
     LONG RecursionCount;
     HANDLE OwningThread;        // from the thread''s

ClientId->UniqueThread
     HANDLE LockSemaphore;
     ULONG_PTR SpinCount;        // force size on 64-bit systems when packed
 } RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;

     InitializeCriticalSectionAndSpinCount 函數中首先對臨界區結構進行了初始化

     DebugInfo 字段指向初始化臨界區時分配的RTL_CRITICAL_SECTION_DEBUG結構;
     LockCount 字段是臨界區中最重要的字段,初始值為-1,當臨界區被獲取(Hold)時此字段大于等于0;
     RecursionCount 字段保存當前臨界區所有者線程的獲取緩沖區嵌套層數,初始值為0;
     OwningThread 字段保存當前臨界區所有者線程的句柄,初始值為0;
     LockSemaphore 字段實際上是一個auto-reset的事件句柄,用于喚醒等待獲取臨界區的阻塞線程,初始值為0;
     SpinCount 字段用于在多處理器環境下完成輕量級的CPU見同步,單處理器時沒有使用(初始值為0),多處理器時設置為SpinCount參數值(最大為MAX_SPIN_COUNT=0x00ffffff)。此外 RtlSetCriticalSectionSpinCount 函數(resource.c:1374)的代碼與這兒設置SpinCount的代碼完全一樣。

     初始化臨界區結構后,函數會根據SpinCount參數的一個標志位判斷是否需要預先初始化 LockSemaphore 字段,如果需要則使用NtCreateEvent創建一個具有訪問權限的同步用事件核心對象,初始狀態為沒有激發。這一初始化本來是在 EnterCriticalSection 函數中完成的,將之顯式提前可以進一步優化 EnterCriticalSection 函數的性能。
     值得注意的是,這一特性僅對Win2k有效。MSDN里面說明如下:

以下為引用:

 Windows 2000:  If the high-order bit is set, the function preallocates the event used by the EnterCriticalSection function. Do not set this bit if you are creating a large number of critical section objects, because it will consume a significant amount of nonpaged pool. This flag is not necessary on Windows XP and later, and it is ignored.

     與之對應的 DeleteCriticalSection 函數完成關閉事件句柄和是否調試結構的功能。

     臨界區真正的核心代碼在win2kprivate tosdlli386critsect.asm里面,包括_RtlEnterCriticalSection、_RtlTryEnterCriticalSection和_RtlLeaveCriticalSection三個函數。

     _RtlEnterCriticalSection 函數 (critsect.asm:85) 首先檢查SpinCount是否為0,如果不為0則處理多處理器架構下的問題[分支1];如為0則使用原子操作給LockCount加一,并判斷是否其值為0。如果加一后LockCount大于0,則此臨界區已經被獲取[分支2];如為0則獲取當前線程TEB中的線程句柄,保存在臨界區的OwningThread字段中,并將RecursionCount設置為1。調試版本則調用RtlpCriticalSectionIsOwned函數在調試模式下驗證此緩沖區是被當前線程獲取的,否則在調試模式下激活調試器。最后還會更新TEB的CountOfOwnedCriticalSections計數器,以及臨界區調試結構中的EntryCount字段。
     如果此臨界區已經被獲取[分支2],則判斷獲取臨界區的線程句柄是否與當前線程相符。如果是同一線程則直接將RecursionCount和調試結構的EntryCount字段加一;如果不是當前線程,則調用RtlpWaitForCriticalSection函數等待此臨界區,并從頭開始執行獲取臨界區的程序。
     多CPU情況的分支處理方式類似,只是多了對SpinCount的雙重檢查處理。

     接著的_RtlTryEnterCriticalSection(critsect.asm:343)函數是一個輕量級的嘗試獲取臨界區的函數。偽代碼如下:

以下為引用:

 if(CriticalSection->LockCount == -1)
 {
   // 臨界區可用
   CriticalSection->LockCount = 0;
   CriticalSection->OwningThread = TEB->ClientID;
   CriticalSection->RecursionCount = 1;

   return TRUE;
 }
 else
 {
   if(CriticalSection->OwningThread == TEB->ClientID)
   {
     // 臨界區是當前線程獲取
     CriticalSection->LockCount++;
     CriticalSection->RecursionCount++;

     return TRUE;
   }
   else
   {
     // 臨界區已被其它線程獲取
     return FALSE;
   }
 }
 
 

 

 

     最后的_RtlLeaveCriticalSection(critsect.asm:271)函數釋放已獲取的臨界區,實現就比較簡單了,實際上就是對嵌套計數器和鎖定計數器進行操作。偽代碼如下:

 

以下為引用:

 if(--CriticalSection->RecursionCount == 0)
 {
bsp;  // 臨界區已不再被使用
   CriticalSection->OwningThread = 0;
 

   if(--CriticalSection->LockCount)
   {
     // 仍有線程鎖定在臨界區上
     _RtlpUnWaitCriticalSection(CriticalSection)
   }
 }
 else
 {
   --CriticalSection->LockCount
 }



Sandy 2008-12-01 14:05 發表評論
]]>
宏的學習一http://www.shnenglu.com/SpringSnow/archive/2008/12/01/68287.htmlSandySandyMon, 01 Dec 2008 05:54:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2008/12/01/68287.htmlhttp://www.shnenglu.com/SpringSnow/comments/68287.htmlhttp://www.shnenglu.com/SpringSnow/archive/2008/12/01/68287.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/68287.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/68287.html一、宏中“#”和“##”的用法:

        一般用法:使用“#”把宏參數變為一個字符串,用”##”把兩個宏參數結合在一起

       例子:

#include <iostream>
using namespace std;

#define TEST1(x) (cout<<id##x<<endl);
#define TEST2(p) (cout<<#p<<endl);
int main()
{
    
int id1 = 1001;
    
int id2 = 1002;
    TEST1(
1);    // == cout<< id1 << endl;
    TEST2(2);    // == cout<< "2" << endl;
    TEST1(2);    // == cout<< id2 << endl;

    system(
"pause");
    
return 0;
}

二、防止一個頭文件被重復包含
        #ifndef COMDEF_H
        #define COMDEF_H
        //頭文件內容
        #endif
    當你所建的工程有多個源文件組成時,很可能會在多個文件里頭包含了同一個頭文件,如果借用上面的宏定義就能夠避免同一個頭文件被重復包含時進行多次編譯。因為當它編譯第一個頭文件時總是沒有定義#define COMDEF_H,那么它將編譯一遍頭文件中所有的內容,包括定義#define COMDEF_H。這樣編譯再往下進行時如果遇到同樣要編譯的頭文件,那么由于語句#ifndef COMDEF_H的存在它將不再重復的編譯這個頭文件。

三、常用的宏定義
  __DATE__
  進行預處理的日期(“Mmm dd yyyy”形式的字符串文字)

  __FILE__
  代表當前源代碼文件名的字符串文字

  __LINE__
  代表當前源代碼中的行號的整數常量

  __TIME__
  源文件編譯時間,格式微“hh:mm:ss”

參考文章:
   C中的預編譯宏定義  http://blog.readnovel.com/article/htm/tid_900939.html
   C標準中一些預定義的宏 http://www.programfan.com/article/2883.html
   C語言常用宏定義技巧  http://blog.21ic.com/user1/3074/archives/2008/51567.html
   C語言宏定義技巧(常用宏定義) http://blog.21ic.com/user1/69/archives/2006/13695.html
   宏定義:http://blog.csdn.net/believefym/archive/2007/10/21/1836162.aspx

好好學習!
 


Sandy 2008-12-01 13:54 發表評論
]]>
分享:C++中extern “C”含義深層探索http://www.shnenglu.com/SpringSnow/archive/2008/12/01/68276.htmlSandySandyMon, 01 Dec 2008 03:43:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2008/12/01/68276.htmlhttp://www.shnenglu.com/SpringSnow/comments/68276.htmlhttp://www.shnenglu.com/SpringSnow/archive/2008/12/01/68276.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/68276.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/68276.html
一直對extern "c"不是很明白。今天,看了一篇文章《C++中extern “C”含義深層探索》,對這個問題終于有所認識。轉過來與大家分享。

鏈接:http://developer.51cto.com/art/200510/9066.htm
作者:宋寶華

1.引言
C++語言的創建初衷是“a better C”,但是這并不意味著C++中類似C語言的全局變量和函數所采用的編譯和連接方式與C語言完全相同。作為一種欲與C兼容的語言,C++保留了一部分過程式語言的特點(被世人稱為“不徹底地面向對象”),因而它可以定義不屬于任何類的全局變量和函數。但是,C++畢竟是一種面向對象的程序設計語言,為了支持函數的重載,C++對全局函數的處理方式與C有明顯的不同。
2.從標準頭文件說起

某企業曾經給出如下的一道面試題:
面試題:為什么標準頭文件都有類似以下的結構?
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */
分析
顯然,頭文件中的編譯宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止該頭文件被重復引用。
那么
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
的作用又是什么呢?我們將在下文一一道來。

3.深層揭密extern "C"

extern "C" 包含雙重含義,從字面上即可得到:首先,被它修飾的目標是“extern”的;其次,被它修飾的目標是“C”的。讓我們來詳細解讀這兩重含義。
被extern "C"限定的函數或變量是extern類型的;
extern是C/C++語言中表明函數和全局變量作用范圍(可見性)的關鍵字,該關鍵字告訴編譯器,其聲明的函數和變量可以在本模塊或其它模塊中使用。記住,下列語句:
extern int a;
僅僅是一個變量的聲明,其并不是在定義變量a,并未為a分配內存空間。變量a在所有模塊中作為一種全局變量只能被定義一次,否則會出現連接錯誤。
通常,在模塊的頭文件中對本模塊提供給其它模塊引用的函數和全局變量以關鍵字extern聲明。例如,如果模塊B欲引用該模塊A中定義的全局變量和函數時只需包含模塊A的頭文件即可。這樣,模塊B中調用模塊A中的函數時,在編譯階段,模塊B雖然找不到該函數,但是并不會報錯;它會在連接階段中從模塊A編譯生成的目標代碼中找到此函數。
與extern對應的關鍵字是static,被它修飾的全局變量和函數只能在本模塊中使用。因此,一個函數或變量只可能被本模塊使用時,其不可能被extern “C”修飾。
被extern "C"修飾的變量和函數是按照C語言方式編譯和連接的;
未加extern “C”聲明時的編譯方式
首先看看C++中對類似C的函數是怎樣編譯的。
作為一種面向對象的語言,C++支持函數重載,而過程式語言C則不支持。函數被C++編譯后在符號庫中的名字與C語言的不同。例如,假設某個函數的原型為:
void foo( int x, int y );
該函數被C編譯器編譯后在符號庫中的名字為_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不同,但是都采用了相同的機制,生成的新名字稱為“mangled name”)。
foo_int_int這樣的名字包含了函數名、函數參數數量及類型信息,C++就是靠這種機制來實現函數重載的。例如,在C++中,函數void foo( int x, int y )與void foo( int x, float y )編譯生成的符號是不相同的,后者為_foo_int_float。

同樣地,C++中的變量除支持局部變量外,還支持類成員變量和全局變量。用戶所編寫程序的類成員變量可能與全局變量同名,我們以"."來區分。而本質上,編譯器在進行編譯時,與函數的處理相似,也為類中的變量取了一個獨一無二的名字,這個名字與用戶程序中同名的全局變量名字不同。
未加extern "C"聲明時的連接方式
假設在C++中,模塊A的頭文件如下:
// 模塊A頭文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif
在模塊B中引用該函數:
// 模塊B實現文件 moduleB.cpp
#include "moduleA.h"
foo(2,3);

實際上,在連接階段,連接器會從模塊A生成的目標文件moduleA.obj中尋找_foo_int_int這樣的符號!
加extern "C"聲明后的編譯和連接方式
加extern "C"聲明后,模塊A的頭文件變為:
// 模塊A頭文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif
在模塊B的實現文件中仍然調用foo( 2,3 ),其結果是:
(1)模塊A編譯生成foo的目標代碼時,沒有對其名字進行特殊處理,采用了C語言的方式;
(2)連接器在為模塊B的目標代碼尋找foo(2,3)調用時,尋找的是未經修改的符號名_foo。
如果在模塊A中函數聲明了foo為extern "C"類型,而模塊B中包含的是extern int foo( int x, int y ) ,則模塊B找不到模塊A中的函數;反之亦然。
所以,可以用一句話概括extern “C”這個聲明的真實目的(任何語言中的任何語法特性的誕生都不是隨意而為的,來源于真實世界的需求驅動。我們在思考問題時,不能只停留在這個語言是怎么做的,還要問一問它為什么要這么做,動機是什么,這樣我們可以更深入地理解許多問題):
實現C++與C及其它語言的混合編程。

明白了C++中extern "C"的設立動機,我們下面來具體分析extern "C"通常的使用技巧。

4.extern "C"的慣用法
(1)在C++中引用C語言中的函數和變量,在包含C語言頭文件(假設為cExample.h)時,需進行下列處理:
extern "C"
{
#include "cExample.h"
}
而在C語言的頭文件中,對其外部函數只能指定為extern類型,C語言中不支持extern "C"聲明,在.c文件中包含了extern "C"時會出現編譯語法錯誤。
筆者編寫的C++引用C函數例子工程中包含的三個文件的源代碼如下:
/* c語言頭文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif
/* c語言實現文件:cExample.c */
#include "cExample.h"
int add( int x, int y )
{
return x + y;
}
// c++實現文件,調用add:cppFile.cpp
extern "C"
{
#include "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}
如果C++調用一個C語言編寫的.DLL時,當包括.DLL的頭文件或聲明接口函數時,應加extern "C" { }。
(2)在C中引用C++語言中的函數和變量時,C++的頭文件需添加extern "C",但是在C語言中不能直接引用聲明了extern "C"的該頭文件,應該僅將C文件中將C++中定義的extern "C"函數聲明為extern類型。

筆者編寫的C引用C++函數例子工程中包含的三個文件的源代碼如下:
//C++頭文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif
//C++實現文件 cppExample.cpp
#include "cppExample.h"
int add( int x, int y )
{
return x + y;
}
/* C實現文件 cFile.c
/* 這樣會編譯出錯:#include "cExample.h" */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}
如果深入理解了第3節中所闡述的extern "C"在編譯和連接階段發揮的作用,就能真正理解本節所闡述的從C++引用C函數和C引用C++函數的慣用法。



Sandy 2008-12-01 11:43 發表評論
]]>
如何添加代碼注釋http://www.shnenglu.com/SpringSnow/archive/2008/11/20/67354.htmlSandySandyThu, 20 Nov 2008 01:38:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2008/11/20/67354.htmlhttp://www.shnenglu.com/SpringSnow/comments/67354.htmlhttp://www.shnenglu.com/SpringSnow/archive/2008/11/20/67354.html#Feedback1http://www.shnenglu.com/SpringSnow/comments/commentRss/67354.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/67354.html這個我一直沒有搞懂。自己隨便涂鴉,也懶得加注釋。但是真正參加項目,有時也會找理由不添注釋。看了一些書,參加一些培訓,有的人說,加注釋好,有人說加注釋不好。好有好的地方,壞也有壞的味道。對于我這個初來乍到之人來說,反而倒是非常迷惑。
最近看到一篇文章“添加代碼注釋13個技巧”,也解除了心中的部分疑團。貼在這里與大家分享,也備以后參考。
鏈接:
http://sunxinhe.yo2.cn/articles/%E3%80%90%E8%BD%AC%E3%80%91%E6%B7%BB%E5%8A%A0%E4%BB%A3%E7%A0%81%E6%B3%A8%E9%87%8A13%E4%B8%AA%E6%8A%80%E5%B7%A7.html

添加代碼注釋13個技巧

作者:José M. Aguilar(西班牙語)
英文譯者:Timm Martin
中文譯者:numenzq

下面的13個技巧向你展示如何添加代碼注釋,這些技巧都很容易理解和記憶。

1. 逐層注釋
為每個代碼塊添加注釋,并在每一層使用統一的注釋方法和風格。例如:
針對每個類:包括摘要信息、作者信息、以及最近修改日期等
針對每個方法:包括用途、功能、參數和返回值等
在團隊工作中,采用標準化的注釋尤為重要。當然,使用注釋規范和工具(例如C#里的XML,Java里的Javadoc)可以更好的推動注釋工作完成得更好。
2. 使用分段注釋
如果有多個代碼塊,而每個代碼塊完成一個單一任務,則在每個代碼塊前添加一個注釋來向讀者說明這段代碼的功能。例子如下:

// Check that all data records
// are correct
foreach (Record record in records)
{
if (rec.checkStatus()==Status.OK)
{
. . .
}
}
// Now we begin to perform
// transactions
Context ctx = new ApplicationContext();
ctx.BeginTransaction();
. . .

3. 在代碼行后添加注釋
如果多行代碼的每行都要添加注釋,則在每行代碼后添加該行的注釋,這將很容易理解。例如:

const MAX_ITEMS = 10; // maximum number of packets
const MASK = 0x1F;    // mask bit TCP

在分隔代碼和注釋時,有的開發者使用tab鍵,而另一些則使用空格鍵。然而由于tab鍵在各編輯器和IDE工具之間的表現不一致,因此最好的方法還是使用空格鍵。

4. 不要侮辱讀者的智慧

避免以下顯而易見的注釋:

if (a == 5)      // if a equals 5
counter = 0; // set the counter to zero

寫這些無用的注釋會浪費你的時間,并將轉移讀者對該代碼細節的理解。

5. 禮貌點
避免粗魯的注釋,如:“注意,愚蠢的使用者才會輸入一個負數”或“剛修復的這個問題出于最初的無能開發者之手”。這樣的注釋能夠反映到它的作者是多么的拙劣,你也永遠不知道誰將會閱讀這些注釋,可能是:你的老板,客戶,或者是你剛才侮辱過的無能開發者。

6. 關注要點

不要寫過多的需要轉意且不易理解的注釋。避免ASCII藝術,搞笑,詩情畫意,hyperverbosity的注釋。簡而言之,保持注釋簡單直接。

7. 使用一致的注釋風格
一些人堅信注釋應該寫到能被非編程者理解的程度。而其他的人則認為注釋只要能 被開發人員理解就行了。無論如何,Successful Strategies for Commenting Code已經規定和闡述了注釋的一致性和針對的讀者。就個人而言,我懷疑大部分非編程人員將會去閱讀代碼,因此注釋應該是針對其他的開發者而言。

8. 使用特有的標簽
在一個團隊工作中工作時,為了便于與其它程序員溝通,應該采用一致的標簽集進行注釋。例如,在很多團隊中用TODO標簽表示該代碼段還需要額外的工作。

int Estimate(int x, int y)
{
// TODO: implement the calculations
return 0;
}

注釋標簽切忌不要用于解釋代碼,它只是引起注意或傳遞信息。如果你使用這個技巧,記得追蹤并確認這些信息所表示的是什么。

9. 在代碼時添加注釋
在寫代碼時就添加注釋,這時在你腦海里的是清晰完整的思路。如果在代碼最后再添 加同樣注釋,它將多花費你一倍的時間。而“我沒有時間寫注釋”,“我很忙”和“項目已經延期了”這都是不愿寫注釋而找的借口。一些開發者覺得應該 write comments before code,用于理清頭緒。例如:
public void ProcessOrder()
{
// Make sure the products are available
// Check that the customer is valid
// Send the order to the store
// Generate bill
}

10. 為自己注釋代碼

當注釋代碼時,要考慮到不僅將來維護你代碼的開發人員要看,而且你自己也可能要看。用Phil Haack大師的話來說就是:“一旦一行代碼顯示屏幕上,你也就成了這段代碼的維護者”。因此,對于我們寫得好(差)的注釋而言,我們將是第一個受益者(受害者)。

11. 同時更新代碼和注釋

如果注釋沒有跟隨代碼的變化而變化,及時是正確的注釋也沒有用。代碼和注釋應該同步變化,否則這樣的注釋將對維護你代碼的開發者帶來更大的困難。使用重構工具時應特別注意,它只會自動更新代碼而不會修改注釋,因此應該立即停止使用重構工具。

12. 注釋的黃金規則:易讀的代碼
對于開發者的一個基本原則就是:讓你的代碼為己解釋。雖然有些人懷疑這會讓那些不愿意寫注釋的開發者鉆空子,不過這樣的代碼真的會使你容易理解,還不需要額外維護注釋。例如在Fluid Interfaces文章里向你展示的代碼一樣:

Calculator calc = new Calculator();
calc.Set(0);
calc.Add(10);
calc.Multiply(2);
calc.Subtract(4);
Console.WriteLine( "Result: {0}", calc.Get() );

在這個例子中,注釋是不需要的,否則可能就違反了技巧4。為了使代碼更容易理解,你可以考慮使用適當的名字 (Ottinger's Rules里講解得相當好),確保正確的縮進,并且采用coding style guides,違背這個技巧可能的結果就像是注釋在為不好的代碼apologize。

13. 與同事分享技巧
雖然技巧10已經向我們表明了我們是如何從好的注釋中直接受益,這些技巧將讓所有開發者受益,特別是團隊中一起工作的同事。因此,為了編寫出更容易理解和維護的代碼,嘗試自由的和你的同事分享這些注釋技巧。


好東西拿出來一起分享


Sandy 2008-11-20 09:38 發表評論
]]>
有意思的討論:將ASSERT與正常程序混在一起好么?http://www.shnenglu.com/SpringSnow/archive/2008/11/04/65949.htmlSandySandyTue, 04 Nov 2008 10:28:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2008/11/04/65949.htmlhttp://www.shnenglu.com/SpringSnow/comments/65949.htmlhttp://www.shnenglu.com/SpringSnow/archive/2008/11/04/65949.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/65949.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/65949.html

下面摘自http://www.shnenglu.com/woaidongmao/archive/2008/11/03/65897.html

看到他們的在爭論很有意思,我不是很懂。

有利還是有弊呢?


EXT_ASSERT將ASSERT與if結合在一起

ASSERT在DEBUG程序時候幫了太多太多忙,不過在ASSERT判斷傳入參數后,還需要if再按相同條件判斷一遍,不符合規則return,這樣才是正確的邏輯。但這樣代碼難看,且工作重復無趣,又容易出現差漏。

剛弄了個簡單EXT_ASSERT宏,按我的理解應該可以解決問題,但不確定是否有漏洞,發出來大家一起瞄瞄。

 

#define RET_VOID
#define EX_ASSERT(exp, ret) {ASSERT(exp);if(!(exp))return(ret);}

 

BOOL CXXX::FunXXX(const data* p_data)
{
   EXT_ASSERT(p_data, FALSE);//---- 返回BOOL型

}

int CXXX::FunXXX(const data* p_data)
{
   EXT_ASSERT(p_data, -1);//---- 返回int型

}

const retdata* CXXX::FunXXX(const data* p_data)
{
    EXT_ASSERT(p_data, NULL);//---- 返回NULL指針

}

retdata CXXX::FunXXX(const data* p_data)
{
    EXT_ASSERT(p_data, retdata());//---- 返回空對象

}

void CXXX::FunXXX(const data* p_data)
{
    EXT_ASSERT(p_data, RET_VOID);//---- 僅僅return

}

posted on 2008-11-03 23:34 肥仔 閱讀(333) 評論(7)  編輯 收藏 引用 所屬分類: C++存檔

評論

# re: EXT_ASSERT將ASSERT與if結合在一起  回復  更多評論   

哥們兒,如果你是說MFC里的ASSERT的話(看你的類命名風格,估計是吧),在retail build里,ASSERT是完全不會被放到代碼里的。你這樣用ASSERT,把ASSERT和if條件綁在一起就等于把處理錯誤的斷言和正常程序邏輯綁在了一起,不是一個好的設計。如果一定要這么干,也該是綁VERIFY,至少在retail build里VERIFY里的邏輯還會被執行。

ASSERT應該拿來斷言程序正常執行完全不可能出現的錯誤(這些錯誤會在debug build里出現是因為當前程序還不完善),在正常邏輯中,他們是不應該用程序邏輯去handle的錯誤,所以一句ASSERT夠了。
2008-11-04 05:27 | www.helpsoff.com.cn

# re: EXT_ASSERT將ASSERT與if結合在一起  回復  更多評論   

@www.helpsoff.com.cn
我在Imperfect C++中看到過相同的言論。
不過我的應用是,常常用ASSERT檢測參數的合法性,ASSERT之后,當然還要if一把了,對于ASSERT和if不要放在一起這種觀點,我不是很認同,我覺得放在一起很好用的。

另外,在Release下,ASSERT沒了,但是if留下了,這是需要的效果。
2008-11-04 10:45 | 肥仔

# re: EXT_ASSERT將ASSERT與if結合在一起  回復  更多評論   

我不會這么用,斷言的目的去那了。
2008-11-04 11:30 | Touchsoft

# re: EXT_ASSERT將ASSERT與if結合在一起  回復  更多評論   

@肥仔
哥們兒,你還是沒理解,ASSERT的不是拿來干這個的。你愛怎樣玩就怎樣玩吧,反正自己的代碼自己維護,其他人的意見聽不聽在你。
2008-11-04 12:29 | www.helpsoff.com.cn

# re: EXT_ASSERT將ASSERT與if結合在一起  回復  更多評論   

@www.helpsoff.com.cn
謝謝你的意見,但是不采納。原因有3點經歷:

1、ASSERT判斷函數參數合法性,調試時會幫了很大的忙;
2、if判段函數參數合法性,是健壯性的一部分;
3、ASSERT和if 合在一起,不覺得有任何不妥,且ASSERT不出現在Release中,這正是需要的。

可能涉及到的一個爭論是,檢測參數合法形是調用者,還是被調用者的責任?
C/C++的主流是調用者保證參數的合法性,被調用者不檢測參數合法性,這就是為什么認為,只要ASSERT,不需要if了。
strcpy(szBuf, NULL)之所以讓一個程序崩潰也是這個原因,但是為什么要讓它崩潰?能夠不崩潰,繼續執行豈不是更好嗎?
2008-11-04 13:44 | 肥仔

# re: EXT_ASSERT將ASSERT與if結合在一起  回復  更多評論   

1) 沒人否認ASSERT的用處;
2) 需要if判斷處理的參數和用ASSERT斷言的不合法參數,不應屬于一個范疇,不應該混合在一起處理;
3) 代碼不管怎么寫在沒遇到問題前都不會有什么不妥,自己覺得好就好吧。

你當然可以去寫一個萬能的strcpy,但是如何能保證你的strcpy是真正的“萬能”的呢?不崩潰繼續執行倒是沒問題,但是出問題的真正根源在哪里呢,你這樣做不就掩蓋了問題嗎?應該做的是出現這樣的問題時,能有用且有效的指出錯誤,而不是做garbage in, garbage out。

設計代碼,不去扯那些玩得出花花的設計模式,有些很基本很直白的原則,比如說“garbage in, garbage out”,比如高內聚/低耦合...說多了也沒意思,樓主愛怎么玩怎么玩,大家都是這么過來的,其中的東西自己去體會了。
2008-11-04 15:35 | www.helpsoff.com.cn

# re: EXT_ASSERT將ASSERT與if結合在一起  回復  更多評論   

@www.helpsoff.com.cn
程序以外,人生很多地方都需要與別人探討,對于不合己見者,請不必太在懷,更沒必要帶著情緒和語氣,擺出姿態。這樣才能贏得更多的合作,我想我的這幾句話還算中肯。
2008-11-04 16:32 | 肥仔


Sandy 2008-11-04 18:28 發表評論
]]>
Essential C++ 學習——異常異常處理(一)http://www.shnenglu.com/SpringSnow/archive/2008/11/03/65833.htmlSandySandyMon, 03 Nov 2008 05:35:00 GMThttp://www.shnenglu.com/SpringSnow/archive/2008/11/03/65833.htmlhttp://www.shnenglu.com/SpringSnow/comments/65833.htmlhttp://www.shnenglu.com/SpringSnow/archive/2008/11/03/65833.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/65833.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/65833.html練習一
        以下函數完全沒有檢查可能的數據錯誤以及可能的執行失敗。請指出此函數中所有可能發生錯誤的地方。本題并不考慮出現異常。
        int *alloc_and_init(string file_name)
        {
            ifstream infile(file_name);
            int elem_cnt;
            infile >> elem_cnt;
            int *pi = allocate_array(elem_cnt);

            int elem;
            int index = 0;
            while(infile >> elem)
            {
               pi[index++] = elem;
             }

               sort_array(pi, elem_cnt);
               register_data(pi);

               return pi;
             }
這是書中第203頁的練習7.1。

         我自己的答案:打開文件后,未對infile進行判斷,是否打開文件成功;pi是否分配成功,未進行判斷,它是否為null。

          侯捷老師給的答案如下:
          第一個錯誤便是“型別不符”。ifstream constructor 接受的參數型別是const char*而非string。這個沒有注意到。解決方法是利用string的c_str member function取得其c-style字符串表現形式:
        ifstream infile(file_name.c_str());
        第二個錯誤是檢查infile是否成功開啟。
        if  ( !infile ) // 開啟失敗
        第三個錯誤就是infile >> elem_cnt 可能執行失敗。
        如,文件內含的是文字,那么企圖“讀入某個數值并置于elem_cnt內”的操作便告失敗。此外,文件也有可能是空的。必須檢查讀取是否成功。
        infile >> elem_cnt;
        if (! infile) // 讀取失敗

        第四個錯誤int *pi = allocate_array(elem_cnt);
         無論何時,當我們處理指針時,必須隨時注意指針是否的確指向實際存在的對象。如果allocate_array()無法配置足夠內存,pi便會被設為0,我們必須檢驗如下:
          if  ( ! pi ) // allocate_array() 沒有配置到內存

 需要說明的是:程序的假設是(1)elem_cnt代表文件中的元素個數;(2)數組索引值index絕不會發生溢出。但是除非我們檢查,否則實在無法保證index永遠不大于elem_cnt。

第一個錯誤和第三個錯誤,沒有考慮到。分析看,對于“型別不符”這個問題,一直沒有注意到。此外,對于讀入文字,沒有思考那么多。

努力學習ing……



Sandy 2008-11-03 13:35 發表評論
]]>
久久91精品国产91久久麻豆| 久久久久成人精品无码| 久久精品国产只有精品2020| 国产精品伊人久久伊人电影 | 久久成人18免费网站| 91久久精品国产91性色也| 久久久久国产视频电影| 亚洲国产精品久久久天堂| 国产成人久久精品激情| 精品久久国产一区二区三区香蕉 | 久久国产乱子伦精品免费午夜| 偷窥少妇久久久久久久久| 国产精品久久久久久| 久久精品国产99久久久古代 | 91精品国产91久久| 777午夜精品久久av蜜臀| 久久se精品一区二区影院| 久久综合狠狠色综合伊人| 久久亚洲精品人成综合网| 尹人香蕉久久99天天拍| 久久精品国产99久久香蕉| 狠狠色丁香婷综合久久| 精品久久久久久久无码 | 91久久婷婷国产综合精品青草 | 精品无码人妻久久久久久| 97久久香蕉国产线看观看| 色88久久久久高潮综合影院| 午夜精品久久久久久影视777 | 久久99精品久久久久久齐齐| 国产精品9999久久久久| 久久一日本道色综合久久| 亚洲欧美日韩中文久久| 精品伊人久久大线蕉色首页| 久久青青色综合| 久久精品国产乱子伦| 天天躁日日躁狠狠久久| 亚洲AV无码久久寂寞少妇| 亚洲AV无码久久精品狠狠爱浪潮| 亚洲AV日韩精品久久久久久 | 狠狠综合久久综合中文88| 久久精品夜夜夜夜夜久久|