嵌入式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聲明。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章