• 第七章 函数


    第七章  函数

    7.1  函数的基础知识

        要使用函数,必须完成如下工作:

        Ø  提供函数定义

        Ø  提供函数原型

        Ø  调用函数

        7.1.1  函数的定义

        函数总体来说可以分为两类,一类是没有返回值的,另一类是具有返回值的,两类的函数定义的格式如下

    void functionName(parameterList)
    {
        statement(s)
        return;            //可以有也可以没有
    }
    typeName functionName(parameterList)
    {
        statement(s)
        return value;    // value 的类型是typeName或者可以自动转换为typeName
    }

        C++对于返回类型有限制,不能是数组,但可以是整型、浮点型、指针、结构和对象。函数内在遇到第一个返回语句将结束。     有返回值的函数,必须使用返回语句,返回值可以是常量、变量或者表达式。其结果必须可以转化为typeName类型。

        7.1.2  函数原型和函数调用

        函数原型描述了函数到编译器的接口,把函数的返回类型、参数列表(数量,类型和顺序)告知编译器。

        函数原型是一条语句,必须以分号结束,可以通过复制函数头并添加分号来快速构建函数原型。

        函数原型不要求提供变量名,但是拥有变量名的函数原型可以提高程序的可读性。

        函数原型的作用有3个:

        Ø  编译器正确处理函数返回值;

        Ø  编译器检查使用的参数数目是否正确;

        Ø  编译器检查使用的参数类型是否正确,如果不正确,则转化为正确的类型(如果可能)。

        7.1.3  ANSI C和C++ 的函数原型的区别

        在ANSI C中函数原型是可选的,而在C++中函数原型是必须的。

        在C++中,圆括号中参数列表为空与在圆括号中使用void是等效的,意味着没有参数。在ANSI C中圆括号为空意味着不指出参数,将在后面定义参数列表。C++不指定参数列表应该使用省略号(…),例如:

    void say_bye(...);        //不指出参数,将在后面的定义中指出参数列表

        C++中,通常仅当与可变参数C函数交互时才这样做。

    7.2  函数参数与按值传递

        按值传递参数时,将数值参数传递给函数,而后将其赋值给新变量。如:

    double cube(double x);
    double a = 1.0;
    double volume = cube(a);

        函数cube被调用时,将创建一个名为x的新变量,并初始化为a的值1.0。这样在函数内部对x的修改,将不会改变a的值。

        形参:接收传递值的变量。

        实参:传递给函数的值。

        C++使用参数(argument)来表示实参,参量(parameter)来表示形参。

        函数中声明的变量是函数私有的,函数被调用是,为这些变量分配内存,在函数结束时,释放这些内存。这样的变量成为局部变量。存储类型为自动存储。

    7.3  函数和数组

        看如下原型:

    int sum_arr(int arr[], int n)    //arr是数组,n是数组元素数量

        参数arr是一个数组,但实际上在函数内部,arr并不是数组,而是一个指针。

        在C++中,当且仅当用于函数头或函数原型时,int arr[]和int * arr等价。但是int arr[]可以具有提醒的作用,表示传递的指针为数组第一个元素的地址。

        当把数组第一个元素的地址和数组元素的数目传递给函数时,并没有将函数的内容传递给函数,而是将数组第一个元素的地址,数组元素的类型和数组元素的数目传递给了函数。当传递常规变量时,函数将使用该变量的拷贝,淡传递数组时,函数将直接使用该数组,这一位置对数组内容的改变会改变原来的数组。

        实际上传递数组时,所传递的地址也可以看做为普通变量,函数将使用指针的拷贝,把拷贝的指针指向其他地址,将不会修改原来的指针值。

        传递数组地址可以节省复制整个数组内容所需的时间和内存,但是另一方面会对原数据带来风险。ANSI C和C++使用const限定符来解决这个问题。如下:

    void show_arr(const double arr[], int n);        //参数为指向const的指针
    void show_arr(const double *arr, int n);        //与上一个完全等价

        这两个函数将不能在函数内部修改数组的内容,任何赋值操作将会被编译器报告错误。

        可以用2种方式将关键字const用于指针。

        1.        使指针指向常量;

        2.        将指针本身声明为常量。

        如下代码:

    int n = 10;
    const int * p1 = &n;    //可以修改p1的值,但是不可以修改*p1的值
    int * const p2 = &n;    //不可以修改p2的值,但是可以修改*p2的值

        可以将const变量或者非const变量的地址赋值给指向const的指针,但是只可以将非const变量的地址赋值给常规指针。

        当在函数内部不修改指针或者数组的内容时,应当使用指向const的指针。有2个理由:

        Ø  可以避免无意的修改而导致的错误

        Ø  使用const作为形参,可以接受const和非const实参,否则只能接受非const实参。

    7.4  函数和二维数组

        看如下原型:

    int sum(int m[][4], int size);    //size指定行数,4为列数
    int sum(int(*m)[4], int size);    //与上一个等价

        上面的int (*m)[4]的圆括号必不可少。带括号表示m是一个数组,数组内元素的类型是指向含有4个元素数组的指针;不带括号表示m是一个指针的数组,作为形参就是int **类型。原因是[]运算符的优先级高于*,不加括号时,[]先作用于m,表示m是一个具有4个元素的数组,而元素的类型是int *;加括号时,*先作用于m,表示m是一个指针,所指类型为一个具有4个int型元素的数组。

    7.5  函数和C-风格字符串

        表示字符串的方式有3种:

        Ø  Char数组

        Ø  用引号括起的字符串常量

        Ø  被设置为字符串地址的char指针

        C-风格字符串内置有’’,表示字符串的结尾,因此作为参数可以不用将元素的数量传递给函数。

    7.6  函数和结构

        函数处理结构和处理基本类型的变量类似。

        需要注意的是,当结构很大时,应该选用指向const的指针来代替直接传递结构,这样做可以提高效率。

    7.7  函数与array对象

        Array为C++11的模板类。将array对象作为参数传递给函数时,将拷贝array的内容。

    7.8  递归

        C++函数可以自己调用自己,与C语言不同的是C++不允许main函数调用自己。这种功能成为递归。一般的格式如下:

    void recurs(argumentList)
    {
        statements1
        if (test)
        {
            recurs(arguments)
        }
        statements2
    }

       这里如果test值为false时,调用链将结束。

    7.9  函数指针

        函数指针的好处是可以将函数名作为参数传递给函数,这样可以给函数传递策略。当修改策略时,不用修改函数本身,只需要传递不同的函数指针实参,这就是委托协议。

        使用函数指针必须完成以下工作:

        Ø  获取函数地址

        Ø  声明一个函数指针

        Ø  使用函数指针调用函数

        1) 获取函数地址

          获取函数地址就是把函数名作为参数传递给其他函数,如:

    process(think);        //把think函数地址传递给process
    thought(think());    //把think函数的返回值传递给thought


         
    2) 声明函数指针

          声明方法如下:

    double f(int);            //f是函数
    double(*pf1) (int);        //pf1是函数指针
    pf1 = f;                //为函数指针赋值
    double(*pf2) (int) = f;    //声明时初始化

         通常,为了声明特定类型的函数指针,可以首先编写函数原型,再用(*pf)替换函数名,这样pf就是这种类型的函数的指针。

        3) 使用函数指针调用函数

        使用函数指针有两种方式调用函数。一种是使用(*pf),另一种是直接使用pf。如下:

    double x = f(4);               //通过函数名调用函数
    double y = (*pf1)(5);         //通过函数指针调用函数
    double z = pf2(6);           //函数指针名直接调用函数

         实际上函数名就是函数的起始地址,C++折衷考虑使用两种方式都可以调用函数。

    7.10  深入探讨函数指针

        先看下面几个等价的函数:

        const double * f1(const double arr[], int n);
        const double * f2(const double[], int);
        const double * f3(const double *, int);

         声明函数指针并赋予初值:

    const double * (*p1) (const double*, int) = f1;

         可以使用C++11的自动类型推断:

    auto p2 = f2;

         函数指针数组声明如下:

    const double * (*pa[3]) (const double*, int) = { f1, f2, f3 };

         注意这里[3]的位置。这里不用为*pa加括号是因为[]运算符的优先级高于*运算符。[]首先作用于pa,表示pa是一个具有3个元素的数组,元素的类型是函数指针。这里也不能用auto自动类型推断。因为自动类型推断只能用于单值初始化,而不能用于初始化列表。但是可以声明同样类型的数组:

    auto pb = pa;

         Pa和pb都是指向函数指针的指针,可以这样调用:

    double av[3] = { 1.0, 2.0, 3.0 };
    const double * px = pa[0](av, 3);
    const double * py = (*pb[0])(av, 3);
    double x = *pa[1](av, 3);
    double y = *(*pb[1])(av, 3);

         可以使用自动类型推断简单声明指向整个数组的指针:

    auto pc = &pa;

        Pc的类型为

    const double * (*(*pd)[3]) (const double*, int) = &pa;

        使用typedef简化声明:

    typedef double real;    //real是double的别名
    typedef const double * (*p_fun) (const double*, int);    //p_fun是函数指针类型
    p_fun p1 = f1;
    p_fun pa[3] = { f1, f2, f3 };
    p_fun(*pd)[3] = &pa;

        这里再说一下使用auto的利弊:

        利:使用auto可以简化声明;

        弊:可能提供错误的初值,使声明的类型不正确。

  • 相关阅读:
    MyBatis映射文件中用#和$传递参数的特点
    使用谷歌浏览器进行Web开发技巧
    YYYY-mm-dd HH:MM:SS 备忘录
    java通过UUID生成16位唯一订单号
    idea如何设置类头注释和方法注释
    如何用符号构建人的思维系统?
    临界点思维模型
    复利思维模型-拥抱人生的指数增长
    提升自我认知的有效方式
    如何去培养顶尖的思维模型?
  • 原文地址:https://www.cnblogs.com/Dream-Fish/p/3963467.html
Copyright © 2020-2023  润新知