▶疑问描述

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. Natas25-writeup

    前言 题目链接: http://natas25.natas.labs.overthewire.org 做这一题花了一些时间,也是由于自己知识点掌握不足,所以分享下解题过程. 题目分析 首先,登录后看到 ...

  2. Django中ModelViewSet的应用

    ModelViewSet源码 class ModelViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateM ...

  3. vue之ajax

    [前言] Vue里发送AJAX有很多工具可以供开发者使用 ①浏览器自带的fetch函数 ②vue之前推荐的vue-resource第三方模块 ③vue官方目前强力推荐的axios第三方模块 axios ...

  4. Linux 命令 mv

    mv 命令 --no-target-directory 参数确保对目录进行重命名而不是移动 https://www.gnu.org/software/coreutils/manual/html_nod ...

  5. 关于selectpicker的多选问题

    刚开始拿到这个需要求的时候,我时很没有头绪的,再前期做的时候是将这个多选的作为一个数组,传入到后端,然后再插入数据库中,然后根据关系表查询,因为但是考虑显示的问题,不知道怎么将多选的数据显示出来,我就 ...

  6. django请求限制

    django.views.decorators.http 包里的装饰器可以基于请求的方法来限制对视图的访问. 限制视图只能服务规定的http方法.用法: from django.views.decor ...

  7. SpringBoot配置文件加载位置与优先级

    1. 项目内部配置文件 spring boot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件 –fil ...

  8. day 19

    If you think you can, you can. And if you think you can't, you're right.

  9. Linux中fork()函数详解(转载)

    linux中fork()函数详解 一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事, ...

  10. gethostbyaddr

    函数原型: #include<netdb.h> struct hostent * gethostbyaddr(const char *addr, socklen_t len, int fa ...