嵌入式linux學習筆記:03_C語言_函數

一、函數概念?

1、 在linux C語言中,什麼是函數?
C語言是一種面向過程的語言,C語言稱之爲函數式語言,可以將任何功能都封裝成一個函數接口。
2、 在C語言中,封裝函數意義?
在某些功能比較複雜,往往用戶需要將其封裝成一個函數接口,調用者使用該函數時,不需要關注過程,只需要知道傳遞什麼參數和函數有什麼返回值即可。

play_music(“xxx.mp3”); -> 參數就是這首歌名字。

聽到這首歌 -> 返回值就是聽到聲音。

二、函數的書寫規則?

1、 在linux C語言中,函數有哪些種類?
main函數: 主函數,在C語言中有且僅有一個,一般都在main函數開始執行,然後調用別的函數。

系統函數(system calls):man手冊的2手冊中,這些函數不需要用戶寫實現過程。

庫函數(library calls):man手冊的3手冊中,這些函數不需要用戶寫實現過程。

自定義函數:不能在man手冊查詢得到,用戶必須寫清楚實現過程。

例子:

#include <stdio.h>
//1. main函數
int main(int argc,char *argv[])
{
       //2. 調用printf()庫函數
       printf("helloworld!\n");
       //3. 調用自定義函數
       func();  //undefined reference to `func'  -> func()實現過程沒有定義!  
       return 0;
}

2、自定義函數的書寫規則?
1)確定函數的函數名 -> 函數名字最好體現函數的功能
命名規則: 與C語言中變量一樣。 例子: my_fun

2)確定函數的形式參數列表
有形式參數:my_fun(形式參數1,形式參數2, … ,形式參數n) -> 有多少個形參,將來就需要傳遞多少個實參。

無形式參數:my_fun()

什麼是形式參數?

形式參數在函數中作用,首先聲明好,要調用該函數,首先需要什麼參數。

例子: 露營(一張大被子,一張厚席子,一個高枕頭,一個綠色桶)

例子:my_fun(int a,int b) -> 說明調用該函數時,需要傳遞二個整型數據給這個函數!

3)確定返回值類型 -> 寫在名字前面
有返回值 -> 確定返回值類型

     int   -> 無論這個函數是調用成功還是失敗,都會返回一個int類型。

     char  -> 無論這個函數是調用成功還是失敗,都會返回一個char類型。

     float -> 無論這個函數是調用成功還是失敗,都會返回一個float類型。
     
例子:
 int my_fun(int a,int b)

  return 0;

無返回值 -> void -> 代表該函數沒有返回值

       例子: void my_fun(int a,int b)

  返回值類型 函數名字(形式參數列表)  -> 自定義函數頭

4)函數體 -> {}
例子:

int my_fun(int a,int b)

{   -> 功能就寫在函數體中

}

3、如何調用函數?
1)確保調用函數實現過程已經寫好了!
例子:

int my_fun(int a,int b)
{
       printf("a = %d\n",a);
       printf("b = %d\n",b);
       return 0;

} 

2)直接寫函數名字,後面緊跟一個圓括號(),圓括號中存放東西就是實際參數(實參)。
例子:

my_fun(5,10);

結論: 形式參數值由實際參數直接初始化。 -> 這是不可逆的過程。

上述例子: a=5,b=10;

4、函數返回值情況

int my_fun(int a,int b)  -> 需要接受兩個參數,返回一個int類型數據。
int ret;
ret = my_fun(5,10);  -> 將my_fun函數的返回值結果賦值給ret。

練習3:寫一個函數實現接收三個int參數,經過約定的運算後,得到結果並打印出來。

    前面兩個參數是計算,最後參數計算方式。
    計算方式: 
    		 1  -> +
             2  -> -
             3  -> *
             4  -> /

   fun(3,4,2)  -> 結果: 3-4=-1
   fun(3,4,3)  -> 結果: 3*4=12
#include <stdio.h>
int my_fun(int x,int y,int z)  // x=a y=b z=c
{
       int ret;
       switch(z)
       {
              case 1:
                     ret=x+y;
                     break;                 
              case 2:
                     ret=x-y;
                     break;            
              case 3:
                     ret=x*y;
                     break;             
              case 4:
                     ret=x/y;
                     break;             
              default:
                     printf("enter error!\n");
                     break;    
       }
       return ret;
}
 
int main(int argc,char *argv[])
{
       int a,b,c,result;    
       while(1)
       {
              scanf("%d %d %d",&a,&b,&c);
              result = my_fun(a,b,c);
              printf("result = %d\n",result);
       }
       return 0;
}

練習4:以下程序運行結果是?

int fun(int a,int b)
{
       if(a > b)  return a;
       else    return b;
}

int main(int argc,char *argv[])
{
       int x=3,y=8,z=6,r;
       r = fun(fun(x,y),2*z);
       printf("%d\n",r); //12
}

三、函數的聲明?

1、 函數的定義與函數的聲明有什麼區別?
函數的定義包含了函數的名字,返回值類型,形式參數列表,具體實現過程。
例子:

int my_fun(int a)
{
       printf("a = %d\n",a);
       return 0;
}

函數的聲明包含了名字,返回值類型,形式參數列表,函數聲明必須寫在函數調用之前。
用通俗的話來講,函數的聲明意思是: 在你調用某個函數之前你必須要告訴我,這個函數長什麼樣子。
例子:

int my_fun(int a);

2、 函數聲明需要注意的點
1)如果函數定義寫在函數調用之前,就不要聲明瞭。
例子:

int my_fun2();  -> 需要聲明
int my_fun1()  -> 不需要聲明
{

}
int main()
{
      my_fun1();
      my_fun2();
}
int my_fun2()  -> 需要聲明
{

}

2)一般庫函數/系統函數聲明都是放在頭文件中
例子:
printf()函數聲明寫在#include <stdio.h>裏面.
所以當我們調用printf()函數時,不需要自己聲明,只需要包含對應的頭文件即可。

四、從內存的角度分析自定義函數運行過程

1、 回顧棧區以及函數特徵
1)函數內部申請的變量都是棧區申請。
2)函數返回時,在函數內部申請過的全部棧區的內存空間都要全部釋放。
3)函數返回時,會返回到函數調用的地方。

例題1: 請從內存角度分析以下代碼,求出結果?

int swap(int x,int y)
{
       int t;
       t = x;
       x = y;
       y = t;
       return 0;
}

int main(int argc,char *argv[])
{
      int a,b;
       scanf("%d %d",&a,&b);  //5  3
       swap(a,b);
       printf("a = %d,b = %d\n",a,b);  //5  3 
       return 0;
}

例題2: 請從內存角度分析以下代碼,求出結果?

int a,b;

int swap()

{

       int t;

       t = a;

       a = b;

       b = t;

       return 0;

}

int main(int argc,char *argv[])
{
       scanf("%d %d",&a,&b);  //5  3
       swap();
       printf("a = %d,b = %d\n",a,b);  //3  5
       return 0;
}

結論:
1)形式參數與實際參數佔用不同的儲存單位。
2)形式參數值由實際參數直接初始化。

五、函數嵌套?

1、 什麼是函數嵌套?
函數嵌套就是調用某個函數內部再調用另外一個函數。

2、 有函數嵌套程序在內存有什麼特點?
如果嵌套的函數很多,就會在棧區累積非常多空間沒有被釋放。

3、 函數嵌套與遞歸函數有什麼區別?
函數嵌套:自己調用別人的函數。

例子:

void fun()
{
       my_fun();
}

遞歸函數:自己調用自己的函數。

例子:

void fun()

{
       fun();
}

六、遞歸函數?

1、 特點?
遞歸函數特點自身調用自身,無限遞歸,沒有終止的時刻。

例子:

void fun()
{
       int a;
       fun();  -> 無限遞歸,沒有終止條件  -> 導致棧空間溢出!
       return;
}

int main()
{
       fun();
       return 0;
}

結論: 爲了防止棧空間溢出,所以遞歸函數一般都會攜帶終止條件,也就是說到達某個條件時,函數返回!

2、題型1: 給程序,求結果。
例題,求出下面程序的結果?

void fun(int n)
{
       if(n > 1) 
       {
              fun(n-1);
      }
       printf("n = %d\n",n);
       return;
}

int main()
{
       fun(100);
       return 0;
}

答案:1~100
思路: 找出終止條件,列出幾項,找規律,再畫圖分析!

3、 題型2: 給規則,寫程序。

例題:寫出下列程序。

有5個學生坐在一起,問第5個學生多少歲,他說比第4個學生大2歲。問第4個學生多少歲,他說比第3個學生大2歲。問第3個學生多少歲,他說比第2個學生大2歲。問第2個學生多少歲,他說比第1個學生大2歲。最後問第1個學生多少歲,他說他是10歲,請問第5個同學多少歲?使用遞歸函數來完成。

#include <stdio.h>
int get_child_age(int n)
{
       int age;
       if(n <= 0)  //  -> 搞事情
              return -1;      
       if(n == 1)  //  -> 終止條件
              age = 10;             
       if(n > 1)   //  -> 正常
              age = get_child_age(n-1) + 2;
    return age;
}

int main(int argc,char *argv[])
{
       int ret;
       ret = get_child_age(5);
       printf("ret = %d\n",ret);
       return 0;
}

思路: 寫框架,分析情況 -> 最終目標與終止條件之間的關係。

練習1: 畫出以上例子的內存圖。

練習2: 有以下程序,求出結果?

int fun(int n)  10   9
{
       if(n==1)  return 1;
       else return (n+fun(n-1));  10+9+8+7+6+5+4+3+2+1
}

int main()
{
       int x;
       scanf("%d",&x);  //若輸入一個數字10。
       x = fun(x); 10
       printf("%d\n",x);  //55
}

練習3:第1天存100塊,後面每一天都比昨天多存10塊,第312天存了多少?

#include <stdio.h> 
int get_money(int n)
{
       int money;
       if(n <= 0)
              return -1;
       if(n == 1)
              money = 100;
       if(n > 1)
              money = 10 + get_money(n-1);
       return money;
}
int main()
{
       int ret;
       ret = get_money(10);
       printf("ret = %d\n",ret);
       return 0;
}

練習4: 使用循環方式完成練習3,畫內存圖分析兩種方式差別。

循環 -> 多次使用一個變量
遞歸 -> 每次都開闢一個新的變量空間 --> 容易造成棧空間溢出!

七、回調函數

1、 什麼是回調函數?
先定義好函數A(喝水),再將這個函數A作爲另外一個函數B(爬山)的參數,在函數B(爬山)可以回過頭調用這個參數(喝水),這個函數A就稱之爲回調函數。

2、 基本框架?

void funB(funA)
{
       funA.....  在函數B中調用函數A
}

void funA()  -> 回調函數
{

}

int main()
{
       funB(funA);
}

例題: 從鍵盤中獲取兩個數值,求出兩個值的和,要求使用回調函數完成。

int get_result(int a,int b,int(*p)(int,int))  -> 就把這個p當成函數名字 
{
       int ret;
       ret = p(a,b);
       return ret;
}

int add_fun(int x,int y)
{
       int z;
       z = x + y;
      return z;
}

int main()

{
       int a,b,ret;
       scanf("%d %d",&a,&b);
       ret = get_result(a,b,add_fun);  -> 函數名字作爲參數,其實是傳遞了函數的地址過來。
       printf("ret = %d\n",ret);

}

補充:
函數指針參數怎麼寫?
1)先寫一個 *
2)在*後面寫一個變量名,使用圓括號括住 (*p)
3)確定函數是哪個? int add_fun(int x,int y)
4)把第3步函數名與形參的變量名都去掉 int (int,int)
5)把第2步結果寫在第4步結果的返回值類型與形式參數列表之間 int(*p)(int,int)

3、 回調函數使用場景?
在數據庫、系統編程信號通信中常常看到回調函數。

練習4: 連續從鍵盤中獲取4個數字,求出其中的最大值,要求回調函數來完成。

int max4(int a,int b,int c,int d,int(*p)(int,int))
{
       int m;
       m = p(a,b);
       m = p(m,c);
       m = p(m,d);
       return m;
}

int max2(int x,int y)
{
       int z;
       z = (x > y ? x : y);
       return z;
}

int main()
{
      int a,b,c,d,ret;
       ret = max4(a,b,c,d,max2);
       return 0;
}

八、變參函數

1、 什麼是變參函數?
變參函數是指該函數的參數不固定,例如:printf()函數。

  printf("helloworld!\n");  -> 參數爲1
  printf("a = %d\n",a);     -> 參數爲2
  printf("(%d %d)\n",x,y);  -> 參數爲3

結論: printf()參數爲"1+x"

2、如何判斷一個函數是不是變參函數?

  #include <stdio.h>
  int printf(const char *format, ...);
   const char *format: 輸出字符串格式
   ... :  參數不定

3、 類似的變參函數有很多: printf() scanf() ioctl() fcntl()

九、內聯函數?

1、 什麼是內聯函數?
在程序中調用函數時,需要花費一定的時間進行保護現場與恢復現場,但是使用了內聯函數,就既可以使用函數,又不需要花費時間來進行保護現場與恢復現場。

封裝:test.c

#include <stdio.h>
void fun()
{
       printf("hello!\n");
}

int main()
{
       fun();
       fun();
       fun();
       return 0;
}

不封裝:test.c

#include <stdio.h>
int main()
{
       printf("hello!\n");
       printf("hello!\n");
       printf("hello!\n");
       return 0;
} 

2、 如何實現內聯函數?
test.c

int main()
{
       fun();
       fun();
       fun();
       return 0;
} 

head.h

#include <stdio.h>
inline void fun()
{
       printf("hello!\n");
}

3、如何解決保護恢復現場問題?
1)使用內聯函數 ->inline
2)不要過多封裝成函數,當某些表達式組成一個功能時,才封裝爲一個函數。

4、在嵌入式中哪裏看到內聯函數?
在內核鏈表時看到內聯函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章