iT邦幫忙

2021 iThome 鐵人賽

DAY 14
0
Software Development

三十天內用C++寫出一個小遊戲系列 第 14

Day 14 - 函式拌飯

簡介

今天會像是筆記一樣QQ,但是就是有關函式的知識!

Scope of variable (變數生命週期)

!!!IMPORTANT!!!!

Variable lifetime (變數的出生到死亡的時間)

  • Local

    在 block 裡面出生(宣告),在結束的時候死掉

    lifetime : (declaration → end of block)

    int main()
    {
    	int i = 50; // it will be hidden
    	for (int i = 0; i < 20; i++) //這個 i 也是 local variable
    	{                           //裡面的 i 會比外面的大(外面的 i 先藏起來)
    		cout << i << " "; // print 0 1 2 ... 19
    	}
    	cout << i << "\n"; //50
    	return 0;
    }
    
  • Global

    意旨不在 block 裡面宣告的:

    #include<iostream>
    using namespace std;
    
    int i = 5; //他就是一個 global variable
    
    int main()
    {
    	for (; i < 20 ; i++)
    		cout << i << " ";//?
    	cout << "\n";
    	int i = 2;
    	cout << i << "\n"; //?
    	cout << ::i << "\n"; //?
    	return 0;
    }
    

    因為它比 local variable還早宣告,所以會被他們藏起來

    像是 下面宣告了 int i = 2; 就會先 cout 2

    但是如果在那個變數前面加:: 就會找到 global variable出來。

  • External

    在大型的系統裡面,會有很多 program 在同時進行。

    如果有一個 program 想要讀取其他 program 裡面的變數,就要用這個東西:extern

    • extern int a

      這個 a 需要在別的 program 裡面宣告過(可能是 global 或是 local)

      且這兩個程式要在一起跑的狀態下extern才能作用

    但是現在已經不太喜歡extern,因為這兩個 program會使用同一個記憶體空間,會讓兩個程式無法被分割(coupling),所以整個程式會被搞得非常的複雜。

    所以 global variable 也盡量不要使用,因為他也會導致一個程式裡面每一個 function 或是 block 都 couple 在一起

  • static

    • 當 local variable 死掉的時候(block 結束的時候),記憶體會把它清掉 (recycled)

    • global 會到整個program結束後才會被清掉

    • static variable 就像是介於兩者之間,在 block 裡面被宣告,可是會到 program 結束之後才會被清掉

      static 的規則是,如果他被宣告後,那個宣告的 statement 就不會再執行了

    int test();
    int main(){
    	for (int a = 0; a < 10; a++)
    		cout << test() << " ";
    	return 0;//1, 1, ..., 1  
    }
    int test()
    {
    	int a = 0;
    	a++; 
    	return a; 
    }
    

    上面這個結果會跑出 1 1 1 1 1 1 1 ...1

    如果改寫成這樣:

    int test();
    int main(){
    	for (int a = 0; a < 10; a++)
    		cout << test() << " ";
    	return 0;//??
    }
    int test()
    {
    	static int a = 0;
    	a++; 
    	return a; 
    }
    

    卻會跑出 1 2 3 4 5 6 7 8 9 10

    回到剛剛的 static 的規則,如果他被宣告後,那個宣告的 statement 就不會再執行了。

    所以在下面的程式,執行過一次 static int a = 0; 後,就不會再執行了,所以a 就會一直累加上去了。

    那它可以拿來幹嘛?

    他可以拿來數一個 function 被呼叫了幾次。像是上面那個方式。

    為什麼不用 global?

    因為 global 有破壞模組化的缺點。

GOOD Programming style

  • need to distinguish between local and global variables
  • always try to use local variables to replace global variables
    • communicate by passing values instead of by passing variables
    • One better condition to use global variables: when you are define constants that is used by many functions.
  • we may not need static and external variables now or in the future(less people use now)

Variable initialization

  • why should we initialize local variable yet not global and static variables

    In fact, the system initializes global and static variables to 0.

    Because there are too many local variables and few global and static variables.

Though troublesome for programmers to initialize, this act actually improve the efficiency of C++ compared to other language like Python.

Advances of functions

  1. Call-by-value mechanism

    void swap(int x , int y);
    int main()
    {
    	int a = 10, b = 20;
    	cout << a << " " << b << endl;
    	swap(a, b);
    	cout << a << " " << b << endl;
    }
    void swap(int x, int y)
    {
    	int temp = x;
    	x = y;
    	y = temp;
    }
    

    如果看上面的 code,理論上 a 與 b 應該是會交換才對,但是實際上跑出來卻並沒有交換,這是為什麼?

    在C++裡面,呼叫函式的機制,是所謂的 "Call-by-value"。

    void swap(int x, int y)
    {
    	int temp = x;
    	x = y;
    	y = temp;
    }
    int main()
    {
    	int a = 10, b = 20;
    	swap(a, b)
    }
    

    如果我們把上面的程式以記憶體來看

    【Memory】

    Address Identifier Value
    - x -
    - y -
    ... - -
    - a 10
    - b 20
    1. 在 main function 裡面,記憶體會先宣告 a = 10, b = 20
    2. 接下來呼叫 swap 之後, 會宣告 x 還有 y
    3. 最後 a 跟 b 的值會被 copy 到 x 還有 y
    4. 但是當 function 結束的時候,x 還有 y就會被 released (消滅)
    5. 所以 a 跟 b 從頭到尾都沒有變過,只是把他們的值 copy 然後再傳進去function 裡面

    Why using Call-by-value mechanism but not call-by-variable?

    • Functions can be written as independent entities.
    • Modifying parameter values do not affect any other functions.
    • Thus, it is more easier to divide and modularize the work.

    In cases, we do need a callee(be called) to modify the values of some variables defined in the caller(call the callee).

    • we may "call by reference" (Future)
    • or may pass an array to a function
  2. Passing an array as an argument
    要怎麼在 function 中回傳一個 array?

    example_1

    void printArray(int[], int);
    int main()
    {
    	int num[5] = {1, 2, 3, 4, 5};
    	printArray(num, 5);
    	return 0;
    }
    void printArray(int a[], int length)
    {
    	for (int i = 0; i < length; i++)
    		cout << a[i] << " ";
    	cout << "\n";
    }
    

    為什麼不直接在 declare function 時直接宣告 array 的長度?

    • 因為當你需要不同長度的 array 時,這樣就必須要寫很多種 function 才能達到想要的功能。
    • 且 array variable 儲存的只是一串 address,所以只需要 passing an array 就只是告訴 function 要從哪裡取得那個 address。

    example_2

    void shiftArray(int[], int);  //可把 int[]想像成一個array variable
    int main()
    {
    	int num[5] = {1, 2, 3, 4, 5};
    	shiftArray(num, 5);
    	for (int i = 0; i < 5; i++)
    		cout << num[i] << " ";
    	return 0;
    }
    void shiftArray(int a[], int length)
    {
    	int temp = a[0];
    	for (int i = 0; i < length; i++)
    		a[i] = a[i + 1];
    	a[length - 1] = temp; 
    }
    

    簡單說上面的程式,就是讓 array 每一項往後移,再把第一項放到最後一項。 實際上跑出來就會是 2 3 4 5 1

    實際上在記憶體裡面,這個 function的作用,就是把指定地址上面的東西去做改變,所以最後 num array中的東西(變數)就會被改變。

    也可以傳多維陣列:

    example_3

    void printArray(int[][2], int);
    int main()
    {
    	int num[5][2] = {1, 2, 3, 4, 5}; // five 0s (default)
    	printArray(num, 5);
    	return 0;
    }
    void printArray(int a[][2], int length)
    {
    	for (int i = 0; i < length; i++)
    	{		
    		for (int j = 0; j < 2; j++)
    			cout << a[i][j] << " ";
    		cout << "\n";
    	}
    }
    

    就會跑出:

    1 2
    3 4
    5 0
    0 0
    0 0

    要注意,在傳多維 array 時,要把第二維度以上的數量傳進去,像是第一行寫的 int[ ][2] 這樣。

    可以把它想像成:

    a[0] [] []

    a[1] [] []

    a[2] [] []

    a[3] [] []

    a[4] [] []

    是一個一維陣列,總共有 5 個 elements,每一個 element 又是一個有兩個 element 的一維陣列。

  3. Constant parameters

    example_1

    int factorial (int n)
    {
    	int ans = 1;
    	for (int a = 1; a <= n; a++)
    		ans *= a;
    	return ans;
    } // n 階層
    

    在這個例子裏面,理論上 n 是不應該被 modified,那要怎麼阻止 programmer 自己去將 n 被 modified?

    答案就是:

    int fatorial (const int n)
    {
    	int ans = 1;
    	for (int a = 1; a <= n; a++)
    		ans *= a;
    	return ans;
    }
    int main()
    {
    	int n = 0;
    	cin >> n;
    	cout << factorial(n); // as usual
    	return 0;
    }
    

    因為這樣某一天,你和你一起做一個 project 的夥伴,才不會隨便亂改這個理論上不能改的 integer。

    而且就算是 constant 也沒差,因為 argument 回傳的是一個值,而不是變數本身(call-by-value)

    sometimes an argument's value in a caller may be modified in a callee: e.g., arrays.

    但有時候我們卻需要(甚至是必要)改動這個 argument

    例如 argument 是 array 的時候。

    example_2

    void printArray (const int [5], int);
    int main()
    {
    	int num[5] = {1, 2, 3, 4, 5};
    	printArray(num, 5);
    	return 0;
    }
    void printArray(const int a[5], int len)
    {
    	for(int i = 0; i < len; i++)
    		cout << a[i] << " ";
    	cout << "\n";
    }
    
  4. Function overloading 函數多載

    例如今天有一個 function 會計算 $x^y$

    • int pow(int base, int exp);

    或是

    • double powExpDouble(int base, double exp);

    但如果今天我們要兩個都是分數或是 base 是分數呢?

    如果我們在每次改動的時候都要換一個新的 function

    這樣會有一大堆 function 出生吧,所以在C++中提供了一個功能: function loading。(也就是 雖然有不同的 parameters,但還是可以使用同樣的名字)

    • int pow (int, int);
    • double pow (int, double);
    • double pow (double, int);
    • double pow (double, double);

    所以說,不同的 function 需要有不同的 function signature

    • Function signature
      • Function name

      • Function parameters(numbers of parameters and their types)

      • DO NOT INCLUDE return type! WHY?

        因為在呼叫一個函數的時候,回傳值不重要(因為還不會處理)

    example_1

    void print(char c, int num)
    {
    	for (int i = 0; i < num; i++)
    		cout << c;
    }
    
    void print(char c)
    {
    	cout << c;
    }
    

    在這個例子中,如果沒有傳入 num 就會跑下面那個 function

    或是 可以把 num 預設為 1:

  5. Default arguments

    example_1

    double circleArea(double, double = 3.14);
    //
    double circleArea(double radius, double pi)
    {
    	return radius * radius * pi;
    }
    
    • 簡單而言,default argument 會寫在 function declaration
    • default argument 只會被 assign 一次
    • default argument 會放在 parameters 的最後幾個(或是最後一個)
    • 一旦使用 default argument 後面的 default value 都會被用到
  6. Inline function

    (沒甚麼人在用)

    因為宣告函式會讓系統頗為負擔沉重,但是又不能不寫 function,所以C++中會 define inline function。

    • inline function
      • 在寫 function 的時候,前面加上 inline
      • 當 compiler 發現一個 inline function,會在 function 被呼叫的時候複製貼上到呼叫的地方。
      • 但其實沒甚麼在使用這個方法,所以就知道就好!
    #include<iostream>
    using namespace std;
    
    int gcd(int a, int b);
    int min(int a, int b);
    int main()
    {
        int a = 0, b = 0;
        cin >> a >> b;
        cout << gcd(a, b);
    }
    
    int min(int a, int b)
    {
        int temp = 0;
        if (a > b)
            return b;
        else
            return a; 
    }
    
    int gcd(int a, int b)
    {
    
    }
    

心得

函式要寫好真的...好難 ?



上一篇
Day 13 - 函式烤肉
下一篇
Day 15 - 演算大法好ㄟ
系列文
三十天內用C++寫出一個小遊戲30

尚未有邦友留言

立即登入留言