我走了很遠的路,吃了很多的苦,才將這份博士學位論文送到你的面前,二十二在求學路,一路風雨泥濘,許多不容易,如夢一場…這一路,信念很簡單,把書念下去,然后走出去,不枉活一世…理想不偉大,只愿年過半百,歸來仍是少年,希望還有機會重新認識這個世界,不辜負這一生吃過的苦,最后如果還能做出點讓別人生活更美好的事,那這輩子就賺了 ,(—黃國平博士論文致謝內容)
感言:一路走來不容易,飽經磨難,方成人杰,寶劍鋒從磨礪出,梅花香自苦寒來,
文章目錄
- 1、函式是什么?
- 2、函式型別:
- ①庫函式
- ②自定義函式
- 3、函式的引數
- ①實際引數(實參) :
- ②形式引數(形參)∶
- 4、函式的呼叫
- ①傳值呼叫:
- ②傳址呼叫:
- 5、函式練習
- 練習1:寫一個函式可以判斷一個數是不是素數
- 練習2:寫一個函式判斷一年是不是閏年
- 練習3:寫一個函式,實作一個整型有序陣列的二分查找
- 練習4:寫一個函式,每呼叫一次這個函式,就會將num的值增加1
- 6、函式的嵌套呼叫和鏈式訪問
- ①嵌套呼叫
- ②鏈式訪問
- 7、函式的宣告和定義
- 函式宣告:
- 函式定義:
- 有趣的小應用:
1、函式是什么?
子程式,負責完成特定的任務或功能,具有相對獨立性,
一般會有輸入引數 在定義函式的時候需要說明輸入引數的型別,會有回傳值,定義函式的時候也需要定義函式回傳值的型別,
#include<stdio.h>//1.加法函式
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 1;
int b = 3;
int sum = Add(a, b);
printf("sum = %d\n", sum);
return 0;
}
2、函式型別:
①庫函式
定義:c語言本身提供的函式,直接呼叫即可
參考庫檔案需要用尖括號<> eg:#include<stdio.h>
例如:strcpy-- - 字串拷貝函式

memset-- - 記憶體塊設定函式

#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = { 0 };
char arr2[] = "hallo world!";
strcpy(arr1, arr2);
printf("%s\n", arr1);
memset(arr2, '-', 6);
printf("%s\n", arr2);
return 0;
}

這里我們使用strcpy函式將arr2的內容拷貝給arr1,再使用memset記憶體塊設定函式,將arr2前6個元素設定成 “ - ”,
為什么會有庫函式 ?
1.我們知道在我們學習C語言編程的時候,總是在一個代碼撰寫完成之后迫不及待的想知道結果,想把這個結果列印到我們的螢屏上看看,這個時候我們會頻繁的使用一個功能︰將資訊按照一定的格式列印到螢屏上(printf),
2.在編程的程序中我們會頻繁的做一些字串的拷貝作業(strcpy),
3.在編程是我們也計算,總是會計算n的k次方這樣的運算(pow),
像上面我們描述的基礎功能,它們不是業務性的代碼,我們在開發的程序中每個程式員都可能用的到,為了支持可移植性和提高程式的效率,所以C語言的基礎庫中提供了一系列類似的庫函式,方便程式員進行軟體開發,
庫函式參考網站:
http://www.cplusplus.com/
https://zh.cppreference.com/w/%E9%A6%96%E9%A1%B5
②自定義函式
定義:由程式員設計
參考自定義函式需要用雙引號 " " eg: #include"Add.h"

請寫一個自定義函式將會兩個變數的值,
//1.交換函式
//當實參傳給形參的時候
// 形參其實是實參的一份臨時拷貝
// 對形參的修改是不會改變實參的
void Swap1(int x, int y)//有問題,值交換不過去 x y有獨立的空間
{
int temp = x;
x = y;
y = temp;
}
void Swap2(int* pi, int* pj)//正確,通過指標間接操控主函式中的i,j進行值互換
{
int temp = 0;
temp = *pi;
*pi = *pj;
*pj = temp;
}
int main()
{
int a = 10;
int b = 20;
printf("i = %d,j = %d\n", i, j);
//Swap1(i, j);//呼叫Swap1函式 傳值呼叫
Swap2(&i, &j);//呼叫Swap2函式 傳地呼叫
printf("i = %d,j = %d\n", i, j);
return 0;
}

上面的兩個交換函式Swap1,Swap2詳解:
Swap1采用傳值的方式,傳值只是變數的一份臨時拷貝,在交換函式中改變變數的值,只是改變臨時拷貝中的內容,不會影響到函式外面的變數,
Swap2采用傳址的方式,傳址臨時拷貝的是變數的地址,并不是直接拷貝變數本身,對地址的解參考操作可以通過交換函式內的地址資訊找到其對應的函式外的變數,并對其進行操作,
什么時候傳值?什么時候傳址?
不需要改變實參的時候傳值,需要改變實參的時候傳址,
3、函式的引數
①實際引數(實參) :
真實傳給函式的引數,叫實參,實參可以是︰常量、變數、運算式、函式等,無論實參是何種型別的量,在進行函式呼叫時,它們都必須有確定的值,以便把這些值傳送給形參,
②形式引數(形參)∶
形式引數是指函式名后括號中的變數,因為形式引數只有在函式被呼叫的程序中才實體化(分配記憶體單元),所以叫形式引數,形式引數當函式呼叫完成之后就自動銷毀了,因此形式引數只在函式中有效,
4、函式的呼叫
①傳值呼叫:
函式的形參和實參分別占有不同記憶體塊,對形參的修改不會影響實參,
②傳址呼叫:
傳址呼叫是把函式外部創建變數的記憶體地址傳遞給函式引數的一種呼叫函式的方式,這種傳參方式可以讓函式和函式外邊的變數建立起正真的聯系,也就是函式內部可以直接操作函式外部的變數,(通過指標找到變數)
5、函式練習
練習1:寫一個函式可以判斷一個數是不是素數
void Judge_prime(int x)
{
int i = 0;
for (i = 2; i <= sqrt(x); i++)
{
if (!(x % 2))
break;
}
if (i > sqrt(x))
printf("%d是素數\n", x);
else
printf("%d不是素數\n", x);//注意不要在呼叫函式里面進行列印
}
int main()
{
int n = 0;
printf("請輸入一個正整數:>>\n");
scanf("%d", &n);
Judge_prime(n);
return 0;
}

這種方式其實并不好,判斷素數和列印素數放在同一個函式中了,這種方式寫的函式不夠單一,不夠獨立,后續如果需要在用到判斷素數函式的時候,上面寫的這個函式是沒法用的,
例如:進階:列印100 - 200之間的素數
#include<stdio.h>
#include<math.h>
is_prime(int x)
{
int j = 0;
for (j = 2; j <= sqrt(x); j++)
{
if (x % j == 0)
return 0;
}
return 1;
}
int main()
{
int i = 0;
int count = 0;
for (i = 100; i < 200; i++)//判斷100-200之間的素數
{
if ((is_prime(i) == 1))
{
printf("%d ", i);
count++;
}
}
printf("\n count = %d\n", count);
return 0;
}
這里我們統計了素數的個數,所以添加一個count變數來計數統計,

練習2:寫一個函式判斷一年是不是閏年
is_Leap(int a)
{
if ((a % 4 == 0) && (a % 100 != 0) || (a % 400 == 0))
return 1;
else
return 0;
}
int main()
{
int year = 0;
for (year = 1000; year <= 2000; year++)
{
if (is_Leap(year) == 1)
printf("%d ", year);
}
return 0;
}
進階:如果我們能理解條件判斷是成立,運算式值 = 1,不成立,運算式值 = 0,就可以使用直接回傳運算式的方式,如下圖:

練習3:寫一個函式,實作一個整型有序陣列的二分查找
int binary_search(int k, int arr[], int sz)//陣列如何傳遞引數?//形參arr[]實際上是一個指標
{
int left = 0;
int right = sz - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (k > arr[mid])
{
left = mid + 1;
}
else if (k < arr[mid])
{
right = mid - 1;
}
else
return mid;
}
return -1;
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int k = 7;
int ret = 0;
int sz = sizeof(arr) / sizeof(arr[0]);//不能在自定義函式中進行,陣列不會進行拷貝,只是傳遞首地址
ret = binary_search(k, arr, sz);//arr傳遞的陣列首元素的地址
if (ret == -1)
printf("查找不到!\n");
else
printf("找到了,下標為:%d\n", ret);
return 0;
}

陣列傳參,本質上傳遞的是陣列首元素的地址,也就是指標,寫成陣列的形式實際上是“掛羊頭賣狗肉”,并非將整個陣列拷貝復制!
練習4:寫一個函式,每呼叫一次這個函式,就會將num的值增加1
void Number_plus(int* num)
{
*num += 1;//不能用*num++,因為++優先級高于* 會先執行++ 再執行*
}
int main()
{
int num = 0;
int i = 0;
for (i = 0; i < 10; i++)
{
Number_plus(&num);
printf("%d ", num);
}
return 0;
}

6、函式的嵌套呼叫和鏈式訪問
函式和函式之間可以有機的組合
①嵌套呼叫
#include<stdio.h>
void new_line()
{
printf("haha\n");
}
void three_line()
{
int i = 0;
for (i = 0; i < 3; i++)
{
new_line();
}
}
int main()
{
three_line();
return 0;
}

注意:函式可以嵌套呼叫,但是不可以嵌套定義!
②鏈式訪問
把一個函式的回傳值作為另外一個函式的引數
(1)看下面這個求字串長度的例子:

利用鏈式訪問的方式我們可以這樣寫:

當然,我們還可以這樣寫:

(2)字串拷貝例子:

鏈式訪問的方式可以寫成:

注意:strcpy函式的回傳值就是arr1
為了更進一步理解鏈式訪問的含義和應用,請看下面這個例子:
#include<stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}

printf()函式回傳值是列印字符的個數

7、函式的宣告和定義
函式宣告:
1.告訴編譯器有一個函式叫什么,引數是什么,回傳型別是什么,但是具體是不是存在,無關緊要,
2.函式的宣告一般出現在函式的使用之前,要滿足先宣告后使用,
3.函式的宣告一般要放在頭檔案中的,
函式定義:
函式的定義是指函式的具體實作,交代函式的功能實作,
int Add(int x, int y);//函式宣告
int main()
{
int a = 10;
int b = 20;
int sum = 0;
sum = Add(a, b);//函式呼叫
printf("%d\n", sum);
return 0;
}
int Add(int x, int y)//函式定義
{
int z = x + y;
return z;
}
一般來說,我們最好將函式的定義部分放到函式呼叫前面,這樣就可以避免函式宣告的部分(編譯器讀取代碼是從上往下依次掃描的),例如上面的這個代碼可以寫成這樣:

函式的宣告為什么要放到頭檔案.h里面?
我們在使用 #include + 頭檔案的時候,實際上相當于將頭檔案的內容拷貝到該條陳述句處, 函式的定義和宣告為什么要放到不同的檔案里面呢?
有趣的小應用:
假設我們的張同學寫了一個減法的函式檔案,有一個人要購買這個函式,但是我們的張同學又不想直接將源代碼賣給買家,而買家也沒必要知道原始碼,只要能使用減法的函式就ok了,
新建一個工程檔案sub,寫一個sub.c 源檔案和 sub.h頭檔案
右鍵點擊專案工程sub — 屬性 — 更改為靜態庫,


改成靜態庫.lib后,Ctrl + F7 編譯一下

這時候我們進入 Debug路徑下面

此時該目錄還是空的,Ctrl + F5 運行一下

再回到剛剛的Debug路徑下面:會出現一個sub.lib檔案,

這個sub.lib檔案就是 sub.c和sub.h編譯產生的靜態庫檔案,當我們用notepad++打開這個檔案的時候,看到的都是亂碼:

.lib檔案中都是按照二進制的方式保存的,所以看不到其原始碼,
這時候我們的張同學就可以將sub.lib檔案賣給買家了,為了能讓買家很好的使用這個函式,sub.h也需要賣給買家,(sub.h里面可以詳細介紹函式的作用,引數,使用方式)
買家買到sub.h + sub.lib后怎么使用呢?
將這兩個檔案放到要使用的工程檔案路徑下面,和自創建的源檔案同一級目錄中:

1、添加頭檔案 添加-- - 現有項


2、匯入靜態庫
添加頭檔案后,如果我們不匯入靜態庫,編譯會發生錯誤

匯入靜態庫后:

跨檔案函式中的頭檔案中,我們經常可以看到以下這些代碼:
#ifndef XXXXXX
#define XXXXXX
.....
#endif

我們看到過很多t頭檔案都會加上類似上面的代碼,為什么要加上這些代碼呢?
防止同一個頭檔案被多次使用
頭檔案在使用的時候,實際上是將整個頭檔案的內容復制到這條陳述句處,如果同一個頭檔案多次使用,就會產生大量重復且無效的代碼,降低代碼的效率和增加代碼所在記憶體,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/279306.html
標籤:其他
上一篇:大一學妹對Java的Condition介面理解讓我佩服,當面給她offer!
下一篇:django搭建個人博客(一)
