學習筆記之——C語言 函數
採用函數的原因:
隨着程序規模的變大,產生了以下問題:
——main函數變得相當冗雜
——程序複雜度不斷提高
——代碼前後關聯度提高,修改代碼往往牽一髮而動全身
——變量使用過多,命名都成了問題
——為了在程序中多次實現某個功能,不得不重複多次寫相同的代碼
小甲魚將函數一部分作為自學內容放在課後作業s1e23里,要求自學。閱覽後,認為講解不清,網上到處查詢。看到CSDN里一篇詳解,認為可用,抄錄下來以備查詢。(//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、 當自定義的函數名與庫函數重名的時候:
根據//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 |
√ |
√ |
√ |
√ |
√ |