摘要:語言在設計中考慮了函數的高效性和易用性兩個原則。在語言中,最常見的當屬函數了。以上就是一個函數,它被稱為語言的入口函數,或者主函數。例如和都是函數名。形式參數當函數調用完成之后就自動銷毀了。
函數可以把大的計算任務分解成若干較小的任務,然后通過調用的方式達到代碼復用;一個邏輯不寫多遍,減少代碼維護成本。
調用函數的一方不需要了解函數的具體實現,對于它來說,這部分是一個“黑盒子”,從而使得程序結構更加清晰。
C語言在設計中考慮了函數的 高效性 和 **易用性 **兩個原則。
函數的實現應該盡量簡短,因為函數可以套函數,一個程序應該盡量由許多小的函數組成,而不是少量較大的函數組成。
在刷題的過程中,系統會給我們事先提供一個函數讓我們來實現,而它則是調用函數的一方。
在C語言中,最常見的當屬main
函數了。
int main(){ printf("5201314/n"); return 0;}
以上就是一個函數,它被稱為C語言的 入口函數,或者 主函數。
所有程序執行都是從這個函數開始的,以它為例,我們引出函數的一些基本概念。
如圖:
通過這個圖,我們類比main()
這個函數,它的:
返回類型 是int
32位整型,
函數名 為main
,
參數列表 為空
,
函數體 為 printf("5201314/n");
,
返回值 為0
。
函數的返回類型可以是任意類型;
例如:整型int
,浮點型float
,字符型char
,自定義類型等待;
返回類型 和 返回值 是配套的,當返回類型為void
時,函數內部的返回值可以寫return
;也可以省略不寫。
函數名可以類比我們自己的名字。是給函數調用方用的。
例如:main
和printf
都是函數名。
函數的參數列表必須用()
括起來,參數是函數需要處理的數據;
例如:
printf("hello/n");
這一段代碼用來輸出字符串,"hello/n"
就是一個參數,參數類型是字符串。
下面會詳細解釋:printf
;
函數內部就是你可以任意發揮的部分,也就是函數的核心邏輯部分,可以是各種語句的組合;當然也可以是另一個函數,也就是函數說,函數是支持嵌套的。
函數的返回值則表示:這個函數最后返回給調用方的數據;
如果返回值的類型和函數的返回類型不一致,則會進行強制類型轉換;前提是能夠強制轉換的情況下。
為什么會有庫函數?
我們學習C語言編程的時候,總是在一個代碼編寫完成之后迫不及待的想知道結果, 把這個結果打印到我們的屏幕上看看。這個時候我們會頻繁的使用一個功能:將信息按照一定的格式打印到屏幕上(printf
)。
在編程的過程中我們會頻繁的做一些字符串的拷貝工作(strcpy
)。
編程是我們也計算,總是會計算n的k次方這樣的運算(pow
)
像上面我們描述的基礎功能,它們不是業務性的代碼。
我們在開發的過程中每個程序員都可能用的到, 為了支持可移植性和提高程序的效率,所以C語言的基礎庫中提供了一系列類似的庫函數,方便程序員進行軟件開發。
這里推薦一個網站:cplusplus.com
簡單的總結,C語言中,我們常用的庫函數都有:
IO函數:輸入和輸出函數
字符串操作函數:比如求字符串長度的strlen
函數
字符操作函數:比如判斷字符的大小寫,將小寫字母轉化為大寫字母
內存操作函數:內存復制、查找等操作
時間/日期函數:獲取時間等
數學函數:開平方sqrt
等函數
等等…
我們參照文檔,學習幾個庫函數;
strcpy
從上面的庫函數網站可以找到strcpy的具體用法:
其實strcpy
就是字符串拷貝的意思
因此在使用這個函數的時候,我們需要向函數參數傳入destination(目標數組)和source(源數組)兩個字符串,同時該函數的返回值是char*
,它會將拷貝后的destination(目標數組)起始地址返回給我們。
strcpy
時要引用頭文件#include
官方寫法:
char* strcpy(char* strDestination, const char* strSource);
即第一個參數是目的地地址,第二個參數源地址;
代碼示例:
#include #include int main(){ char arr1[] = "Fighting Boy"; char arr2[] = "AAA"; strcpy(arr1, arr2);//將arr2數組的內容拷貝到arr1數組中 printf("arr1 = %s/n", arr1);//arr1 = "AAA" printf("arr2 = %s/n", arr2); return 0;}
運行結果:
注意:
這里的復制其實并不是真正的復制,準確的說是覆蓋!
即把arr2的所有內容(包括/0
)都覆蓋到arr1
對應位置;
也就是說,雖然打印出來arr1
是AAA
,但是實質上arr1
等于AAA/0ting Boy
;
我們可以運行調試一下:
補充:
/0
結束。如果源字符串沒有/0
,就會一直拷貝源字符串地址后面的所有內容,直到找到值為0
。/0
拷貝到目標數組中。const
修飾(關于const
的用法后續會寫一篇博客詳解)
注意: 使用庫函數,必須包含 #include
對應的頭文件。
如果庫函數能干所有的事情,那還要程序員干什么?
所有更加重要的是自定義函數。
自定義函數和庫函數一樣,有函數名,返回值類型和函數參數。
但是不一樣的是這些都是我們自己來設計。
自定義函數的定義語法為:
ret_type fun_name(para1, *){ statement;//語句項 return 返回值;//該返回值與返回類型必須相同,如果是void型函數,則不需要返回值,因此可以不寫。}ret_type 返回類型fun_name 函數名para1 函數參數
在使用函數的過程中,我們用函數名(函數參數)
的形式來調用自定義函數;
由于一般函數在調用完以后產生一個返回值(比如一個兩個數相加的加法函數,實現兩個數相加以后返回兩個數的和),因此我們可以用一個變量來接收這個返回值。
例題:寫一個函數可以找出兩個整數中的最大值。
代碼示例:
int get_max(int x, int y){ return (x > y) ? (x) : (y); //三目操作符,在操作符中講過,如果x>y則返回x,反之則返回y;}int main(){ int a = 20; int b = 10; int max = get_max(a, b);//定義一個max變量,用來接受最大值; printf("max=%d/n", max);}
運行結果:
在程序運行過程中,給get_max
這個函數傳入a、b
這兩個參數,函數調用完后會返回a和b中的最大值;
因此可以用max
來接收這個返回值。當然也可以不用接收,因為在函數運行完以后,get_max(a, b)
就相當于這個返回值,該返回值可以當做printf
的參數直接進行打印操作。
例題:寫一個函數可以交換兩個整形變量的內容。
void Swap(int* pa, int* pb) //我們只需要交換a和b當中的內容,不需要返回值,所以用void{ int t = 0;//定義一個臨時變量用于交換 t = *pa; *pa = *pb; *pb = t;}int main(){ int a = 1; int b = 2; printf("交換前: a=%d b=%d/n", a, b); Swap(&a, &b); printf("交換前: a=%d b=%d/n", a, b); return 0;}
運行結果:
分為:
真實傳給函數的參數,叫實參。
實參可以是:常量、變量、表達式、函數等。
無論實參是何種類型的量,在進行函數調用時,它們都必須有確定的值,以便把這些值傳送給形參。
代碼示例:
void Swap1(int x, int y){ int tmp = 0; tmp = x; x = y; y = tmp;}void Swap2(int* px, int* py) //我們只需要交換a和b當中的內容,不需要返回值,所以用void{ int tmp = 0; tmp = *px; *px = *py; *py = tmp;}int main(){ int a = 1; int b = 2; 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
函數中的參數 x,y,px,py
都是形式參數。
在main函數中傳給Swap1
的a,b
和傳給Swap2函數的&a,&b
是實際參數。
這里我們對函數的實參和形參進行分析:
可以看出:
實參a、b與形參x、y不是同一空間
&a:0x00d3fa28&b:0x00d3fa1c &x:0x00def944&y:0x00d3f948
這里可以看到Swap1
函數在調用的時候,x,y
擁有自己的空間,同時擁有了和實參一模一樣的內容。
簡單來說:形參實例化之后其實相當于實參的一份臨時拷貝。
分為:
傳值調用
傳址調用
例題:寫一個函數來實現對兩個數的交換
void Swap1(int x, int y){ int tmp = 0; tmp = x; x = y; y = tmp;}int main(){ int a = 1; int b = 2; Swap1(a, b); printf("Swap1: a=%d b=%d/n", a, b); return 0;}
運行結果:
由于形參并不影響實參,函數在調用過程中只是對形參x,y進行了交換,并沒有影響到a,b;
并且函數在調用結束以后,x,y就已經被銷毀了。
調試看一下:
從監視中就可以看出,x,y
的地址和a,b
不同,他們相當于一塊獨立的空間,自身的改變并不會影響a,b
。
還是看這個例題,只不過我們稍微做點改變
void Swap2(int* px, int* py){ int tmp = 0; tmp = *px; *px = *py; *py = tmp;}int main(){ int a = 1; int b = 2; Swap2(&a, &b); printf("Swap2: a=%d b=%d/n", a, b); return 0;}
運行結果:
通過這種方式可以使得變量進行真正的交換。
還是調試看一下:
通過監視可以看出,px、py
就是一個指針,其中放的就是a,b
的地址,*
是解引用操作符,它可以通過地址找到地址中存放的變量值;
比如px
就是a
的指針,它的值是a的地址,對px
解引用就可以找到a地址中存放的變量1,然后我們就可以對變量1進行操作了。
函數內部的形參只需要借用函數外部實參的值的時候用傳值調用,比如求兩個數的較大值。
當函數內部需要對函數外部變量進行操作時用傳址調用,比如交換兩個數。
關于函數的形參、實參和傳值、傳址,可以看我之前寫的這篇文章:重點詳解函數的形參和實參、傳值和傳址
函數和函數之間是可以互相調用的。
代碼示例:
void fun2(){ printf("hello/n");}void fun1(){ int i = 0; for (i = 0; i < 3; i++) { fun2(); }}int main(){ fun1(); return 0;}
運行結果:
我們通過main
函數調用fun1
,通過fun1
調用三次fun2
,這就是函數的嵌套調用。
注意:函數可以嵌套調用,但是不能嵌套定義。
把一個函數的返回值作為另外一個函數的參數。
int get_max(int x, int y){ return (x > y) ? x : y;}int main(){ int num1 = 10; int num2 = 20; int max = get_max(num1, num2); printf("max=%d/n", get_max(num1, num2));}
運行結果:
下面我們看一個有趣的代碼:
int main(){ printf("%d", printf("%d", printf("%d", 43))); //結果是啥? //注:printf函數的返回值是打印在屏幕上字符的個數 return 0;}
運行結果:
這個程序實際上就是用printf
的返回值作為printf
的參數;
因此想要弄明白這個程序,我們得先知道printf
的返回值;
通過查找printf的用法可以知道printf
返回值:
所以printf
返回值是寫入的字符總數,也就是字符的個數。
最內層的printf
打印43;
第二層的printf
打印的是最內層printf的返回值,也就是43
這個內容的元素個數2;
最外層打印的是printf("%d",2)
的返回值,返回值是其元素個數1;
所以會打印出4321
這四個數。
函數的定義是指函數的具體實現,交待函數的功能實現。
test.h
的內容
#ifndef __TEST_H__#define __TEST_H__//函數的聲明int Add(int x, int y);
test.c
的內容
#include "test.h"//函數Add的實現int Add(int x, int y){ return x + y;}
這種分文件的書寫形式,在后期寫三字棋和掃雷的時候,就可以用分模塊來寫。
我之前寫過一篇三子棋的小游戲,就是用這種分模塊的方式來寫的;
這里我們通過代碼來詳解函數的遞歸
接受一個整型值(無符號),按照順序打印它的每一位。
例如: 輸入:1234,輸出 1 2 3 4.
思考一下:
我們想要從高位向低位輸出,就必須要依次獲?。鹤罡呶坏阶畹臀坏臄底郑?/p>
因此我們可以這1234
每次除以10,并把1234 / 10
的結果進行判斷,看其是否小于10;
如果小于10則不再進行除以10的操作,這樣我們就可以得到最高位1
了;
為了獲取其他位的數字,我們可以在每次除以10之前進行取模10的操作,得到剩下的位。
代碼示例:
void print(int n){ if (n > 9) { print(n / 10); } printf("%d ", n % 10);}int main(){ int num = 1234; print(num); //這里的print是我們自己定義的函數,不是庫函數printf return 0;}
運行結果:
關于這道題,我之前也做過詳解,并且畫了圖,可以去看下:深入理解C語言中函數的遞歸算法
有些問題是以遞歸的形式進行解釋的,這只是因為它比非遞歸的形式更為清晰。
但是這些問題的迭代實現往往比遞歸實現效率更高,雖然代碼的可讀性稍微差些。
當一個問題相當復雜,難以用迭代實現時,此時遞歸實現的簡潔性便可以補償它所帶來的運行時開銷
其實后面關于函數的嵌套、遞歸、迭代都只是寫了一些知識點,所以還是打算通過例題的方式來詳解。
更多的一些重點函數(比如strlen
,strcmp
,strcat
等)的實現,我會在之后多帶帶拿出來說明。
函數這一章的很多東西都需要練題+深刻理解,為后續的知識打下基礎!
等例題剖析完了,會附上鏈接!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/124776.html
摘要:二什么是文件磁盤上的文件就是文件。文件指針變量定義是一個指向類型數據的指針變量。表示向何種流中輸出,可以是標準輸出流,也可以是文件流。文件結構體指針,將要讀取的文件流。 ...
摘要:大家好,我是冰河有句話叫做投資啥都不如投資自己的回報率高。馬上就十一國慶假期了,給小伙伴們分享下,從小白程序員到大廠高級技術專家我看過哪些技術類書籍。 大家好,我是...
目錄 一、什么是C語言? 二、第一個C語言程序 代碼 程序分析 ?程序運行 一個工程中出現兩個及以上的main函數 代碼 運行結果 分析 三、數據類型 數據各種類型 為什么會有這么多的數據類型? 計算機單位 ?各個數據類型的大小 ?注意事項 數據類型的使用 四、變量和常量 變量的分類 變量的使用 變量的作用域和生命周期 ?常量 五、字符串+轉義字符+注釋 字符串 ?轉義字符 注釋 六、選擇語句 ?...
閱讀 1698·2023-04-26 01:02
閱讀 4841·2021-11-24 09:39
閱讀 1803·2019-08-30 15:44
閱讀 2872·2019-08-30 11:10
閱讀 1783·2019-08-30 10:49
閱讀 984·2019-08-29 17:06
閱讀 609·2019-08-29 16:15
閱讀 902·2019-08-29 15:17