解决老大难疑惑:指针 vs 引用
▶疑问描述
1. 引用reference的本质: 常指针
——> 什么时候用指针?= 就按Java中的引用变量那样用?
——> 什么时候用引用? ①函数的入参/返回值时 ②T& p 为什么不直接用T* const p ?
——> instance在程序中的传递? 典型的,被好几个类型的对象所引用? 以及函数调用时,作为函数入参/ 函数返回值的传递?
▶指针是什么; 变量名是什么
Rule1: 指针是一种基本数据类型(或者说一个泛型,它的参数类型是其指向的对象的类型),就如同int一样。它的值是内存单元地址。 指针变量是访问一个对象的间接手段。
Rule2: 就data域来讲,Class,Struct是复合类型。
Rule3: 变量名就是内存单元地址。在底层的符号表中就已经把变量名对应成了地址。所以 “myvar” 是内存单元 [0x134D2317] 的‘助记符’。
同时符号表中还记录了这个符号的类型信息。(内存单元中无法记录类型信息)?
问:变量名“myvar” (在其生命周期内) 可不可能 改变其代表的地址——> 更换目标对象呢?
答:不可能。变量名"myvar"的唯一作用,就是通过它去改变这个内存中这个目标对象的值。
不要被 “指针变量 pt可以换指向另一个对象” 弄糊涂了。。。指针变量的值就是内存单元地址,所以也是在改 pt的值,而pt这个指针变量自身的地址是不能改变的。
✇ C++ Usage:在给指针换指instance前,若原instance在heap上,记得先手动释放掉(delete)!
▶幕后的实质:内存中的instance才是No.1,指针/引用都是围着它转的
一个instance,可能在构造时就有了变量名,也可能通过引用多了几个变量名;可能在构造时就用1个指针指了它,也可能后期又用几个指针来指它。
Rule3: 摆脱语法的复杂规定,内存中的实际只有如下几种实体:(假设是T类型)
①T类型的instance。在内存占一块内存区域。 生成方式为 ①T* pt = new T() ; 或 ② T t ();
a. 如果是new T()的话就在堆上,否则T t 在当前栈上。
b. 如果是new T()的话:这个instance就是匿名的,(instance所在的地址并没有关联名字。pt是指针变量自身所在内存单元的名字),必须通过指针来间接访问它们。
如果是T t(), 则 t就是该instance的变量名,即 t就是该instance所在内存单元地址的助记符。
② T类型的指针变量。 T* pt
③ T类型的引用。 T& rt
可能性1:引用即别名。这里的rt跟上面的t是同一种实体。(肯定不在符号表。编译器实现应该是按照常指针来实现的 ⇙)
可能性2:引用的实质是常指针,即 T* const rt;
“常指针” (指针rt的值为常量) ➱ *rt是一个固定的地址 ➱ *rt 就是该内存单元地址的‘助记符’ ➱ 引用变量rt为该instance的变量名
前提 T* pt = new T(), 当接着在代码中声明一个新的引用的时候: T& rt = *pt;
▶函数调用/返回时的传递: 3种形式
只存在两种类型:对象/ 对象的指针。 (引用不是第三种类型,引用变量的类型就是其对象的类型)
实验:T& ref; typeid(ref).name() 的结果: T
作为函数调用的入参时:
1. 拿对象去调用: T t; 或 T& t ; 调用 foo(t);
foo(T): 函数体内多了个上述实例t的拷贝。函数体内的修改和外部那个t实例没关系。
按值拷贝进去(进行了Dog对象的拷贝构造,在进入的函数栈上建立了一个Dog instance);在方法体内操作的是栈上新建的这的instance,与外部那个instance无关。
foo(T&) :函数体内拿到了 外部那个 t 实例本身。并可以修改。
没有进行Dog对象的拷贝构造。用起来跟Java的引用传递一样!
foo(T*):类型不匹配,无法调用。 用 foo( &t);
2.拿对象的指针去调用:T* pt = &t, 调用 bar(pt);
bar(T) : 类型不匹配,无法调用。 用 bar( *pt) ;
bar(T&): 类型不匹配,无法调用。 用 bar( *pt);
bar(T*): 函数体内多了个指向这个t实例的指针变量的拷贝。可以通过它修改外部的t实例本身。
没有进行 t 对象的拷贝构造,进行了 pt 这个指针变量的拷贝。它和入参的指针指向同一个外部的instance。
作为函数调用的返回值时:
一样的,调用传参 是把外面的东西往函数栈内传(入参—>形参,类型一致), 而返回值 是把函数栈内的东西往外面传 (return的—> 调用处的接收变量,类型一致)
☹ 提防“野指针”: 如果在函数栈上建立了instance,则必须用 T foo() { T t ; return t} 的形式将此 instance通过拷贝构造返回外面,
否则函数调用完 清栈后instance被清除,传出去的 T&就找不到instance,T*也变成“野指针”。
☺解决方案: move语义了解一下
▶作为成员变量:3种形式
我想知道,作为成员变量, Dog*, Dog&, Dog 这3种情况有什么区别?
一个Man1类型的实例变量man,它包含的是: ①一个指针变量dog, 其类型为Dog ② String实例? ③
class Man1 {
Dog* dog;
String name;
int age;
} class Man2 {
Dog& dog;
String& name;
int age;
}
✇ C++ Usage:由于函数传递时C++提供的默认拷贝构造函数都是“浅拷贝”, 所以当Class的成员变量包含 指针类型时, 务必重写该Class的拷贝构造函数 T(const T& rt)。
▶更好把握:和Java的对比
Java里除了8种基本数据类型外,复合类型都是用‘引用变量’,其instance都放在heap上。(String类型比较特殊,涉及到Constant Pool)
即:① Java中的‘引用变量’ Man man; 就是C++中的指针Man* man;
② 对象的实例化采用的是C++中的new方式。
③ Java中的引用变量都可以随时换指instance,所以它是指针。
④类的成员变量含有 Dog dog; 时,则它持有1个Dog类型的引用,对应的是包含一个 Dog类型的指针变量。
⑤ 作为函数入参/返回时,Java中的 Dog f( Cat cat), 效果(调用处、函数体内使用)等同于C++的按引用传递: Dog& f( Cat& cat)。
当然,也可以用 Dog* f( Cat* pcat) 来传递 instance。
——> ✇ C++ Usage:除非函数内在栈上构造了instance 并要返回,这时返回形式需用T (拷贝出去)。
否则一律模仿Java:①需要传来传去的 instance都通过new() 建立在堆上,不要放当前方法栈上。(爆栈、拷贝的效率低、野指针风险)
+ ② 复杂对象 instance的传递:在函数调用/返回时都用 指针/引用形式,不要用T 实例拷贝。
+ ③ 对于堆上的instance,在其生命周期结束时(没啥用了),要注意delete释放!
▶给出答案 & 最佳实践
Q: 成员变量用 Dog& 是什么吊意思? 存的内容是 Dog* 还是 Dog instance呢?
别把引用变量作为成员变量,坏处多多!还很麻烦,关键是没必要!指针就够了,java不就是这样嘛。
理由:请看 6/17日发表的另外两篇博文。
扩展阅读:<When to Use Reference Member Variables> 答案:“You can store members by reference if they are guaranteed to exist elsewhere.”
Q: Java里成员变量全用 Dog* 就可以。 那什么时候用 Dog呢?
T作为成员变量的话,说明你这个man对象直接持有dog对象的值。
它可能是从外部的dog instance拷贝过来的,但它自己是就是个instance值,而不是关联的其他处的instance。
—————————————————————————————————————
Q: 什么时候新建 引用(起别名)?
只有当 这个instance没有名字时(是用new建立在堆上的), 才有必要给它起个别名吧?!
—————————————————————————————————————
Q: 构造instance,用 T t( ) 还是 T* pt = new T() ?
用new放在堆上
————> 麻烦是要动态管理内存(instance没用时delete掉)】
不用new放当前栈上 (更麻烦)
————> ★★★即使把它的指针值送去堆上的对象中(持有本对象的指针),这个instance也不注意就没了。
————> ★★★麻烦的是要管理在栈上的该instance的生命周期
————> 除非你把这个instance通过拷贝构造复制了好多份。 一般没必要啊,麻烦。】
Q: 函数入参/返回形式,用 T , T& ,还是 T* ?
若你的instance一般都是用new放在堆上,那操作对象就都是用指针变量了, 那么大多数函数接口应当采用 T*
同时,若在方法体内建立了对象,同上,也一般用new放在堆上,所以返回形式一般也会是 T*
Rule: 函数接收参数/返回 形式 一律不出现T ,(避免拷贝传递 (除非傻逼地把instance放在了临时栈上,得拷贝出去))
传递当然实际目的都是要传instance,排除掉拷贝传递后,可用的函数接口形式为 T& , T*
而该instance目前的引用名/指针 情况,又与实例化此对象时选择 用new放堆/ 不用new放栈上 有关
Q: 见到 T& / T*这种传递形式各自 说明了什么呢?
★★★ 若不是new完起别名,T&这种形式说明传的是 栈上的instance,
而 T* 说明我传的都是 堆上的instance,这和java一样,应该是可取的。
—————————————————————————————————————
var1是 instance1的名字,
var2是 instance2的名字,
则 var1= var2 的意思是,把内存中instance1处的值擦除,改成instance2的值。
而不是 “让 var1 指向 instance2” ……(为什么这也能误会。。。)
—————————————————————————————————————
不用担心调用时类型没法弄匹配。 只关注这个instance:①在堆/某个栈?②有几名字/几指针?③没拷贝吧?
在调用时类型匹配上的:
若是拿对象 t去调的,则 t 匹配 T/ T& ,或用 &t 去匹配 T*
若是拿对象的指针 pt去调的, 则pt 匹配 T*,或用*pt 去匹配 T& /T
T/T&都是要拿对象t 去调,不过是用不同的传递的方式。
▶展望
当已经非常熟悉这些惯常写法后,(而且明白这样写的原因)
再遇到不惯常的写法(比如调用函数时用T 传递),就能猜测到其背后的处理有啥不一样了 !~~~
就像小白时理解了public/ private这些访问修饰符的意义时那样。
解决老大难疑惑:指针 vs 引用的更多相关文章
- C++学习笔记 指针与引用
指针与引用 1. 指针 (1) 指针是一个变量(实体),存储的是一个地址,指向内存的一个存储单元,指针可以为空 (2) 指针可以为空,在声明定义时可以不初始化 (3) 指针在初始化之后可以重新指向其 ...
- 指针和引用的区别(c/c++)
http://blog.csdn.net/thisispan/article/details/7456169 ★ 相同点: 1. 都是地址的概念: 指针指向一块内存,它的内容是所指内存的地址:引用 ...
- C++指针和引用
★ 相同点: 1. 都是地址的概念: 指针指向一块内存,它的内容是所指内存的地址:引用是某块内存的别名. ★ 区别: 1. 指针是一个实体,而引用仅是个别名: 2. 引用使用时无需解引用(*),指 ...
- C程序设计语言--指针和引用的区别
在看了一篇文章以后,http://coolshell.cn/articles/7992.html,说的是C和C++之间的缺陷,当然这篇文章说的非常高深了.所以就找了一些资料,分析了这两者的区别 在&l ...
- C++基础回顾2(函数, 指针和引用)
接着回顾函数.指针和应用. 函数 1.多维数组作为形参时,第一维的大小可以省略(也可以不省略),但是其他维的大小必须指定.比如二维数组形参,int array[3][]不正确,int arry[][1 ...
- [C++]变量存储类别,指针和引用,类与对象,继承与派生的一些摘要
C++中共有四种存储类别标识符:auto/static/register/extern 1.auto 函数或分程序内定义的变量(包括形参)可以定义为auto(自动变量).如果不指定存储类别,则隐式定义 ...
- 指针 vs 引用 (2)
这波要针对上篇分析里 标红的问题(成员变量用 T,T&啥情况)继续思考, 要学习以下材料: 1. 知乎上:用指针还是引用 2. StackOverflow上的相关问题 https://stac ...
- [速记]关于指针,引用和递归和解递归——C++
在写基于二叉排序树的查找时,分为三个过程 1.二叉排序树的插入 2.二叉排序树的建立 3.基于二叉排序树的查找 其中第三部可以递归方式实现,也可以用while循环解递归,于是我想也解解第一步的递归,看 ...
- C++指针参数引用
粘个代码占位置,以后有时间把指针函数,函数指针都补上 #include <iostream> using namespace std; void freePtr1(int* p1){ /* ...
随机推荐
- Natas25-writeup
前言 题目链接: http://natas25.natas.labs.overthewire.org 做这一题花了一些时间,也是由于自己知识点掌握不足,所以分享下解题过程. 题目分析 首先,登录后看到 ...
- Django中ModelViewSet的应用
ModelViewSet源码 class ModelViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateM ...
- vue之ajax
[前言] Vue里发送AJAX有很多工具可以供开发者使用 ①浏览器自带的fetch函数 ②vue之前推荐的vue-resource第三方模块 ③vue官方目前强力推荐的axios第三方模块 axios ...
- Linux 命令 mv
mv 命令 --no-target-directory 参数确保对目录进行重命名而不是移动 https://www.gnu.org/software/coreutils/manual/html_nod ...
- 关于selectpicker的多选问题
刚开始拿到这个需要求的时候,我时很没有头绪的,再前期做的时候是将这个多选的作为一个数组,传入到后端,然后再插入数据库中,然后根据关系表查询,因为但是考虑显示的问题,不知道怎么将多选的数据显示出来,我就 ...
- django请求限制
django.views.decorators.http 包里的装饰器可以基于请求的方法来限制对视图的访问. 限制视图只能服务规定的http方法.用法: from django.views.decor ...
- SpringBoot配置文件加载位置与优先级
1. 项目内部配置文件 spring boot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件 –fil ...
- day 19
If you think you can, you can. And if you think you can't, you're right.
- Linux中fork()函数详解(转载)
linux中fork()函数详解 一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事, ...
- gethostbyaddr
函数原型: #include<netdb.h> struct hostent * gethostbyaddr(const char *addr, socklen_t len, int fa ...