(六)C++篇-函数非引用形参和引用形参

2022-06-17  本文已影响0人  GoodTekken

(1)非引用形参
普通的非引用类型的参数通过复制对应的实参实现初始化。当用实参副本初始化形参时,函数并没有访问调用所传递的实参本身,因此不会修改实参的值

// return the greatest common divisor
int gcd(int v1, int v2)
{
while (v2) {
int temp = v2;
v2 = v1 % v2;
v1 = temp;
}
return v1;
}

(2)指针形参 void reset(int *ip)
函数的形参可以是指针,此时将复制实参指针。与其他非引用类型的形参一样,该类形参的任何改变也仅作用于局部副本。如果函数将新指针赋给形参,主调函数使用的实参指针的值没有改变。
事实上被复制的指针只影响对指针的赋值。如果函数形参是非 const 类型的指针,则函数可通过指针实现赋值,修改指针所指向对象的值:

#include <iostream>
using namespace std;
void reset(int *ip);
int main()
{
    int i = 42;
    int *p = &i;
    cout << "i: " << *p << '\n';  // prints i: 42
    reset(p); // changes *p but not p
    cout << "i: " << *p << endl; // ok: prints i: 0
    return 0;
}

void reset(int *ip)
{
//(二选一)*ip=0,可以改变实参的值
    *ip = 0; // changes the value of the object to which ip points
    cout << "in_i: " << ip << endl; //print: in_i: 0x7ffde64b447c
//(二选一)ip=0,将指针地址改成0,但不可以改变实参的值
    //ip = 0;  // changes only the local value of ip; the argument is unchanged
    //cout << "in_i: " << ip << endl; //print: in_i: 0
}

如果保护指针指向的值(保护实参不被改变),则形参需定义为指向 const 对象的指针:

void reset(const int *ip)
{
    *ip = 0;// error: assignment of read-only location ‘* ip’
}

指针的初始化规则: 可以将指向 const 对象的指针初始化为指向非 const
对象,但不可以让指向非 const 对象的指针向 const 对象。

//ok
int i = 42;
const int *p = &i;

//fail
const int i = 42;
int *p = &i;
     // |  int *p = &i;
     // |           ^~
    //  |           |
    //  |           const int*

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

(3)引用形参
为了使 swap 函数以期望的方式工作,交换实参的值,需要将形参定义为引
用类型(int &v1, int &v2)
引用形参直接关联到其所绑定的对象,而并非这些对象的副本。定义引用时,必须用与该引用绑定的对象初始化该引用。引用形参完全以相同的方式工作。每次调用函数,引用形参被创建并与相应实参关联。
形参 v1 只是对象 i 的另一个名字,而形参v2 则是对象 j 的另一个名字。对v1 的任何修改实际上也是对 i 的修改。同样地,对v2 的任何修改实际上也是对 j 的修改。

引用形参的另一种用法是向主调函数返回额外的结果。

测试代码如下:

#include<iostream>
using namespace std;

void swap(int &v1, int &v2);
int main()
{
    int i = 10;int j = 20;
    cout << "Before swap():\ti: "<< i << "\tj: " << j << endl;
    cout << "i:\ti: "<< &i << endl;
    swap(i, j);
    cout << "After swap():\ti: "<< i << "\tj: " << j << endl;
    cout << "i:\ti: "<< &i << endl;
    return 0;
}

// ok: swap acts on references to its arguments
void swap(int &v1, int &v2)
{
    int tmp = v2;
    v2 = v1;
    v1 = tmp;
    cout << "v1:\ti: "<< &v1 << endl;
}

输出结果

tekken@tekken:~/C++WS$ ./a.out 
Before swap():  i: 10   j: 20
i:  i: 0x7ffc06cccef0
v1: i: 0x7ffc06cccef0
After swap():   i: 20   j: 10
i:  i: 0x7ffc06cccef0

使用 const 引用就可避免复制:

// compare the length of two strings
bool isShorter(const string &s1, const string &s2)
{
      return s1.size() < s2.size();
}

(1)因为形参是引用,所以不复制实参。又因为形参是 const 引用,所以 isShorter 函数不能使用该引用来修改实参。
(2)非 const 引用形参只能与完全同类型的非 const 对象关联。

字面型常量参数对应const形参
变量可以对应引用形参
值得注意,如果string word = "Hello World";改成const string word = "Hello World";,由于类型不一致,同样会编译失败。
测试代码:

#include<iostream>
#include<string>
using namespace std;

string::size_type find_char(const string &s, char c);
string::size_type find_char_2(string &s, char c);
int main()
{
    string::size_type i = find_char("Hello World", 'o');//(1)字面型常量参数对应const形参
    cout<<"i: "<<i<<endl;
    string word = "Hello World";
    //const string word = "Hello World";  //使用不一致的参数类型会导致编译失败
    string::size_type i2 = find_char_2(word, 'o');      //(2)变量可以对应引用形参
    cout<<"i2: "<<i2<<endl;
    return 0;
}

// returns index of first occurrence of c in s or s.size() if c isn't in s
// Note: s doesn't change, so it should be a reference to const
string::size_type find_char(const string &s, char c)
{
    string::size_type i = 0;
    while (i != s.size() && s[i] != c)
    ++i; // not found, look at next character
    return i;
}

string::size_type find_char_2(string &s, char c)
{
    string::size_type i = 0;
    while (i != s.size() && s[i] != c)
    ++i; // not found, look at next character
    return i;
}

输出:

tekken@tekken:~/C++WS$ ./a.out 
i: 4
i2: 4

传递指向指针的引用
假设我们想编写一个与前面交换两个整数的 swap 类似的函数,实现两个指针的交换。已知需用 * 定义指针,用 & 定义引用。现在,问题在于如何将这两个操作符结合起来以获得指向指针的引用。这里给出一个例子:
(形参int *&v1的定义应从右至左理解:v1 是一个引用,与指向 int 型对象的指针相关联。也就是说,v1 只是传递进 ptrswap 函数的任意指针的别名。)
最后是指针的值被交换了。在调用 ptrswap 时,pi 指向 i,而 pj 则指向 j。
在 ptrswap 函数中, 指针被交换, 使得调用 ptrswap 结束后, pi 指向了原来 pj所指向的对象。换句话说,现在 pi 指向 j,而 pj 则指向了 i。
测试代码如下:

#include<iostream>
using namespace std;

void ptrswap(int *&v1, int *&v2);
int main()
{
    int i = 10;
    int j = 20;
    cout<<"i:"<<&i<<endl;
    cout<<"j:"<<&j<<endl;
    
    int *pi = &i; // pi points to i
    int *pj = &j; // pj points to j
    cout<<"pi:"<<pi<<endl;
    cout<<"pj:"<<pj<<endl;
    
    cout << "Before ptrswap():\t*pi: "<< *pi << "\t*pj: " << *pj << endl;
    ptrswap(pi, pj); // now pi points to j; pj points to i
    cout << "After ptrswap():\t*pi: "<< *pi << "\t*pj: " << *pj << endl;
    
    cout<<"pi:"<<pi<<endl;
    cout<<"pj:"<<pj<<endl;
    return 0;
}

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

输出结果:

tekken@tekken:~/C++WS$ ./a.out 
i:0x7ffe11831990
j:0x7ffe11831994
pi:0x7ffe11831990
pj:0x7ffe11831994
Before ptrswap():   *pi: 10 *pj: 20
After ptrswap():    *pi: 20 *pj: 10
pi:0x7ffe11831994
pj:0x7ffe11831990
上一篇下一篇

猜你喜欢

热点阅读