第十七篇 -- 研究下函数

基于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中访问。

原文地址:https://www.cnblogs.com/smart-zihan/p/11295033.html