采用函式的原因:
隨著程式規模的變大,產生了以下問題:
——main函式變得相當冗雜
——程式復雜度不斷提高
——代碼前后關聯度提高,修改代碼往往牽一發而動全身
——變數使用過多,命名都成了問題
——為了在程式中多次實作某個功能,不得不重復多次寫相同的代碼
小甲魚將函式一部分作為自學內容放在課后作業s1e23里,要求自學,閱覽后,認為講解不清,網上到處查詢,看到CSDN里一篇詳解,認為可用,抄錄下來以備查詢,(https://blog.csdn.net/qq_43469639/article/details/123765064)
1、 函式是什么
在維基百科中,對于函式的定義是子程式,子程式是一個大型程式中的某部分代碼,由一個或多個陳述句塊組成,他負責完成某項特點的任務,而且相較于其他代碼,具備相對的獨立性,
C語言中函式分為庫函式和自定義函式兩大類,
2、 庫函式
為什么會有庫函式
2.1我們知道在我們學習C語言編程的時候,總是在一個代碼撰寫完成后迫不及待的想要知道結果,想要把這個結果列印到我們的螢屏上看看,這個時候我們會頻繁的使用一個功能,將資訊按照一定的擱置列印到螢屏上,
2.2在編程的程序中我們會頻繁的做一些字串的拷貝作業(strcpy)
2.3在編程時,我們也會計算,也總數會計算n的k次方這樣的運算
像上面我們描述的基本功能,他們不是業務性的代碼,我們在開發的程序中每個程式員都有可能用到,為了支持可移植性和提高程式的效率,所以C語言的基礎庫中提供了一些列類似的庫函式,方便程式員進行軟體開發,
簡單總結,C語言常用的庫函式都有:
IO函式 printf scanf getchar putchar
字串操作函式 strcmp strlen
字符操作函式 toupper
記憶體操作函式 memcpy memcmp menset
時間/日期函式 time
數學函式 pow sqrt
其他庫函式
注:不同與作者,我的觀點是:庫函式精煉了大量人員需要重復使用的比較復雜的函式,并組裝成庫,方便呼叫,其他語言也有類似情況,比如我認為python就是大量的各種應用場景比較小的庫的組合,使用時,根據規則呼叫即可直接使用,
3、 自定義函式(前方高能,函式的精髓所在)
如果庫函式能干所有的事情,那還要程式員干什么,所以有更加重要的自定義函式
自定義函式和庫函式一樣,有函式名,回傳值型別,函式引數,但是不一樣的是這些都是我們自己設計的,這給程式員一個很大的發揮空間,
int add(int x,int y)//int 回傳值型別 add為函式名 括號里為函式的引數
{
intz=0;
return z=x+y;
}
注:自定義函式是程式員為了滿足自己程式里的需要,避免重復輸入,或者簡化程式邏輯負責性,或者滿足庫函式以外的功能,自行編制的函式,
4、 函式引數
函式的引數分為實際引數(實參)和形式引數(形參)
實參:在呼叫函式時傳遞給函式的引數
真實傳遞給函式的引數,叫做實參,實參可以是:常量、變數、運算式、函式等,無論實參是何種型別的量,在進行函式呼叫的時候,它們必須有確定的值,以便把這些值傳給形參,
形參:在函式里,用于接收實參的引數
形式引數是指函式名后括號中的變數,因為形式引數只有在函式被呼叫的程序中才實體化(為形式引數分配記憶體單元),所以叫形式引數,形式引數當函式呼叫完成之后就自動銷毀了,因此形式引數只在函式中有效,
函式例子:
#include<stdio.h>
int add(int x,int y) //這里為形式引數,用于接收實際引數的值
{
int z=0;
return z=x+y;
}
int main()
{
int a=0;
int b=0;
scanf(“%d %d”,&a,&b);
add(a,b); //這里是實際傳過去的引數,要與對應函式的引數型別和數量對應,這里是為函式傳遞實際引數
return 0;
}
5、 函式的呼叫
傳值呼叫
函式的形參和實參分別占有不同的記憶體塊,對形參的修改不會影響實參
傳址呼叫
傳址呼叫是把函式外部創建變數的記憶體地址傳遞給函式引數的 一種呼叫函式的方式
這種傳參方式可以讓函式和函式外邊的變數建立起真正的聯系,也就是函式內部可以直接處理外部的變數
int add(int x,int y)
{
int z=0;
return z=x+y;
}
int add2(int* x,int* y)
{
int z=0;
return =*x+*y;
}
int main()
{
int a=0;
int b=9;
scanf(“%d %d”,&a,&b);
printf(“%d\n”,add(a,b)); //傳值呼叫
printf(“%d\n”,add2(&a,&b)); //傳址呼叫
return 0;
}
6、 函式的嵌套呼叫和鏈式訪問
嵌套呼叫
函式和函式之間可以有機的組合的
例:
int main()
{
int a=0;
int b=0;
scanf(“%d %d”,&a,&b);
add(a,b);
add2(&a,&b); //本句與上一句組成嵌套函式(沒搞懂)
return 0;
}
鏈式訪問
把一個函式的回傳值作為另一個函式的引數
int add(int x,int y)
{
int z=0;
return z=x+y;
}
int add2(int * x,int * y)
{
int z=0;
return z=*x+*y;
}
include<stdio.h>
int main()
{
int a=0;
int b=0;
scanf(“%d %d”,&a,&b);
printf(“%d\n”,add(add2(&a,&b),add2(&a,&b)); //鏈式訪問
return 0;
}
7、 函式的宣告和定義
函式宣告
1) 告訴編譯器有一個函式叫什么,引數是什么,回傳型別是什么,但是具體是不是存在,無關緊要
2) 函式的宣告一般出現在函式使用之前,要滿足先宣告后使用
3) 函式的宣告一般要放在頭檔案中
注:以博客作者的井字棋為例:
#include “game.h” //參考頭檔案
//棋盤
void.showborad(char board[row][col],int row,int col);//宣告
//函式的定義
//函式的定義是指函式的具體實作,交代函式的功能實作
//初始化棋盤
void showborad(char board[row][col],int row ,int col)
{
int i=0;
int j=0;
for(i=0;i<row;i++)
{
for(j=0;j<col;j++)
{
board[i][j]=’ ’;
printf(“%c”,board[i][j]);
if(j<col-1)
{
printf(“|”);
}
}
printf(“\n”);
for(j=0;j<col;j++)
{
printf(“-“);
if(j<col-1)
{
printf(“|”);
}
}
printf(“\n”);
}
}
8、 函式的遞回
什么是遞回
程式呼叫自身的編程技巧稱為遞回,遞回作為一種演算法在程式設計語言中廣泛應用,一個程序或函式在其定義活說明中有直接或間接呼叫自身的一種方法,它通常把一個大型復雜的問題蹭蹭轉化為一個與原題目相似的規模較小的問題來求解,遞回策略只需少量的程式就可以描述出解題程序所需要的多次重復計算,大大的減少了程式的代碼量,遞回的主要思考方式在于:把大事化小
遞回的兩個必要條件
1) 存在限制條件,當滿足這個限制條件的時候,遞回便不再繼續
2) 每次遞回呼叫之后越來越接近這個限制條件
9、 當自定義的函式名與庫函式重名的時候:
根據https://zhidao.baidu.com/question/369199943840037324.html中說明:當自定義函式與庫函式同名時,一般的呼叫是自定義函式優先,但是標準庫函式并不失去意義,只是呼叫方式要有所改變:用雙冒號::開頭是呼叫庫函式,直接寫函式名是呼叫自定義的函式,
舉例說明:
#include<stdio.h>
void printf()
{
puts(“12345”);
}
int main()
{
::printf(“abc\n”);
printf();
return 0;
}
運行結果如下:
abc
12345
10、
以下摘抄自譚浩強主編的《C語言程式設計》(第3版),算是我對照課本的學習筆記吧:
1、 函式是什么:
每一種計算機語言里都有子程式、函式之類,用于表達一小部分具體的功能,可以被其他部位的 函式呼叫使用,函式這個概念來源于英文的function,譯成中文可以是函式、概念、功能等,函式的存在使大規模的問題處理可以分解為一個個小的問題來解決,把整個問題分成模塊化組裝來解決,這是函式存在的主要意義,另外利用呼叫函式的方法可以減少代碼的數量,在主程式里不同部位出現的重復的功能組成單獨的、可以呼叫的模塊,在使用時不必重復敲入,同時還讓代碼直觀,
C語言使用函式化編程,main函式成為主函式,程式在編譯程序中,從前往后編譯,實際運行的時候通過運行程式的命令直接呼叫main主程式,在主程式里通過直接或者間接的呼叫其他子函式,
2、 函式在未呼叫時候,不占用記憶體,不分配位置,在實參傳遞給形參后,程式分配記憶體,在呼叫結束后(子函式運行完畢),釋放形參單元,所以函式是相互獨立的,定義需要分別定義,不能嵌套定義,這個應該屬于區域變數的范疇了,后面學習區域變數應該容易理解,
3、 由于編譯程序是從上到下,那么函式的撰寫在被參考點前的話,在參考的函式里不需要宣告出來,在參考點后面撰寫的函式,在參考的函式里要宣告,
函式的宣告方法:形式與函式名一致,后面要求加分號,
4、 當沒有回傳值的時候(return為0),函式要指定為viod型別,有回傳值的時候,函式型別為回傳值型別,這樣的定義,有助于在呼叫層函式里把這個函式可以作為一個變數使用,一個函式只能帶回一個回傳值,
5、 定義函式: 型別名 函式名(形參串列){ }
6、 宣告函式:型別名 函式名(形參串列){ };
7、 在函式的最后位置對函式回傳值進行限制,
限制陳述句為 return (回傳變數名),或者簡寫為return 變數名;
8、 函式在被呼叫的時候,可以當作一個本函式里的變數看待,呼叫方法和使用一個變數一樣,譚浩強教程中給出3種呼叫方式:單獨一個陳述句printf_star; 函式運算式c=max(x,y); 作為函式的引數printf(“%d”,max(a,b);
9、 函式在被呼叫的時候,實參的串列順序和資料型別和形參一致,
這一條里面有兩個問題沒有搞懂:
1) 宣告函式的時候,形參可以不寫引數名,比如void print(int,float,char);是合法的,那么在函式里怎么判斷該呼叫那個引數了呢?(解決:在需要呼叫函式的程式里,宣告函式只是給函式預留出足夠用的記憶體,所以可以不用寫引數名,但是在編程程序中,形參位置一定要寫引數名,)
2) 存在呼叫函式可以少寫引數的情況,比如主函式main在規定里是帶有兩個引數的,平時可以不寫,根據要求必須一一對應,那么如何處理呼叫程序中引數預設情況呢?
10、 函式可以呼叫其他函式,稱為嵌套呼叫,
函式可以直接或者間接的呼叫函式本身,稱為遞回呼叫,遞回呼叫的時候要注意設定結束條件,
11、 呼叫函式的程序,其實是一個把實參傳遞給形參,函式再回傳一個值的程序,呼叫的程序中,單個的數值(包括單個變數和陣列元素)被傳遞給函式(值傳遞方式),在編譯程序中,對形參分配一個對應資料型別的地址,呼叫程序中,實參被存盤在形參準備好的地址里使用(地址傳遞方式),呼叫程序中,陣列名被傳遞給函式,由于陣列的本質是指標,故傳遞過來的只是實引陣列的指標地址,所以在函式里,形參部位需要用方括號“[ ]”來體現傳遞過來的是一個陣列(注重資料型別,不注重陣列長度可以不指定陣列大小,譚浩強在202頁上半部說明,并指明“形引陣列名實際上是一個指標變數”),
12、 使用陣列傳遞引數的時候,第一維大小可以省略,第二維及以上不可省略,
13、 全域變數:在主函式開始前,程式最開始定義的變數可以作用與所有的函式里,直到最終,稱為全域變數,
在一個函式或者復合陳述句中定義的變數,在這個函式或復合陳述句結束后就失去效果的變數,稱為區域變數,
全域變數與區域變數的作用域舉例:國家有統一的法律法規,各省可以根據需要指定地方的法律和法規,在甲省,國家統一的法律法規和甲省的法律法規都是有效的,而在乙省,則國家統一的法律法規和乙省的法律法規有效,顯然,甲省的法律法規在乙省無效,
全域變數可以在別的函式里改變這個函式的值,效果上相當于通過函式呼叫得到了一個以上的值,
14、 建議不在必要時使用全域變數:
1) 全域變數在程式的全部執行程序中都占有存盤單元,而不是僅在需要時才開辟單元,
2) 全域變數降低了函式的通用性,因為函式在執行程序中依賴其所在的程式檔案中定義的外部變數,當把一個函式移植到另一個檔案中時,還需要將有關外部變數和值一起移過去,而且一旦與移植到檔案中出現同名變數,會發生沖突,降低程式的可靠性,
使用全域變數過多,會降低程式的清晰性,往往難以清楚的判斷出每個瞬間各個外部變數的值,容易出錯,
15、 變數的靜態存盤和動態存盤方式:
譚浩強《C語言程式設計》(第3版)中提到變數的生存期,也就是變數值的存在時間,我理解和區域變數、全域變數一致,僅僅是從具體空間、時間的角度來解釋變數,
靜態存盤方式是指在程式運行期間由系統在鏡頭存盤區分配存盤空間的方式,在程式運行期間不釋放,而動態存盤方式則是在函式呼叫期間根據需要在動態存盤區分配存盤空間的方式,全域變數采用靜態存盤方式,在函式中定義的變數,在函式呼叫開始時分配動態存盤空間,函式結束時釋放空間,
程式里可以指定變數的存盤方式,來強制存放位置和是否靜態存盤,
1)auto ——宣告自動變數 auto int b,c=3; //定義b,c為自動變數
在函式的形參位置、函式內部、復合陳述句里定義的變數都屬于這一類,在函式呼叫的時候分配存盤空間,在呼叫結束時釋放空間,auto可以省略(就是指平時使用的函式內變數),
屬于動態存盤方式
2)static——宣告靜態變數 static int f=1;
在函式中,如果希望在函式結束后變數不消失,可以在下一次呼叫這個函式的時候使用里面的數值,那么指定為static,(注意到:教程里提到的是這個變數可以在下一次這個函式被呼叫的時候使用,語意中不能被別的函式呼叫,是否屬實,須待核實,)static宣告的變數,只限于被本檔案參考,而不能被其他檔案參考,對于區域變數來說,它使變數有動態存盤方式改為靜態存盤方式,而對于全域變數來說,它使變數區域化(其他檔案不能使用),
可以避免被別的檔案呼叫,
屬于靜態存盤方式
3) register——宣告暫存器變數 register int f;
普通變數存盤在記憶體中,每次呼叫時,從記憶體送到運算器使用,每次存盤資料時,從運算器送到記憶體保存,對于使用極度頻繁的變數,相對于存取變數的時間就是可觀的,為了提高效率,C語言允許將區域變數的值房子CPU的暫存器里,減少來回取存的時間,現在由于計算機性能的提高,速度越來越快,編譯系統能夠識別高頻使用的變數,可以自動將變數放在暫存器里,而不需要設計者指定,故現有已經不需要指定register宣告了,僅僅用于看別人程式時知道就可以了,
4) extern——宣告外部變數作用范圍 extern b;
大型的程式都是由多個人撰寫的,都是多個檔案組成的,普通全域變數只是在所在檔案的宣告點到檔案結束有效,別的檔案不能使用這個變數,需要用一個個的函式進行傳遞,
可以在變數宣告部位加入 “extern b;”陳述句將別的檔案中的外部變數合法的引入這個檔案中使用,在定義時候,要在前面加關鍵字:“extern int fun (int a,int b);”
各類變數作用域與存在性情況
|
變數存盤類別 |
函式內 |
函式外 |
|||
|
作用域 |
存在性 |
作用域 |
存在性 |
||
|
本檔案內 |
多個檔案間 |
||||
|
自動變數auto、暫存器變數register |
√ |
√ |
× |
× |
√ |
|
靜態區域變數(普通全域變數) |
√ |
√ |
× |
× |
√ |
|
靜態外部變數static |
√ |
√ |
√ |
× |
√ |
|
外部變數extern |
√ |
√ |
√ |
√ |
√ |
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/523113.html
標籤:其他
