一篇文章帶你入門C語言:函數(shù)
函數(shù)
定義
程序里的函數(shù)又被叫做子程序,他作為一個大型程序的部分代碼,有一或多個語句項組成。函數(shù)負(fù)責(zé)完成某項特定任務(wù),提供了對過程的封裝和對細(xì)節(jié)的隱藏,這樣的代碼通常會被集成為軟件庫。
特點:
具備相對的獨立性一般有輸入值和返回值功能單一且靈活
函數(shù)的分類有:庫函數(shù)和自定義函數(shù)。
庫函數(shù)
定義
庫函數(shù),顧名思義,放在庫里供他人使用的函數(shù)。如打印輸出這樣的基礎(chǔ)功能,他不是業(yè)務(wù)性的代碼,在開發(fā)過程中使用率高且可移植性強(qiáng),故C語言的基礎(chǔ)庫里提供了這樣的一系列基礎(chǔ)功能的代碼。
一般庫函數(shù)有:
IO函數(shù)(input&output)—— printf scanf getchar putchar …字符串操作函數(shù) —— strlen strcmp strcat strcpy …字符操作函數(shù) —— tolower toupper …內(nèi)存操作函數(shù) —— memcpy menset memmove memcmp …時間/日期操作函數(shù) —— time …數(shù)學(xué)函數(shù) —— sqrt abs fabs pow …其他庫函數(shù)
介紹
為了掌握庫函數(shù)的使用方法的學(xué)習(xí),我們可以參照權(quán)威網(wǎng)站 cplusplus 的解析為樣本,一般在不同的平臺上也是大同小異。一般都是按照這樣的順序?qū)瘮?shù)進(jìn)行解析。
函數(shù)的基本信息功能描述函數(shù)參數(shù)返回值例子拓展
Example 1 strcpy
char * strcpy ( char * destination, const char * source);


當(dāng)然這里 strcpy 函數(shù)的返回值是目標(biāo)空間的首地址,故接收是也可以使用函數(shù)的返回值。
char arr1[20] = { 0 };
char arr2[] = "damn it!";
//1.
char* ret = strcpy(arr1, arr2);
printf("%s\n", ret);
//2.
printf("%s\n",strcpy(arr1, arr2));
Example 2 memset
void * ( void * ptr, int value, size_t num );

char arr[20] = "damn it!";
memset(arr, 'x', 2);
//1.
printf("%s\n", arr);
//2.
printf("%s\n", (char*)memset(arr, 'x', 2));
memset函數(shù)是以字節(jié)為單位,去修改我們的地址中的內(nèi)容。
int arr[30] = { 0 };
memset(arr, 1, 5 * sizeof(int));

這樣的話只能把整型變量中每一個字節(jié)都變成1,而若想用此法置零則是可行的。
就按照這樣的方式去讀網(wǎng)站上對函數(shù)的解析內(nèi)容。
注意
- 每次使用庫函數(shù)都有引用#include頭文件
自定義函數(shù)
定義
庫函數(shù)雖好,但不可貪杯哦~ 庫函數(shù)雖多,但是畢竟不能實現(xiàn)所有功能,所以還是需要自定義函數(shù)來滿足我們的各種各樣的需求。自定義函數(shù)和庫函數(shù)一樣,有函數(shù)名、返回類型和函數(shù)參屬,但不同的是這些都由我們自己來設(shè)計。
形式
ret_type fun_name(para1,...)
{
statment;//語句項
}
ret_type//返回類型
fun_name//函數(shù)名
para//參數(shù)
有了這樣的形式模板,我們就可以照葫蘆畫瓢了。
Example 1
找出兩個數(shù)的最大值

如圖所示,寫函數(shù),函數(shù)名、參數(shù)、返回類型都要對應(yīng)。
Example 2 兩數(shù)交換
先看再程序設(shè)計中如何進(jìn)行兩數(shù)交換,用醬油、醋和空瓶舉例。

先把a賦值給t,那么現(xiàn)在t里面存有a的值現(xiàn)在再把b賦值給a,這樣a還在t里不會被覆蓋最后把t(里的a)賦值給b,這樣就完成了a和b的互換。
void Swap1(int x, int y) {
int t = 0;
t = x;
x = y;
y = t;
}
void Swap2(int* px, int* py){
int t = 0;
t = *px;
*px = *py;
*py = t;
}
int main(){
int a = 10;
int b = 20;
Swap1(a,b);
printf("Swap1:a=%d,b=%d\n", a, b);
Swap2(&a, &b);
printf("Swap2:a=%d,b=%d\n", a, b);
return 0;
}
Swap1和Swap2那個函數(shù)能夠?qū)崿F(xiàn)這樣的功能呢?
Swap1僅僅是把a和b傳值給x和y,此時去修改x和y是影響不到a和b的。
Swap2是把a,b的地址傳給指針變量px和py,這樣的話,再函數(shù)內(nèi)去將px和py解引用再修改,就可以指向a,b的內(nèi)容了。簡而言之,通過指針指向?qū)崊⒌牡刂肥沟眯螀⑴c實參同時發(fā)生變化。

由圖可知,px和py內(nèi)部存儲的是變量a和b的地址,這樣對px和py解引用就可以修改a和b的值。
由右圖的監(jiān)視可看出,Swap1函數(shù)x和y確實發(fā)生了交換,但并沒有影響到a和b,Swap2函數(shù)的px、py和&a、&b是一個意思。
參數(shù)
函數(shù)參數(shù)分為實際參數(shù)和形式參數(shù)兩種,
實際參數(shù)又叫實參,實參可以是任意有確定值的形式,以便在進(jìn)行函數(shù)調(diào)用時,將其傳給形參。形式參數(shù)又叫形參,只有當(dāng)函數(shù)調(diào)用時,他們才被分配確定值以及內(nèi)存單元,調(diào)前不存在,調(diào)后銷毀,所以形參只是形式上存在。
函數(shù)調(diào)用
1.傳值調(diào)用
形參實例化之后相當(dāng)于實參的一份臨時拷貝,并且形參和實參占用不同的內(nèi)存單元,本質(zhì)上是兩個不同的變量,形參的修改不影響實參。
2.傳址調(diào)用
將外部變量的地址傳給函數(shù)參數(shù),這樣的調(diào)用可使函數(shù)內(nèi)外建立真正的聯(lián)系,即形參實參建立聯(lián)系。
練習(xí)
1.寫一個函數(shù)能夠判斷素數(shù)
#include <math.h>
int is_prime(int n)
{
//試除法
int j = 0;
for (j = 2; j <= sqrt(n); j++)
{
if (n % j == 0)
return 0;
}
return 1;
}
函數(shù)的功能要單一且靈活,判斷素數(shù)就是判斷素數(shù),打印的操作留給其他函數(shù),這樣的話,寫出來的代碼才能夠很好的互相配合。
2.寫一個函數(shù)判斷一年是否為閏年
//是閏年返回1,不是閏年返回0
int is_leap_year(int y)
{
return (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0));
}
不可以將兩個或者的條件分開成if…else…的形式,這樣的話1200,1600,2000這樣可整除400,也可整除100的數(shù)據(jù)就在第一輪判斷就淘汰了,進(jìn)入不了第二個條件的判斷。
3.寫一個函數(shù)實現(xiàn)整型有序數(shù)組的二分查找
int binary_search(char arr[], int k, int sz)
{
int left = 0;
int right = sz - 1;
while (left <= right)
{
int mid = (left+right)/2;
if (arr[mid] > k)
{
right = mid - 1;
}
else if (arr[mid] < k)
{
left = mid + 1;
}
else
return mid;
}
return -1;
}
而在主程序中是這樣的
int main()
{
char arr[20] = { 1,2,3,4,5,6,7,8,9,10 };
int key = 7;
int sz = sizeof(arr) / sizeof(arr[0]);
//計算數(shù)組元素個數(shù)
int ret = binary_search(arr, key, sz);//TDD - 測試驅(qū)動開發(fā)
//找到返回下標(biāo)0~9
//找不到返回-1
if (ret == -1)
printf("找不到\n");
else
printf("找到了,下標(biāo)為%d", ret);
return 0;
}
在主程序編寫代碼時,把binary_search函數(shù)當(dāng)成庫函數(shù)一樣寫,并將函數(shù)的實現(xiàn)邏輯實現(xiàn)規(guī)定好,最后再去寫函數(shù)實現(xiàn)。
這樣的方法叫TDD(test drive develop)—測試驅(qū)動開發(fā)。
1.寫一個函數(shù)每調(diào)用一次,就將num的值加1
int Add(int num)
{
num++;
}
int main()
{
int num = 0;
num = Add(num);
return 0;
}
講到這里基本內(nèi)容就講完了,下面開始進(jìn)一步的深入。
嵌套調(diào)用
函數(shù)可不可以嵌套定義?
當(dāng)然是不可以的,函數(shù)與函數(shù)是平等的,是并列關(guān)系,不可以在任意函數(shù)(包括主函數(shù))中定義其他函數(shù)。
但是函數(shù)是可以互相調(diào)用的。
void fun1()
{
printf("hanpidiaoyong\n");
}
void fun2()
{
fun1();
}
int main()
{
fun2();
return 0;
}
如代碼所示,main函數(shù)調(diào)用fun2函數(shù),fun2函數(shù)又調(diào)用fun1函數(shù),最終在屏幕上打印憨批調(diào)用字樣/[doge]。
鏈?zhǔn)皆L問
鏈?zhǔn)皆L問(chain access),顧名思義,把一個函數(shù)的返回值作為另一個函數(shù)的參數(shù)。像是用鏈子把函數(shù)首尾相連拴起來。
如:
int main()
{
printf("%d\n",strlen("abcde")); //把strlen的返回值作為printf的參數(shù)
return 0;
}
int main()
{
char arr1[20] = "xxxxxxx";
char arr2[20] = "abcde";
//strcpy(arr1,arr2);
printf("%s\n", strcpy(arr1, arr2));//strcpy函數(shù)的返回值是目標(biāo)空間首元素地址
return 0;
}
Example 1
如果覺得掌握了的話,可以看看這個經(jīng)典例子。
printf("%d", printf("%d", printf("%d", 43)));
請問這條語句輸出什么?
想要知道這個,那必然要先了解 printf 函數(shù)的返回值是什么,通過MSDN或者cplusplus.com網(wǎng)站去查找。
可以看到 printf 函數(shù)的返回值是打印字符的個數(shù),如果發(fā)生錯誤,則返回負(fù)值。(ps:scanf的返回值是輸出字符的個數(shù))
首先可以看出第三個printf打印了43;
然后第二個printf打印了第三個printf的返回值為2;
最后第一個printf打印第二個printf的返回值1;
所以屏幕上打印了4321。
筆者良心說一句,如果學(xué)校里有人說自己C語言不錯,那么請拿這題考考他。
函數(shù)聲明
代碼是從前往后執(zhí)行的,如果函數(shù)定義在后面的話,調(diào)用時便會發(fā)出警告:函數(shù)未定義,若想消除警告,我們便需要在前面聲明一下。
void test();
int main(){
test();
}
void test()
{}
定義:聲明就是把函數(shù)定義在加個;,目的是告訴編譯器函數(shù)的返回類型、函數(shù)名、參數(shù)這些具體信息。
特點
函數(shù)的聲明一般出現(xiàn)在函數(shù)使用之前。
函數(shù)的聲明一般放在頭文件中。
在工作的時候,一般是把函數(shù)的聲明、定義和使用放在三個不同的文件內(nèi),方便所有人協(xié)作。如:

鏈接:兩個.c的源文件編譯之后,會分別生成.obj的目標(biāo)文件,然后再鏈接起來,最后生成.exe的可執(zhí)行文件。
在C語言,不提供源碼,也可以使用文件的內(nèi)容,怎么做的呢?
請移步至我的其他博客:關(guān)于vs2019的各種使用問題及解決方法(隨即更新)
頭文件引用
#include的預(yù)編譯指令是在預(yù)編譯階段將頭文件內(nèi)的所有內(nèi)容拷貝到源文件內(nèi)。
所以,頭文件中的內(nèi)容若是內(nèi)容重復(fù)包含則會造成效率降低。那么,怎么解決這件事呢?
- 頭文件中包含語句#pragma once使得頭文件中不會重復(fù)包含其他頭文件;
- 添加這樣的代碼,將語句包含起來。
#ifndef __ADD_H__// if not define #define __ADD_H__//define //Add函數(shù)聲明 extern int Add(int x, int y); #endif//end if
早期都是用第二種方法的,這兩種方法是完全等價的。
函數(shù)遞歸
什么叫函數(shù)遞歸呢?
程序自身調(diào)用自身的編程技巧叫遞歸。
特點
大型復(fù)雜問題層層轉(zhuǎn)化為小規(guī)模的問題少量程序描述除多次運算
遞歸的思維方法在于:大事化小。
Example 1
接收一個無符號整型值,按照順序打印其每一位。如輸入:1234,輸出:1 2 3 4 .
那我們創(chuàng)建一個函數(shù)叫print,若print(1234),則剝離一位變成print(123)+4,再剝離一位成print(12)+3+4,再來一位就是print(1)+2+3+4,最后只有一位了,那就全部用printf 函數(shù)打印。如:

我們發(fā)現(xiàn)只要將數(shù)字1234模10就可以得到4,除10便可以得到123,如此模10除10循環(huán)往復(fù),可以將1 2 3 4全部剝離出來。
//函數(shù)遞歸
void print(size_t n)
{
if (n > 9)//只有1位便不再往下進(jìn)行
{
print(n / 10);
}
printf("%d ", n%10);
}
具體流程可參考下面這張圖。

紅線部分即在返回的時候,n是本次函數(shù)n,而不是前一次調(diào)用的n。
遞歸遞歸,就是遞推加回歸。
現(xiàn)在有兩個問題
- if(n > 9)這個條件沒有行不行?沒有會怎么樣?
自然是不行的,我們在上面的推到中發(fā)現(xiàn),最后1<9條件不成立,就結(jié)束了遞歸,否則會永遠(yuǎn)遞歸下去,造成死循環(huán)且耗干了棧區(qū)。
- 或者是我們不論代碼的正確性,將print(n / 10)改成print(n)會怎么樣?
改成print(n)的話每次遞歸都是相同的值,遞歸也會無止境的延續(xù)下去。
這樣便引出了我們遞歸的兩個重要的必要條件:
必要條件
- 必須存在限制條件,滿足條件時,遞歸不在繼續(xù)
- 每次遞歸調(diào)用后必須越來越接近限制條件 函數(shù)棧幀
在第一個問題中,如果我們要去試驗的話,編譯器會報出這樣的錯誤:

Stackoverflow(棧溢出)。

內(nèi)存粗略的劃分為棧區(qū),堆區(qū),靜態(tài)區(qū)。
棧區(qū)主要存放:局部變量,形參(形參和局部變量差不多)動態(tài)內(nèi)存分配:malloc calloc等函數(shù)開辟空間靜態(tài)區(qū)主要存放:全局變量,static修飾的靜態(tài)變量
若是把棧區(qū)放大細(xì)看的話,如圖所示,有為main函數(shù)開辟的空間和print函數(shù)開辟的空間,為函數(shù)開辟的空間叫函數(shù)棧幀也可以叫運行時堆棧。
程序開始執(zhí)行時開辟空間,程序結(jié)束時銷毀空間函數(shù)每調(diào)用一次就在棧上開辟一次空間,遞歸返回時,空間會被回收
Example 2
不創(chuàng)建臨時變量,實現(xiàn)Strlen函數(shù)
int my_strlen1(char* pa)
{
int count = 0;
while (*pa++ != '\0')
{//pa++;
count++;
}
return count;
}
//my_strlen求字符串長度
int my_strlen(char* pa)
{
if (*pa == 0){
return 0;
}
return 1+my_strlen(pa + 1);//直接返回長度
}
具體的思考方式呢,就是只要第一個字符不是0,那我們就在外面+1并且跳到下一個字符,直到找到‘\0',那么我們返回0。
ps:
字符指針+1,向后跳一個字節(jié)
整型指針+1,向后跳四個字節(jié)
指針+1都是向后跳一個元素的地址,指針類型不同向后跳的字節(jié)也不同


函數(shù)迭代
遞歸、迭代的區(qū)別?
遞歸是重復(fù)調(diào)用函數(shù)自身實現(xiàn)循環(huán)。
迭代是函數(shù)內(nèi)某段代碼實現(xiàn)循環(huán),循環(huán)代碼中變量既參與運算同時也保存結(jié)果,當(dāng)前保存的結(jié)果作為下一次循環(huán)計算的初始值。
遞歸循環(huán)中,遇到滿足終止條件的情況時逐層返回來結(jié)束。
迭代則使用計數(shù)器結(jié)束循環(huán)。
當(dāng)然很多情況都是多種循環(huán)混合采用,這要根據(jù)具體需求。
Example 3
求n的階乘
int fac(int n)
{
if (n <= 1)
return 1;
else
return n * fac(n - 1);
}
Example 4
求第n個斐波那契數(shù)
int fib(int n)
{
if (n <= 2)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
但是這個方法效率是非常低的,當(dāng)數(shù)字特別大時,層層拆分下來,時間效率是 O ( 2 n ) O(2^n) O(2n)。
根據(jù)公式可知,第三個斐波那契數(shù)可由前兩個得到,我們利用這個規(guī)律
int fib(int n)
{
int a = 1;
int b = 1;
int c = 1;
while (n >= 3)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
上一個c變成了b,上一個b變成了a。如此循環(huán)往復(fù)。
利用迭代的方式,計算一個數(shù)只需要計算n-2次,這樣的話時間復(fù)雜度就是 O ( n ) O(n) O(n)。效率大大提高。
有這兩題我們可以發(fā)現(xiàn),什么時候用遞歸簡單呢?
1.有公式有模板的時候
2.遞歸簡單,非遞歸復(fù)雜的時候
3.有明顯問題的時候
學(xué)有余力的話,還可以考慮實現(xiàn)倆個經(jīng)典題目
1.漢諾塔問題
2.青蛙跳臺階問題
總結(jié)
本篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
C/C++?Qt數(shù)據(jù)庫SqlRelationalTable關(guān)聯(lián)表詳解
這篇文章主要介紹了QT中SqlRelationalTable關(guān)聯(lián)表組件的使用,文中代碼對我們的學(xué)習(xí)和工作具有一定價值,感興趣的朋友可以了解一下2021-12-12
C/C++ Qt 數(shù)據(jù)庫與Chart歷史數(shù)據(jù)展示
這篇文章主要介紹了Qt利用Qchart組件展示數(shù)據(jù)庫中的歷史數(shù)據(jù)。文中的示例代碼講解清晰,具有一定的學(xué)習(xí)和工作價值,感興趣的小伙伴可以學(xué)習(xí)一下2021-12-12
在C/C++與Python之間實現(xiàn)通信的常見方法
在C/C++與Python之間實現(xiàn)通信的方式有很多,本文給大家介紹了一些常見的方法,文中通過代碼示例介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2023-12-12
C語言實現(xiàn)影院管理系統(tǒng)程序設(shè)計
這篇文章主要為大家詳細(xì)介紹了C語言實現(xiàn)影院管理系統(tǒng)程序設(shè)計,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-08-08
vscode遠(yuǎn)程連接服務(wù)器(免密登錄+遠(yuǎn)程開發(fā))
vscode的遠(yuǎn)程連接功能十分方便,本文就來介紹一下vscode遠(yuǎn)程連接服務(wù)器,主要包括免密登錄和遠(yuǎn)程開發(fā),感興趣的可以了解一下2024-07-07

