wordpress 內(nèi)容采集重慶seo主管
C語?中的函數(shù)也被稱做子程序,意思就是?個完成某項特定的任務的?小段代碼。
C語?標準中提供了許多庫函數(shù),點擊下面的鏈接可以查看c語言的庫函數(shù)和頭文件。
C/C++官?的鏈接:https://zh.cppreference.com/w/c/header
目錄
一、函數(shù)頭與函數(shù)體
二、實參與形參
三、return的用法事宜
四、main的返回類型與參數(shù)(了解即可)
#1. main的返回類型
#2. main的參數(shù)個數(shù)
#3. 參數(shù)argc和argv(了解即可)
五、數(shù)組作為函數(shù)參數(shù)(重中之重)
#1. 數(shù)組傳參的本質(zhì)
#2. 不存在形式數(shù)組(下標操作符失效):
#3. 二維數(shù)組傳參不能省略列
六、嵌套調(diào)用與鏈式訪問
七、函數(shù)的聲明與定義
#1. 聲明與定義的區(qū)別 和 注意事項
#2. 未聲明的函數(shù)?
#3.?頭文件聲明的底層邏輯 和 函數(shù)調(diào)用操作符()
一、函數(shù)頭與函數(shù)體
?函數(shù)頭?:即函數(shù)定義中的第一行代碼,函數(shù)頭內(nèi)包含著(1)函數(shù)的返回類型、(2)函數(shù)的名字、(3)形式參數(shù)的類型和個數(shù)(即參數(shù)表)。
?函數(shù)體?:即函數(shù)定義中的大括號{ }部分,它是是真正調(diào)用形參、執(zhí)行子代碼、返回任務值的重要部分。
舉例:執(zhí)行加法運算的函數(shù)
int? add(int? a, int b)? ?//該行就是函數(shù)頭(信息:函數(shù)名add,返回類型int,2個形參都是整型)
{? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ????????? // (int a, int b) 是參數(shù)表
? ? ? ? return? a+b;? ? //整個大括號的內(nèi)容就是函數(shù)體(這里負責加法運算和返回運算的結(jié)果)
}
二、實參與形參
實參的幾種理解和概念:
1. main函數(shù)中的參數(shù)(包括常數(shù)和變量等)
2.?實參是在調(diào)用函數(shù)時傳遞給函數(shù)的具體參數(shù)值。
3. 實參變量在主函數(shù)被創(chuàng)建時會直接申請內(nèi)存空間,程序結(jié)束時才歸還內(nèi)存空間。
實參的特點:
1. 作用域:可以在mian函數(shù)內(nèi)使用,不能在函數(shù)(子程序)被使用。
2. 生命周期:從變量創(chuàng)建到程序結(jié)束。(長時,僅一次)
形參的幾種理解和概念:
1.?形參是函數(shù)定義中的參數(shù),是一種占位符,用于接收傳遞給函數(shù)的數(shù)據(jù)。
2.?形參是實參在函數(shù)調(diào)用時的?份臨時拷貝。
3. 函數(shù)不被調(diào)用時,形參變量不會申請內(nèi)存空間;調(diào)用時才申請空間,調(diào)用完就歸還空間。
形參的特點:
1. 作用域:可以在函數(shù)(子程序)使用,不能在mian函數(shù)內(nèi)被使用。
2. 生命周期:從函數(shù)調(diào)用到函數(shù)結(jié)束。(短時,可多次)
總的來說,形參由實參而來,它們的討論載體是函數(shù)傳遞。
注意:在函數(shù)中創(chuàng)建的變量并不屬于形參變量,而是局部變量,因為它的創(chuàng)建并沒有傳遞關系。
三、return的用法事宜
作用:return后邊可以是?個數(shù)值,也可以是?個表達式,如果是表達式則先執(zhí)?表達式。return語句執(zhí)?后,函數(shù)就徹底返回(有返回值的話帶回返回值),后邊的代碼不再執(zhí)行。(主函數(shù)也是如此)
return在下面4種情況的用法細則:
?i. 函數(shù)返回值是void:
因為無需返回類型,可以寫成“ return ; ”。return后面除了英文分號,其他什么都不要寫。
?ii. 函數(shù)返回值類型與return的值不同:
return返回的值和函數(shù)返回類型不?致,系統(tǒng)會?動將返回的值隱式轉(zhuǎn)換為函數(shù)的返回類型。
比如函數(shù)的返回類型是整型,而return的任務值它的數(shù)據(jù)類型是浮點型,那么返回的結(jié)果是在任務值的基礎上去掉小數(shù)部分,留下整數(shù)部分:
int f()
{return 1.5 + 3.7;
}
int main()
{int a = f();printf("返回值為:%d\n", a);return 0;
}
1.5 + 3.7等于5.2,但是返回類型是int,所以被隱式類型轉(zhuǎn)換為5。函數(shù)返回值的隱式轉(zhuǎn)換與算術的隱式類型轉(zhuǎn)換是相同的,詳細請看我這篇文章:《數(shù)學計算類操作符 和 算術類型轉(zhuǎn)換》
?iii. 函數(shù)內(nèi)容是分支結(jié)構(gòu)的:
如果函數(shù)中存在 if 等分?的語句,則要保證每種情況下都有return返回,否則容易出現(xiàn)編譯錯誤。
?iv. main函數(shù)中的return語句:
return 語句在 main 函數(shù)中的作用是結(jié)束程序的執(zhí)行并返回一個整數(shù)值給操作系統(tǒng)。這個整數(shù)值通常用于表示程序的退出狀態(tài),其中0表示程序正常退出,非零值表示程序異常退出。
返回非0值的錯誤信息:(了解即可)
- 返回1——文件打開失敗:如果程序試圖打開一個不存在的文件或者沒有權限訪問的文件
- 返回2——內(nèi)存分配失敗:如果程序在運行過程中無法分配足夠的內(nèi)存空間
- 返回3——無效的輸入?yún)?shù):如果程序接收到無效的輸入?yún)?shù)
- 返回4——運行時錯誤:如果在程序執(zhí)行過程中發(fā)生了除以零、數(shù)組越界等運行時錯誤
這里列舉了4個錯誤的整型返回值,實際上還有很多種。
四、main的返回類型與參數(shù)(了解即可)
#1. main的返回類型
?i. " int? main() " 形:(標準)
這是C99中標準的main函數(shù)使用形式。int返回類型使得main函數(shù)能夠向操作系統(tǒng)返回程序的退出狀態(tài),返回0通常表示程序正常退出,而返回非0值表示程序異常退出。(上面也有舉例提及)
?ii. " main() " 形:
早期的C代碼中(歷史上),常??梢钥吹?jīng)]有明確指定返回值的主函數(shù),即main()。這樣寫是因為,函數(shù)定義不明確返回類型時系統(tǒng)會默認為int型,所以這樣寫也是返回一個整數(shù)值。
在C99的規(guī)定中,這是一種不標準的寫法,建議不要這樣寫。
?iii. "void? main() "?形:
首先要明確的一點,這是一種錯誤的寫法,歷史上void main()也從未在C++標準或C標準中得到定義。
雖然返回值是void也能執(zhí)行main函數(shù)里的內(nèi)容,但是main函數(shù)也是會被其他函數(shù)調(diào)用的,而調(diào)用main函數(shù)的函數(shù)需要知道程序是否正常退出、異常退出的原因是什么。比如mainCRTStartup()函數(shù)與__mainCRTStartup()函數(shù)。{調(diào)用邏輯:mainCRTStartup()函數(shù) —>?__mainCRTStartup()函數(shù) —> main()函數(shù) }
#2. main的參數(shù)個數(shù)
雖然main函數(shù)可以不寫參數(shù),但其實參數(shù)表上是有參數(shù)的
?i. 一個參數(shù)
在某些平臺上,main函數(shù)可能會接受一個參數(shù),例如main(argc),但這種形式并不標準,且現(xiàn)在很少見。
?ii.?兩個參數(shù)
這是最標準的main函數(shù)形式,接受命令行參數(shù)個數(shù)和參數(shù)數(shù)組,即int main(int? argc, char* argv[ ]) 。
?iii. 三個參數(shù)
在一些特定的系統(tǒng)或編譯器擴展中,main函數(shù)可能會有第三個參數(shù),用于傳遞環(huán)境變量,例如int main(int? argc, char* argv[ ], char* envp[ ])。這種形式不是標準形式,但在Windows等系統(tǒng)中比較常見。
#3. 參數(shù)argc和argv(了解即可)
-
argc
是一個整型變量,表示傳遞給程序命令的參數(shù)數(shù)量,程序名本身作為第一個參數(shù)。例如:如果你運行一個名為"program"的程序,并傳遞了兩個參數(shù)"arg1"和"arg2",那么argc的值將是3。 argv
是一個字符指針數(shù)組,其中每個元素指向一個字符串,這些字符串是傳遞給程序的命令行參數(shù)。除了argv[0]通常是程序的名稱,其他argv[n]代表第n-1個參數(shù)。比如:argv[1]是第一個參數(shù),argv[2]是第二個參數(shù),依此類推。
int main(int argc, char* argv[])
{printf("文件名: %s\n\n", argv[0]);printf("參數(shù)個數(shù): %d\n", argc);return 0;
}
五、數(shù)組作為函數(shù)參數(shù)(重中之重)
#1. 數(shù)組傳參的本質(zhì)
數(shù)組傳參的本質(zhì)是傳遞該數(shù)組的地址(首元素地址),傳遞的地址值交給形式指針變量來管理。而并不是像形參變量那樣,調(diào)用函數(shù)時會申請內(nèi)存空間,數(shù)組的傳遞并不會開辟新空間,且函數(shù)操作的是原數(shù)組的內(nèi)存空間。當然,函數(shù)調(diào)用的時候,會申請空間給形式指針變量的創(chuàng)建。
以下面的代碼為例:(參數(shù)表中的arr其實并不是數(shù)組,而是指針,后面我會講解)
void print_arr(int arr[], int n)
{for (int i = 0; i < n; i++)printf("%d ", arr[i]);printf("\n");
}
int main()
{int a[5] = { 1,2,3,4,5, };size_t sz = sizeof a / sizeof a[0];print_arr(a, sz);return 0;
}
可以發(fā)現(xiàn),arr的值是與a的值、&a的值和&a[0]的值是相等的,這說明函數(shù)調(diào)用時,數(shù)組a傳參給函數(shù)并沒有申請一塊新的連續(xù)空間來拷貝數(shù)組a,而是用指針arr來對原數(shù)組a的內(nèi)存空間進行操作。
#2. 不存在形式數(shù)組(下標操作符失效):
先說結(jié)論:其實參數(shù)表中的接收數(shù)組,并不是真正的數(shù)組(因為函數(shù)并未對原數(shù)組進行拷貝),而是一個指針。由于這是形式參數(shù),可以稱之為形式指針。
補充知識:數(shù)組也是有數(shù)據(jù)類型的,去掉數(shù)組名就是數(shù)組的類型了,比如int? arr[2][4]的類型是int [2][4]。詳細請看《一維、二維數(shù)組的基礎知識超詳細總結(jié)》
下標操作符[ ]失效:而在函數(shù)參數(shù)表中,下標操作符失去了確定數(shù)組類型的作用。但它起到了新的作用:下標操作符的個數(shù)反映著形式指針的級數(shù)。
補充:因為接收變量是個形式指針,而不是數(shù)組。所以你在函數(shù)定義時,在“偽數(shù)組”下標寫上數(shù)字和不寫數(shù)字是一樣。
其實剛剛的例子除了arr的值與&arr的值不同可以看出來arr是指針,其實還有另一種看法:
偽數(shù)組arr的類型是 int* 型,這是個標準的指針類型;而原數(shù)組a的類型是 int[5] 型,這是個標準的數(shù)組類型。所以可以看出,arr并不是一個形式數(shù)組,而是一個形式指針。
#3. 二維數(shù)組傳參不能省略列
數(shù)組傳地址給函數(shù)后,函數(shù)只知道這是一片連續(xù)空間,但不知道你有沒有對這塊連續(xù)空間進行特殊的規(guī)劃。又由于內(nèi)存尋址公式的存在,你必須寫下列數(shù),函數(shù)才知道這塊連續(xù)空間是怎么劃分。
如果你把函數(shù)聲明(或定義)寫成:int? arr_print( int arr[ ][ ],int row,int col )。這樣是不夠的,此時函數(shù)只知道用1個二級指針來管理這塊連續(xù)的內(nèi)存空間,但并不知道要怎么劃分。
補充:函數(shù)必須通過列數(shù)才能知道劃分方法,與一維數(shù)組傳參一樣,接收變量的類型是指針類型,所以行下標寫不寫上都一樣。
六、嵌套調(diào)用與鏈式訪問
嵌套調(diào)用:函數(shù)內(nèi)部調(diào)用另一個函數(shù)。
(補充,遞歸調(diào)用是函數(shù)內(nèi)部調(diào)用自己)
假設我們計算某年某?有多少天?我們可以設計2個函數(shù)來實現(xiàn):
- is_leapyear():根據(jù)年份確定是否是閏年?
- days():調(diào)?函數(shù)is_leapyear確定是否是閏年后,再根據(jù)月份計算這個月的天數(shù)
int is_leapyear(int year)
{if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))return 1;elsereturn 0;
}
int days(int year, int month)
{int days[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };int day = days[month];if (is_leapyear(year) && month == 2) //這里嵌套調(diào)用了is_leapyear()day += 1;return day;
}
鏈式訪問:將?個函數(shù)的返回值作為另外?個函數(shù)的參數(shù)。
比如“ printf( "%zd\n", strlen("abcdef") ) ”,這里函數(shù)strlen的返回值也是函數(shù)printf的參數(shù),像鏈條?樣將函數(shù)串連起來。
七、函數(shù)的聲明與定義
#1. 聲明與定義的區(qū)別 和 注意事項
格式上:函數(shù)聲明與函數(shù)定義相比,去除了大括號以及其中的內(nèi)容,在函數(shù)頭的末尾加上了分號。
這是函數(shù)定義:
int add(int a, int b)
{ return a+b; }這是函數(shù)聲明:
int add (int a, int b);
1. 單個文件中:
建議:函數(shù)被調(diào)用前一定要有聲明,有定義也可以,因為函數(shù)定義算是一種特殊的函數(shù)聲明。
2. 多個文件中:
建議:為了有條理,建議把函數(shù)聲明、函數(shù)定義和主函數(shù)測試分開放在3個文件里。分成一個.h文件和兩個.c文件,(1)自定義頭文件.h存放自定義函數(shù)的聲明以及庫函數(shù)的頭文件,(2)其中一個.c文件存放函數(shù)定義,(3)另一個.c文件存放main函數(shù)和測試用例函數(shù)。
注意:兩個.c文件要包含自定義頭文件.h
例如:
#2. 未聲明的函數(shù)?
對于未聲明的函數(shù),編譯器會默認函數(shù)的返回值是int型。
用代碼舉例,此時兩個.c文件都沒有包含函數(shù)聲明的頭文件:
我們把鼠標放在函數(shù)名上,就能顯示函數(shù)的類型:
我們可以發(fā)現(xiàn),如果沒有頭文件聲明,那么函數(shù)默認返回類型是int型。
當我們點擊生成解決方案,代碼是通過了檢測的:
其實準確來說,失去了頭文件關于該函數(shù)的聲明,代碼是失去了對該函數(shù)的格式檢查的。
#3.?頭文件聲明的底層邏輯 和 函數(shù)調(diào)用操作符()
1.頭文件的本質(zhì):
頭文件本身不會參與鏈接,它們只是包含了一些函數(shù)聲明、宏定義和類型定義等信息。編譯器會將這些信息整合到目標文件中,然后鏈接器會將多個目標文件以及庫文件鏈接在一起,生成最終的可執(zhí)行文件或庫文件。所以,頭文件的作用是提供代碼的結(jié)構(gòu)和依賴關系信息。
頭文件提供的依賴關系信息,使得編譯器在編譯前可以檢查函數(shù)格式是否正確。對于函數(shù)的檢測,檢查的東西包括(1)函數(shù)返回類型、(2)參數(shù)的個數(shù)、(3)每個參數(shù)的數(shù)據(jù)類型。
2. 無頭文件時的底層邏輯:
沒有頭文件聲明時,編譯器會把這些東西看作外部函數(shù),在其他地方有定義。這時候編譯器會使用默認的函數(shù)聲明,即“?int? 函數(shù)名 (void)?”。
編譯器使用默認函數(shù)聲明后,系統(tǒng)不再對使用的函數(shù)格式進行檢查,鏈接器會把這兩個.c文件鏈接成一個.exe文件,然后交給CPU運行。
因為后續(xù)系統(tǒng)不再做檢查,所以使用函數(shù)時即使輸入的參數(shù)類型不符、參數(shù)個數(shù)不符,這些問題都會被忽略。
代碼舉例:
test.c文件:main函數(shù)測試
#include<stdio.h>
int main()
{int a[5] = { 1,2,3,4,5 };int c = arr_print(a, 5, "dad"); 這里還多輸入了一個無關參數(shù)“dad”printf("\n函數(shù)返回值:%d\n", c); 用c接收默認返回類型(int型)的值return 0;
}
function.c文件:數(shù)組打印函數(shù)
void arr_print(int arr[], int n)
{for (int i = 0; i < n; i++)printf("%d ", arr[i]);printf("\n");
}
運行結(jié)果:
現(xiàn)在是默認聲明“int? arr_ptint(void)”,因為參數(shù)表是void的,系統(tǒng)不管你寫不寫。但既然我們寫進了參數(shù),它還是會按照正常的參數(shù)表順序讀取參數(shù)的。這里我們多了一個參數(shù)“dad”,因為沒有第3個接收變量,所以并沒有被函數(shù)讀入。
如果我們補齊頭文件的聲明,那么還沒編譯就會報錯:
這里指的是,明明聲明函數(shù)的返回類型是void,你卻用一個整型變量c來接收非法返回值。
為什么程序還能找到函數(shù)體并運行函數(shù)?因為有函數(shù)調(diào)用操作符()。
多個文件被鏈接器鏈接成一個文件后,函數(shù)調(diào)用操作符()可以解析函數(shù)名并找到函數(shù)體的地址。
本期的內(nèi)容到這里就結(jié)束了。函數(shù)的知識真的是多而雜,寫得我真的是太難受了,還請您多多支持
Thanks?(・ω・)ノ