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)
    {
    
    }
    

心得

函式要写好真的...好难 ?



<<:  企划实现(11)

>>:  第五章之二

为了转生而点技能~day1:javascript 起手篇(RHS、LHS、语法作用域

本系列是为了转生,为了点技能而解任务的攻略提示,皆无营利、亦非营利取向。 Javascript:属於...

[烧烤吃到饱-2] 好好吃肉韩式烤肉吃到饱-台中公益店 #中秋节烤肉精选店家

这样的食材,才299吃到饱,别挑剔了啦~ 这家好好吃肉,就位在前几天分享过的「咕咕家」正对面。 好好...

【Day24】I2C Master 的实现及验证(最终章)

今天,我们要来完成整个 I2C 的最後一个部份了! 先来看看这个 I2C Master write ...

D29-(9/29)-广达(2382)-有肉松之称的电脑代工厂

注:发文日和截图的日期不一定是同一天,所以价格计算上和当日不同,是很正常的。 声明:这一系列文章并无...

Day26. Blue Prism取号一把罩–BP自动取得订单编号

一般订购的程序都是由下订单开始, 接着取单号为依据来分批或批次采购相关物资, 因此订单编号有举足轻重...