zoukankan      html  css  js  c++  java
  • 第十七篇 -- 研究下函数

    基于C++宝典学习。关于前面的函数组成,定义,声明,调用就不讲了。这里从指针和引用参数部分讲起。

    函数已传值的方式传递参数,传递的只是实参的值,而不是实参本身。这样做一方面会有效率的问题,因为对于大的数据类型有一次赋值过程;另一方面在函数中也并不能改变实参的值,因为函数中只是构建了实参的一个副本。用指针和引用作为参数,可以弥补上述不足。

    一、指针参数

    在函数的参数列表中,可以使用指针类型的参数。传递给指针参数的实参可以是一个指针变量,也可以是一个变量的地址。例如,下面函数的参数就是一个int类型的指针:

    void function(int *);

    调用函数function时也必须传入一个int型的指针变量。例如:

    int *p;                     //定义一个int型指针变量
    .....                       //某些操作改变了p的值
    function(p);                //以p作为实参,调用函数function
    int iVal = 0;               //定义整型变量
    function(&iVal);            //获取整型变量的地址,并以指针的形式传递给函数
    View Code

    使用指针作为参数可以弥补值传递的不足:

      1. 提高了传递参数的效率

      2. 函数可以修改实参指针所指变量的值

    对于比较大的数据类型,例如定义了很多数据成员的结构体,假设是100个整型数成员。如果将这样的结构体变量作为参数来传递,则在函数内部也要构建同样的一个结构体变量,并将其成员一次赋值。这样在内存中就会有两份同样的结构体变量,占据了800(200 * 4)个字节的内存。这无论在空间和时间上都是巨大的浪费。类也是如此。其实在C++中,结构体与类的差别很小。

    如果用指针传递,则可以消除值传递带来的空间和时间上的浪费。这主要是因为指针的值是一个地址值(在32位系统中是一个4字节的整型数),参数传递时只需要传递这个地址值即可。

    提示:用指针作为参数,遵从的也是值传递的原则。实际传递的值是指针的值,而不是指针所指变量的值。

    例如,对于SomeStruct这样一个大的结构体,用指针作为参数可以写为:

    void function(SomeStruct *p);

    而用SomeStruct变量作为参数调用function函数时可以写为:

    SomeStruct a;        //定义一个SomeStruct变量
    ......               //定义变量a的操作
    SomeStruct *p = &a;  //定义一个SomeStruct类型的指针,并指向上述变量(获取a的地址值)
    function(p);         //用SomeStruct类型的指针作为实参,调用function函数
    View Code

    这样在调用函数时,函数就可以只构造一个指针变量的副本,而不用构造整个结构体的副本,大大节省了内存,而且省去了给每个数据成员赋值的时间,也大大提高了时间效率。用指针作为参数还有一个好处,就是函数可以修改指针实参所指变量的值。这是因为通过指针参数传递的是变量的地址,在函数内可以通过这个地址获取变量,从而也就可以修改变量的地址。例如:

    void function(int *p)   //定义函数function
    {
        *p = 123;           //获取指针形参所指变量,并给该变量赋值
    }
    ......
    int a = 0;              //定义整型变量a,并初始化为0
    function(&a);           //将变量a的地址传递给function函数
    cout << a;              //输出变量a的值
    View Code

    上述程序将输出123,而不是0。这就是因为在函数内部将变量a的值修改了。而如果参数int类型的,则达不到这种效果。下面的例子定义一个函数,交换两个变量的值。由于使用普通类型的参数不能修改实参的值,所以可以用指针作为参数。

    void swap(int *a, int *b) {
        int temp = *a;                            //用一个临时变量保存a指针所指变量的值
        *a = *b;                                  //将b指针所指变量的值赋给a指针所指的变量
        *b = temp;                                //将临时变量的值赋给b指针所指的变量
    }
    
    int main(int argc, char *argv[]) {
        cout << "——指针参数——" << endl;            //输出提示信息
        cout << "请输入两个整型数:" << endl;
        int a = 0, b = 0;                         //保存用户输入的变量
        cin >> a;                                 //输入
        cin >> b;
        swap(&a, &b);                             //调用交换函数
        cout << "交换后:" << a << "	" << endl;   //输出交换后的值
        system("PAUSE");                          //等待用户反应
        return EXIT_SUCCESS;
    }
    View Code

    在swap函数的定义中,用指针作为参数,避免了使用普通类型的参数,从而达到了交换变量值的目的。其实看到这里,或许会产生疑问,之前学过引用,那个也可以改变变量的值,和指针好像啊,对不对,然后回头去看了下引用,引用其实是一个变量实体的别名。指针可以重新赋值指向新的对象,引用在初始化之后就不可以改变指向对象了。然后呢,通过冒泡排序,使用这两种方法,以示区别。compare引用,swap指针

    #include "pch.h"
    #include <iostream>
    using namespace std;
    
    #define len 10  //define array length equal 10
    
    //function prototype
    void Method_one();
    void Method_two();
    void compare(int &a, int &b);
    void swap(int *a, int *b);
    
    int break_node = 0;//when break_node is true, jump the loop
    
    int main()
    {
        //Bubble sort
        //Method_one();
        Method_two();
        
    }
    
    void Method_one() {
        //define an array which length is 10
        int array_sort[len];
    
        //input array value
        cout << "Please input " << len << " number: " << endl;
        for (int i = 0; i < len; i++)
            cin >> array_sort[i];
    
    
        //start sort
        for (int m = len; m > 0; m--) {
            for (int j = 0; j < m - 1; j++) {
                compare(array_sort[j], array_sort[j + 1]);
            }
            if (break_node) {
                break_node = 0;
            }
            else {//while no change data in inner loop, jump out the external loop
                break;
            }
        }
    
        //output sorted array(high-->low)
        cout << "Sorted array: " << endl;
        for (int k = 0; k < len; k++) {
            cout << array_sort[k] << " ";
        }
        cout << endl;
    }
    
    void Method_two() {
        int length = 0;
        cout << "Please input the length of array : " << endl;
        cin >> length;
        cout << "Please input " << length << " values" << endl;
        int *pArr = new int[length];
    
        //input array value
        for (int i = 0; i < length; i++)
            cin >> pArr[i];
    
        //start sort
        for (int m = length; m > 0; m--) {
            for (int j = 0; j < m - 1; j++) {
                compare(pArr[j], pArr[j + 1]);
            }
            if (break_node) {
                break_node = 0;
            }
            else {//while no change data in inner loop, jump out the external loop
                break;
            }
        }
    
        //output sorted array(high-->low)
        cout << "Sorted array: " << endl;
        for (int k = 0; k < length; k++) {
            cout << pArr[k] << " ";
        }
        cout << endl;
    
        delete pArr;
    }
    
    void compare(int &a, int &b) {
        if (a >= b);
        else {
            //int temp;
            //temp = a;
            //a = b;
            //b = temp;
            //break_node++;
            swap(&a, &b);
            break_node++;
        }
    }
    
    //change two variable position
    void swap(int *a, int *b) {
        int temp = *a;
        *a = *b;
        *b = temp;
    }
    View Code

    二、数组参数

    用数组作为函数的参数,即函数的形参是数组。例如:

    void function(int a[10]);

    上述的函数声明表示function函数接受一个整型数组作为参数。但是,用数组作为函数参数时,数组的长度是没有意义的,也就是说上述的声明同以下的声明是等效的:

    void function(int a[]);

    void function(int a[100]);  //数组长度没有意义,所以a[10]同a[100]是等效的

    所以在定义函数时,不能依赖于数组参数中数组的长度。实际上,编译器会自动将数组参数作为指针进行解释,这个指针指向一块儿连续的内存。而这样的一个指针中不会保存长度信息,所以函数声明时,数组参数的长度是没有意义的。为了弥补这个缺点,可以在参数列表中在附加一个参数,用以传递数组的长度,例如:

    void function(int a[], int n);

    其中第二个参数n就是数组的长度。

    说明:定义函数时数组参数作为指针使用,而这个指针就指向数组实参的首地址,也就是数组中第一个元素的地址。

    正因为在定义函数时,数组参数被当做指针,所以数组参数也可以用指针参数表示:

    void function(int *p);

    这个声明同function(int a[])的声明是等价的。一般来讲,如果用数组作为函数的参数,则函数调用时应当将一个数组作为实参。因为数组参数在函数定义时被当做指针,所以也可以将指针作为实参。至于这个指针到底 指向什么样的内存,则是由调用者确定的。用数组作为实参,则传递给函数的是数组名,调用过程如下:

    int a[] = {1, 2, 3};   //定义数组

    function(a);           //以数组名作为实参,调用函数function

    或者也可以先取得数组的首地址,作为指针传递给函数:

    int a[] = {1, 2, 3};    //定义数组

    int *p = a;    //获取数组首地址,也可以写成int p = &a[0];

    function(p);   //以指向数组首地址的指针作为实参,调用函数function

    因为数组参数在定义函数时被当做指针使用,传递的值是数组的首地址。所以在函数内如果修改了数组参数中某个元素的值,也就修改了对应的数组实参中元素的值。C++程序中常用的排序函数就利用了数组参数的这个特性,它在函数中对数组参数进行排序,函数运行完后,数组中的元素就是排序后的结果。

    三、函数中的变量

    1. 多个源文件共享全局变量

    全局变量的作用域虽然是整个程序,但在使用时仍然有特殊的要求。假设有两个源文件file1.cpp和file2.cpp,其中file1.cpp中定义了一些全局变量,如果file2.cpp中的函数要使用这些全局变量,则必须在使用前声明。声明的方法是:

    extern 类型 全局变量名;

    其中extern关键字表明这个全局变量是在别的文件中定义的,需要在本文件中使用。例如,假设在file1.cpp中定义了如下的全局变量:

    ///////////////////////////////////////////////////////////////////////
    ////////file1.cpp 源文件1
    int gVal1 = 0;
    double gVal2 = 0.0;

    则在file2.cpp中要使用上述两个变量时,必须做如下的声明:

    ////////////////////////////////////////////////////////////////////
    /////////file2.cpp
    extern int gVal1 = 0;
    extern double gVal2 = 0.0;

    当心:在全局变量前加上extern关键字只是用来声明一个全局变量,而不是定义全局变量。如果只有extern声明,没有定义,则全局变量仍然不存在。编译器会报告“某某全局变量没有定义”的错误。

    2. 静态变量

    静态变量分两种,一种是函数内的静态变量,一种是全局的静态变量,其特点是变量定义时带有static关键字。例如:

    static int gVar;     //在函数外,定义全局的静态变量
    
    void function()
    {
      static int iVar;    //在函数内,定义函数内的静态变量
      ...
    }

    函数内的静态变量也称为局部静态变量,其作用域只限于函数内部,别的函数不能访问。局部静态变量的生命周期同全局变量一样,在整个程序运行期间都有效。例如:

    void function()
    {
      static int iVal = 0;
      cout <<iVal++ << ',';
    }
    ...
    for(int i = 0; i < 3; i++)
    {
      function();
    }

    上述程序运行时将输出“0,1,2”,而不是“0,0,0”。这是因为iVal是一个局部静态变量,其生命周期在程序运行期间一直有效,所以函数function每一次运行结束后,并没有销毁iVal。而且,函数function每一次访问到的iVal的值都是上一次函数运行的结果。例如,function第一次运行后,iVal的值是1,第二次运行访问iVal的值就是1,同样第三次访问到的值就是2。

    基于这个特性,可以利用局部静态变量保存每一次函数运行时的状态,以供函数再次被调用时使用。虽然全局变量也可以做到这一点,但是任何函数都可以访问,不利于控制。而局部静态变量只有本函数能够访问,可以有效的限制其作用域。例如,某些严格的安全系统对用户试图登录的次数有限制,可以用静态变量记录这个次数,超过限定次数后则阻止用户继续尝试,用全局变量则给了其他函数修改变量的机会,不符合安全性的要求。

    #include "pch.h"
    #include <iostream>
    using namespace std;
    
    
    void login();
    
    int main()
    {
        for (int i = 0; i < 4; i++) {
            login();    //调用登录函数
        }
        return 0;
    }
    
    void login() {
        static int sLogNum = 0;    //记录登录次数的静态变量
        sLogNum++;                 //递增登录次数
        if (sLogNum > 3) {
            cout << "登录次数已超过3次,不允许登录!" << endl;      //输出禁止信息
        }
        else {
            cout << "登录成功" << endl;
        }
    }
    View Code

    3. 全局静态变量

    同一般全局变量类似,全局静态变量也是在函数外部定义的变量,只是定义之前带有static关键字。例如:

    static int gVar;

    跟一般全局变量不同的是:全局静态变量的作用域仅限于定义这个变量的源文件,而不是整个程序。例如,假设程序中有file1.cpp和file2.cpp两个源文件,其中file1.cpp中定义了全局静态变量gVar,则在file2.cpp中试图访问gVar时,会遇到一个编译错误:“变量gVar未定义”,这就是因为gVar是定义在file1.cpp中的全局静态变量,只能在file1.cpp中访问。

  • 相关阅读:
    网站测试中如何做好安全性测试
    Web安全性测试总结
    文件上传验证绕过技术总结
    Burp Suite使用介绍
    Burpsuite教程与技巧之HTTP brute暴力破解
    burpsuite绕过本地javascripte上传文件
    文件上传漏洞演示脚本之js验证
    上传验证绕过
    Burp Suite详细使用教程
    关于post和get传递参数的区别
  • 原文地址:https://www.cnblogs.com/smart-zihan/p/11295033.html
Copyright © 2011-2022 走看看