▶疑问描述

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 ();

          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 引用的更多相关文章

  1. C++学习笔记 指针与引用

    指针与引用  1. 指针 (1) 指针是一个变量(实体),存储的是一个地址,指向内存的一个存储单元,指针可以为空 (2) 指针可以为空,在声明定义时可以不初始化 (3) 指针在初始化之后可以重新指向其 ...

  2. 指针和引用的区别(c/c++)

      http://blog.csdn.net/thisispan/article/details/7456169 ★ 相同点: 1. 都是地址的概念: 指针指向一块内存,它的内容是所指内存的地址:引用 ...

  3. C++指针和引用

     ★ 相同点: 1. 都是地址的概念: 指针指向一块内存,它的内容是所指内存的地址:引用是某块内存的别名.  ★ 区别: 1. 指针是一个实体,而引用仅是个别名: 2. 引用使用时无需解引用(*),指 ...

  4. C程序设计语言--指针和引用的区别

    在看了一篇文章以后,http://coolshell.cn/articles/7992.html,说的是C和C++之间的缺陷,当然这篇文章说的非常高深了.所以就找了一些资料,分析了这两者的区别 在&l ...

  5. C++基础回顾2(函数, 指针和引用)

    接着回顾函数.指针和应用. 函数 1.多维数组作为形参时,第一维的大小可以省略(也可以不省略),但是其他维的大小必须指定.比如二维数组形参,int array[3][]不正确,int arry[][1 ...

  6. [C++]变量存储类别,指针和引用,类与对象,继承与派生的一些摘要

    C++中共有四种存储类别标识符:auto/static/register/extern 1.auto 函数或分程序内定义的变量(包括形参)可以定义为auto(自动变量).如果不指定存储类别,则隐式定义 ...

  7. 指针 vs 引用 (2)

    这波要针对上篇分析里 标红的问题(成员变量用 T,T&啥情况)继续思考, 要学习以下材料: 1. 知乎上:用指针还是引用 2. StackOverflow上的相关问题 https://stac ...

  8. [速记]关于指针,引用和递归和解递归——C++

    在写基于二叉排序树的查找时,分为三个过程 1.二叉排序树的插入 2.二叉排序树的建立 3.基于二叉排序树的查找 其中第三部可以递归方式实现,也可以用while循环解递归,于是我想也解解第一步的递归,看 ...

  9. C++指针参数引用

    粘个代码占位置,以后有时间把指针函数,函数指针都补上 #include <iostream> using namespace std; void freePtr1(int* p1){ /* ...

随机推荐

  1. 【Servlet】Servlet的配置

    创建时间:6.15 Servlet的配置 1. 基本配置 其中url-pattern的配置方式: 1)完全匹配 访问的资源与配置的资源完全相同才能访问到 2)目录匹配 格式:/虚拟的目录../*   ...

  2. Codeforces Round #579 (Div. 3)

    Codeforces Round #579 (Div. 3) 传送门 A. Circle of Students 这题我是直接把正序.逆序的两种放在数组里面直接判断. Code #include &l ...

  3. 文件转换神器pandoc

    pandoc  :可以在各种文件之间进行相互转化.比如从md文件转为pdf,docx转为tex文件,html文件和txt文件相互转化,等等. 在终端启用命令行执行命令. 我最近要完成的任务是把有很多个 ...

  4. 02-赵志勇机器学习-Logistics_Regression-test(转载)

    # coding:UTF-8 ''' Date:20160901 @author: zhaozhiyong ''' import numpy as np from lr_train import si ...

  5. MySQL数据库索引类型、MySQL索引的优化及MySQL索引案例

    关于MySQL索引的好处,如果正确合理设计并且使用索引的MySQL是一辆兰博基尼的话,那么没有设计和使用索引的MySQL就是一个人力三轮车.对于没有索引的表,单表查询可能几十万数据就是瓶颈,而通常大型 ...

  6. String.format()的详细用法

    问题 在开发的时候一段字符串的中间某一部分是需要可变的 比如一个Textview需要显示”XXX用户来自 上海 年龄 21 性别 男” 其中的 XXX 是用户名 每个用户也是不一样的 地区 上海 为可 ...

  7. Adobe Illustrator 入门 新建 保存图片

    下载 AI 的破解版 我这里用的是 Adobe_Illustrator CC 2019 Lite 精简特别版 V23.0.2 简体中文版 64位 安装略 新建文档 通常是 A4 图形绘制 选择 矩形工 ...

  8. 【Gamma阶段】第十次Scrum Meeting

    [Gamma阶段]第十次Scrum Meeting 每日任务内容 今日工作任务 明日待完成任务 完成人 准备测试质量保证的展示材料 准备测试展示视频 赵智源 修复热评的子评论BUG 准备前端技术展示材 ...

  9. eclipse&myeclipse 生成jar包后,spring无法扫描到bean定义

    问题:eclipse&myeclipse 生成jar包后,spring无法扫描到bean定义 在使用getbean或者扫包时注入bean失败,但在IDE里是可以正常运行的? 原因:导出jar未 ...

  10. spark 基本操作整理

    关于spark 的详细操作请参照spark官网 scala 版本:2.11.8 1.添加spark maven依赖,如需访问hdfs,则添加hdfs依赖 groupId = org.apache.sp ...