http://mpvideo.qpic.cn/0bf2jeabmaaa5mamohecafqfasodczeqafqa.f10002.mp4?dis_k=d8f7c67a37d41e17ebd38a4bad34f671&dis_t=1619591396&spec_id=MzA3OTY3OTE1MQ==1619591396&vid=wxv_1838679048772730883&format_id=10002
一直有一个困惑,引用就是对象一个别名,别人问引用和指针区别?隐藏引用就不是指针
并且很多例子,参数传递和构造函数,看很多八股文, 到底一样不一样,课本上没有说,很模糊,不分配怎么操作。
直觉,感觉上判断,肯定有,如果没有。怎么被使用呢
这样认知陷入了矛盾:感觉上判断,肯定有,但是课本上没说,感觉痛苦难受!!!
为此展开了调查。
大胆猜测
------->>指针-->>-----引用------->
从c 发展历史来看,先出现指针( go也有指针类,没有引用类),后又引用,
- 一个高级概念不会凭空出现,现在技术是不支持的,肯定是指针基础上发展起来的。
因此引用这个概念技术对指针高级封装。
- 基本操作:对指针操作分为 ptr(对象本身), *ptr(指向对象)
- 基本操作:指针
我推测对引用操作,就是对对象的操作,一定是做一封封装,就像写函数一样。
- 也就是说操作引用,就是隐藏操作*ptr。
这样合情合理,顿时感觉完美来了!
- 会有那么神奇吗?操作引用,最后变成操作引用对象,神奇魔法 不存在
- ra === (*pa)
小心求证1- 看汇编
- 代码:https://godbolt.org/z/xrcobvar3
从汇编角度看 指针和引用变量初始化产生汇编代码是一样的【都是三行】
- 代码:
int a = 3;
int &ra = a;
int b = 4;
int *pa = &b;
- 汇编:
int a = 3;
int &ra = a;
012E33F8 mov dword ptr [a],3
012E33FF lea eax,[a]
012E3402 mov dword ptr [ra],eax
int b = 4;
int *pa = &b;
012E3405 mov dword ptr [b],4
012E340C lea eax,[b]
012E340F mov dword ptr [pa],eax
- 解释
而 b 和 pa 也同样是这样步骤。
int a = 3;
012E33F8 mov dword ptr [a],3 //把 3 放入地址为 [a] 的内存
int &ra = a;
012E33FF lea eax,[a] //a 的地址放入 eax
012E3402 mov dword ptr [ra],eax ,//最后把 eax 的值放入地址为 [ra] 的内存
//实际上,就是把 a 的地址放入了 ra 里。
int b = 4;
012E3405 mov dword ptr [b],4 // 把 4放入地址为 [b] 的内存
int *pa = &b;
012E340C lea eax,[b] //b 的地址放入 eax
012E340F mov dword ptr [pa],eax ,//最后把 eax 的值放入地址为 [pa] 的内存
从汇编的角度来看,引用是通过指针来实现的
- 代码
ra ;
(*pa) ;
- 汇编
(*pa) ;
013F4498 mov eax,dword ptr [pa]
013F449B mov ecx,dword ptr [eax]
013F449D add ecx,1
013F44A0 mov edx,dword ptr [pa]
013F44A3 mov dword ptr [edx],ecx
代码语言:javascript复制ra ;
013F4475 mov eax,dword ptr [ra]
013F4478 mov ecx,dword ptr [eax]
013F447A add ecx,1
013F447D mov edx,dword ptr [ra]
013F4480 mov dword ptr [edx],ecx
- 解释:多了2个寄存器帮助完成计算 ecx,edx
ra ;
013F4475 mov eax,dword ptr [ra] //获取rad地址 eax
013F4478 mov ecx,dword ptr [eax] //获取rad地址里的值:对象的地址 ecx
013F447A add ecx,1 //在寄存器完成计算 ecx
013F447D mov edx,dword ptr [ra] //获取rad地址里的值:对象的地址 edx
013F4480 mov dword ptr [edx],ecx //写回到 原来对象里面
小心求证2- sizeof 计算大小
代码语言:javascript复制#include <iostream>
#include <string.h>
using namespace std;
class Base
{
public:
virtual ~Base(){}
private:
int data;
};
class Test
{
public:
Base* m_ptr;
Base &m_base;
public:
Test( Base& base):m_base(base),m_ptr(&base)
{
cout<< m_ptr<<endl;
cout<< &m_base<<endl;
//Test( Base &base):m_base(&base),m_ptr(&base)
//invalid initialization of non-const reference of type ‘Base&’ from a temporary of type ‘Base*’
}
};
void test_ref()
{
Base base;
Test a(base);
cout << "sizeof(base)=" <<sizeof(base) <<endl; //16
cout << "sizeof(a)=" <<sizeof(a) <<endl;//16 神奇 为是不是24呢?表现出来是指针呢
cout << "sizeof(a._base)=" <<sizeof(a.m_base) <<endl; //16
}
int main()
{
test_ref();
return 0;
}
- gdb
int a= 10;
int*ptr = &a;
int& b= a;
(gdb) p &a
$11 = (int *) 0x7fffffffe07c
(gdb) p ptr
$12 = (int *) 0x7fffffffe07c
(gdb) p b
$13 = (int &) @0x7fffffffe07c: 10
- 引用是通过指针来实现的,如何打印出来 这个"指针"存放地址呢
还有区别吗?指针不能指向右值,因为右值地址不在栈上。
- const & listenAddr
SudokuServer(EventLoop* loop, const InetAddress& listenAddr)
: server_(loop, listenAddr, "SudokuServer"),
startTime_(Timestamp::now())
{
server_.setConnectionCallback(
std::bind(&SudokuServer::onConnection, this, _1));
server_.setMessageCallback(
std::bind(&SudokuServer::onMessage, this, _1, _2, _3));
}
扩展 :占用了位置--确不能直接访问/修改
- const变量无法修改,但是如果获取cosnt变量地址就可以了
理解 C/C 中的左值和右值
http://thbecker.net/articles/rvalue_references/section_01.html
- Rvalue references solve at least two problems:
- Implementing move semantics
- Perfect forwarding
引用至少解决了两个问题:
- 实现 move 语义
- 完美转发
- 右值引用不一定是右值 判断依据是如果右值引用修饰变量有名字 就是左值
Is an Rvalue Reference an Rvalue?
代码语言:javascript复制hings that are declared as rvalue reference can be lvalues or rvalues. The distinguishing criterion is: if it has a name, then it is an lvalue. Otherwise, it is an rvalue.
In the example above, the thing that is declared as an rvalue reference has a name, and therefore, it is an lvalue:
//参数有名字,调用X(X const & rhs)
void foo(X&& x)
{
X anotherX = x; // calls X(X const & rhs)
}
Here is an example of something that is declared as an rvalue reference and does not have a name, and is therefore an rvalue:
//返回值没有名字,调用X(X&& rhs)
X&& goo();
X x = goo(); // calls X(X&& rhs) because the thing on
// the right hand side has no name
X& X::operator=(X&& rhs)
{
// Perform a cleanup that takes care of at least those parts of the
// destructor that have side effects. Be sure to leave the object
// in a destructible and assignable state.
// Move semantics: exchange content between this and rhs
return *this;
}
左值和右值的主要区别是,左值可以被修改,而右值不能。不过,C 11 改变了这一区别。在一些特殊的情况下,我们可以使用右值的引用,并对右值进行修改。
Rvalue 并不意味着对象是不可变的
塔山
- https://www.zhihu.com/question/40720890
- mov eax,[ebx 8]则是把内存地址为ebx 8处的数据赋给eax。
- lea eax,[ebx 8]就是将ebx 8这个值直接赋给eax。load effective address
- lea取地址的 mov仅仅赋值
- http://thbecker.net/articles/rvalue_references/section_01.html
- values and rvalues:
- rvalue is an expression that can only appear on the right hand side of an assignment.
- Rvalue 并不意味着对象是不可变的
- Move Semantics?深度拷贝 vs swap vs move
- This is called move semantics
- Rvalue References void foo(X& x); // lvalue reference overload void foo(X&& x); // rvalue reference overload
- a = std::move(b);
- X& X::operator=(X&& rhs) any part of an object's destruction that has side effects should be performed explicitly in the rvalue reference overload of the copy assignment operator:
- move 不是swap ,move 负责原来数据的清理工作
- 栈是操作系统自动管理的,这句话没错,需要进一步解释
- 函数产生汇编
- 移动方向
- https://nettee.github.io/posts/2018/Understanding-lvalues-and-rvalues-in-C-and-C/
- 看10遍
- 深刻理解引用、const引用、右值引用的本质 https://blog.csdn.net/z914022466/article/details/76851363
- 例子必须运行一次,看是看不懂,对指针操作太复杂了。addr=(int *)((int)pj dis);谁看的懂 dis=(int)pj-(int)pi;//地址差
- int &&a=1;和const int &a=1;
- 是完全一样的操作,先在数据区开辟一个值为1的无名整型量,再将引用a与这个整型量进行绑定