如何正确使用函数形参提高C++程序性能
Posted Android编程精选
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何正确使用函数形参提高C++程序性能相关的知识,希望对你有一定的参考价值。
Python实战社群
Java实战社群
长按识别下方二维码,按需求添加
扫码关注添加客服
进Python社群▲
扫码关注添加客服
进Java社群▲
作者丨写代码的牛顿
来源丨写代码的牛顿
在C++中如何避免过度调用构造函数和析构函数可以有效的增加程序性能,调用构造函数和析构函数会增加运行开销,在C++中很多人会说传引用比传值效率更高,那么传值和传引用分别会发什么呢?下面我们就讲解一下尽量用pass-by-refrence-const代替pass-by-value是如何如何提高程序性能。
01
—
pass-by-value时发生了什么
在C++里面传值往往意味着会调用拷贝构造函数,形参是实参的副本,修改行参的值不会影响到实参。在C++中形参数据类型是基本数据类型传值和传引用没什么区别,在不需要修改实参的情况下,建议基本数据类型使用传值的方式。那么自定义数据类型呢?往往有多个成员变量,在pass-by-value情况下成员变量又可能会调用自身的拷贝构造函数,在离开函数作用时,又会调用形参的析构函数进而调用成员变量的析构函数。下面我们用实际代码说话,我们新建一个Person类。
class Person {
public:
Person(std::string Name, const int Age); //构造函数
Person(const Person& Rhs); //拷贝构造函数
~Person();
std::string GetName() const;
int GetAge() const;
private:
std::string MyName;
int MyAge;
};
接着我们在Person类的构造函数和析构函数中分别打印一条消息,且实现其他成员函数。
Person::Person(std::string Name, const int Age) : MyName(Name), MyAge(Age)
{
std::cout << "Person constructor run" << std::endl;
}
Person::Person(const Person& Rhs) : MyName(Rhs.MyName), MyAge(Rhs.MyAge)
{
std::cout << "Person copy constructor run" << std::endl;
}
Person::~Person()
{
std::cout << "Person destructor run" << std::endl;
}
std::string Person::GetName() const
{
return MyName;
}
int Person::GetAge() const
{
return MyAge;
}
接下来我们定义一个函数Print(Person P),再传入一个Person类型实参看看发生了什么,下面是Print(Person P)函数的定义。
void Print(Person P)
{
std::cout << P.GetName() << std::endl;
std::cout << P.GetAge() << std::endl;
}
在主函数代码如下:
int main(int argc, char* argv[])
{
Person P("Lihua", 20);
Print(P);
return 0;
}
编译运行结果如下:
从输出结果我们可以看出,通过paa-by-value形参会调用一次拷贝构造函数在离开函数作用域后会调用一次析构函数,MyName成员变量在拷贝函数中还会调用std::string的拷贝构造函数,离开作用域后还会调用std::string的析构函数。因此通过paa-by-value会调用多次拷贝构造函数和析构函数,增加程序运行开销。
02
—
pass-by-value会发生对象切割
我们知道一个类是可以被继承扩展功能,现在我们定义一个Student类继承Person类并扩展功能。
Person类源码修改如下:
class Person {
public:
Person(std::string Name, const int Age); //构造函数
Person(const Person& Rhs); //拷贝构造函数
virtual ~Person();
std::string GetName() const;
int GetAge() const;
virtual void GetAddr()const;
private:
std::string MyName;
int MyAge;
};
Person类虚函数GetAddr实现源码如下:
void Person::GetAddr() const
{
std::cout << "Person GetAddr run" << std::endl;
}
Student类源码如下:
class Student : public Person{
public:
Student(std::string Name, const int Age, std::string School);
~Student();
std::string GetName() const;
int GetAge() const;
void GetAddr() const;
private:
std::string MyAddr;
};
Student类成员函数实现如下:
Student::Student(std::string Name, const int Age, std::string Addr) : Person(Name, Age), MyAddr(Addr)
{
std::cout << "Student constructor run" << std::endl;
}
Student::~Student()
{
std::cout << "Student destructor run" << std::endl;
}
std::string Student::GetName() const
{
return Person::GetName();
}
int Student::GetAge() const
{
return Person::GetAge();
}
//成员变量不做修改,只打印一条信息
void Student::GetAddr() const
{
std::cout << "Student GetAddr run" << std::endl;
}
我们在Person类中声明了一个虚函数GetAddr(),并将Person类的析构函数声明为虚析构函数。由于多态性质,基类引用绑定派生类或者基类指针指向派生类,在运行过程中通过基类引用/指针调用虚函数,实际调用的是派生类的虚函数。那么pass-by-value此时会发生什么呢?现在我将Print函数修改如下,形参类型是Person。
void Print(Person P)
{
std::cout << P.GetName() << std::endl;
std::cout << P.GetAge() << std::endl;
P.GetAddr();
}
主函数修改如下:
int main(int argc, char* argv[])
{
Student S("Lihua", 20, "Beijing");
Print(S);
return 0;
}
编译运行输出结果如下:
Print函数形参类型是Person,我们传入实参类型是Student。在运行过程中,并没有实际调用Student类的GetAddr函数,而是调用Person类的GetAddr。因为发生了对象切割,派生类Student扩展部分被切割了,只保留了基类Person部分。
03
—
pass-by-refrence-const会有什么神奇效果
在C++编译器底层引用是用指针来实现的,但是引用和指针又有些不同。引用不能重新绑定到其他对象,而非T *const类型指针可以重新指向其他对象,同样的const T *和const T&一样不能修改绑定的变量值。在形参不需要重新绑定到其他变量的情况下,建议使用引用代替指针。Print函数不需要修改实参的值,所以我们将形参改为pass-by-refrence-const,源代码如下:
void Print(const Person& P)
{
std::cout << P.GetName() << std::endl;
std::cout << P.GetAge() << std::endl;
P.GetAddr();
}
主函数不变,编译运行结果如下:
通过观察输出结果可知,实际运行的是Student类的GetAddr函数。而且和pass-by-value相比没有调用多余的拷贝构造函数、析构函数,大大减少了运行时开销。
程序员专栏 扫码关注填加客服 长按识别下方二维码进群
近期精彩内容推荐:
在看点这里好文分享给更多人↓↓
以上是关于如何正确使用函数形参提高C++程序性能的主要内容,如果未能解决你的问题,请参考以下文章