首页 > 编程语言 >C++ 函数指针,指针函数,左值右值

C++ 函数指针,指针函数,左值右值

时间:2024-02-19 23:23:46浏览次数:17  
标签:std 右值 int 左值 C++ 引用

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

相关文章

  • c++vector
    下面是我在学习过程中对vector的理解,希望能够帮助到大家;vector(容器)(本质和数组一致)不过其对象长度不定.与数组不一样的是在使用vector是需要写头文件#include下面举出几个例子来说明:vectorv;  建立了一个名为v的int类型的容器,在这里并没有初始话值为多少,所以此时为......
  • 一文总结 C++ 常量表达式、constexpr 和 const
    TLDR修饰变量的时候,可以把constexpr对象当作加强版的const对象:const对象表明值不会改变,但不一定能够在编译期取得结果;constexpr对象不仅值不会改变,而且保证能够在编译期取得结果。如果一个const变量能够在编译期求值,将其改为constexpr能够让代码更清晰易读。constexp......
  • C++ 深拷贝浅拷贝
    C++深拷贝浅拷贝C++默认生成的拷贝构造函数,他的行为就是浅拷贝,他只会复制一个一模一样的的指针,并不会操作指针指向的东西。要想实现我们的逻辑需求,就要自定义拷贝构造函数,实现深拷贝。我们来具体说明一下上面的话首先我们创建一个简单的类#include<iostream>usingnamespa......
  • 线性插值计算百分位数的C++示例
    代码如下#include<iostream>#include<vector>#include<algorithm>doublepercentile_linear_interpolation(conststd::vector<double>&data,doublepercentile){//确保百分位数在合理范围内if(percentile<0.0||percentile>1......
  • C/C++ 宏区分不同系统、编译器、语言版本
    目录区分不同系统区分不同编译器及其版本区分不同语言及其版本参考区分不同系统1)_WIN32,Windows系统_WIN64:32bitand64bit系统M_WIN64:仅64bitWindows系统M_WIN32:仅32bitWindows系统_WINDOWS:GUIApplication_CONSOLE:consoleApplication2)__APPLE__,苹果系统,包括MAC、IOST......
  • VC++ 中 CT2A CA2T 两个宏进行字符串转换简单测试
    #include"afxwin.h"#include<iostream>usingnamespacestd;intmain(){CStringcs=_T("西游记");AfxMessageBox(_T("CString:")+cs);//CString转ACSIICT2Aa_str(cs);stringstd_str(a_str);......
  • KY78 最大上升子序列和C++
    这个解决问题的思路使用动态规划,即用已知状态去得到未知状态。思路逻辑是这样sum[i]记录以A[i]为末上升子序列的和的最大值然后从j从0-i-1遍历如果A[j]<A[i]那么sum[i]=sum[j]+A[i];然后找出sum[i]中的的最大值,就是以A[i]为末上升子序列的和的最大值。这样就实现了从前......
  • FUN GAME 一款普通的C++游戏
    凑合看吧,不是完整版。#include<bits/stdc++.h>#include<windows.h>#include<conio.h>usingnamespacestd;#defineptputs#definepfprintf#definepcputchar#definesfscanf#definegtgets#defineslSleepcharname[101];stack<int>gun;bo......
  • 15. C++类中成员变量的初始化总结
    C++类中成员变量的初始化总结1.普通的变量:一般不考虑啥效率的情况下可以在构造函数中进行赋值。考虑一下效率的可以再构造函数的初始化列表中进行。classCA{public:intdata;public:CA();};/*********/CA::CA():data(0)//……#1……初始化列表方式{......
  • KY148 还是畅通工程C++
    求图的最小生成树。克鲁斯卡尔算法来解决。就是选择n-1条最小边且无回路。回路判断用并查集就行。即要加入的边(两个节点)具有相同的父节点说明如果这两个节点本来就存在路径,再加入一条边就会产生回路,舍去。#include<iostream>#include<algorithm>usingnamespacestd;struc......