C++ Primer第六章②

C++ Primer

第六章 函数

参数传递

前面我们学过,每次调用函数的时候,都会重新创建它的形参,并用传入的实参去初始化它。记住这句话:形参的类型决定了形参和实参交互的方式:

  • 形参为引用:形参绑定到实参上,形参就是实参的绰号,是同一个人。
  • 形参为值类型(不加引用),形参和实参是两个独立的对象,只不过是实参的值拷贝给形参了。

    传值参数

    我们来看一下传值的时候发生了什么,我随手写一段程序,只是为了说明问题哈:
    void f(int val)
    {
      val = val - 1;
    }
    int main()
    {
      int a = 2;
      f(a);
      cout << a << endl;
      return 0;
    }
    
    main函数中对a调用了一个减1的函数f,但是最后打印出来的并没有减1,还是2,其实等价于如下程序:
    int main()
    {
      int a = 2;
      int val = a;
      val = val - 1;
      cout << a << endl;
      return 0;
    }
    
    这样看就很清楚了吧?那我们用传值的函数就不能得到减1的功能了吗?当然不是,我们可以借助返回值啊:
    int f(int val)
    {
      val = val - 1;
      return val;
    }
    int main()
    {
      int a = 2;
      a = f(a);
      cout << a << endl;
      return 0;
    }
    
    不过它是经过了两次拷贝。
    指针形参
    指针形参其实没什么特别的,跟上面那些值传递基本一样,只不过它是指针,指来指去容易搞混,所以我单独拿出来说一下:
    void reset(int *ip)
    {
      *ip = 0; //改变ip所指的对象的值为0
      ip = 0; //改变指针本身的值,其实就是改变了地址,
      //但是没有改变i的地址,只是改变了拷贝后的ip
    }
    int main()
    {
      int i = 1;
      cout << &i << endl; 
      reset(&i);
      cout << i << endl;
      cout << &i << endl;
      return 0;
    }
    
    先给你看输出: 0x9ffe4c 0 0x9ffe4c 自己想想吧~

    传引用参数

    记住一点:引用和对象本身是一样的,所以对引用的操作就是对对象本身的操作。 我们来实现一个志玲函数,置零函数,不需要通过返回值哦:
    void reset(int &i) //引用形参
    {
      i = 0;
    }
    int main()
    {
      int i = 1;
      reset(i);
      cout << i << endl; //打印输出0
      return 0;
    }
    
    使用引用避免拷贝
    对吧?前面减1那个函数要拷贝,我们这个置零函数就不用了,是不是省了很多啊。更重要的是,有些类型是不支持拷贝的,比如前面学的数组,那我们只能用引用传参了。 举个栗子:我们要写个函数比较两个string的长度,string可能会很长,所以我们不要拷贝,用引用传参:
    bool isShorter(const string &s1, const string &s2) //为毛用const,尽量都得用它
    {
      return s1.size() < s2.size();
    }
    
    使用引用形参返回额外信息
    什么意思呢?比如说,我想写这么个函数:它实现一个阶乘功能,除了返回给我阶乘的结果外,我还要知道它乘了几次,你肯定会说这不是没事找事吗,它是几就乘几次呗,我不过是为了说明问题嘛,看代码吧:
    int fact(int &i, int &cnt) //简单些,只考虑正整数
    {
      int sum = 1;
      while(i>1)
      {
          sum *= i--;
          ++cnt;
      }
      return sum;
    }
    int main()
    {
      int a = 5;
      int b = 0;
      cout << fact(a, b) << endl;
      cout << b << endl;
      return 0;
    }
    
    其实我就是借助了引用传参会改变对象本身的值,所以我在求阶乘的时候多传进去一个b来记录乘法的次数,为什么它能记录呢,因为在被调函数中改变的cnt就是改变了b啊。

    const形参和实参

    来看一个很有意思的函数定义:
    void f(const int i){}
    
    我们当然可以给它传一个const int的类型,那么我们可以给它传一个int型的吗?要回答这个问题,我们要知道函数调用的时候参数是怎么过去的呢,其实值传递的话就是一个拷贝对吧,我把int的拷贝给const int的有什么不可以,这个你如果忘了的话,请回顾之前第二章的内容。再联系我们说过的一个概念,顶层const,即对象本身是常量,我们在拷贝时会忽略顶层const:
    const int a = 1;
    int b = a; //你看,忽略了吧
    
    所以啊,如果我们在程序中再定义一个函数:
    void f(int i){}
    
    就会报错了,因为它跟之前带const的f函数重复了。
    指针或引用形参与const
    这里要注意的跟以前变量初始化的时候类似,我先写两个函数,然后我们来看看下列调用方式是否合法(就是看参数传递是否合法):
    void reset(int *p){} //我就写个壳子
    void reset(int &p){} //重载函数
    int main()
    {
      int i = 42;
      const int ci = i;
      string::size_type ctr = 0;
      reset(&i); //正确:调用1(上面那个)
      reset(&ci); //错误:传入的是底层const,不能初始化int *
      reset(i); //正确:调用2
      reset(ci); //错误:不能把普通引用绑定到常量const上
      reset(42); //错误:理由基本同上
      reset(ctr); // 错误:类型不匹配
    }
    
    如果看着有困难的话,你就看实参去初始化形参的时候,是否符合以前的规矩,就是第二章学的那些。
    尽量使用常量引用
    只要不需要改变形参的值,都尽量用常量,理由有两点:
  • 该用常量而不用常量,会给函数的调用者一种误导,即函数可以改变它的实参的值
  • 使用引用而非常量引用会限制函数所能接受的实参类型。刚刚就学过,我们不能把const给非const的,但可以把非const的给const。

    数组形参

    为什么要单独把数组的形参拿出来说呢,有两个原因:
  • 数组不允许被拷贝:所以无法使用值传递
  • 使用数组名时会将其转换为指针。
    //以下三个函数声明等价(还记得函数声明吧)
    void f(const int*);
    void f(const int[]); //数组名转换为数组首元素指针
    void f(const int[5]); //我们期望它有五个元素,其实都行
    
    编译器在处理f函数调用时,只会检查传入的参数是不是const int*类型。 其实我们还漏了一点,因为数组是以指针的形式传给函数的,所以我们的被调函数不知道数组有多长,这个责任是调用者的,所以调用者应该提供一些额外信息来说明:
    使用标记指定数组长度
    C就是这样的,以前老师在教C的时候一定会说,不要忘了反斜杠零,它是结束标志,就是这个意思。有些像字符串什么的还是好用的,但是对于int这种所有取值都有效的就不适用了。
    使用标准库规范
    就是用begin()和end():
    void f(const int *beg, const int *end)
    {
      while(beg != end)
      {
          cout << *beg++ << endl;
      }
    }
    //这样就把数组首和尾后信息都传进去了
    //调用如下
    int j[3] = {1, 2, 3};
    f(begin(j), end(j));
    
    显式传递一个表示数组大小的形参
    就是多一个传入参数表示数组长度,easy老套。
    数组形参和const
    一句金玉良言:当函数不需要对数组元素执行写操作时,数组的形参应该是指向const的指针;只有当函数确实要改变元素值得时候,才把形参定义成指向非常量的指针

数组引用形参

数组当然也可以引用绑定:

//这里的10形参,是数组类型的一部分,所以只接受10个元素的数组
void f(int (&arr)[10])
{
    for(auto elem : arr)
    {
        cout << elem << endl;
    }
}
传递***数组

***数组就是数组的数组。和所有数组一样,在将***数组传递给函数时,真正传递的是指向数组首元素的指针。因为我们处理的是数组的数组,所以首元素本身就是一个数组,指针就是一个指向数组的指针,要完全匹配才能调用。

void print(int (*matrix)[10], int rowsize){}

调用这个函数,第一个参数虽然是指针,但是是指向数组的指针,所以数组的维度也是数组类型的一部分,所以再调用的时候那个数字10也要完全匹配。

含有可变参数的函数

有时候我们不清楚应该向函数传递几个实参,为了能处理不同数量的实参,C++提供了两种方法:

  1. 如果所有的实参类型相同,则可以传递一个名为initializer_list的标准库类型。
  2. 编写可变参数模板(以后再介绍) 比如我们要写一个输出错误的程序,我们又不知道具体有多少错误,但是这些错误的类型输出都是string,于是:
    void errorMsg(initializer_list<string> il) //这个是类模板嘛
    {
     for(auto beg = il.begin(); beg != il.end(); ++beg)
     {
         cout << *beg << endl;
     }
    }
    
    我们可以用不同的参数数量去调用:
    errorMsg({"aaa", a, b}); //向initializer_list传递值序列要用花括号
    errorMsg({"a", "b"}); //不同数量的实参
    
    除了initializer_list表示的数量不定的同种类型的参数,当然还可以加其他类型的参数,另外加上就行。
    void errorMsg(int a, initializer_list<string> il)
    
    省略符形参
    void f(int a, ...){}
    int main()
    {
     int a = 5;
     f(a,6);
     f(a, "a"); //懂了吧~
     return 0;
    }
    
#C++工程师#
全部评论

相关推荐

ArisRobert:统一解释一下,第4点的意思是,公司按需通知员工,没被通知到的员工是没法去上班的,所以只要没被通知到,就自动离职。就是一种比较抽象的裁员。
点赞 评论 收藏
分享
3 收藏 评论
分享
牛客网
牛客企业服务