C++ 函数指针,指针函数,左值右值
1.函数指针
是一个指针类型的变量,存放的内容都是函数的指针,用来间接调用函数,格式如下:
int add( int a, int b)
{
return a+b;
}
int (*fadd)(int a,int b); //函数的指针,变量名需要被括号括起来,并且前面+*
注意:函数指针的变量名要在前面+*号,同时用括号括起来
使用方法:
fadd=add;//也可以这样写 fadd = &add
cout<<"add function="<<add(1,1)<<endl;
cout<<"fadd="<<(*fadd)(1,1)<<endl;
都输出为2
2.指针函数
指针函数是一个函数,返回一个指针,例如:
int c;
int* sub (int a, int b) //指针函数返回是一个指针
{
c=a-b;
return &c;
}
我们也可以使用函数指针来保存指针函数,并且使用指针函数
//指针函数的函数指针
int* (*fsub)(int a,int b);
cout<<"fsub="<<*(*fsub)(7,2)<<endl; //使用这种格式输出内容
如果想要获取内容就添加号,如果想要获取地址就(fsub)(7,2)写成这样。
3.C++ 左值和右值引用与完美转发
前言:看了这个博主的C++右值引用文章,深有启发,写下这篇笔记
C++左值和右值引用
C++中左值和右值的简单定义 在等号左边为左值,右边为右值。
左值为可以长久保存在内存中的实际存在值,右值是暂时的临时值。
左值都可以取地址,而右值不可能。
左值相当于一个存放东西的箱子(有地址),而右值相当于箱子内的东西
简单例子:
int a=0; //这里a是左值,0是右值
a++; //右值 ,拷贝a的副本后+1返回给a
++a; //左值,直接对a的内存地址里的值+1
cout<<&"hello"<<endl;
//0x7ff665394039
//特殊情况,"hello"也是左值,因为字符串计算机会分配内存空间
右值引用,如果我们想要把一个变量的值转移给另外一个变量,如下
string str="hello";
string bar= str;
这样会复制一个副本再传递过去,内存开销会增加。
如果在后需不会使用到str变量,我们可以使用右值引用,直接把左值内的值移动到对象中。
string str="hello";
cout<<str<<endl;
string bar=(string&&)str; //右值引用
//string bar(move(str)); //右值引用
cout<<bar<<endl; //输出hello
cout<<str<<endl;//没有内容输出
通过4行代码的例子解释右值引用的种种概念
第一个例子
int i = getVar();
上面的这行代码很简单,从getVar()函数获取一个整形值,然而,这行代码会产生几种类型的值呢?答案是会产生两种类型的值,一种是左值i,一种是函数getVar()返回的临时值,这个临时值在表达式结束后就销毁了,而左值i在表达式结束后仍然存在,这个临时值就是右值,具体来说是一个纯右值,右值是不具名的。区分左值和右值的一个简单办法是:看能不能对表达式取地址,如果能,则为左值,否则为右值。
int i = 0;
在这里i是左值,0是右值,具体来说上面的表达式中等号右边的0是纯右值(prvalue),在C++11中所有的值必属于左值、将亡值、纯右值三者之一。比如,非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和lambda表达式等都是纯右值。而将亡值是C++11新增的、与右值引用相关的表达式,比如,将要被移动的对象、T&&函数返回值、std::move返回值和转换为T&&的类型的转换函数的返回值等。关于将亡值我们会在后面介绍,先看下面的代码:
int j = 5;
auto f = []{return 5;};
上面的代码中5是一个原始字面量,[]{return 5;}是一个lambda表达式,都是属于纯右值,他们的特点是在表达式结束之后就销毁了。
通过地行代码我们对右值有了一个初步的认识,知道了什么是右值,接下来再来看看第二行代码。
第二个例子
T&& k = getVar();
第二行代码和第一行代码很像,只是相比第一行代码多了“&&”,他就是右值引用,我们知道左值引用是对左值的引用,那么,对应的,对右值的引用就是右值引用,而且右值是匿名变量,我们也只能通过引用的方式来获取右值。虽然第二行代码和第一行代码看起来差别不大,但是实际上语义的差别很大,这里,getVar()产生的临时值不会像第一行代码那样,在表达式结束之后就销毁了,而是会被“续命”,他的生命周期将会通过右值引用得以延续,和变量k的声明周期一样长。
右值引用的一些小技巧1:
A a = GetA(); 拷贝构造调用了两次,析构调用了两次
A&& a = GetA(); 右值引用 拷贝构造调用一次,析构调用一次
const A& a = GetA(); 常量左值引用 同上
//A& a = GetA(); 这个写法报错
输出的结果和右值引用一样,因为常量左值引用是一个“万能”的引用类型,可以接受左值、右值、常量左值和常量右值。需要注意的是普通的左值引用不能接受右值,上面的代码会报一个编译错误,因为非常量左值引用只能接受左值。
右值引用的一些小技巧2:
右值引用独立于左值和右值。意思是右值引用类型的变量可能是左值也可能是右值。比如下面的例子:
int&& var1 = 1;
var1类型为右值引用,但var1本身是左值,因为具名变量都是左值。
关于右值引用一个有意思的问题是:T&&是什么,一定是右值吗?让我们来看看下面的例子:
template<typename T>
void f(T&& t){}
f(10); //t是右值
int x = 10;
f(x); //t是左值
从上面的代码中可以看到,T&&表示的值类型不确定,可能是左值又可能是右值,这一点看起来有点奇怪,这就是右值引用的一个特点。
右值引用的一些小技巧3:
T(T&& a) : m_val(val){ a.m_val=nullptr; }
这行代码实际上来自于一个类的构造函数,构造函数的一个参数是一个右值引用,为什么将右值引用作为构造函数的参数呢?
因为类似于这种代码
A GetA()
{
return A();
}
A a = GetA();
上面代码中的GetA函数会返回临时变量,然后通过这个临时变量拷贝构造了一个新的对象a,临时变量在拷贝构造完成之后就销毁了,如果堆内存很大的话,那么,这个拷贝构造的代价会很大,带来了额外的性能损失。每次都会产生临时变量并造成额外的性能损失,有没有办法避免临时变量造成的性能损失呢?答案是肯定的,C++11已经有了解决方法。
A(A&& a) :m_ptr(a.m_ptr)
{
a.m_ptr = nullptr;
cout << "move construct" << endl;
}
这个构造函数并没有做深拷贝,仅仅是将指针的所有者转移到了另外一个对象,同时,将参数对象a的指针置为空,这里仅仅是做了浅拷贝,因此,这个构造函数避免了临时变量的深拷贝问题。
我们知道移动语义是通过右值引用来匹配临时值的,那么,普通的左值是否也能借助移动语义来优化性能呢,那该怎么做呢?
事实上C++11为了解决这个问题,提供了std::move方法来将左值转换为右值,从而方便应用移动语义。move是将对象资源的所有权从一个对象转移到另一个对象,只是转移,没有内存的拷贝,这就是所谓的move语义。
关于move的例子
{
std::list< std::string> tokens;
//省略初始化...
std::list< std::string> t = tokens; //这里存在拷贝
}
std::list< std::string> tokens;
std::list< std::string> t = std::move(tokens); //这里没有拷贝
如果不用std::move,拷贝的代价很大,性能较低。使用move几乎没有任何代价,只是转换了资源的所有权。他实际上将左值变成右值引用,然后应用移动语义,调用移动构造函数,就避免了拷贝,提高了程序性能。如果一个对象内部有较大的对内存或者动态数组时,很有必要写move语义的拷贝构造函数和赋值函数,避免无谓的深拷贝,以提高性能。事实上,C++11中所有的容器都实现了移动语义,方便我们做性能优化。
这里也要注意对move语义的误解,move实际上它并不能移动任何东西,它唯一的功能是将一个左值强制转换为一个右值引用。如果是一些基本类型比如int和char[10]定长数组等类型,使用move的话仍然会发生拷贝(因为没有对应的移动构造函数)。所以,move对于含资源(堆内存或句柄)的对象来说更有意义。
最后例子 C++ 完美转发
C++11之前调用模板函数时,存在一个比较头疼的问题,如何正确的传递参数。比如:
template <typename T>
void forwardValue(T& val)
{
processValue(val); //右值参数会变成左值
}
template <typename T>
void forwardValue(const T& val)
{
processValue(val); //参数都变成常量左值引用了
}
C++11引入了完美转发:在函数模板中,完全依照模板的参数的类型(即保持参数的左值、右值特征),将参数传递给函数模板中调用的另外一个函数。C++11中的std::forward正是做这个事情的,他会按照参数的实际类型进行转发。看下面的例子:
#include <iostream>
using namespace std;
void processValue(int& a){ cout << "lvalue" << endl; }
void processValue(int&& a){ cout << "rvalue" << endl; }
template <typename T>
void forwardValue(T&& val)
{
processValue(std::forward<T>(val)); //照参数本来的类型进行转发。
}
void Testdelcl()
{
int i = 0;
forwardValue(i); //传入左值
forwardValue(0);//传入右值
}
int main() {
Testdelcl();
return 0;
}
/*
lvalue
rvalue
*/
右值引用T&&是一个universal references(通用引用),可以接受左值或者右值,正是这个特性让他适合作为一个参数的路由,然后再通过std::forward按照参数的实际类型去匹配对应的重载函数,最终实现完美转发。
我们可以结合完美转发和移动语义来实现一个泛型的工厂函数,这个工厂函数可以创建所有类型的对象。具体实现如下:
template<typename… Args>
T* Instance(Args&&… args)
{
return new T(std::forward<Args >(args)…);
}
这个工厂函数的参数是右值引用类型,内部使用std::forward按照参数的实际类型进行转发,如果参数的实际类型是右值,那么创建的时候会自动匹配移动构造,如果是左值则会匹配拷贝构造。
标签:std,右值,int,左值,C++,引用 From: https://www.cnblogs.com/AndreaDO/p/17975801