C++函数传参的一个细节

问题引入

接下来我给出两种函数参数的传递方式,你是否能迅速且确定地说明二者的不同? 检测以下你是否知道这个知识点,你是否需要继续阅读这篇文章。

class A {};
void func(A a) {}

// 第一种方式
A a;
func(a);

// 第二种方式
func (A());

你看出来区别了吗?二者的在参数传递过程的区别有什么区别呢?

第一种和第二种方式分别调用了 A 类的那些构造函数呢?

分析

函数的参数传递过程,无非就是涉及到形式参数的构建、形参与实参的关系等等。

为了更好的监控函数参数的创建过程,我们首先写个简单类,检查类对象从创建到销毁的全部过程。

class B
{
public:
    B() :data(0)       { cout << "默认构造函数 " << endl; }
    explicit B(int i) :data(i)  { cout << "带参数的构造函数 " << endl; }
    B(const B &b)               { cout << "复制(拷贝)构造函数 " << endl; data = b.data; }
    B(B&& b)                    {cout << "移动构造函数" << endl; data = b.data; }
    B& operator =  (const B &b) { cout << "赋值运算符的重载函数 " << endl; }
    ~B()                        { cout << "析构函数" << endl;}
    int get() { return data; }
private:
    int data;
};

接着,我们分别测试上面给出的两种函数调用方式,来观察一下变量的改变情况。

测试

测试一

void func(B b)  // 定义测试函数
{
    cout << b.get() << endl;
}

int main() 
{
    puts("方式一");
    B b(23);
    func(b);
    puts("方式二");
    func(B (1));
}

/*output
	方式一
    带参数的构造函数 
    复制(拷贝)构造函数 
    23
    析构函数
    方式二
    带参数的构造函数 
    1
    析构函数
    析构函数
*/

方式一:先创建一个对象 b,然后传入到 func 中,会先调用构造函数创建 b , 然后调用函数时,调用一次拷贝构造函数,创建一个形参。

方式二:直接调用了构造函数,其他什么什么构造函数都没调用。说明我们在函数外创建的对象,直接放到函数内使用,中间没经过任何加工。相当于这个变量直接在函数内部构造。

方式二比方式一少调用一次拷贝构造函数和析构函数。

走到这一步显然是不够的,我们在测试一下其他函数。

测试二

void func2(B &b)
{
    cout << b.get() << endl;
}

int main() 
{
    puts("方式一");
    B b(23);
    func2(b);
    puts("方式二");
    func2(B (1));
}

在这里插入图片描述

运行一下,发现报错,看看他的错误提示。

他说 B (1) 创建的是一个右值( rvalue ),而我们 func2 要的是左值引用,这肯定是不行。

B (1) 创建的是右值,是这样吗?我们来验证一下。

编写一个参数为右值引用的函数。

测试三

void func3(B&& b) {
    cout << b.get() << endl;
}

int main() 
{
    puts("方式一");
    B b(23);
    func1(std::move(b));
    puts("方式二");
    func1(B (1));
    
    return 0;
}

/*output
 	带参数的构造函数
    移动构造函数
    23
    析构函数
    方式二
    带参数的构造函数
    1
    析构函数
    析构函数
*/

函数正常运行。我们发现采用第二种方式少了一次移动构造和一次析构函数。

汇总

经过上面三次测试,我们发现,直接使用 func(B (1)) 的方式总是比方式一少一次拷贝构造函数或者移动构造函数。

少调用一次拷贝构造函数和移动构造函数有什么用?

  1. 代码运行效率增加。
  2. 对于一些无法进行拷贝构造的类来说,这是相当重要的。

总结

如何理解 func(B(1)) ;

可以把函数展开来看。void func(B b); func(B(1)); 其实就是 对形参b 进行 func(B b = B(1)); ,也就是func(B b(1));,可不就是只进行一次构造函数的调用。

有何应用?

当我们往 vector 中 push_back 数据,交给vector 管理时,就不需要先创建对象,然后调用移动构造和拷贝构造函数在 vector 中在创建一个副本变量。
可以直解通过push_back(B(1))往vector中添加数据,少一次拷贝或移动构造。

当然,使用emplace_back也能实现。

此时,你可能会想到:我不如直接创建函数,让函数的参数不是对象,而是构造对象所需要的数据元素,然后在函数内部构造对象不就行了吗?

当然可以。但是设想一下,如果系统提供了函数,且给定了函数参数的类型是对象,此时就派上用场。

总的来说,应用不是很广。但是呢,我们要理解,如果看到别人写了个 func(B(2)); 的代码,我们要知道这行代码干了什么事?

相当于直接在函数内部创建一个对象。且 B(2) 创建的是临时对象,需要函数的参数类型为B或B&&来接收参数,不能是B&。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值