關(guān)于網(wǎng)站制作整站優(yōu)化工具
?
??個(gè)人主頁(yè):?熬夜學(xué)編程的小林
💗系列專欄:?【C語(yǔ)言詳解】?【數(shù)據(jù)結(jié)構(gòu)詳解】
函數(shù)
1、嵌套調(diào)用和鏈?zhǔn)皆L問(wèn)
1.1、嵌套調(diào)用
1.2、鏈?zhǔn)皆L問(wèn)
2、函數(shù)的聲明和定義
2.1、單個(gè)文件
2.2、多個(gè)文件
2.3、static 和 extern
2.3.1、static 修飾局部變量:
2.3.2、static 修飾全局變量
2.3.3、static 修飾函數(shù)
總結(jié)
1、嵌套調(diào)用和鏈?zhǔn)皆L問(wèn)
1.1、嵌套調(diào)用
嵌套調(diào)用就是函數(shù)之間的互相調(diào)用,每個(gè)函數(shù)就像?個(gè)樂(lè)高零件,正是因?yàn)槎鄠€(gè)樂(lè)高的零件互相無(wú)縫的配合才能搭建出精美的樂(lè)高玩具,也正是因?yàn)楹瘮?shù)之間有效的互相調(diào)用,最后寫(xiě)出來(lái)了相對(duì)大型的程序。
假設(shè)我們計(jì)算某年某月有多少天?如果要函數(shù)實(shí)現(xiàn),可以設(shè)計(jì)2個(gè)函數(shù):
? is_leap_year():根據(jù)年份確定是否是閏年
? get_days_of_month():調(diào)?is_leap_year確定是否是閏年后,再根據(jù)月計(jì)算這個(gè)月的天數(shù)
閏年的判斷規(guī)則:
1、能被400整除為閏年
2、能被4整除但是不能被100整除為閏年
int is_leap_year(int y)
{if(((y%4==0)&&(y%100!=0))||(y%400==0))return 1;//是閏年返回1elsereturn 0;//不是閏年返回0
}
int get_days_of_month(int y, int m)
{int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};//數(shù)組的下標(biāo)代表月份 初始化為不是閏年的情況每月的天數(shù)int day = days[m];if (is_leap_year(y) && m == 2)day += 1;//為閏年則將2月加一天return day;//返回天數(shù)
}
int main()
{int y = 0;int m = 0;scanf("%d %d", &y, &m);int d = get_days_of_month(y, m);printf("%d\n", d);return 0;
}
這?段代碼,完成了?個(gè)獨(dú)立的功能。代碼中反應(yīng)了不少的函數(shù)調(diào)用:
? main 函數(shù)調(diào)用?scanf 、 printf 、 get_days_of_month
? get_days_of_month 函數(shù)調(diào)用?is_leap_year
未來(lái)的稍微??些代碼都是函數(shù)之間的嵌套調(diào)用,但是函數(shù)是不能嵌套定義的。
1.2、鏈?zhǔn)皆L問(wèn)
所謂鏈?zhǔn)皆L問(wèn)就是將?個(gè)函數(shù)的返回值作為另外?個(gè)函數(shù)的參數(shù),像鏈條?樣將函數(shù)串起來(lái)就是函數(shù)的鏈?zhǔn)皆L問(wèn)。
比如:
#include <stdio.h>
int main()
{int len = strlen("abcdef");//1.strlen求?個(gè)字符串的?度printf("%d\n", len);//2.打印?度?return 0;
}
前面的代碼完成動(dòng)作寫(xiě)了2條語(yǔ)句,把如果把strlen的返回值直接作為printf函數(shù)的參數(shù)呢?這樣就是?個(gè)鏈?zhǔn)皆L問(wèn)的例子了。
#include <stdio.h>
int main()
{printf("%d\n", strlen("abcdef"));//鏈?zhǔn)皆L問(wèn)return 0;
}
在看?個(gè)有趣的代碼,下?代碼執(zhí)行的結(jié)果是什么呢?
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}
這個(gè)代碼的關(guān)鍵是明白?printf 函數(shù)的返回是啥?
int printf ( const char * format, ... );
printf函數(shù)返回的是打印在屏幕上的字符的個(gè)數(shù)。
上面的例子中,我們就第?個(gè)printf打印的是第?個(gè)printf的返回值,第?個(gè)printf打印的是第三個(gè)
printf的返回值。
第三個(gè)printf打印43,在屏幕上打印2個(gè)字符,再返回2
第?個(gè)printf打印2,在屏幕上打印1個(gè)字符,再放回1
第?個(gè)printf打印1
所以屏幕上最終打印:4321
2、函數(shù)的聲明和定義
2.1、單個(gè)文件
?般我們?cè)谑褂煤瘮?shù)的時(shí)候,直接將函數(shù)寫(xiě)出來(lái)就使用了。
比如:我們要寫(xiě)?個(gè)函數(shù)判斷?年是否是閏年。
#include <stdio.h>
//判斷?年是不是閏年
int is_leap_year(int y)//第三行
{if(((y%4==0)&&(y%100!=0))||(y%400==0))return 1;//是閏年返回1elsereturn 0;//不是閏年返回0
}//第八行
int main()
{int y = 0;scanf("%d", &y);int r = is_leap_year(y);//第十三行if(r == 1)printf("閏年\n");elseprintf("?閏年\n");return 0;
}
上面代碼中第三行到第八行是函數(shù)的定義,第十三行是函數(shù)的調(diào)用。
這種場(chǎng)景下是函數(shù)的定義在函數(shù)調(diào)用之前,沒(méi)啥問(wèn)題。
那如果我們將函數(shù)的定義放在函數(shù)的調(diào)用后邊,如下:
#include <stdio.h>
int main()
{int y = 0;scanf("%d", &y);int r = is_leap_year(y);//第6行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;
}
這個(gè)代碼在VS2022上編譯,會(huì)出現(xiàn)下面的警告信息:
這是因?yàn)镃語(yǔ)言編譯器對(duì)源代碼進(jìn)行編譯的時(shí)候,從第?行往下掃描的,當(dāng)遇到第6行的is_leap_year函數(shù)調(diào)用的時(shí)候,并沒(méi)有發(fā)現(xiàn)前面有is_leap_year的定義,就報(bào)出了上述的警告。
把怎么解決這個(gè)問(wèn)題呢?就是函數(shù)調(diào)用之前先聲明?下is_leap_year這個(gè)函數(shù),聲明函數(shù)只要交代清楚:函數(shù)名,函數(shù)的返回類型和函數(shù)的參數(shù)。
如:int is_leap_year(int y);這就是函數(shù)聲明,函數(shù)聲明中參數(shù)只保留類型,省略掉名字也是可以
的。
代碼變成這樣就能正常編譯了。
#include <stdio.h>
int is_leap_year(int y);//函數(shù)聲明
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;
}
函數(shù)的調(diào)用?定要滿足,先聲明后使用;
函數(shù)的定義也是?種特殊的聲明,所以如果函數(shù)定義放在調(diào)用之前也是可以的。
2.2、多個(gè)文件
?般在企業(yè)中我們寫(xiě)代碼時(shí)候,代碼可能比較多,不會(huì)將所有的代碼都放在?個(gè)文件中;我們往往會(huì)根據(jù)程序的功能,將代碼拆分放在多個(gè)文件中。
?般情況下,函數(shù)的聲明、類型的聲明放在頭文件(.h)中,函數(shù)的實(shí)現(xiàn)是放在源?件(.c)?件中。
如下:
add.c
//函數(shù)的定義
int Add(int x, int y)
{return x+y;
}
add.h
//函數(shù)的聲明
int Add(int x, int y);
test.c
#include <stdio.h>
#include "add.h"
int main()
{int a = 10;int b = 20;//函數(shù)調(diào)?int c = Add(a, b);printf("%d\n", c);return 0;
}
運(yùn)行結(jié)果:
有了函數(shù)聲明和函數(shù)定義的理解,我們寫(xiě)代碼就更加方便了。
2.3、static 和 extern
static 和 extern 都是C語(yǔ)?中的關(guān)鍵字。
static 是 靜態(tài)的 的意思,可以用來(lái):
? 修飾局部變量
? 修飾全局變量
? 修飾函數(shù)
extern 是?來(lái)聲明外部符號(hào)的。
在講解 static 和 extern 之前再講?下:作用域和生命周期。
作用域(scope)是程序設(shè)計(jì)概念,通常來(lái)說(shuō),?段程序代碼中所用到的名字并不總是有效(可用)的,而限定這個(gè)名字的可用性的代碼范圍就是這個(gè)名字的作用域。
1. 局部變量的作用域是變量所在的局部范圍。
2. 全局變量的作用域是整個(gè)工程(項(xiàng)目)。
?命周期指的是變量的創(chuàng)建(申請(qǐng)內(nèi)存)到變量的銷毀(收回內(nèi)存)之間的?個(gè)時(shí)間段。
1. 局部變量的生命周期是:進(jìn)入作用域變量創(chuàng)建,生命周期開(kāi)始,出作用域生命周期結(jié)束。
2. 全局變量的生命周期是:整個(gè)程序的生命周期。
2.3.1、static 修飾局部變量:
?
//代碼1
#include <stdio.h>
void test()
{int i = 0;i++;printf("%d ", i);
}
int main()
{int i = 0;for(i=0; i<5; i++){test();}return 0;
}
?
//代碼2
#include <stdio.h>
void test()
{//static修飾局部變量static int i = 0;i++;printf("%d ", i);
}
int main()
{int i = 0;for(i=0; i<5; i++){test();}return 0;
}
對(duì)比代碼1和代碼2的效果,理解 static 修飾局部變量的意義。
代碼1的test函數(shù)中的局部變量 i 是每次進(jìn)入test函數(shù)先創(chuàng)建變量(生命周期開(kāi)始)并賦值為0,然后
++,再打印,出函數(shù)的時(shí)候變量生命周期將要結(jié)束(釋放內(nèi)存)。
代碼2中,我們從輸出結(jié)果來(lái)看,i?的值有累加的效果,其實(shí) test函數(shù)中的 i 創(chuàng)建好后,出函數(shù)的時(shí)候是不會(huì)銷毀的,重新進(jìn)入函數(shù)也就不會(huì)重新創(chuàng)建變量,直接上次累積的數(shù)值繼續(xù)計(jì)算。
結(jié)論:static修飾局部變量改變了變量的生命周期,生命周期改變的本質(zhì)是改變了變量的存儲(chǔ)類型,本來(lái)?個(gè)局部變量是存儲(chǔ)在內(nèi)存的棧區(qū)的,但是被 static 修飾后存儲(chǔ)到了靜態(tài)區(qū)。存儲(chǔ)在靜態(tài)區(qū)的變量和全局變量是?樣的,生命周期就和程序的生命周期?樣了,只有程序結(jié)束,變量才銷毀,內(nèi)存才回收。但是作用域不變的。
使用建議:未來(lái)?個(gè)變量出了函數(shù)后,我們還想保留值,等下次進(jìn)入函數(shù)繼續(xù)使用,就可以使用static修飾。
2.3.2、static 修飾全局變量
代碼1
add.c
int g_val = 2018;
test.c
#include <stdio.h>
extern int g_val;
int main()
{printf("%d\n", g_val);return 0;}
代碼2
add.c
static int g_val = 2018;
test.c
#include <stdio.h>
extern int g_val;
int main()
{printf("%d\n", g_val);return 0;
}
extern 是用來(lái)聲明外部符號(hào)的,如果?個(gè)全局的符號(hào)在A文件中定義的,在B文件中想使用,就可以使用?extern 進(jìn)行聲明,然后使用。
代碼1正常,代碼2在編譯的時(shí)候會(huì)出現(xiàn)鏈接性錯(cuò)誤。
結(jié)論:
?個(gè)全局變量被static修飾,使得這個(gè)全局變量只能在本源文件內(nèi)使用,不能在其他源文件內(nèi)使用。
本質(zhì)原因是全局變量默認(rèn)是具有外部鏈接屬性的,在外部的文件中想使用,只要適當(dāng)?shù)穆暶骶涂梢允褂?#xff1b;但是全局變量被 static 修飾之后,外部鏈接屬性就變成了內(nèi)部鏈接屬性,只能在自己所在的源文件內(nèi)部使用了,其他源?件,即使聲明了,也是無(wú)法正常使用的。
使用建議:如果?個(gè)全局變量,只想在所在的源文件內(nèi)部使用,不想被其他文件發(fā)現(xiàn),就可以使用
static修飾。
2.3.3、static 修飾函數(shù)
代碼1
add.c
int Add(int x, int y)
{return x+y;
}
test.c
#include <stdio.h>
extern int Add(int x, int y);
int main()
{printf("%d\n", Add(2, 3));return 0;
}
代碼2
add.c
static int Add(int x, int y)
{return x+y;
}
test.c
#include <stdio.h>
extern int Add(int x, int y);
int main()
{printf("%d\n", Add(2, 3));return 0;
}
代碼1是能夠正常運(yùn)行的,但是代碼2就出現(xiàn)了鏈接錯(cuò)誤。
其實(shí) static 修飾函數(shù)和 static修飾全局變量是?模?樣的,?個(gè)函數(shù)在整個(gè)?程都可以使用,被static修飾后,只能在本文件內(nèi)部使用,其他?件?法正常的鏈接使用了。
本質(zhì)是因?yàn)楹瘮?shù)默認(rèn)是具有外部鏈接屬性,具有外部鏈接屬性,使得函數(shù)在整個(gè)?程中只要適當(dāng)?shù)穆暶骶涂梢员皇褂谩?/span>但是被 static 修飾后變成了內(nèi)部鏈接屬性,使得函數(shù)只能在自己所在源?件內(nèi)部使用。
使用建議:?個(gè)函數(shù)只想在所在的源文件內(nèi)部使用,不想被其他源文件使用,就可以使用?static 修
飾。
總結(jié)
本篇博客就結(jié)束啦,謝謝大家的觀看,如果公主少年們有好的建議可以留言喔,謝謝大家啦!