天天看點

嵌入式C語言完全學習筆記進階篇

1、資料類型

1.1、基本資料類型

資料類型分2類:基本資料類型+複合類型

基本類型:char short int long float double

複合類型:數組 結構體 共用體 類(C語言沒有類,C++有)

1.1.1、記憶體占用與sizeof運算符

資料類型就好像一個一個的模子,這個模子執行個體化出C語言的變量。變量存儲在記憶體中,需要占用一定的記憶體空間。一個變量占用多少空間是由變量的資料類型決定的。

每種資料類型,在不同的機器平台上占用記憶體是不同的。我們一般講的時候都是以32位CPU為預設硬體平台來描述:

char         1位元組        8位

short         2位元組        16位

int         4位元組        32位

long         4位元組        32位

float         4位元組        

double        8位元組        

1.1.2、有符号數和無符号數

對于char short int long等整形類型的數,都分有符号有無符号數。

而對于float和double這種浮點型數來說,隻有有符号數,沒有無符号數。

對于C語言來說,數(也就是變量)是存儲在記憶體中一個一個的格子中的。存儲的時候是以二進制方式存儲的。對于有符号數和無符号數來說,存儲方式不同的。譬如對于int來說

unsigned int 無符号數,32位(4位元組)全部用來存數的内容 是以表示的數的範圍

是0 ~ 4294967295(2^32 - 1)

signed int   有符号數,32位中最高位用來存符号(0表示正數,1表示負數),剩餘的31位用

來存資料。是以可以表示的數的範圍是     -2147483648(2^32)    ~ 2147483647(2^31 - 1)

結論:從絕對數值來說,無符号數所表示的範圍要大一些。因為有符号數使用1個二進制位來表示正負号。

1.1.3、整形數和浮點型數存儲方式上的不同

對于float和double這種浮點類型的數,它在記憶體中的存儲方式和整形數不一樣。是以float和

int相比,雖然都是4位元組,但是在記憶體中存儲的方式完全不同。是以同一個4位元組的記憶體,如果存儲時是按照int存放的,取的時候一定要按照int型方式去取。如果存的時候和取的時候了解的方式不同,那資料就完全錯了。

備注:詳細的數制存儲可以查找資料:計算機原碼、反碼、補碼等知識。

總結:存取方式上主要有兩種,一種是整形一種是浮點型,這兩種存取方式完全不同,沒有任何關聯,是以是絕對不能随意改變一個變量的存取方式。在整形和浮點型之内,譬如說4種整形char、short、int、long隻是範圍大小不同而已,存儲方式是一模一樣的。float和double存儲原理是相同的,方式上有差異,導緻了能表示的浮點型的範圍和精度不同。

1.2、空類型(關鍵字void)

C語言中的void類型,代表任意類型,而不是空的意思。任意類型的意思不是說想變成誰就變成誰,而是說它的類型是未知的,是還沒指定的。

void *    是void類型的指針。void類型的指針的含義是:這是一個指針變量,該指針指向一個

void類型的數。void類型的數就是說這個數有可能是int,也有可能是float,也有可能是個結構體,哪種類型都有可能,隻是我目前不知道。

void型指針的作用就是,程式不知道那個變量的類型,但是程式員自己心裡知道。程式員如何知道?當時給這個變量指派的時候是什麼類型,現在取的時候就還是什麼類型。這些類型對不對,能否相容,完全由程式員自己負責。編譯器看到void就沒辦法幫你做類型檢查了。

在函數的參數清單和傳回值中,void代表的含義是:

一個函數形參清單為void,表示這個函數調用時不需要給它傳參。

傳回值類型是void,表示這個函數不會傳回一個有意義的傳回值。是以調用者也不要想着去使用該傳回值。

C語言設計基本理念:

C語言相信程式員永遠是對的,C語言相信程式員都是高手,C語言賦予了程式員最大的權利。是以C語言的程式員必須自己對程式的對錯負責,必須随時腦袋清楚,知道自己在幹嘛。

1.3、資料類型轉換

C語言中有各種資料類型,寫程式時需要定義各種類型的變量。這些變量需要參與運算。C語言有一個基本要求就是:不同類型的變量是不能直接運算的。

也就是說,int和float類型的變量不能直接加減等運算。你要運算,必須先把兩種類型轉成相同的類型才可以。

1.3.1、隐式轉換

隐式轉換就是自動轉換,是C語言預設會進行的,不用程式員幹涉。

C語言的理念:隐式類型轉換預設朝精度更高、範圍更大的方向轉換。

1.3.2、強制類型轉換

C語言預設不會這麼做,但是程式員我想這麼做,是以我強制這麼做了。

1.4、C語言與bool類型

C語言中原生類型沒有bool,C++中有。在C語言中如果需要使用bool類型,可以用int來代替。

很多代碼體系中,用以下宏定義來定義真和假

#define TRUE    1

#define FALSE    0

2、變量和常量

2.1、變量

變量,指的是在程式運作過程中,可以通過代碼使它的值改變的量。

2.1.1、局部變量

定義在函數中的變量,就叫局部變量。

2.1.1.1、普通局部變量(auto)

普通的局部變量定義時直接定義,或者在定義前加auto關鍵字

void func1(void)

{

    int i = 1;

    i++;

    printf("i = %d.\n", i);

}

局部變量i的解析:

在連續三次調用func1中,每次調用時,在進入函數func1後都會創造一個新的變量i,并且給它賦初值1,然後i++時加到2,然後printf輸出時輸出2.然後func1本次調用結束,結束時同時殺死本次創造的這個i。這就是局部變量i的整個生命周期。

下次再調用該函數func1時,又會重新創造一個i,經曆整個程式運算,最終在函數運作完退出時再次被殺死。

2.1.1.2、靜态局部變量(static)

靜态局部變量定義時前面加static關鍵字。

總結:

1、靜态局部變量和普通局部變量不同。靜态局部變量也是定義在函數内部的,靜态局部變量定義時前面要加static關鍵字來辨別,靜态局部變量所在的函數在多調用多次時,隻有第一次才經曆變量定義和初始化,以後多次在調用時不再定義和初始化,而是維持之前上一次調用時執行後這個變量的值。本次接着來使用。

2、靜态局部變量在第一次函數被調用時創造并初始化,但在函數退出時它不死亡,而是保持其值等待函數下一次被調用。下次調用時不再重新創造和初始化該變量,而是直接用上一次留下的值為基礎來進行操作。

3、靜态局部變量的這種特性,和全局變量非常類似。它們的相同點是都創造和初始化一次,以後調用時值保持上次的不變。不同點在于作用域不同

2.1.1.4、register關鍵字

register(寄存器),C語言的一個關鍵字

register int i = 3;

總結:register類型的局部變量表現上和auto是一樣的,這東西基本沒用,知道就可以了。register被稱為:C語言中最快的變量。C語言的運作時環境承諾,會盡量将register類型的變量放到寄存器中去運作(普通的變量是在記憶體中),是以register類型的變量通路速度會快很多。但是它是有限制的:首先寄存器數目是有限的,是以register類型的變量不能太多;其次register類型變量在資料類型上有限制,譬如你就不能定義double類型的register變量。一般隻在核心或者啟動代碼中,需要反複使用同一個變量這種情況下才會使用register類型變量。

2.1.2、全局變量

定義在函數外面的變量,就叫全局變量。

2.1.2.1、普通全局變量

    普通全局變量就是平時使用的,定義前不加任何修飾詞。普通全局變量可以在各個檔案中使

用,可以在項目内别的.c檔案中被看到,是以要確定不能重名。

2.1.2.2、靜态全局變量

    靜态全局變量就是用來解決重名問題的。靜态全局變量定義時在定義前加static關鍵字,

告訴編譯器這個變量隻在目前本檔案内使用,在别的檔案中絕對不會使用。這樣就不用擔心重名問題。是以靜态的全局變量就用在我定義這個全局變量并不是為了給别的檔案使用,本來就是給我這個檔案自己使用的。

2.1.1.3、跨檔案引用全局變量(extern)

就是說,你在一個程式的多個.c源檔案中,可以在一個.c檔案中定義全局變量g_a,并且可以在别的另一個.c檔案中引用該變量g_a(引用前要聲明)

函數和全局變量在C語言中可以跨檔案引用,也就是說他們的連接配接範圍是全局的,具有檔案連接配接屬性,總之意思就是全局變量和函數是可以跨檔案看到的(直接影響就是,我在a.c和b.c中各自定義了一個函數func,名字相同但是内容不同,編譯報錯。)。

局部變量和全局變量的對比:

1、定義同時沒有初始化,則局部變量的值是随機的,而全局變量的值是預設為0.

2、使用範圍上:全局變量具有檔案作用域,而局部變量隻有代碼塊作用域。

3、生命周期上:全局變量是在程式開始運作之前的初始化階段就誕生,到整個程式結束退出的時候才死亡;而局部變量在進入局部變量所在的代碼塊時誕生,在該代碼塊退出的時候死亡。

4、變量配置設定位置:全局變量配置設定在資料段上,而局部變量配置設定在棧上。

判斷一個變量能不能使用,有沒有定義,必須注意兩點:第一,該變量定義的作用域是否在目前有效,是否包含目前位置;第二,變量必須先定義後使用。是以變量引用一定要在變量定義之前

基本概念:

作用域:起作用的區域,也就是可以工作的範圍。

代碼塊:所謂代碼塊,就是用{}括起來的一段代碼。

資料段:資料段存的是數,像全局變量就是存在資料段的

代碼段:存的是程式代碼,一般是隻讀的。

棧(stack):先進後出。C語言中局部變量就配置設定在棧中。

C語言對記憶體的管理方式。

2.2、常量

常量,程式運作過程中不會改變的量。常量的值在程式運作之前初始化的時候給定一次,以後都不會變了,以後一直是這個值。

2.2.1、#define定義的常量

    #define N 20            // 符号常量

    int a[N];

2.2.2、const關鍵字

    const int i = 14

const和指針結合,共有4種形式

const int *p;    p是一個指針,指針指向一個int型資料。p所指向的是個常量。        

int const *p;    p是一個指針,指針指向一個int型資料。p所指向的是個常量。    

int *const p;    p是一個指針,指針指向一個int型資料。p本身是常量,p所指向的是個變量

const int *const p;    p是一個指針,指針指向一個int型資料。p本身是常量,指向的也是常量

結論和記憶方法:

1、const在*前面,就表示const作用于p所指向的量。是以這時候p所指向的是個常量。

2、const在*後面,表示p本身是常量,但是p指向的不一定是常量。

const型指針有什麼用?

char *strcpy(char *dst, const char *src);

字元串處理函數strcpy,它的函數功能是把src指向的字元串,拷貝到dst中。

2.2.3、枚舉常量

枚舉常量是宏定義的一種替代品,在某些情況下會比宏定義好用。

enum

3、多檔案C語言項目

3.1、簡單的C語言程式(項目)隻有一個C檔案(a.c),編譯的時候gcc a.c -o a,執行的時候./a

3.2、複雜的C語言程式(項目)是由多個C檔案構成的。譬如一個項目中包含2個c檔案(a.c, b.c),編譯的時候 gcc a.c b.c -o ab,執行的時候 ./ab

實驗:

在a.c和b.c中分别定義main函數,各自單獨編譯時沒問題;但是兩個檔案作為一個項目來編譯

gcc a.c b.c -o ab的時候,就會報錯。multiple definition of `main'

為什麼報錯?

因為a.c和b.c這時候組成了一個程式,而一個程式必須有且隻能有一個main函數。

3.3、為什麼需要多檔案項目?為什麼不在一個.c檔案中寫完所有的功能?

因為一個真正的C語言項目是很複雜的,包含很多個函數,寫在一個檔案中不利于查找、組織、識别,是以人為的将複雜項目中的很多函數,分成了一個一個的功能子產品,然後分開放在不同的.c檔案中,于是乎有了多檔案項目。

是以,在b.c中定義的一個函數,很可能a.c中就會需要調用。你在任何一個檔案中定義的任何一個函數,都有可能被其他任何一個檔案中的函數來調用。但是大家最終都是被main函數調用的,有可能是直接調用,也可能是間接調用。

3.4、多檔案項目中,跨檔案調用函數

在調用函數前,要先聲明該被調用函數的原型。隻要在調用前聲明了該函數,那麼調用時就好像這個函數是定義在本檔案中的函數一樣。

總結:函數使用的三大要素:函數定義、函數聲明、函數調用

1、如果沒有定義,隻有聲明和調用:編譯時會報連接配接錯誤。undefined reference to `func_in_a'

2、如果沒有聲明,隻有定義和調用:編譯時一般會報警告,極少數情況下不會報警告。但是最好加上聲明。

3、如果沒有調用,隻有定義和聲明:編譯時一般會報警告(有一個函數沒有使用),有時不會報警告。這時候程式執行不會出錯,隻是你白白的寫了幾個函數,而沒有使用浪費掉了而已。

實驗:在一個項目的兩個.c檔案中,分别定義一個名字相同的函數,結果?

編譯報錯 multiple definition of `func_in_a'

結論:在一個程式中,不管是一個檔案内,還是該程式的多個檔案内,都不能出現函數名重複的情況,一旦重複,編譯器就會報錯。主要是因為編譯器不知道你調用該函數時到底調用的是哪個函數,編譯器在調用函數時是根據函數名來識别不同的函數的。

3.5、跨檔案的變量引用

(1)通過實驗驗證得出結論:在a.c中定義的全局變量,在a.c中可以使用,在b.c中不可以直接使用,編譯時報錯 error: ‘g_a’ undeclared (first use in this function)

(2)想在b.c中使用a.c中定義的全局變量,有一個間接的使用方式。在a.c中寫一個函數,然後函數中使用a.c中定義的該全局變量,然後在b.c中先聲明函數,再使用函數。即可達到在b.c中間接引用a.c中變量的目的。

(3)想在b.c中直接引用a.c中定義的全局變量g_a,則必須在b.c中引用前先聲明g_a,如何聲明變量? extern int g_a;    

extern關鍵字:

extern int g_a;    這句話是一個全局變量g_a的聲明,這句話告訴編譯器,我在外部(程式中

不是本檔案的另一個檔案)某個地方定義了一個全局變量 int g_a,而且我現在要在這裡引用它

告訴你編譯器一聲,不用報錯了。

問題:

1、我隻在b.c中聲明變量,但是别的檔案中根本就定義這個變量,會怎麼樣?

答案是編譯報錯(連接配接錯誤)undefined reference to `g_b'

2、我在a.c中定義了全局變量g_a,但是b.c中沒有聲明g_a,引用該變量會怎麼樣?

答案是直接抱錯了,未定義

3、在a.c中定義,在b.c中聲明,a.c和b.c中都沒有引用該變量,會怎麼樣?

答案是不會出錯。隻是白白的定義了一個變量沒用,浪費了

結論:不管是函數還是變量,都有定義、聲明、引用三要素。其中,定義是創造這個變量或者函數,聲明是向編譯器交代它的原型,引用是使用這個變量或函數。是以如果沒有定義隻有聲明和引用,編譯時一定會報錯。undefined reference to `xxx'

在一個程式裡面,一個函數可以定義一次,引用可以有無數次,聲明可以有無數次。因為函數定義或者變量的定義實際上是創造了這個函數/變量,是以隻能有一次。(多次創造同名的變量會造成變量名重複,沖突;多次創造同名的函數也會造成函數名重名沖突)。聲明是告訴編譯器變量/函數的原型,在每個引用了這個全局變量/函數的檔案之前都要聲明該變量/函數

局部變量能不能跨檔案使用?

不能。因為局部變量屬于代碼塊作用域。他的作用域隻有他定義的那個函數内部。

靜态局部變量能不能跨檔案使用?

不能。因為本質上還是個局部變量。

讨論跨檔案使用問題,隻用讨論全局變量和函數就可以了。

3.6、頭檔案的引入

3.6.1、為什麼需要頭檔案?

從之前可以看到,函數的聲明是很重要的。當我們在一個龐大的項目中,有很多個源檔案,每一個源檔案中都有很多個函數,并且需要在各個檔案中互相穿插引用函數。

怎麼解決函數的聲明問題?靠頭檔案。

3.6.2、#include包含頭檔案時,用<>和""的差別

<>用來包含系統自帶的頭檔案,系統自帶指的是不是你寫的,是編譯器或者庫函數或者作業系統提供的頭檔案。

""用來包含項目目錄中的頭檔案,這些一般是我們自己寫的。

3.6.3、防止重複包含頭檔案

#ifndef __A_H__

#define __A_H__

// C語言頭檔案中的聲明

#endif

3.6.4、寫程式時,最好不要在頭檔案中定義變量。因為這時該頭檔案被多個源檔案包含時,就會出現重複定義問題。全局變量的定義就應該放在某個源檔案中,然後在别的源檔案中使用前是extern聲明。