當前位置:首頁 > 公眾號精選 > 嵌入式微處理器
[導讀]一、能力錯覺 當書本(或谷歌)擺在眼前時,大腦會產(chǎn)生錯覺,以為學習材料也同樣存入了大腦,閱讀畢竟比回想簡單多了。 以為反復的閱讀資料就是自己已經(jīng)掌握知識,這就是能力錯覺。 解決能力錯覺的方法: 積極回想——讓大腦提取關鍵概念,而非通過重復閱讀被


一、能力錯覺

當書本(或谷歌)擺在眼前時,大腦會產(chǎn)生錯覺,以為學習材料也同樣存入了大腦,閱讀畢竟比回想簡單多了。

以為反復的閱讀資料就是自己已經(jīng)掌握知識,這就是能力錯覺

解決能力錯覺的方法:

  • 積極回想——讓大腦提取關鍵概念,而非通過重復閱讀被動地獲取知識,這樣才能高效地學習。

現(xiàn)在網(wǎng)絡上盛行各種it類的視頻教程,我不否認不少視頻教程是高質量的,但是所有視頻類資料都有一個問題:

  • 可以讓人不用閱讀書籍,減少思考,只要被動地聽老師們講課就能舒舒服服地獲取到知識,這很容易會讓某些初級的軟件開發(fā)人員形成能力錯覺,仿佛視頻里寫過的代碼,解決過的 bug 都是自己已經(jīng)學到的知識似的。

有效的解決辦法是

  • 公開學習筆記和練習代碼。公開學習筆記的目的是借助外部壓力,高效回想,進而提高自己的學習標準。

  • 另外,公開寫作則會給你的寫作增加很多維度的外部壓力,你會想如何讓別人更好地理解我要表達的意思;如何傳遞更多價值,讓別人讀完有所收獲;如何讓更多人看到;如何讓別人讀得下去;如何排版讓大家看得更舒服;


二、數(shù)組和指針有什么區(qū)別?

正文目錄:

1. 用于聲明時兩者有重大區(qū)別
2. 你真的理解聲明和定義嗎?
3. 數(shù)組和指針的底層是如何訪問數(shù)據(jù)的?
4. 哪些場景可以用指針代替數(shù)組?
5. 為什么C語言要把數(shù)組形參退化為指針?
6. 如何使用指針訪問多維數(shù)組?
7. 相關面試題

寫作目的:

  • 正確看待數(shù)組和指針。

測試環(huán)境:

  • Ubuntu 16.04
  • Gcc 5.4.0

1. 用于聲明時兩者有重大區(qū)別

1) 誤導新手的說法:

由于數(shù)組和指針的所謂等價性非常接近,不少程序員有時忽視了二者之間的其他重要區(qū)別 ,最誤導新手的說法之一就是 “數(shù)組和指針是相同的",這是一種非常危險的說法。

看下面這個例子:

extern int *x;
extern int x[];
  • 第一條語句聲明 x 是個 int 型的指針;

  • 第二條語句聲明 x 是個 int 型數(shù)組,長度尚未確定,即存儲長度在別處定義。

2) 為什么有些人會誤以為指針和數(shù)組總是可以互換?

最主要原因是
對數(shù)組的引用 ( x[i] ) 總是可以寫成對指針的引用 ( *(x+i) )。

  • 即確實存在一種指針和數(shù)組的定義完全相同的上下文環(huán)境。不幸的是,這只是數(shù)組的一種極為普通的用法,并非所有情況下都是如此。

2. 你真的理解聲明和定義嗎?

想要要真正理解為什么 extern int *x 不等于 extern int x[],我們首先需要搞清楚什么是聲明,什么是定義。

1) 鏈接器的視角:

  • C 語言中的對象必須有且只有一個定義,但它可以有多個 extern 聲明。這里所說的對象跟 C++ 中的對象并無關系,這里說的對象是 從鏈接器的視角來看的,鏈接器將各個函數(shù)、變量都視為對象。

2) 定義和聲明的聯(lián)系與區(qū)別:

  • 定義是一種特殊的聲明,它創(chuàng)建了一個對象;聲明簡單地說明了在其他地方創(chuàng)建的對象的名字。

  • 定義只能出現(xiàn)在一個地方,它指定了對象的類型并分配內存以創(chuàng)建新的對象。聲明可以多次出現(xiàn) 以描述對象的類型,用于指代其他地方定義的對象,它不為對象分配內存。

  • extern 對象聲明告訴編譯器對象的類型和名字,對象的內存分配則在別處進行。由于并未在聲明中為數(shù)組分配內存,所以并不需要提供關于數(shù)組長度的信息 (多維數(shù)組例外)。

3) 總結成一句話:

  • 定義 = 聲明 + 分配內存 (創(chuàng)建對象)

4) 回過頭來看這個例子:

extern int *x;
extern int x[];

前者聲明了一個指針,后者聲明了一個數(shù)組,那么它們對應的指針和數(shù)組的定義(最重要的是內存分配) 能相等嗎?


3. 數(shù)組和指針的底層是如何訪問數(shù)據(jù)的?

現(xiàn)在我們來看看指針和數(shù)組的定義與使用。

1) "地址 X (Address)" 和 "地址 X 的內容(Contents of Address)" 之間的區(qū)別:

對于"地址 X" 和 "地址 X 的內容",在 C 語言中是用同一個符號來表示這兩樣東西,由編譯器根據(jù)上下文環(huán)境判斷它的具體含義。

2) 看下面這個例子:

X = Y
  • 符號 X 的含義是 X 所代表的地址,它是左值,編譯時可知;

  • 符號 Y 的含義是 Y 所代表的地址上的內容,它是右值,運行時才知;

  • 左值包括可修改的左值和不可修改的左值,C 語言中,一般的數(shù)據(jù)類型都是都可作為可修改的左值,只有數(shù)組是不可修改的左值;

  • 數(shù)組的地址在編譯時可知,編譯器有了這個地址 (即數(shù)組首地址),就可以直接進行讀寫操作。而指針必須在運行時取得它的當前值,然后才能對它進行解除引用操作,才能進行讀寫操作。

3) 數(shù)組和指針的訪問方式是不同的:

char a[9] = "abcedefgh";

訪問數(shù)組
  • 上面這個例子中,a 是一個數(shù)組。

  • 在編譯器符號表里有一個符號 a ,它的地址為9980;

  • 數(shù)組內的字符都可以從這個地址 + 偏移量找到,編譯器甚至并不需要知道數(shù)組的總長度;

char c = 'F';
char *p = &c;

使用指針訪問字符
  • 上面這個例子中,p 是一個指針。

  • 在編譯器符號表中有一個符號 p, 它的地址為 4624;

  • p 指向的對象是一個字符。為了取得這個字符,必須得到地址 p 的內容 (5081),把它作為字符的地址并從這個地址中取得這個字符 ('F')。

4) 當定義為指針 (char *p),并以數(shù)組方式 (p[i]) 引用時會發(fā)生什么?

char *p = ”abcdefgh”
printf("%c\n", p[3]);

char *a = ”abcdefgh”
printf("%c\n", a[3]);
  • p[3] 和 a[3] 都能成功訪問到字符 'd';

  • a[i] 表示 "從 a 的地址開始,前進 i 步,每步都是一個字符(數(shù)組類型的長度)”;

  • p[i] 表示 "從 p 所指的地址開始,前進 i 步,每步都是一個字符(即指針所指類型的長度)”;

  • 所以,當你用 extern char *p 來聲明 char p[10]時,編譯器會把 p[i] 當成一個指針(Address),然后去獲取 *(p[i]) (即 Content of Addrss),這時最好的結果是程序立馬崩潰,你能快點發(fā)現(xiàn)問題。最糟糕的情況是,程序崩潰在將來的某個時刻,你則 debug 到懷疑人生。


4. 哪些場景可以用指針代替數(shù)組?

數(shù)組和指針容易混淆使用的 2 大類場景:

  • 聲明

  • 在表達式中使用;

1) 聲明:
聲明的場景包括 3 種:

  • 1> 不可以的場景:定義也是一種聲明,定義數(shù)組時不能用指針的形式;

  • 2> 不可以的場景:extern 數(shù)組時不能改寫成指針的形式, 例如:

int char[10];    // define
extern char a[]; // ok
extern char *a;  // error
  • 3> 可以的場景:函數(shù)的形參,用數(shù)組形式還是指針形式,隨你自己的喜好。

2) 在表達式中使用:

  • 在表達式中,指針形式和數(shù)組形式等效。

3) 幾條重要的規(guī)則:

  • 規(guī)則1:"表達式中的數(shù)組名" 就是指針;

  • 規(guī)則2:把數(shù)組下標可當作指針的偏移量;

  • 規(guī)則3: "作為函數(shù)參數(shù)的數(shù)組名" 等同于指針;


5. 為什么 C 語言要把數(shù)組形參退化為指針?

1) 出于效率的考慮:
  • 在 C 語言中,所有非數(shù)組形式的數(shù)據(jù)實參均以傳值形式(對實參作一份拷貝并傳遞給調用的函數(shù),函數(shù)不能修改作為實參的實際變量的值)。

  • 如果要拷貝整個數(shù)組,無論在時間上還是在內存空間上的開銷都可能是非常大的。

2) 出于簡化編譯器的考慮:

  • 在 C 語言中,所有的數(shù)組在作為參數(shù)傳遞時都轉換為指向數(shù)組起始地址的指針,而其他的參數(shù)均采用傳值調用。

  • 允許程序員把形參聲明為數(shù)組 (程序員打算傳遞給函數(shù)的東西) 或者指針 (函數(shù)實際所接收到的東西)。在函數(shù)內部,編譯器始終把它當作一個指向數(shù)組第一個元素的指針。

3) 看下面這個例子:

static int array[10], array2[10];

static void func1(int *ptr)
{
    ptr[1] = 3;
    *ptr = 3;
    ptr = array2;
}

static void func2(int array[])
{
    array[1] = 3;
    *array = 3;
    array = array2;   // OK, because array is a pointer
    printf("*array=%d\n", *array);
}

int main(void)
{
    func1(array);
    func2(array);
    
    array[1] = 3;
    *array = 3;
    array = array2; // ERROR
    return 0;
}

編譯運行:

// main 中調用 array = array2時:
11: error: assignment to expression with array type

// 去掉 main / array = array2時:
$ ./point_array_arg
*array=0

6. 如何使用指針訪問多維數(shù)組?

1) C 語言的多維數(shù)組:

  • 采用最右的下標先變化原則,其最大的用途是存儲多個字符串;

  • 單個元素的存儲和引用實際上是以線性形式排列在內存中;

  • 不能把一個數(shù)組賦值給另一個數(shù)組,因為數(shù)組作為一個整體不能成為賦值的對象;

  • 可以把數(shù)組名賦值給一個指針,是因為在表達式中的數(shù)組名被編譯器當作一個指針;

  • 指針下標引用的規(guī)則告訴我們 pea[i][j] 被編譯器解釋為 ((pea + i) + j);

  • 可以通過聲明一個一維指針數(shù)組 ( (char *)pea[4],下標方括號的優(yōu)先級比指針的星號高),其中每個指針指向一個字符串,來取得類似二維字符數(shù)組的效果;

指針數(shù)組

7. 相關面試題

1) 找錯:計算字符串長度

下面這段程序是為了把字符串轉換為大寫:

#include <stdio.h>

void UpperCase(char str[])
{
    int test = sizeof(str);
    int test2 = sizeof(str[0]);

    for(size_t i=0; i<sizeof(str)/sizeof(str[0]); ++i) {
        if('a'<=str[i] && str[i]<='z')
            str[i] -= ('a'-'A');
    }
}



int main(void)
{

    char str[] = "aBcDeefGHijKL";
    printf("The length of str is %d\n"sizeof(str)/sizeof(str[0]));

    UpperCase(str);
    printf("result: %s\n", str);
    return 0;
}

運行結果:

$ ./sizeof_array 
The length of str is 14
result: ABCDEEFGHijKL

問題出在 UpperCase() 里的 sizeof(str),這里的 str 是一個指針而不是數(shù)組

正確的寫法有2種

  • 給UpperCase 傳遞長度參數(shù),這是最穩(wěn)妥的方式。
  • 在UpperCase 內使用strlen 獲取字符串長度,這種方法僅適用于以 ‘\0’ 結尾的字符數(shù)組;

相關參考

  • 《你必須知道的 495 個 C語言問題》, 第 6.1-6.23 章節(jié)
  • 《C 專家編程》, 第 4/9/10 章節(jié)
  • 《C Primer Plus 6th》, 第 10 章節(jié)
  • 《C 和 C++ 程序員面試秘籍》, 第 3 章
  • 《C 和指針》, NULL

-END-


本文授權轉載自公眾號“嵌入式Hacker”,作者吳偉東Jack




推薦閱讀



【01】單片機C語言,必知的數(shù)據(jù)存儲與程序編寫知識!
【02】入門C語言20問20答
【03】C語言之父:因拒付論文裝訂費錯失博士學位,論文52年后重見天日
【04】C語言開發(fā)單片機為啥都是全局變量形式?
【05】分享10個值得關注的C語言開源項目


免責聲明:整理文章為傳播相關技術,版權歸原作者所有,如有侵權,請聯(lián)系刪除

免責聲明:本文內容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

嵌入式ARM

掃描二維碼,關注更多精彩內容

本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內容侵犯您的權益,請及時聯(lián)系本站刪除。
關閉
關閉