一、函數的概念
?C語?中的函數:?個完成某項特定的任務的??段代碼
?函數又被翻譯為子函數
(更準確)
?在C語?中我們?般會?到兩類函數:庫函數
?定義函數
二、庫函數
1 .標準庫和頭文件
?C語?的國際標準ANSIC規定了?些常?的函數的標準,被稱為標準庫,那不同的編譯器?商根據ANSI提供的C語?標準就給出了?系列函數的實現,這些函數就被稱為庫函數
?庫函數相關頭文件
:https://zh.cppreference.com/w/c/header
2.庫函數的使用方法
(1)庫函數學系工具鏈接
?C/C++官方的鏈接:
https://zh.cppreference.com/w/c/header
?cplusplus.com:
https://legacy.cplusplus.com/reference/clibrary/
(2)庫函數使用順序
?包含頭文件
:頭文件中聲明了庫函數的原型,告訴編譯器函數的名稱、返回類型和參數類型
?調用函數
:調用函數時,需要按照函數的原型提供正確的參數,并根據需要處理返回值
注意事項:
? 參數類型和數量:調用函數時,確保提供的參數類型和數量與函數原型一致
? 庫函數的限制:了解庫函數的使用限制和前提條件,某些函數對輸入參數有特定的要求
(3)舉例庫函數 sprt
? 庫函數sprt
的功能:Compute square root
(計算平方根) 和Returns the square root of x
(返回平?根)
? sqrt
的頭文件是math.h
#include <stdio.h>//scanf printf的頭文件
#include <math.h>//sqrt 的頭文件
int main()
{double n,r;scanf("%lf", &n);r = sqrt(n);//調用函數printf("%lf", r);return 0;
}
三、自定義函數
1.自定義函數的語法形式
return_type fun_name(形式參數)
{}
?return_type
:是?來表?函數計算結果的類型(int
float
double
char
…),返回類型可以是void
,表示什么都不返回
?fun_name
:是函數名,函數名盡量要根據函數的功能起
?形式參數
:函數的參數也可以是void
,明確表?函數沒有參數。如果有參數,要交代清楚參數的類型和名字,以及參數個數
?{}
:括起來的是函數體
2.自定義函數簡單舉例
寫?個除法函數,完成2個浮點型變量的除法法操作
#include <stdio.h>
double Division(double x, double y)
{double z = x / y;return z;
}
int main()
{double a, b, result;scanf("%lf %lf", &a, &b);//輸入result = Division(a, b);//調用除法函數,完成a和b相除//求商的結果放在result中printf("%lf\n", result);//輸出return 0;
}
Division
函數可以簡化為:
double Division(double x, double y)
{return x / y;
}
注意:
?函數的參數部分需要交代清楚:參數個數,每個參數的類型是什么,形參的名字是什么
上?只是?個例?,未來我們是根據實際需要來設計函數,函數名、參數、返回類型都是可以靈活變化的
四、形參和實參
以上面的代碼為例
#include <stdio.h>
double Division(double x, double y)
{double z = x / y;return z;
}
int main()
{double a, b, result;scanf("%lf %lf", &a, &b);//輸入result = Division(a, b);//調用除法函數,完成a和b相除//求商的結果放在result中printf("%lf\n", result);//輸出return 0;
}
1.實參
result = Division(a, b);
?調?Division
函數時,傳遞給函數的參數a
和b
,稱為實際參數,簡稱實參
?實際參數就是真實傳遞給函數的參數
2.形參
double Division(double x, double y)
?函數名Division
后的括號中寫的x
和y
,稱為形式參數,簡稱形參
?如果只是定義了Division
函數,?不去調?的話, Add 函數的參數x
和y
只是形式上存在,不會向內存申請空間,所以叫形式參數。形式參數只有在函數被調?的過程中為了存放實參傳遞過來的值,才向內存申請空間,這個過程是形參的實例化
3.形參和實參的關系
?形參和實參各?是獨?的內存空間
x和y確實得到了a和b的值,但是x和y的地址和a和b的地址是不?樣的,所以我們可以理解為形參是實參的?份臨時拷?
五、return語句
運用return的注意事項:
1.函數中有可能出現return
,也可能沒有
void menu()//無需返回任何類型的函數
{~~~~~~~
}
2.return
后邊可以是?個數值,也可以是?個表達式,如果是表達式則先執?表達式,再返回表達式的結果
例子:寫一個函數,判斷一個整數是奇數是偶數,是偶數返回0,是奇數返回1
return
后面是數值
#include <stdio.h>
int judge(int x)
{if (x % 2 == 0){return 0;//數值}if (x % 2 == 1){return 1;//數值}
}
int main()
{int n = 0;scanf("%d", &n);int ret = judge(n);if (ret == 1){printf("奇數");}if(ret == 0){printf("偶數");}return 0;
}
return
后面是表達式
#include <stdio.h>
int judge(int x)
{return x % 2;//表達式
}
結果都可以正確判斷奇偶
3.return
后邊也可以什么都沒有,直接寫return;
這種寫法適合函數返回類型是void
的情況
?return;適合:一個函數在執行的過程,遇到某個條件需要提前返回,但是不需要帶回任何值的情況
4.return
語句執?后,函數就徹底返回,后邊的代碼不再執?
例子:輸入一個數,如果是偶數輸出I LOVE A
和I LOVE B
,如果是奇數輸出I LOVE A
#include <stdio.h>
void test(int x)
{printf("I LOVE A\n");if (x % 2 == 1){return;//輸入的是奇數直接返回,下面的語句不會執行}printf("I LOVE B\n");
}
int main()
{int n = 0;scanf("%d", &n);test(n);return 0;
}
結果:
5.return
返回的值和函數返回類型不?致,系統會?動將返回的值轉換為函數的返回類型
舉例:
#include <stdio.h>
int test()//函數的返回類型是整型
{return 3.5;// 需要返回的數字是浮點型,// return的返回類型與函數的不同,// 系統會?動將return返回的值轉換為函數的返回類型// return返回的值為3
}
int main()
{double r = test();printf("%.1f", r);//輸出一位小數,3.0
}
6.如果函數中存在if
等分?的語句,則要保證每種情況下都有return
返回,否則會出現編譯錯誤
舉例:
#include <stdio.h>
double test(double x)
{if (x == 1){return 3.5;// n!=1的時候,有些編譯器的函數返回值是不可預測的}
}
int main()
{double n = 0;scanf("%lf", &n);double r = test(n);printf("%.1f", r);return 0;
}
編譯器會報錯
7.函數的返回類型如果不寫,編譯器會默認函數的返回類型是int
舉例:
#include <stdio.h>
test()//函數無返回類型,默認返回類型是int
{return 3.5;//返回的是浮點型
}
int main()
{double r = test();//函數真正值是3printf("%.1f", r);//輸出一位小數,3.0
}
結果:
8.函數寫了返回類型,但是函數中沒有使?return
返回值,那么函數的返回值是未知的
舉例:
#include <stdio.h>
int test()
{//沒有return
}
int main()
{int r = test();printf("%d", r);
}
結果:
六、數組做函數參數
數組傳參的?個重點知識:
? 函數的形式參數要和函數的實參個數匹配
? 函數的實參是數組,形參也是可以寫成數組形式的
? 形參如果是一維數組,數組大小可以省略不寫
? 形參如果是二維數組,行可以省略,但 是列不能省略
? 數組傳參,形參是不會創建新的數組的
? 形參操作的數組和實參的數組是同一個數組
舉例:寫兩個函數,一個函數設置數組的值,一個負責輸出函數的值
#include <stdio.h>
void set_arr(int arr[], int sz)//設置對應的形參
{int i = 0;for (i = 0; i < sz; i++){arr[i] = i;//設置arr數組的值0~9}
}void printf_arr(int arr[], int sz)//設置對應的形參{int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);//輸出arr數組的值0~9}
}
int main()
{ int arr[10] = { 0 };//計算數組的元素個數int sz = sizeof(arr) / sizeof(arr[1]);//設置數組的值set_arr(arr,sz);//傳入arr數組和數組的元素個數//書櫥數組的值、printf_arr(arr, sz);//傳入arr數組和數組的元素個數return 0;
}
結果:
七、嵌套調用和鏈式訪問
1.嵌套調用
嵌套調用就是在函數中調用其他函數
舉例:計算某年某月有多少天
邏輯:
1.每個月的天數通常是固定的,除了二月份:
? 一月(1月):31天
? 二月(2月):28天或29天(閏年)
? 三月(3月):31天
? 四月(4月):30天
? 五月(5月):31天
? 六月(6月):30天
? 七月(7月):31天
? 八月(8月):31天
? 九月(9月):30天
? 十月(10月):31天
? 十一月(11月):30天
? 十二月(12月):31天
2. 判斷閏年對于二月份,需要判斷該年是否為閏年
3. 閏年的判斷規則是:年份能被4整除,但不能被100整除,或者能被400整除
4. 計算天數根據月份和是否為閏年,返回相應的天數:
? 如果是二月份且是閏年,則返回29天
? 如果是二月份但不是閏年,則返回28天
? 對于其他月份,直接返回固定的天數(31天或30天)
#include <stdio.h>//判斷閏年
int is_LeapYear(int year)
{if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)){return 29;}else{return 28;}
}//判斷有多少天
int day_In_month(int year, int month)
{switch (month){//有31天的月份{case 1:case 3:case 5:case 7:case 8:case 10:case 12:return 31;}//有30天的月份{case 4:case 6:case 9:case 11:return 30;}//二月:28天或29天(閏年)case 2:{return is_LeapYear(year);//判斷閏年}}
}
int main()
{int year, month, day;regain1:printf("請輸入你要查詢的年和月:");regain2:while (scanf("%d %d", &year, &month) != EOF){//判斷年月輸入是否正確if (year < 0 || month <= 0 || month>12){if (year < 0 && (month > 0 && month <= 12)){printf("年份輸入錯誤,請輸 (year>=0)的數\n請重新輸入你要查詢的年和月:");goto regain2;}if (year >= 0 && (month <= 0 || month > 12)){printf("月份輸入錯誤,請輸入(month>0 && month<=12)的數\n請重新輸入你要查詢的年和月:");goto regain2;}if (year < 0 && (month <= 0 || month > 12)){printf("年份和月份輸入錯誤,請輸入(year>=0 && month>0 && month<=12)的數\n請重新輸入你要查詢的年和月:");goto regain2;}}day = day_In_month(year, month);printf("%d年%d月有%d天\n", year, month, day);goto regain1;}return 0;
}
? main
函數調? scanf
、 printf
day_In_month
? day_In_month
函數調? is_LeapYear
2.鏈式訪問
鏈式訪問就是將?個函數的返回值作為另外?個函數的參數
例子:printf
的返回值
?printf
函數返回的是打印在屏幕上的字符的個數
?printf
的詳細內容(https://blog.csdn.net/Siri_s12/article/details/144370792?spm=1001.2014.3001.5501)
#include <stdio.h>
int main()
{printf("%d", printf("%d", printf("%d", 43)));return 0;
}
?第三個printf打印43,在屏幕上打印2個字符,再返回2
?第?個printf打印2,在屏幕上打印1個字符,再放回1
?第?個printf打印1
所以屏幕上最終打印:4321
八、函數的聲明和定義
1.單個文件
函數的定義(實現函數功能的代碼)
//判斷閏年
int is_LeapYear(int year)
{if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)){return 29;}else{return 28;}
}
函數的調用(運用函數的地方)
比如這里的 return is_LeapYear(year);
//二月:28天或29天(閏年)case 2:{return is_LeapYear(year);//判斷閏年}
?如果函數的定義在函數的調用之后,編譯時就會有報錯,把怎么解決這個問題呢?就是函數調?之前先聲明函數,聲明函數只要交代清楚:函數名,函數的返回類型和函數的參數,比如int is_LeapYear(int year);
#include <stdio.h>int is_leap_year(int y);//函數聲明int main()
{int y = 0;scanf("%d", &y);int r = is_leap_year(y);if (r == 1)printf("閏年\n");elseprintf("?閏年\n");return 0;
}
//判斷是不是閏年
int is_leap_year(int y)
{if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))return 1;elsereturn 0;
}
?函數的調??定要滿?,先聲明后使?;
?函數的定義也是?種特殊的聲明,所以如果函數定義放在調?之前也是可以的
2.多個文件
函數的聲明、類型的聲明放在頭?件(.h)
中,函數的實現是放在源?件(.c)
?件中
比如:
add.c
//函數的定義
int is_LeapYear(int year)
{if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)){return 29;}else{return 28;}
}int day_In_month(int year, int month)
{switch (month){//有31天的月份{case 1:case 3:case 5:case 7:case 8:case 10:case 12:return 31;}//有30天的月份{case 4:case 6:case 9:case 11:return 30;}//二月:28天或29天(閏年)case 2:{return is_LeapYear(year);//判斷閏年}}
}
add.h
//函數的聲明
int is_LeapYear(int year);
int day_In_month(int year, int month);
test.c
#include <stdio.h>
#include "add.h"
int main()
{int year, month, day;regain1:printf("請輸入你要查詢的年和月:");regain2:while (scanf("%d %d", &year, &month) != EOF){//判斷年月輸入是否正確if (year < 0 || month <= 0 || month>12){if (year < 0 && (month > 0 && month <= 12)){printf("年份輸入錯誤,請輸 (year>=0)的數\n請重新輸入你要查詢的年和月:");goto regain2;}if (year >= 0 && (month <= 0 || month > 12)){printf("月份輸入錯誤,請輸入(month>0 && month<=12)的數\n請重新輸入你要查詢的年和月:");goto regain2;}if (year < 0 && (month <= 0 || month > 12)){printf("年份和月份輸入錯誤,請輸入(year>=0 && month>0 && month<=12)的數\n請重新輸入你要查詢的年和月:");goto regain2;}}day = day_In_month(year, month);printf("%d年%d月有%d天\n", year, month, day);goto regain1;}return 0;
}
3.static和extern
(1)作用域
? 定義:在C語言中,作用域(Scope)指的是一個標識符(如變量名、函數名等)有效、可被訪問的區域,通俗來說,一個變量在哪里可以使用,哪里就是它的作用域
?局部變量的作?域是變量所在的局部范圍
這個代碼中,a
的作用域在
{int a = 0;printf("%d", a);
}
?全局變量的作?域是整個?程(項?)
int a = 10
所有函數內都可以使用
(2)生命周期
??命周期指的是變量的創建(申請內存)到變量的銷毀(收回內存)之間的?個時間段
?局部變量的?命周期是:進?作?域變量創建,?命周期開始,出作?域?命周期結束
?全局變量的?命周期是:整個程序的?命周期
(3)static和extern
static
是靜態的的意思,可以?來:
? 修飾局部變量
? 修飾全局變量
? 修飾函數
extern
用來聲明外部符號
(a)static修飾局部變量
對比兩個函數的結果
代碼1的test
函數中的局部變量a
是每次進?test
函數先創建變量(?命周期開始)并賦值為0,然后a++
,再打印,出函數的時候變量?命周期將要結束(釋放內存)
代碼2中,我們從輸出結果來看,a
的值有累加的效果,其實 test
函數中的a
創建好后,出函數的時候是不會銷毀的,重新進?函數也就不會重新創建變量,直接上次累積的數值繼續計算
結論:
static
修飾局部變量改變了變量的?命周期,?命周期改變的本質是改變了變量的存儲類型,本來?個局部變量是存儲在內存的棧區的,但是被static
修飾后存儲到了靜態區。存儲在靜態區的變量和全局變量是?樣的,?命周期就和程序的?命周期?樣了,只有程序結束,變量才銷毀,內存才回收。但是作?域不變的
使?建議:
?個變量出了函數后,還想保留值,等下次進?函數繼續使?,就可以使?static
修飾
(b) static 修飾全局變量
int a = 100;
沒有static
修飾,可以跨文件正常使用
int a = 100;
有static
修飾,不可以跨文件正常使用
結論:
?個全局變量被static
修飾,使得這個全局變量只能在本源?件內使?,不能在其他源?件內使?。 本質原因是全局變量默認是具有外部鏈接屬性的,在外部的?件中想使?,只要適當的聲明就可以使?;但是全局變量被 static
修飾之后,外部鏈接屬性就變成了內部鏈接屬性,只能在??所在的源?件內部使?了,其他源?件,即使聲明了,也是?法正常使?的
使?建議:
如果?個全局變量,只想在所在的源?件內部使?,不想被其他?件發現,就可以使?static
修飾。
(c)static 修飾函數
Add
函數沒有static
修飾,Add
函數可以使用
Add
函數有static
修飾,Add
函數不可以使用
結論:
static
修飾函數和 static
修飾全局變量是?模?樣的,?個函數在整個?程都可以使?,被static
修飾后,只能在本?件內部使?,其他?件?法正常的鏈接使?了
使?建議:
?個函數只想在所在的源?件內部使?,不想被其他源?件使?,就可以使? static
修飾