C++中函數訪問數組的方式


  在書寫C++代碼時,往往為了令代碼更加簡潔高效、提高代碼可讀性,會對定義的函數有一些特殊的要求:比如不傳遞不必要的參數,以此來讓函數的參數列表盡可能簡短。

  當一個函數需要訪問一個數組元素時,出於上述原因,往往也希望令傳入的參數盡可能的少(至少我是這樣...)。

  首先,引出一個例子,對於std::vector<typename>來說,往往只需要傳遞一個參數就足夠了(當只涉及單獨訪問該vector時的確如此),比如要編寫一個show函數,這個函數的功能是打印傳入容器的所有元素,並用空格將這些元素分隔開來。那么當傳入容器為vector時,這個show函數就會簡單無比:

1 void show(const std::vector<int> &ivec) {
2     for (size_t i = 0; i < ivec.size(); ++i)
3         std::cout << ivec[i] << " ";
4     std::cout << std::endl;
5 }

  可以看到,由於標准庫中的vector包含size成員,使得我們很容易獲取這個vector對象的大小,進而方便對這個vector進行訪問。此時,這個函數只需要一個參數就可以完成容器的訪問工作(當然,對於這種容器,也可以用迭代器進行訪問,此方式下函數的參數個數不變)。

  那么... 內置數組呢?

  很遺憾,答案是:不行。

  在《C++ Primer 5th》中,作者指出:“因為數組是以指針的形式傳遞給函數的,所以一開始函數並不知道數組的確切尺寸,調用者應該為此提供一些額外的信息。”(中文版第193頁,英文版第216頁)  

  書中繼而闡述了三種函數訪問數組的方式(原文為:“管理指針形參的三種技術”):

  1. 使用標記指定數組長度

    通俗地說,就是在傳入的這個數組中,含有一些標記數組結束的元素,比如C風格字符串(const char *),顯式地以'\0'字符作為結尾標識符。那么當遍歷到一個'\0'時,即可認定這個字符串結束,此時判定訪問結束。但顯而易見,這樣的訪問方式不具有普適性,畢竟很多容器並不會包含一個指定的結尾標識,甚至一些容器都不能保證存入元素的順序固定。

    這里,當然也可以預定數組就是指定大小的(例如在源文件中聲明const LEN = 20; 或者預處理 #define LEN 20),盡管這樣和上述方法一樣,可以使得函數只需要一個形參:

#define LEN 20
// const int LEN = 20;

void show(const char *cp) {
    if (cp)
        while (*cp)
            std::cout << *cp++;
}

void show(const int lst[]) {
    for (size_t i = 0; i < LEN; ++i)
        std::cout << lst[i] << " ";
    std::cout << std::endl;
}

    但是,這樣的程序是不具有普遍性的,我們可以很肯定地說,我們的數組一定不會正好包含20或更少的元素。

 

  2. 使用標准庫規范

    在標准庫中,對數組定義了begin和end方法,和迭代器不同的是,函數返回的是指針類型;和迭代器相同的是,用法基本一致...

void show(const int *beg, const int *end) {
    for (auto iter = beg; iter != end; ++iter)
        std::cout << *iter << std::endl;
    std::cout << std::endl;
}

/* 很遺憾,玩兒不轉
void show(const int lst[]) {
    for (auto beg = std::begin(lst); beg != std::end(lst); ++beg) 
        std::cout << *beg << " ";
    std::cout << std::endl;
} 
*/

    可以看到,這時的show函數,至少需要兩個參數。當然,說到begin和end函數,很多人都會想到上面第二個這種方式,然而很遺憾,這種方式不行!此時,只能通過顯式地調用

show(std::begin(lst), std::end(lst));

    來實現第一個函數的調用。但... 這並未達到“調用函數只包含一個參數”的期望。

    至於這里的第二種方式不行的原因,我認為是:在C++調用函數時,數組被自動轉換為數組元素類型的指針類型(比如int lst[]就被轉換成了int *lst),這個過程是如此自然,就像是內置類型默認類型轉換一般(比如sum = 1 + 2.0,這里的1就自然從int類型被轉換成了double類型)。因此,函數對這樣一個int *類型,自然無從對之繼續調用begin和end函數以得到其首尾位置(這里重點是無法得到尾位置)。故... 玩兒不轉。

 

  3. 顯式傳遞一個表示數組大小的形參

    這可能是每個學過C/C++的人最常用的方法了。通過顯式控制訪問元素的個數,保證指針前進的深度:

void show(const int lst[], size_t length) {
    for (size_t i = 0; i < length; ++i) 
        std::cout << lst[i] << " ";
    std::cout << std::endl;
}

    形式很簡單,無須解釋。這里需要注意的就是形參處的length的類型是size_t的,因此調用者傳入一個負值,很有可能就美滋滋了。當然,也可以修改為其他類型,但這個問題不容忽視(見之前博客)。

    我想說的是,這種方式下,依然沒達到“調用函數只包含一個參數”的期望...

 

    可能只有強迫症才會有這樣的糾結吧,但很欣慰的是,通過這個糾結,將調用函數訪問數組的三種方式系統地進行了說明闡述(方式2的第二種形式真的讓我很惋惜...)。

    當然,如果讀者有更好的方式(如果能只傳入一個參數即可訪問任意類型任意大小的數組,看在我糾結這么久的份兒上,千萬要告訴我...),歡迎評論區分享,若有寫的不足的地方,敬請評論區斧正,在此感謝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM