當(dāng)前位置:首頁(yè) > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 學(xué)習(xí)筆記 > 嵌入式學(xué)習(xí)筆記:C高級(jí)函數(shù)快速學(xué)
c高級(jí)可以說(shuō)是就比較難了,尤其是c高級(jí)函數(shù)這是學(xué)習(xí)的難點(diǎn),那么如何快速學(xué)c高級(jí)函數(shù)呢,下面總結(jié)了一些函數(shù)知識(shí)點(diǎn)及案例,可以快速學(xué)哦。
1.1定義
返回值類(lèi)型 函數(shù)名(類(lèi)型 形參, 類(lèi)型 形參, ...)
{
語(yǔ)句
語(yǔ)句
return 返回值
}
函數(shù)名:標(biāo)識(shí)符:用一眼要能看懂函數(shù)的功能的標(biāo)識(shí)符來(lái)表示函數(shù)名
返回值類(lèi)型:需要返回的返回值類(lèi)型
當(dāng)不需要返回值時(shí),返回值類(lèi)型為void,return后邊需要加返回值
當(dāng)返回值類(lèi)型為指針時(shí),稱(chēng)為指針函數(shù)
形參: 接收外部參數(shù)的局部變量
不需要接收參數(shù)時(shí), 寫(xiě)為void
6.2函數(shù)的調(diào)用
變量 = 函數(shù)名(實(shí)參1, 實(shí)參2, ......);
實(shí)參:傳給函數(shù)的變量或常量值。
運(yùn)行過(guò)程:為形參分空間,把實(shí)參賦值給形參
運(yùn)行函數(shù)中的程序
返回調(diào)用函數(shù),運(yùn)行調(diào)用點(diǎn)的下一行
補(bǔ)充:多窗口:vsp
1.3函數(shù)的作用范圍
1.3.1 提前聲明,擴(kuò)大定義域(extern)
實(shí)例:
1.3.2 在文件中,調(diào)用另一個(gè)文件中定義的函數(shù)
編譯命令:gcc a.c b.c -g:編譯兩個(gè)文件的時(shí)候,執(zhí)行依然是a.out
1.3.3. 禁止別的文件調(diào)用本文件的函數(shù)(static)
注意:用了statics的話,不同文件中是可以存在相同的文件名的。
stati將函數(shù)的作用范圍限制在本文件中
1.4變量的作用范圍(這里詳細(xì)的打開(kāi)內(nèi)存分配的部分及逆行講解)
1.4.1. 定義
全局變量:定義在函數(shù)外的變量,也可以理解為定義在{}外的變量。
局部變量:定義在函數(shù)內(nèi)的變量,也可以理解為定義在{}內(nèi)的變量。
需要定義在{}的開(kāi)始。
1.4.2 作用域(默認(rèn)作用范圍)
全局變量:從定義開(kāi)始到文件的結(jié)束
局部變量:從定義開(kāi)始到與之前一個(gè)對(duì)應(yīng)的{ }結(jié)束。
注意:小作用范圍的變量,屏蔽大作用范圍的變量
解釋?zhuān)?/p>
這里的擴(kuò)大作用范圍是針對(duì)全局變量來(lái)說(shuō)的,局部變量不能擴(kuò)大
方法:1)使用的位置在定義之前
2)使用的位置子其他文件
3)限制全局變量的作用范圍到默認(rèn)作用域
4)函數(shù)的作用域是從定義開(kāi)始,到本文件結(jié)束(針對(duì)文件來(lái)說(shuō))
實(shí)例:
1.5內(nèi)存的分配
1.5.1.程序未運(yùn)行時(shí)(size a.out可以看到信息)
text:存放CPU可執(zhí)行的機(jī)器指令,由于程序被經(jīng)常使用,防止其被意外修改,代碼區(qū)通常是只讀的。
data: 存放被初始化的全局變量、靜態(tài)變量(全局靜態(tài)變量和局部靜態(tài)變量)、常量數(shù)據(jù)(如字符串常量)。
Bss:存放未初始化的全局變量 (初始化成0,計(jì)算機(jī)并不認(rèn)為這是做了初始化操作,所以初始化成0的全局變量會(huì)被放在bss區(qū)中)
注意:BSS區(qū)的數(shù)據(jù)在程序開(kāi)始執(zhí)行之前被內(nèi)核初始化為0或空指針(NULL)。
具體:
1.5.2.程序運(yùn)行時(shí)
程序運(yùn)行時(shí)占用5個(gè)區(qū):代碼區(qū)、初始化數(shù)據(jù)區(qū)/靜態(tài)數(shù)據(jù)區(qū)、未初始化數(shù)據(jù)區(qū)、堆區(qū)、棧區(qū)
(1)代碼區(qū)(text)
代碼區(qū)指令根據(jù)程序設(shè)計(jì)流程依次執(zhí)行,對(duì)于順序指令,則只會(huì)執(zhí)行一次,如果反復(fù),則需使用跳轉(zhuǎn)指令,如果進(jìn)行遞歸,則需借助棧來(lái)實(shí)現(xiàn)。
代碼區(qū)包括操作碼和要操作的對(duì)象(或?qū)ο蟮牡刂芬?,如果是立即數(shù)(即具體的數(shù)值,如2),將直接包含在代碼中;如果是局部數(shù)據(jù),將在棧中分配空間,然后引用該數(shù)據(jù)的地址;如果是BSS區(qū)和數(shù)據(jù)區(qū),在代碼中同樣引用該數(shù)據(jù)的地址。
(2) 全局初始化數(shù)據(jù)區(qū)/靜態(tài)數(shù)據(jù)區(qū)(data)
在編譯的時(shí)候就會(huì)初始化,并且只初始化一次。上面已經(jīng)說(shuō)過(guò),在程序編譯時(shí),該區(qū)域已經(jīng)被分配好了,這塊內(nèi)存在程序的整個(gè)運(yùn)行期間都存在,當(dāng)程序結(jié)束時(shí),才會(huì)被釋放。
(3)未初始化數(shù)據(jù) 區(qū)(BSS):在運(yùn)行時(shí)改變其值。初始化0不算初始化。
(4)棧區(qū)(stack)
存放函數(shù)的參數(shù)值和局部變量,由編譯器自動(dòng)分配釋放,其操作方式類(lèi)似于數(shù)據(jù)結(jié)構(gòu)的棧。其特點(diǎn)是不需要程序員去考慮內(nèi)存管理的問(wèn)題,很方便;同時(shí)棧的容量很有限,在Linux系統(tǒng)中,棧的容量只有8M,并且當(dāng)相應(yīng)的范圍結(jié)束時(shí)(如函數(shù)),局部變量就不能再使用。
(5)堆區(qū)(heap)
有些操作對(duì)象只有在程序運(yùn)行時(shí)才能確定,這樣編譯器在編譯時(shí)就無(wú)法為他們預(yù)先分配空間,只有程序運(yùn)行時(shí)才分配,這就是動(dòng)態(tài)內(nèi)存分配。堆區(qū)就是用于動(dòng)態(tài)內(nèi)存分配(如malloc的動(dòng)態(tài)內(nèi)存分配),堆在內(nèi)存中位于bss區(qū)和棧區(qū)之間,一般由程序員申請(qǐng)和釋放(free釋放或程序停止)。
實(shí)例:
注意:
(1):堆區(qū)的內(nèi)存需要程序員自己釋放。
(2):棧區(qū)在范圍結(jié)束時(shí),編譯器就會(huì)釋放了,所以要注意棧區(qū)中數(shù)據(jù)的生命周期。
(3):棧區(qū)容量有限,注意不要使用超大局部變量,比如超大數(shù)組。
(4):bss區(qū)中的數(shù)據(jù),你不進(jìn)行初始化的話,系統(tǒng)會(huì)幫我們初始化成0。但其他區(qū)域的數(shù)據(jù),系統(tǒng)不會(huì)為我們做什么,尤其注意堆區(qū)和棧區(qū)中的數(shù)據(jù),未經(jīng)初始化的變量則有可能是臟數(shù)據(jù)。
1.6變量的生命周期
1.6.1分類(lèi)
1. data和bss中的變量的生命周期:程序的開(kāi)始到程序的結(jié)束。==>全局變量,靜態(tài)(全局/局部)變量
2. 棧中的變量的生命周期:函數(shù)的開(kāi)始到函數(shù)的結(jié)束。==>局部變量,形參
6.6.2對(duì)靜態(tài)局部變量的說(shuō)明:
有時(shí)希望函數(shù)中的局部變量的值在函數(shù)調(diào)用結(jié)束后不消失而保留原值,即其占用的存儲(chǔ)單元不釋放,在下一次該函數(shù)調(diào)用時(shí),該變量保留上一次函數(shù)調(diào)用結(jié)束時(shí)的值。這時(shí)就應(yīng)該指定該局部變量為靜態(tài)局部變量(static local variable)。
(1) 靜態(tài)局部變量在靜態(tài)存儲(chǔ)區(qū)內(nèi)分配存儲(chǔ)單元。在程序整個(gè)運(yùn)行期間都不釋放。而自動(dòng)變量(即動(dòng)態(tài)局部變量)屬于動(dòng)態(tài)存儲(chǔ)類(lèi)別,存儲(chǔ)在動(dòng)態(tài)存儲(chǔ)區(qū)空間(而不是靜態(tài)存儲(chǔ)區(qū)空間),函數(shù)調(diào)用結(jié)束后即釋放。
(2) 為靜態(tài)局部變量賦初值是在編譯時(shí)進(jìn)行值的,即只賦初值一次,在程序運(yùn)行時(shí)它已有初值。以后每次調(diào)用函數(shù)時(shí)不再重新賦初值而只是保留上次函數(shù)調(diào)用結(jié)束時(shí)的值。而為自動(dòng)變量賦初值,不是在編譯時(shí)進(jìn)行的,而是在函數(shù)調(diào)用時(shí)進(jìn)行,每調(diào)用一次函數(shù)重新給一次初值,相當(dāng)于執(zhí)行一次賦值語(yǔ)句。
(3) 如果在定義局部變量時(shí)不賦初值的話,對(duì)靜態(tài)局部變量來(lái)說(shuō),編譯時(shí)自動(dòng)賦初值0(對(duì)數(shù)值型變量)或空字符(對(duì)字符型變量)。而對(duì)自動(dòng)變量來(lái)說(shuō),如果不賦初值,則它的值是一個(gè)不確定的值。這是由于每次函數(shù)調(diào)用結(jié)束后存儲(chǔ)單元已釋放,下次調(diào)用時(shí)又重新另分配存儲(chǔ)單元,而所分配的單元中的值是不確定的。
(4) 雖然靜態(tài)局部變量在函數(shù)調(diào)用結(jié)束后仍然存在,但其他函數(shù)是不能引用它的,也就是說(shuō),在其他函數(shù)中它是“不可見(jiàn)”的。
具體解釋?zhuān)?/p>
初始化全局變量未初始化全局變量局部變量靜態(tài)全局變量靜態(tài)局部變量
占用存儲(chǔ)空間data
bss棧datadata
初始化次數(shù)初始化一次
初始化一次每次調(diào)用都初始化初始化一次初始化一次
作用域默認(rèn)作用域,可擴(kuò)大默認(rèn)作用域,可擴(kuò)大默認(rèn)作用域默認(rèn)作用域默認(rèn)作用域
生命周期程序開(kāi)始->程序結(jié)束 程序開(kāi)始->程序結(jié)束定義開(kāi)始,函數(shù)結(jié)束程序開(kāi)始->程序結(jié)束程序開(kāi)始->程序結(jié)束
補(bǔ)充:register變量
register int r_local = 6;要求編譯器盡量放在寄存器,register變量是不能做取地址操作的
register使用寄存器變量會(huì)提升速度,也并不是說(shuō)我們盡可能地多地定義register變量就能加快程序的運(yùn)行速度,畢竟CPU中寄存器是有限的,
如果你把變量指定為register變量,意味著可用于別的用途的寄存器就減少了,如程序運(yùn)算產(chǎn)生的中間結(jié)果,它們的應(yīng)用又很頻繁,
在寄存器不足的情況下,只好借助于內(nèi)存,這樣反倒會(huì)降低程序的運(yùn)算速度。
在現(xiàn)今的C版本中,大多已沒(méi)有定義register變量的必要,因?yàn)榫幾g程序忽略register修飾符,而根據(jù)寄存器的使用情況和變量的情況決定是否把變量解釋為register變量。
1.7函數(shù)的傳參
1.7.1 指針參數(shù)
補(bǔ)充:還有種應(yīng)用場(chǎng)景,就是需要使用結(jié)構(gòu)體作為參數(shù)的時(shí)候,因?yàn)樾螀⑾牡氖菞5目臻g,棧的空間是有限的,如果結(jié)構(gòu)體比較大,一般不建議直接將結(jié)構(gòu)體作為形參,而是通過(guò)指針對(duì)其進(jìn)行訪問(wèn)。
1.7.2 數(shù)組參數(shù)
int atoil(char str[10])
int atoil(char str[])
int atoil(char *str)
這三種定義的形式不同,但是達(dá)到的效果是一模一樣的,不管使用哪一種定義,編譯器都會(huì)將其轉(zhuǎn)換成第三種形式。
1.8指針函數(shù) VS 函數(shù)指針
1.8.1 指針函數(shù)(pointfunc.c)
確定方法:
(1)確定目標(biāo)類(lèi)型 int buf[5]
(2)確定指針類(lèi)型 在目標(biāo)類(lèi)型基礎(chǔ)上加* 加在變量名位置 int (*)[5]
(3)定義指針變量 int (*p)[5]
實(shí)例:
1.8.2 函數(shù)指針:首先是指針,指向的是函數(shù)。
(1) 定義
void swap(int *a, int *b)
void (*pfunc)(int *, int *);
(2) 賦值
pfunc = &swap;
pfunc = swap; (函數(shù)名, 就是函數(shù)的指針常量)
注意:函數(shù)類(lèi)型和函數(shù)指針類(lèi)型兼容,上邊兩種定義形式可以隨便選擇使用
(3) 調(diào)用
(*pfunc)(&a, &b);//將swap理解成函數(shù)
pfunc(&a, &b); //將swap理解成函數(shù)指針,這兩種調(diào)用隨便選擇,跟上邊的定義是沒(méi)有關(guān)系的。
實(shí)例:
補(bǔ)充:strcmp 比較字符串大小 qsort 可以快速排序
用法:
shrcmp
strcmp()函數(shù)是通過(guò)兩個(gè)字符串一個(gè)一個(gè)字符比較的(最多比較次數(shù)為第二個(gè)參數(shù)的長(zhǎng)度+1)
例如strcmp("hello","here");
首先比較第一個(gè)字符'h'= 'h'相等
接著比較第二個(gè)字符'e'= 'e'相等
比較第三個(gè)字符 'l'>'e',返回一個(gè)正值
如果字符串完全相等會(huì)回0
qsort:
qsort是萬(wàn)能數(shù)組排序函數(shù),必須要學(xué)會(huì)使用,簡(jiǎn)單的數(shù)組自然不用說(shuō),這里主要討論一下字符串?dāng)?shù)組的使用。
首先看一下qsort的原型:
void qsort(void *base, size_t nmemb, size_t size,
int(*compar)(const void *, const void *));
正確使用這個(gè)函數(shù)要注意幾點(diǎn):
1.base要傳數(shù)組的首地址
2.size傳的是每個(gè)元素的大小
3.正確編寫(xiě)compar函數(shù)
下面是實(shí)際應(yīng)用:
一個(gè)字符串?dāng)?shù)組:*str[MAX],假設(shè)里面現(xiàn)在保存了n個(gè)字符串了。
首先要正確理解什么是字符串?dāng)?shù)組,簡(jiǎn)單的說(shuō),可以理解成它就是一個(gè)數(shù)組,只不過(guò)其中的元素是一串字符串,而訪問(wèn)這些字符串,得用指針,也就是它們的地址,比如*name[]={"james","henry"},那么訪問(wèn)其中的字符串就是name[0],name[1]...這里就有個(gè)容易混淆的地方了,對(duì)于字符串?dāng)?shù)組,那么每個(gè)元素的大小到底是多少呢?對(duì)name[0]來(lái)說(shuō),到底是字符串“james”的長(zhǎng)度5,還是char*的大小4呢?答案應(yīng)該是4,因?yàn)樽址當(dāng)?shù)組里面保存的是各個(gè)字符串的指針,所以回到上面所說(shuō)的第二點(diǎn)注意,用qsort的時(shí)候應(yīng)該要傳sizeof(char *);
第二,編寫(xiě)compar函數(shù)比較字符串有地方要注意:
不能把strcmp本身傳給qsort,即不能寫(xiě)strcmp(p,q),因?yàn)樾螀⑹莄onst void*類(lèi)型,同理,寫(xiě)成strcmp((char *)p, (char *)q);也是無(wú)效的;正確的寫(xiě)法應(yīng)該是:strcmp(*(char **)p, *(char **)q);先強(qiáng)制轉(zhuǎn)換成char**,在用*減少一層間接尋址操作:
int compar_words(const void *p, const void *q)
{
return strcmp(*(char **)p, *(char **)q);
}
對(duì)于上面的應(yīng)用,最后使用qsort應(yīng)該是這樣:
qsort(str, n, sizeof(char *), compar);
實(shí)例:比較數(shù)組中的字符串的大小
1.9遞歸函數(shù)
定義:直接或間接調(diào)用自己
注意:遞歸程序的效率比較低,空間占用的也很多,唯一的理由是比較容易看懂。
例: 利用函數(shù)遞歸實(shí)現(xiàn)n!
1. 寫(xiě)出遞歸通用項(xiàng):n! = n * (n - 1)!
2. 寫(xiě)出遞歸結(jié)束條件:n = 1
3. 代碼實(shí)現(xiàn):假設(shè)函數(shù)已經(jīng)寫(xiě)好,可以用調(diào)用,不要去想計(jì)算機(jī)執(zhí)行的具體過(guò)程
4. 單步執(zhí)行, 掌握?qǐng)?zhí)行流程
實(shí)例:實(shí)現(xiàn)階乘 將輸入的字符串反向輸出。