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