• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

              C++博客 :: 首頁 :: 聯系 ::  :: 管理
              163 Posts :: 4 Stories :: 350 Comments :: 0 Trackbacks

            常用鏈接

            留言簿(48)

            我參與的團隊

            搜索

            •  

            積分與排名

            • 積分 - 398977
            • 排名 - 59

            最新評論

            閱讀排行榜

            評論排行榜

            6 函數設計

            函數是C++/C程序的基本功能單元,其重要性不言而喻。函數設計的細微缺點很容易導致該函數被錯用,所以光使函數的功能正確是不夠的。本章重點論述函數的接口設計和內部實現的一些規則。

            函數接口的兩個要素是參數和返回值。C語言中,函數的參數和返回值的傳遞方式有兩種:值傳遞(pass by value)和指針傳遞(pass by pointer)。C++ 語言中多了引用傳遞(pass by reference)。由于引用傳遞的性質象指針傳遞,而使用方式卻象值傳遞,初學者常常迷惑不解,容易引起混亂,請先閱讀6.6節“引用與指針的比較”。

            6.1 參數的規則

            l         【規則6-1-1參數的書寫要完整,不要貪圖省事只寫參數的類型而省略參數名字。如果函數沒有參數,則用void填充。

            例如:

            void SetValue(int width, int height);   // 良好的風格

            void SetValue(int, int);                // 不良的風格

            float GetValue(void);       // 良好的風格

            float GetValue();           // 不良的風格

             

            l         【規則6-1-2參數命名要恰當,順序要合理。

            例如編寫字符串拷貝函數StringCopy,它有兩個參數。如果把參數名字起為str1str2

            void StringCopy(char *str1, char *str2);

            那么我們很難搞清楚究竟是把str1拷貝到str2中,還是剛好倒過來。

            可以把參數名字起得更有意義,如叫strSourcestrDestination。這樣從名字上就可以看出應該把strSource拷貝到strDestination

            還有一個問題,這兩個參數那一個該在前那一個該在后?參數的順序要遵循程序員的習慣。一般地,應將目的參數放在前面,源參數放在后面。

            如果將函數聲明為:

            void StringCopy(char *strSource, char *strDestination);

            別人在使用時可能會不假思索地寫成如下形式:

            char str[20];

            StringCopy(str, “Hello World”);   // 參數順序顛倒

             

            l         【規則6-1-3如果參數是指針,且僅作輸入用,則應在類型前加const,以防止該指針在函數體內被意外修改。

            例如:

            void StringCopy(char *strDestinationconst char *strSource);

             

            l         【規則6-1-4如果輸入參數以值傳遞的方式傳遞對象,則宜改用“const &”方式來傳遞,這樣可以省去臨時對象的構造和析構過程,從而提高效率。

             

            ²        【建議6-1-1避免函數有太多的參數,參數個數盡量控制在5個以內。如果參數太多,在使用時容易將參數類型或順序搞錯。

             

            ²        【建議6-1-2盡量不要使用類型和數目不確定的參數。

            C標準庫函數printf是采用不確定參數的典型代表,其原型為:

            int printf(const chat *format[, argument]…);

            這種風格的函數在編譯時喪失了嚴格的類型安全檢查。

            6.2 返回值的規則

            l         【規則6-2-1不要省略返回值的類型。

            C語言中,凡不加類型說明的函數,一律自動按整型處理。這樣做不會有什么好處,卻容易被誤解為void類型。

            C++語言有很嚴格的類型安全檢查,不允許上述情況發生。由于C++程序可以調用C函數,為了避免混亂,規定任何C++/ C函數都必須有類型。如果函數沒有返回值,那么應聲明為void類型。

             

            l         【規則6-2-2函數名字與返回值類型在語義上不可沖突。

            違反這條規則的典型代表是C標準庫函數getchar

            例如:

            char c;

            c = getchar();

            if (c == EOF)

            按照getchar名字的意思,將變量c聲明為char類型是很自然的事情。但不幸的是getchar的確不是char類型,而是int類型,其原型如下:

                    int getchar(void);

            由于cchar類型,取值范圍是[-128127],如果宏EOF的值在char的取值范圍之外,那么if語句將總是失敗,這種“危險”人們一般哪里料得到!導致本例錯誤的責任并不在用戶,是函數getchar誤導了使用者。

             

            l         【規則6-2-3不要將正常值和錯誤標志混在一起返回。正常值用輸出參數獲得,而錯誤標志用return語句返回。

            回顧上例,C標準庫函數的設計者為什么要將getchar聲明為令人迷糊的int類型呢?他會那么傻嗎?

            在正常情況下,getchar的確返回單個字符。但如果getchar碰到文件結束標志或發生讀錯誤,它必須返回一個標志EOF。為了區別于正常的字符,只好將EOF定義為負數(通常為負1)。因此函數getchar就成了int類型。

            我們在實際工作中,經常會碰到上述令人為難的問題。為了避免出現誤解,我們應該將正常值和錯誤標志分開。即:正常值用輸出參數獲得,而錯誤標志用return語句返回。

            函數getchar可以改寫成 BOOL GetChar(char *c);

            雖然gecharGetChar靈活,例如 putchar(getchar()); 但是如果getchar用錯了,它的靈活性又有什么用呢?

             

            ²        【建議6-2-1有時候函數原本不需要返回值,但為了增加靈活性如支持鏈式表達,可以附加返回值。

            例如字符串拷貝函數strcpy的原型:

            char *strcpy(char *strDestconst char *strSrc);

            strcpy函數將strSrc拷貝至輸出參數strDest中,同時函數的返回值又是strDest。這樣做并非多此一舉,可以獲得如下靈活性:

                char str[20];

                int  length = strlen( strcpy(str, “Hello World”) );

             

            ²        【建議6-2-2如果函數的返回值是一個對象,有些場合用“引用傳遞”替換“值傳遞”可以提高效率。而有些場合只能用“值傳遞”而不能用“引用傳遞”,否則會出錯。

            例如:

            class String

            {…

                // 賦值函數

                String & operate=(const String &other);

            // 相加函數,如果沒有friend修飾則只許有一個右側參數

            friend  String   operate+( const String &s1, const String &s2);

            private:

                char *m_data;

            }

             

                   String的賦值函數operate = 的實現如下:

            String & String::operate=(const String &other)

            {

                if (this == &other)

                    return *this;

                delete m_data;

                m_data = new char[strlen(other.data)+1];

                strcpy(m_data, other.data);

                return *this;   // 返回的是 *this的引用,無需拷貝過程

            }

             

            對于賦值函數,應當用“引用傳遞”的方式返回String對象。如果用“值傳遞”的方式,雖然功能仍然正確,但由于return語句要 *this拷貝到保存返回值的外部存儲單元之中,增加了不必要的開銷,降低了賦值函數的效率。例如:

              String a,b,c;

             

              a = b;      // 如果用“值傳遞”,將產生一次 *this 拷貝

              a = b = c; // 如果用“值傳遞”,將產生兩次 *this 拷貝

             

                   String的相加函數operate + 的實現如下:

            String  operate+(const String &s1, const String &s2)  

            {

                String temp;

                delete temp.data;   // temp.data是僅含‘\0’的字符串

                    temp.data = new char[strlen(s1.data) + strlen(s2.data) +1];

                    strcpy(temp.data, s1.data);

                    strcat(temp.data, s2.data);

                    return temp;

                }

             

            對于相加函數,應當用“值傳遞”的方式返回String對象。如果改用“引用傳遞”,那么函數返回值是一個指向局部對象temp的“引用”。由于temp在函數結束時被自動銷毀,將導致返回的“引用”無效。例如:

                c = a + b;

            此時 a + b 并不返回期望值,c什么也得不到,流下了隱患。

            6.3 函數內部實現的規則

            不同功能的函數其內部實現各不相同,看起來似乎無法就“內部實現”達成一致的觀點。但根據經驗,我們可以在函數體的“入口處”和“出口處”從嚴把關,從而提高函數的質量。

             

            l         【規則6-3-1在函數體的“入口處”,對參數的有效性進行檢查。

            很多程序錯誤是由非法參數引起的,我們應該充分理解并正確使用“斷言”(assert)來防止此類錯誤。詳見6.5節“使用斷言”。

             

            l         【規則6-3-2在函數體的“出口處”,對return語句的正確性和效率進行檢查。

                 如果函數有返回值,那么函數的“出口處”是return語句。我們不要輕視return語句。如果return語句寫得不好,函數要么出錯,要么效率低下。

            注意事項如下:

            1return語句不可返回指向“棧內存”的“指針”或者“引用”,因為該內存在函數體結束時被自動銷毀。例如

                char * Func(void)

                {

                    char str[] = hello world; // str內存位于棧上

                   

                    return str;     // 將導致錯誤

                }

            2)要搞清楚返回的究竟是“值”、“指針”還是“引用”。

            3)如果函數返回值是一個對象,要考慮return語句的效率。例如   

                          return String(s1 + s2);

            這是臨時對象的語法,表示“創建一個臨時對象并返回它”。不要以為它與“先創建一個局部對象temp并返回它的結果”是等價的,如

            String temp(s1 + s2);

            return temp;

            實質不然,上述代碼將發生三件事。首先,temp對象被創建,同時完成初始化;然后拷貝構造函數把temp拷貝到保存返回值的外部存儲單元中;最后,temp在函數結束時被銷毀(調用析構函數)。然而“創建一個臨時對象并返回它”的過程是不同的,編譯器直接把臨時對象創建并初始化在外部存儲單元中,省去了拷貝和析構的化費,提高了效率。

            類似地,我們不要將 

            return int(x + y);  // 創建一個臨時變量并返回它

            寫成

            int temp = x + y;

            return temp;

            由于內部數據類型如int,float,double的變量不存在構造函數與析構函數,雖然該“臨時變量的語法”不會提高多少效率,但是程序更加簡潔易讀。

            6.4 其它建議

            ²        【建議6-4-1函數的功能要單一,不要設計多用途的函數。

            ²        【建議6-4-2函數體的規模要小,盡量控制在50行代碼之內。

            ²        【建議6-4-3盡量避免函數帶有“記憶”功能。相同的輸入應當產生相同的輸出。

            帶有“記憶”功能的函數,其行為可能是不可預測的,因為它的行為可能取決于某種“記憶狀態”。這樣的函數既不易理解又不利于測試和維護。在C/C++語言中,函數的static局部變量是函數的“記憶”存儲器。建議盡量少用static局部變量,除非必需。

            ²        【建議6-4-4不僅要檢查輸入參數的有效性,還要檢查通過其它途徑進入函數體內的變量的有效性,例如全局變量、文件句柄等。

            ²        【建議6-4-5用于出錯處理的返回值一定要清楚,讓使用者不容易忽視或誤解錯誤情況。

            6.5 使用斷言

            程序一般分為Debug版本和Release版本,Debug版本用于內部調試,Release版本發行給用戶使用。

            斷言assert是僅在Debug版本起作用的宏,它用于檢查“不應該”發生的情況。示例6-5是一個內存復制函數。在運行過程中,如果assert的參數為假,那么程序就會中止(一般地還會出現提示對話,說明在什么地方引發了assert)。

             

                     void  *memcpy(void *pvTo, const void *pvFrom, size_t size)

            {

                    assert((pvTo != NULL) && (pvFrom != NULL));     // 使用斷言

                    byte *pbTo = (byte *) pvTo;     // 防止改變pvTo的地址

                    byte *pbFrom = (byte *) pvFrom; // 防止改變pvFrom的地址

                    while(size -- > 0 )

                        *pbTo ++ = *pbFrom ++ ;

                    return pvTo;

            }

            示例6-5 復制不重疊的內存塊

             

            assert不是一個倉促拼湊起來的宏。為了不在程序的Debug版本和Release版本引起差別,assert不應該產生任何副作用。所以assert不是函數,而是宏。程序員可以把assert看成一個在任何系統狀態下都可以安全使用的無害測試手段。如果程序在assert處終止了,并不是說含有該assert的函數有錯誤,而是調用者出了差錯,assert可以幫助我們找到發生錯誤的原因。

            很少有比跟蹤到程序的斷言,卻不知道該斷言的作用更讓人沮喪的事了。你化了很多時間,不是為了排除錯誤,而只是為了弄清楚這個錯誤到底是什么。有的時候,程序員偶爾還會設計出有錯誤的斷言。所以如果搞不清楚斷言檢查的是什么,就很難判斷錯誤是出現在程序中,還是出現在斷言中。幸運的是這個問題很好解決,只要加上清晰的注釋即可。這本是顯而易見的事情,可是很少有程序員這樣做。這好比一個人在森林里,看到樹上釘著一塊“危險”的大牌子。但危險到底是什么?樹要倒?有廢井?有野獸?除非告訴人們“危險”是什么,否則這個警告牌難以起到積極有效的作用。難以理解的斷言常常被程序員忽略,甚至被刪除。[Maguire, p8-p30]

             

            l         【規則6-5-1使用斷言捕捉不應該發生的非法情況。不要混淆非法情況與錯誤情況之間的區別,后者是必然存在的并且是一定要作出處理的。

            l         【規則6-5-2】在函數的入口處,使用斷言檢查參數的有效性(合法性)。

            l         【建議6-5-1在編寫函數時,要進行反復的考查,并且自問:“我打算做哪些假定?”一旦確定了的假定,就要使用斷言對假定進行檢查。

            l         【建議6-5-2一般教科書都鼓勵程序員們進行防錯設計,但要記住這種編程風格可能會隱瞞錯誤。當進行防錯設計時,如果“不可能發生”的事情的確發生了,則要使用斷言進行報警。

            6.6 引用與指針的比較

            引用是C++中的概念,初學者容易把引用和指針混淆一起。一下程序中,nm的一個引用(reference),m是被引用物(referent)。

                int m;

                int &n = m;

            n相當于m的別名(綽號),對n的任何操作就是對m的操作。例如有人名叫王小毛,他的綽號是“三毛”。說“三毛”怎么怎么的,其實就是對王小毛說三道四。所以n既不是m的拷貝,也不是指向m的指針,其實n就是m它自己。

            引用的一些規則如下:

            1)引用被創建的同時必須被初始化(指針則可以在任何時候被初始化)。

            2)不能有NULL引用,引用必須與合法的存儲單元關聯(指針則可以是NULL)。

            3)一旦引用被初始化,就不能改變引用的關系(指針則可以隨時改變所指的對象)。

                以下示例程序中,k被初始化為i的引用。語句k = j并不能將k修改成為j的引用,只是把k的值改變成為6。由于ki的引用,所以i的值也變成了6

                int i = 5;

                int j = 6;

                int &k = i;

                k = j;  // ki的值都變成了6;

                上面的程序看起來象在玩文字游戲,沒有體現出引用的價值。引用的主要功能是傳遞函數的參數和返回值。C++語言中,函數的參數和返回值的傳遞方式有三種:值傳遞、指針傳遞和引用傳遞。

                以下是“值傳遞”的示例程序。由于Func1函數體內的x是外部變量n的一份拷貝,改變x的值不會影響n, 所以n的值仍然是0

                void Func1(int x)

            {

                x = x + 10;

            }

            int n = 0;

                Func1(n);

                cout << “n = ” << n << endl;  // n = 0

               

            以下是“指針傳遞”的示例程序。由于Func2函數體內的x是指向外部變量n的指針,改變該指針的內容將導致n的值改變,所以n的值成為10

                void Func2(int *x)

            {

                (* x) = (* x) + 10;

            }

            int n = 0;

                Func2(&n);

                cout << “n = ” << n << endl;      // n = 10

             

                以下是“引用傳遞”的示例程序。由于Func3函數體內的x是外部變量n的引用,xn是同一個東西,改變x等于改變n,所以n的值成為10

                void Func3(int &x)

            {

                x = x + 10;

            }

            int n = 0;

                Func3(n);

                cout << “n = ” << n << endl;      // n = 10

             

                對比上述三個示例程序,會發現“引用傳遞”的性質象“指針傳遞”,而書寫方式象“值傳遞”。實際上“引用”可以做的任何事情“指針”也都能夠做,為什么還要“引用”這東西?

            答案是“用適當的工具做恰如其分的工作”。

                指針能夠毫無約束地操作內存中的如何東西,盡管指針功能強大,但是非常危險。就象一把刀,它可以用來砍樹、裁紙、修指甲、理發等等,誰敢這樣用?

            如果的確只需要借用一下某個對象的“別名”,那么就用“引用”,而不要用“指針”,以免發生意外。比如說,某人需要一份證明,本來在文件上蓋上公章的印子就行了,如果把取公章的鑰匙交給他,那么他就獲得了不該有的權利。

             

            posted on 2007-12-15 15:38 sdfasdf 閱讀(215) 評論(0)  編輯 收藏 引用 所屬分類: C++
            狠色狠色狠狠色综合久久| 无码人妻精品一区二区三区久久久 | 国产综合成人久久大片91| 日本精品久久久中文字幕| 亚洲国产成人精品91久久久| 国产色综合久久无码有码| 色综合久久最新中文字幕| 日韩久久久久中文字幕人妻| 亚洲AV无码一区东京热久久| 青青青青久久精品国产| 思思久久精品在热线热| 日韩亚洲欧美久久久www综合网 | 午夜欧美精品久久久久久久| 久久97精品久久久久久久不卡| 2020久久精品国产免费| 色青青草原桃花久久综合| 伊人久久大香线蕉精品| 亚洲综合伊人久久综合| 亚洲一级Av无码毛片久久精品| 麻豆AV一区二区三区久久| 伊人久久大香线蕉AV一区二区| 午夜精品久久久久久影视riav| 久久精品中文字幕无码绿巨人| 99久久国产综合精品网成人影院| 婷婷久久综合| 色婷婷噜噜久久国产精品12p | 国产精品亚洲综合久久 | 国产一区二区精品久久凹凸 | 麻豆精品久久精品色综合| 国产午夜精品久久久久免费视| 国产精品gz久久久| 蜜桃麻豆www久久| 国产精品gz久久久| 久久久国产精华液| 久久久久无码中| 久久久久亚洲?V成人无码| 久久久久久国产精品无码下载| 久久久久久免费一区二区三区| 奇米影视7777久久精品| 久久天天躁狠狠躁夜夜躁2O2O| 香蕉久久AⅤ一区二区三区|