函數總結
前面講了很多函數的内容,今天再梳理一遍,同時增加一些内容
1、再認識一下函數,比如
我們以前編寫的函數框架,一直采用這樣的結構。
這就是我們前面寫的C程序代碼,也是一個完整的程序,有好幾部分代碼都是固定的,每次書寫的時候都是不變的。
我們再來分析一下一般的一個C語言程序邏輯結構
在編寫一個程序的時候,一般要考慮這幾部分
A、數據變量的定義,也就是程序中任何地方用到的變量都必須先定義
B、變量的數據提供,也就是變量要有值來參與運算
C、業務處理部分,也就是實現函數功能的代碼部分
D、結果的輸出,也就是,程序運行後呈現出來的結果
那好了,這就是一個最直接的C語言函數,以及它的格式,與整個程序結構的邏輯劃分。
問題是,一個大型的程序,需要根據功能進行分解,也可能需要很多的人員進行合作完成,如果把所有的代碼都寫在一個main函數裡,會帶來很多問題與麻煩,比如多人合作,每個人寫的代碼合并到一起後,如果出現變量重名就會報錯,另外程序邏輯出現錯誤後,檢查錯誤是個非常大的任務。
所以,為了代碼的功能獨立,模塊的劃分,代碼的複用與移植,功能的增加與删除,減輕維護與糾錯的壓力,需要對項目的功能進行模塊劃分,而C語言程序中函數就是劃分模塊的單元,所以我們一般将功能獨立的代碼放到一個函數中,将一個完整的程序分解成多個相對獨立的函數,在一個文件中我們将來看到就是一個一個獨立的函數框架!
以前我們編寫程序就隻有一個main函數,現在我們就可能要編寫成多個函數了,另外,因為一個C語言程序文件隻允許有一個入口函數,就是main函數,那同一C程序文件中其他的函數就必須另外起名字了,當然,一個程序文件中有main主函數,那這個文件就可以編譯鍊接運行了,如果一個程序文件中沒有main主函數,隻是一些自定義的函數,那就相當于沒有入口,這些自定義函數隻能編譯鍊接,不能運行,所以就出現了這些自定義函數必須被調用的情況!所以,目前我們寫程序的時候就必須是寫成一個主函數main和多個自定義函數,什麼時候可以隻寫自定義函數,而不用主函數呢?那就是你能給别人幹活了,項目組分給你的任務就是做某個模塊,到那個時候你就不需要管main主函數了。
好了,現在理解了嗎?
接下來,我們過一遍函數的編寫,以回文程序為例
還是剛才這個回文程序。
現在的狀況是函數的整體功能都在一個主函數裡面,那我們按照函數的功能獨立,重新将這個功能部分做成一個自定義函數,這一部分就不在main函數裡面了,讓main函數解放。
看下面
能對比出來嗎?這是不是某人說的,太簡單了,就是送分題呢?
你看,我就沒有做過多更改,本來以前編寫的主函數就是完整的程序,隻不過,我做了一個功能劃分,隻是這個功能劃分呢,不是很徹底。
後面我們一點一點理解。
至少你能看到有些地方不同,有些似乎有關系一樣。
那現在,我們就把上面的這種形式叫做無參數,無返回值函數格式,這個需要記住的!這是死格式,沒啥可以解釋的。
特點就是你在定義函數的時候,
前面必須是void開頭,後面()裡面不能有任何東西,在主函數(或者别地調用這個函數的函數)裡面直接寫函數名();這個格式就行。
可以理解了嗎?
理解的話,繼續在它上面變形。
現在的問題是上面說的,功能獨立不徹底,啥意思呢,假如按照我原來的設想
void HW( )
{
}
這個函數的功能是誰給我一個值,我就能幫助誰判斷出來這個值是不是回文,而不是讓函數自己随意輸入一個值判斷去,而是有針對性的。
所以,代碼應該繼續簡化去雜。
看下面
繼續對比,
通過對比,是不是現在的函數裡我删掉了num變量以及給num變量輸入值的語句?
這就是去掉不應該有的功能了,也就是HW函數你是幫别人判斷是不是回文的,而不是,你自己輸入一個值自己判斷,所以,去掉這兩個無用的。
那現在呢?你把num變量和給num輸入值的scanf("%ld", &num);語句删了,那num在後面要用啊,怎麼辦?
好,那首先你要知道num在原來程序中所起的作用,是不是就是被判斷的那個變量呢,這個變量的值原來通過鍵盤輸入,那現在呢,我不允許你再通過鍵盤輸入,那根據HW函數要被修改的意思,是,你幫助别人來判斷的,那意思就是這個num是要被重新規劃了,這個num的值應該是讓别人來給,别人給你提供,你才可以把這個值帶入進去,對這個值進行判斷。
那這個值咋辦?這個num就要被開放,作為别人調用HW函數的中間連接通道,那這個num怎麼定義,放在哪裡呢?
看
我們把num的定義呢,不再放在程序裡面定義,而是放到了函數名後面的()裡面了。
代碼稍做了一點修改,就是去掉代碼裡面的num,以及對num的輸入語句。然後函數的主要功能并沒有發生變化,區别就是這個修改後的HW函數,不再具有輸入功能了,是不能随便輸入一個值的,對這個值進行判斷的了。這個函數就變成了一個需要給()裡面定義的num傳過來一個值,然後才能判斷,沒人調用它,不給它傳值,那它就是個架子,空殼。
好了,那現在說,讓main函數調用調用它,看能不能用呢。
可以,
那main函數怎麼調用?
根據HW函數功能要求,需要main函數(通用點,叫調用函數,主調函數等名字)必須給它傳一個值,送給定義要求的num,然後num才能帶進去幫你判斷。
那主函數要調用它,怎麼給它傳呢?
主函數要傳的那個值,在哪裡?
沒有?
沒有你就自己定義啊!
所以,
主函數裡面為了給HW傳一個值,就要解決定義一個變量,然後給變量提供值,(可以直接給值,也可以通過scanf函數形式,在鍵盤輸入值),然後再傳給HW函數。
當然,你也可以直接給值,比如
都是可以的。那現在這個函數與前面的函數就有了區别了,區别就是()裡面寫東西了,我們把這種形式,叫做有參數,無返回值函數格式。
那現在你能理解這種格式了嗎?
理解了那繼續往下。
這個函數呢,對傳過去的值進行判斷後,是直接把結果輸出來了,比如
這裡,也就是對回文數的判斷呢,在HW函數裡直接給出了,那我現在的意思呢,不允許你在這個HW函數裡直接給出結果,而是要把判斷的狀态(結果)返回給主函數,由主函數來根據HW函數返回的結果去判斷主函數所傳的這個值是不是回文,那這個怎麼辦?
那就是說,HW函數需要給main函數返回一個表示被傳過來要判斷的值是不是回文的狀态,那這個狀态怎麼設計,那我可以設計成用整數1代表是回文,用0代表不是回文,當然,你也可以設計成用字符‘Y’代表是回文,用‘N’代表不是回文。現在修改程序。
先修改HW函數
看,我修改了兩個紅色框的,以及兩個綠色框的。
紅色框相當于一對,必須一起搭配使用,比較前面,如果使用的是void,那是不是就沒有後面的那個return語句。
或者改成
這次是改成字符了,并沒有多大區别的。
這次是先把HW函數進行了修改,現在的格式就成了HW函數既有返回值,也有參數的格式。
能理解了嗎?
那我再看調用函數咋辦?
現在在主函數裡面呢,如果調用HW函數,這邊傳過去一個值,那邊能得出這個值是回文或者不是回文呢?怎麼能知道結果呢。
HW函數隻給它帶回來狀态,并沒有直接給出是回文不是回文的結果。
那想要這個結果,main自己處理吧,根據HW給它帶回來的狀态結果自己判斷去。
好,現在修改main函數。
看,main函數通過調用HW對123321要進行判斷,而HW函數則隻給main返回來狀态值,那main函數需要再對這個結果值進行處理,最終得出是回文還是不是回文的結果。
理解了嗎?
當然,也可以這樣做,都是很靈活的。
在main函數裡先定義一個變量,由這個變量來接收調用HW函數返回來的值,然後再利用這個值進行判斷,得出回文與不是回文的結果。
好了,用字符’Y’與’N’做狀态的,其實是一樣的。代碼略。
那函數的這部分理解了嗎?
補充,變量的作用域範圍
現在程序用函數來編輯後,程序中的函數功能就被劃分成幾塊了
那免不了會出現你定義變量同名的情況。
比如
我在主函數裡面定義了一個num,也在HW函數裡()中定義了一個num,這兩個一模一樣,沖突嗎?
不沖突!這涉及到函數中變量的作用域,就是這個變量從定義開始所能使用的範圍。
一般地,在一個函數内,不允許定義同名的變量,即使類型不一樣也不行,但是允許在不同的函數内定義同名的變量。原則上,在一個函數内定義的變量隻在這個函數内起作用,跳出了這個函數{ }包圍圈就不起作用了
所以,
這兩個num各在各的定義範圍内起作用,互不影響,它們是兩個變量,如果你理解不了,你可以定義成不同的變量,免得糊塗。
比如前面這樣
有返回值,無參數格式不講解了,都類似的。
可以認為,函數說明2裡面給你舉的四種情況,各種設置都是搭配使用的,原則都是任意組合的,并不是一定固定的。
上面的例子,在設計函數的參數時,全部用的是普通變量,普通變量在作為參數進行傳遞的時候,是當作值來用的,也就是調用函數隻是把變量(參數,實際參數)的值複制一份,傳送給對應被調用函數的()裡的變量(參數,傀儡參數,形式參數,樣子參數),相當于給()裡的變量(參數,傀儡參數,形式參數,樣子參數)賦值,提供值的作用。也相當于替代了用其他方式給變量值。
這種用普通變量的傳遞方式,我們把它叫做函數間的傳值調用!傳值調用原則上一個變量隻能傳遞一個值,如果傳遞多個值,那就必須定義多個變量。比如
void HW(int n, int m, char k, float g)
每個變量都要單獨定義,相互之間用逗号隔開。
那另一種方式呢,傳地址調用是啥呢?
是這樣,比如,我這個主調用函數裡有一個數組,現在想呢,需要定義一個自定義函數,目的是可以直接修改主調用函數裡的那個數組的單元值。如果按照傳值的方式,存在這樣的問題,一是傳值必須是一個一個變量傳,那這個數組如果有很多單元格,那傳值的參數需要定義多少個變量才夠用呢?二十,傳值的話,變量的作用域出不了函數的{ }範圍,出了{ }範圍就失效了。那這達不到跨函數修改數組的目的。那怎麼辦?
那這就是傳地址的作用!
既然是傳地址,傳地址,那就需要理解地址,以及如何在函數中表示地址,以及定義所謂的地址。
看程序,先學習數組的地址表示形式。其他以後再學。
這裡呢,我定義了一個生成随機數的自定義函數void rand_num(int num[]),然後呢,()裡面定義的是一個一維數組int num[],并且[]裡面沒有給數字,是空的。先認識下哦,緊接着在main函數裡進行了調用。隻不過,調用的時候給()裡用的是在main裡面定義的一個一維數組名sorce_num,那這個就要好好認識哦,我們說數組名代表整個數組空間的起始地址,是一個常量,不能更改,隻能用。那現在這裡就是用數組名做實參傳遞的,而自定義函數的形參呢,也對應是定義成數組格式,相互對應,一對一。我們把這種直接用數組名傳遞的方式叫傳地址方式。除了數組外,傳地址方式還有其他的,以後再學。
如何解釋:
這樣,首先定義的時候,這兩個數組各自在各自的函數裡,與前面講的一樣,原則上它的起作用的範圍在函數的{ }内,這是正确的,沒問題,也就是出了它的{ }範圍不起作用的。但是,特殊的就在它反而在它的起作用範圍内能操作它的範圍之外的空間的單元,那你就要理解為啥它能這麼幹了。這就是這兩個數組雖然定義在不同的地方,甚至起作用的範圍也有限制,但是他們的操作地址是合并在一起的,也就是兩個數組操作的是同一個空間。
這就是使用數組名進行傳遞,同時定義數組進行接收,接收的不是值,而是傳過來的數組的地址,然後操作的時候,就操作的是這個地址的空間單元,自然,相當于這兩個數組是重合的。
如果不好理解,讓大家看下,現在很多人使用手機傳遞wps在線文檔,然後你在你的手機上操作這個文檔,别人在别人的手機上也操作這個文檔,所有人操作的都是同一個文檔。
增加一點,看能不能看懂?
運行結果
看看是否能理解?為什麼前面兩個是0?
函數的調用類型
前面介紹的各種調用形式,都是這樣的
在一個C程序文件中,有一個main函數,有多個用戶自定義函數,而且都是自定義函數被main函數調用的,我們把這種不同函數之間的調用,叫做它調用,一般的,大部分是這種,比如,給你的那個月曆函數程序就是典型的它調用形式。
#include "stdio.h"
#include "stdlib.h"
//下面定義三個自定義函數
//函數1
//判斷閏年的函數,返回一個旗幟值
int is_leap_year(int year_1)
{
int flag=0;
if(((!(year_1%4))&&(year_10!=0))||(!(year_1@0)))
flag=1;
//printf("flag:%d\n",flag);
return flag;
}
//函數2,在函數2裡面要調用函數1
//設定每個月的天數
//默認先設置天數為最小28天,然後根據不同的月份調整天數
//這個函數中需要用到一個year和一個month,year是為了調整二月份天數而設定的,因為要判斷是否是閏年
//month是對不同的月,天數不一樣而要求的,因為必須對不同月進行判斷
int every_month_day(int year_2,int month_2)
{
int month_day=28;
//這裡開始用到month變量,對固定月份分别判斷調整天數
if(month_2==1||month_2==3||month_2==5||month_2==7||month_2==8||month_2==10||month_2==12)
month_day=31;
if(month_2==4||month_2==6||month_2==9||month_2==11)
month_day=30;
//這裡需要用到year這個變量對二月份進行判斷調整天數
if(is_leap_year(year_2))
month_day=29;
//printf("month_day:%d\n",month_day);
return month_day;
}
//函數3,實現打印月曆功能,不增加星期對應功能
void print_month(int year_3,int month_3)
{
int i,day;
//這個day是調用函數2來得到對應年月的那個月份天數
//在函數3裡面接收到的year與month接着又傳給函數2了
day=every_month_day(year_3,month_3);
//有了具體day,那我就可以打印月曆了。哈。
printf("\n\n%d年%d月的月曆:\n",year_3,month_3);
printf("日 一 二 三 四 五 六\n");
printf("==========================\n");
for(i=1;i<=day;i )
{
printf("d ",i);
if(!(i%7))
printf("\n");
}
printf("\n");
}
//下面是主函數
void main()
{
int main_year,main_month;
printf("請輸入一個年份值:");
scanf("%d",&main_year);
printf("請輸入一個月份值:");
scanf("%d",&main_month);
//主函數隻需要調用打印月曆函數就行,不調用判斷閏年和調整月份天數
print_month(main_year,main_month);
getchar();
}
結構上是
主函數main ----->(調用)函數3----->(調用)函數2----->(調用)函數1
主函數的級别比較高,不能被用戶自定義函數調用,是被電腦的操作系統調用的。
查看書上或者網上的說明。其實main函數的格式是這樣的
int main(int argc,char* argv[])
也是帶參數或者有返回值的,隻不過,我們平時用不到,沒有到控制台讓系統調用main函數。
main函數是在程序裝到内存之後由操作系統調用的。操作系統調用的時候會傳遞兩個參數進去,一個是argc,一個是*argv[],其中argc代表參數個數,這裡我需要解釋的是程序名本身也是參數,所以在命令行下無參數運行時操作系統會給main第一個參數傳值1;一個參數會傳值2;argv代表參數字符串,argv[0]代表程序名本身,argv[1]代表程序接收的第一個參數,以此類推!你隻要知道main函數是由操作系統調用就好。
除此外,還有一種情況
是自己調用自己。這種叫自調用。
再看一個
很好玩哦,本來定義的一個函數功能,在實現的代碼中居然還要用到自身這個函數,那就要好好理解了哦
從數學算式上理解
比如10!=1*2*3*4*5*6*7*8*9*10,
那在這個中你看1*2*3*4*5*6*7*8*9這一串串是不是本身就是9!呢,那意思就是
10!=9!*10,所以,本來是想求10的階乘,反而求解的算式中出現了9的階乘,按照這個式子,要想得到10!,那必須先求得9!,繼續往下也就是9!必須先求得8!,依次類推,必須到最後能知道結果為止。
畫出圖來,就是
,
更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!