• c++学习(四):函数


    六、 函数
    1. 函数调用做了两件事情:用对应的实参初始化函数的形参,并将控制权转移给被调用函数。主调函数的执行被挂起,被调函数开始执行。函数的运行以形参的(隐式)定义和初始化开始。当执行到 return 语句时,函数调用结束。被调用的函数完成时,将产生一个在return 语句中指定的结果值。执行 return 语句后,被挂起的主调函数在调用处恢复执行,并将函数的返回值用作求解调用操作符的结果,继续处理在执行调用的语句中所剩余的工作。


    2. 函数不能返回另一个函数或者内置数组类型,但可以返回指向函数的指针,或指向数组元素的指针的指针。


    3. 函数形参表可以为空,但不能省略。没有任何形参的函数可以用空形参表或含有单个关键字 void 的形参表来表示。


    4. 每次调用函数时,都会重新创建该函数所有的形参,此时所传递的实参将会初始化对应的形参。形参的初始化与变量的初始化一样:如果形参具有非引用类型,则复制实参的值,如果形参为引用类型,则它只是实参的别名。


    5. 函数的形参可以是指针,此时将复制实参指针。如果函数将新指针赋给形参,主调函数使用的实参指针的值没有改变。事实上被复制的指针只影响对指针的赋值。如果函数形参是非 const 类型的指针(将指针变量的内容复制一份给形参),则函数可通过指针实现赋值,修改指针所指向对象的值。如果需要保护指针指向的值,则形参需定义为指向 const 对象的指针。


    6. 复制实参并不是在所有的情况下都适合,不适宜复制实参的情况包括:
      • 当需要在函数中修改实参的值时。
      • 当需要以大型对象作为实参传递时。对实际的应用而言,复制对象所付出的时间和存储空间代价往往过大。
      • 当没有办法实现对象的复制时。
    对于上述几种情况,有效的解决办法是将形参定义为引用或指针类型。


    7. 函数定义:

    vector<int>::const_iterator find_val(vector<int>::const_iterator beg,vector<int>::const_iterator end,int value,vector<int>::size_type &occurs){
      vector<int>::const_iterator res_iter=end;
      occurs=0;
      for(;beg!=end;++beg){
        if(*beg==value){
          if(res_iter==end)
            res_iter=beg;
          ++occurs;
        }
      }
      return res_iter;
    }

    函数调用:

    vector<int> v;
    v.push_back(2);……
    vector<int>::size_type o=0;
    vector<int>::const_iterator it=find_val(v.begin(),v.end(),2,o);

    8. 非 const 引用形参(int &val)只能与完全同类型的非 const 对象关联。应该将不需要修改的引用形参定义为 const 引用。普通的非 const 引用形参在使用时不太灵活。这样的形参既不能用 const 对象初始化,也不能用字面值或产生右值的表达式实参初始化。

    // function takes a non-const reference parameter
    int incr(int &val)
    {
      return ++val;
    }
    int main()
    {
      short v1 = 0;
      const int v2 = 42;
      int v3 = incr(v1); // error: v1 is not an int
      v3 = incr(v2); // error: v2 is const
      v3 = incr(0); // error: literals are not lvalues
      v3 = incr(v1 + v2); // error: addition doesn't yield an lvalue
      int v4 = incr(v3); // ok: v3 is a non const object type int
    }

    9. 传递指向指针的引用

    // swap values of two pointers to int
    void ptrswap(int *&v1, int *&v2)
    {
        int *tmp = v2;
        v2 = v1;
        v1 = tmp;
    }

    形参:int *&v1的定义应从右至左理解:v1 是一个引用,与指向 int 型对象的指针相关联。也就是说,v1 只是传递进 ptrswap 函数的任意int类型指针的别名。


    10. 数组形参:如果要编写一个函数,输出int型数组的内容,可用下面三种方式指定数组形参: 

    // three equivalent definitions of printValues ,形参类型都是 int*。
      void printValues(int*) { /* ... */ }
      void printValues(int[]) { /* ... */ }
      void printValues(int[10]) { /* ... */ }编译器忽略为任何数组形参指定的长度。

    如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身。在这种情况下,数组大小成为形参和实参类型的一部分。编译器检查数组的实参的大小与形参的大小是否匹配。

    void printValues(int (&arr)[10])

    和其他数组一样,多维数组以指向 0 号元素的指针方式传递。多维数组的元素本身就是数组。除了第一维以外的所有维的长度都是元素类型的一部分,必须明确指定。

    int *matrix[10]; // array of 10 pointers
    int (*matrix)[10]; // pointer to an array of 10 ints

    11. 省略符形参:省略符暂停了类型检查机制。它们的出现告知编译器,当调用函数时,可以有 0 或多个实参,而实参的类型未知。

    void foo(parm_list, ...);
    void foo(...);

    大部分带有省略符形参的函数都利用显式声明的参数中的一些信息,来获取函数调用中提供的其他可选实参的类型和数目。


    12. 函数的返回值用于初始化在调用函数处创建的临时对象。在求解表达式时,如果需要一个地方储存其运算结果,编译器会创建一个没有命名的对象,这就是临时对象。
    用函数返回值初始化临时对象与用实参初始化形参的方法是一样的。如果返回类型不是引用,在调用函数的地方会将函数返回值复制给临时对象。当函数返回非引用类型时,其返回值既可以是局部对象,也可以是求解表达式的结果。
    当函数返回引用类型时,没有复制返回值。相反,返回的是对象本身。当函数执行完毕时,将释放分配给局部对象的存储空间。此时,对局部对象的引用就会指向不确定的内存。返回引用的函数返回一个左值。

      const string &shorterString(const string &s1, const string &s2)
       {
        return s1.size() < s2.size() ? s1 : s2;
      }

    13. 函数声明由函数返回类型、函数名和形参列表组成。形参列表必须包括形参类型,但是不必对形参命名。这三个元素被称为函数原型,函数原型描述了函数的接口。
    定义函数的源文件应包含声明该函数的头文件。


    14. 默认实参:默认实参是通过给形参表中的形参提供明确的初始值来指定的。程序员可为一个或多个形参定义默认值。但是,如果有一个形参具有默认实参,那么,它后面所有的形参都必须有默认实参。函数调用的实参按位置解析,默认实参只能用来替换函数调用缺少的尾部实参。 

    string screenInit(string::size_type height = 24,string::size_type width = 80,char background = ' ' );

    既可以在函数声明也可以在函数定义中指定默认实参。但是,在一个文件中,只能为一个形参指定默认实参一次。通常,应在函数声明中指定默认实参,并将该声明放在合适的头文件中。如果在函数定义的形参表中提供默认实参,那么只有在包含该函数定义的源文件中调用该函数时,默认实参才是有效的。


    15. 自动对象:只有当定义它的函数被调用时才存在的对象称为自动对象。自动对象在每次调用函数时创建和撤销。对于未初始化的内置类型局部变量,其初值不确定。


    16. 静态局部对象:一个变量如果位于函数的作用域内,但生命期跨越了这个函数的多次调用,这种变量往往很有用。则应该将这样的对象定义为static(静态的)。


    17. 内联函数:将函数指定为 inline 函数,(通常)就是将它在程序中每个调用点上“内联地”展开。
    内联函数应该在头文件中定义,这一点不同于其他函数。


    18. 类的成员函数:类的所有成员都必须在类定义的花括号里面声明,此后,就不能再为类增加任何成员。类的成员函数必须如声明的一般定义。类的成员函数既可以在类的定义内也可以在类的定义外定义。编译器隐式地将在类内定义的成员函数当作内联函数。
    每个成员函数都有一个额外的、隐含的形参将该成员函数与调用该函数的类对象捆绑在一起。每个成员函数都有一个额外的、隐含的形参 this。在调用成员函数时,形参 this 初始化为调用函数的对象的地址。

    const成员函数:改变了隐含的 this 形参的类型,用这种方式使用 const 的函数称为常量成员函数。由于 this 是指向 const 对象的指针,const 成员函数不能修改调用该函数的对象。const 对象、指向const对象的指针或引用只能用于调用其const成员函数,如果尝试用它们来调用非 const 成员函数,则是错误的。

    构造函数:构造函数通常应确保其每个数据成员都完成了初始化。
      Sales_item(): units_sold(0), revenue(0.0) { }
    构造函数的初始化列表:为类的一个或多个数据成员指定初值。构造函数的初始化式是一系列成员名,每个成员后面是括在圆括号中的初始值。多个成员的初始化用逗号分隔。具有类类型的成员皆被其默认构造函数自动初始化。

    如果没有为一个类显式定义任何构造函数,编译器将自动为这个类生成默认构造函数。对于具有类类型的成员,则会调用该成员所属类自身的默认构造函数实现初始化。内置类型成员的初值依赖于对象如何定义。如果对象在全局作用域中定义(即不在任何函数中)或定义为静态局部对象,则这些成员将被初始化为 0。如果对象在局部作用域中定义,则这些成员没有初始化。除了给它们赋值之外,出于其他任何目的对未初始化成员的使用都没有定义。


    通常将类的声明放置在头文件(type.h)中。大多数情况下,在类外定义的成员函数则置于源文件(type.cc,必须包含type.h头文件)中。

    19. 重载函数:函数不能仅仅基于不同的返回类型而实现重载。
    如果两个函数声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。如果两个函数的形参表完全相同,但返回类型不同,则第二个声明是错误。

    在函数中局部声明的名字将屏蔽在全局作用域内声明的同名名字。如果局部地声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数。由此推论,每一个版本的重载函数都应在同一个作用域中声明。

    20. 函数重载确定,即函数匹配是将函数调用与重载函数集合中的一个函数相关联的过程。
    匹配结果有三种可能:
      1. 编译器找到与实参最佳匹配的函数,并生成调用该函数的代码。
      2. 找不到形参与函数调用的实参匹配的函数,在这种情况下,编译器将给出编译错误信息。
      3. 存在多个与实参匹配的函数,但没有一个是明显的最佳选择。该调用具有二义性。

      1. 候选函数: 与被调函数同名的函数,并且在调用点上,它的声明可见。
      2. 可行函数:函数的形参个数与该调用的实参个数相同;每一个实参的类型必须与对应形参的类型匹配,或者可被隐式转换为对应的形参类型。
      3. 最佳匹配:实参类型与形参类型越接近则匹配越佳。
        其每个实参的匹配都不劣于其他可行函数需要的匹配。
        至少有一个实参的匹配优于其他可行函数提供的匹配。

    为了确定最佳匹配,编译器将实参类型到相应形参类型转换划分等级。转换等级以降序排列如下:
      1. 精确匹配。实参与形参类型相同
      2. 通过类型提升实现的匹配
      3. 通过标准转换实现的匹配
      4. 通过类类型转换实现的匹配


    对于任意整型的实参值,int 型版本都是优于short型版本的较佳匹配。


    仅当形参是引用或指针时,形参是否为const才有影响。注意不能基于指针本身是否为 const 来实现函数的重载。


    21. 指向函数的指针:函数指针是指指向函数而非指向对象的指针。函数类型由其返回类型以及形参表确定,而与函数名无关。函数指针只能通过同类型的函数或函数指针或0值常量表达式进行初始化或赋值。 

      bool *pf(const string &, const string &);
      typedef bool (*cmpFcn)(const string &, const string &);//用 typedef 简化函数指针的定义

    在引用函数名但又没有调用该函数时,函数名将被自动解释为指向函数的指针。
    指向函数的指针可用于调用它所指向的函数。可以不需要使用解引用操作符,直接通过指针调用函数

      cmpFcn pf = lengthCompare;
      lengthCompare("hi", "bye"); // direct call
      pf("hi", "bye"); // equivalent call: pf1 implicitly dereferenced
      (*pf)("hi", "bye"); // equivalent call: pf1 explicitly dereferenced

    函数指针形参

      void useBigger(const string &, const string &,bool(const string &, const string &));
      // equivalent declaration: explicitly define the parameter as a pointer to function
      void useBigger(const string &, const string &,bool (*)(const string &, const string &));

    返回指向函数的指针
    允许将形参定义为函数类型,但函数的返回类型则必须是指向函数的指针,而不能是函数。具有函数类型的形参所对应的实参将被自动转换为指向相应函数类型的指针。但是,当返回的是函数时,同样的转换操作则无法实现。  

    int (*ff(int))(int*, int);
    typedef int (*PF)(int*, int); PF ff(int);
  • 相关阅读:
    【机器学习】:梯度提升决策树(GBDT)
    【推荐系统】:LFM算法解析
    【SQL】:内连接,自然连接
    【SQL】:保留小数点后几位(除法)
    Ubuntu 18.04:磁盘读取性能不佳
    skynet超时机制实现
    关于 Spring Boot 中创建对象的疑虑 → @Bean 与 @Component 同时作用同一个类,会怎么样?
    记录不存在则插入,存在则更新 → MySQL 的实现方式有哪些?
    记一次线上问题 → 对 MySQL 的 ON UPDATE CURRENT_TIMESTAMP 的片面认知
    WebSocket
  • 原文地址:https://www.cnblogs.com/yshb/p/2725586.html
Copyright © 2020-2023  润新知