[cpp deep dive]引用与指针、引用传递与值传递

`###### stage_0 基本

指针 - 变量。存储的是一个地址
引用

  • 是某个变量的别名。引用和原变量在内存的同一个区域。 <-----这是不是让人想起了什么,软链接硬链接区别是啥? ---硬链接也是别名啊亲

那引用本身有占据空间吗? 这个我认为应该是要看编译器的,标准里并没有说要如何实现引用??,比如我可能自己实现一个符合标准的编译器,其中的引用我就给他编译成一个指针,那这样你说占空间还是不占?但输出引用的地址一定是和原始变量一致的.

  • 引用类型必须初始化。否则报错类似error: ‘a_ref’ declared as reference but not initialized
  • 有const指针,没有const引用;
        int a(0);
        int & const ref = a;//error: 'const' qualifier may not be applied to a reference

指针常量/常量指针

就是以*为分界,看const跟谁比较近就是修饰谁.
int *const ptr; //指针常量,ptr本身不能被更改,*ptr可以被更改
const int *ptr1; //常量指针,ptr本身可以更改,指向的内容不能更改哦.
int const *ptr2; //同上,常量指针

  • ref类型还不能重新被赋值。
    原因是Bjarne(包括我也)觉得如果能重新绑定引用(或者叫重新指代),那么语法会变得很奇怪.就像胡萝卜汁加咖啡一样.
    请check一下这个<a href=http://stackoverflow.com/questions/9293674/can-we-reassign-the-reference-in-c>thread</a>和effective c++37页
    //编译通过,但不代表这就是重新指代
    string k("dog1");
    string k2("dog2");
    string &c = k; 
    c = k2;              //<--------------这句实际上的效果类似于 k = k2,只是改变了值,并没有改变c与k的绑定. 无法想象如何去重新绑定.                   
    printf("%p %p %p\n", &c , &k, &k2); //实际上c依然引用k
  • 不存在多级引用,但有多级间接指针.
    int a(100);
    int* ptr = &a;
    int** ptr_ptr = &ptr;
    
    int &ref = a;
    int &ref2 = ref;//这并不是引用的引用,这还是a的引用.
                    //所以引用的语法我觉得就是在说明这问题,他是平坦的,只是个别名,并不存在间接关系.
    printf("%p %p\n%p %p\n", ptr, *ptr_ptr, &ref, &ref2);
  • sizeof操作符,sizeof引用对象可以得到原对象的大小,而sizeof指针只能得到指针大小.
    <---------------又想起啥来了!
 //确定数组元素个数的宏:
    #define Num_of_Arr(A) (sizeof(A)/sizeof(A[0]))
    int A[100];
    int (&refarr) [100]= A;//必须要指定大小
    //int &refarr [100] = A; // error: declaration of ‘refarr’ as array of references
    printf("%lu %lu %lu\n", sizeof(A), sizeof(refarr), sizeof(A)/sizeof(A[0]));
  • 引用数组与数组的引用
    没有引用数组,编译不过。
    对数组的引用,需要注意,Type (&ref_name) [300] = Arr;要加括号.
    ----------->又想起啥了.函数指针,指向数组的指针也都是这样,需要加括号,因为[]的优先级高于*

  • 引用的自增与指针的自增(这应该很容易推出的吧)

<a href=http://www.cnblogs.com/dolphin0520>参考</a>

stage_1 引用的一些其他

  • 关于函数参数传入方式 - 值/引用/地址
  • 函数参数以值传入时:
    • 参数类型是与传入的原始对象类型一致,则会引发复制构造(隐式);
    • 参数类型不一致,且存在该类型的类型转换构造函数,则会:1. 通过该类型转换构造函数生成一个临时对象,2. 通过复制构造函数复制给参数.但是运行时的表现是只调用了转换构造函数. 因为public复制构造函数被优化.(编译器优化,具体如何优化的不得而知,有人说是public复制构造函数被优化,有人说是堆栈优化)
      1和2都是隐式调用,假如1/2中的构造函数任意一者加了explicit声明,编译都会失败.
      (见附1)
    • 浅拷贝问题: 当函数参数以值传入时,会引发复制构造,如果只是编译器自动生成的,则只是对各个成员进行字面上的复制,假如成员中有指针并且构造/析构会对申请释放一段内存,则很可能会出现二次释放问题。浅拷贝的解决方式:1. 使用引用传递,2.重写复制构造函数,改为深拷贝.
      (见附2)
  • 函数参数以引用传递时:
    • 参数类型一致: 不会引发构造,参数是原始对象的引用(一个别名).
    • 参数类型不一致:1. 会生成一个临时对象(隐式类型转换,可被explicit禁止)2. 参数类型必须带有const修饰.(对临时对象的引用)
      (见附3)
  • 关于函数返回值的方式 - 值/引用/地址(主要是对返回值的生命周期有些疑惑)
  • 以值返回:情况与上面说的一样,会引发复制构造(?),会有浅拷贝的问题.
    • 原理上来讲是需要一个临时变量来存返回值:
      1. 从返回值说起(以局部对象返回):返回值是函数体内部的变量,在函数返回后已经出作用域,需要析构.
      2. 临时对象,在返回值析构之前用一个临时对象暂存该返回值(调用复制构造),该临时对象的生命周期在调用该函数的行后结束.
      3. 如果这行存在赋值或复制初始化等情况,则会调用复制构造函数从临时对象复制.
    • 实现上则存在RVO(Return Value Optimization)的情形.编译器优化了1/2/3,在调用行是初始化时直接把需要初始化的对象搞成了局部对象(该局部对象并没有析构),在调用行是赋值时直接从返回值复制. 省略了临时对象.
      (附4中详细讨论)
  • 以引用返回:不会造成临时对象的复制.但以引用返回实在很尴尬.
    • 引用返回的方式一般是把返回值所在变量以引用参数传递进函数,函数内部对其进行修改后返回该引用.
    • 在类的operator方法重载中经常以*this方式返回一个引用.
      (附6)

附:声明性修饰——仅在函数声明时写该关键字即可,定义时不加.

static
explicit

区别:inline关键字必须加在函数定义之前,只加在声明处不起作用.(甚至会引发编译器警告g++4.8)

附1:值传递

#include <cstdio>
class base{
    public:
        base();
        ~base();
        base(int);  //explicit base(int);foo(90): error: could not convert ‘90’ from ‘int’ to ‘base’
        base(const base &);//explicit base(const base &); foo(k): error: no matching function for call to ‘base::base(base&)’  foo(90): error: no matching function for call to ‘base::base(base)’
    private:
        
        int val;
};

inline base::base():val(0){ }
inline base::~base(){ 
    printf("I[%p] am dying.\n", this);
}
inline base::base(const base & b) : val(b.val){
    printf("I[%p] am copied from %d,%p\n", this, b.val, &b);
}
inline base::base(int k):val(k){
    printf("I[%p] am init from <int>\n", this);
}

int foo(base b){
    //do nothing.
    printf("b - %p\n", &b);
}

int main()
{
    base k(100);
    printf("====\n");
    foo(k);//<1>
    printf("====\n");

    printf("====\n");
    foo(90);//<2>
    printf("====\n");

    return 0;
}
work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
I[0x7ffebc53dd00] am init from <int>
====
I[0x7ffebc53dd10] am copied from 100,0x7ffebc53dd00
b - 0x7ffebc53dd10
I[0x7ffebc53dd10] am dying.
====
====
I[0x7ffebc53dd20] am init from <int>
b - 0x7ffebc53dd20
I[0x7ffebc53dd20] am dying.
====
I[0x7ffebc53dd00] am dying.

附2:浅拷贝

#include <iostream>
#include <assert.h>
#include <string.h>
#include <cstdio>

class base{
    public:
        base(int size);     
        ~base();        
        int in(const void *p, int size);
        int out(void *p);
    private:
        char *buf_ptr;
        int size_;
        int used_;
};
base::base(int size) : buf_ptr(NULL), size_(size), used_(0){
    buf_ptr = new char[size];
}
    
base::~base(){
    delete [] buf_ptr;
}


int base::in(const void *p, int size)
{
    if(size > size_ || used_){
        return -1;
    }
    memcpy(buf_ptr ,p , size);
    buf_ptr[size] = '\0';
    used_ = size + 1;
    return 0;
}

int base::out(void *p)
{
    if(!p || !used_){
        return -1;
    }
    memcpy(p ,buf_ptr, used_);
    return 0;
}
void fuck(base &b)
{
    char buf[256];
    if(0 == b.out(buf))
        std::cout<<buf<<std::endl;
    printf("ref's address %p\n", &b);
}
void fuck2(base b)
{
    char buf[256];
    if(0 == b.out(buf))
        std::cout<<buf<<std::endl;
    printf("ref's address %p\n", &b);
}


int main()
{
    base fff(256);
    const char* str = "today is a good day!";
    fff.in(str, strlen(str));
    printf("obj's address %p\n", &fff);
    fuck2(fff); //fuck(fff); <----------
    return 0;
}
work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
obj's address 0x7fff98c17660
today is a good day!
ref's address 0x7fff98c17670
*** Error in `./test': double free or corruption (top): 0x0000000000a7a010 ***
Aborted (core dumped)

注释处若改成fuck(fff);

work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
obj's address 0x7ffd5f375dd0
today is a good day!
ref's address 0x7ffd5f375dd0

附3:对临时对象的引用

#include <cstdio>
class base{
    public:
        base();
        ~base();
        base(int);
        base(const base &);//<3>explicit base(const base &);
    private:
        
        int val;
};

inline base::base():val(0){ }
inline base::~base(){ 
    printf("I[%p] am dying.\n", this);
}
inline base::base(const base & b) : val(b.val){
    printf("I[%p] am copied from %d,%p\n", this, b.val, &b);
}
inline base::base(int k):val(k){
    printf("I[%p] am init from <int>\n", this);
}

int foo(base b){
    //do nothing.
    printf("b - %p\n", &b);
}
int foo2(base & x)//<2> int foo2(base const & x)
{
    printf("x - %p\n", &x);
}
int main()
{
    base k(100);
    foo2(k);//<1>foo2(90);  
    return 0;
}

没做注释处替换:

work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
I[0x7ffd5f840050] am init from <int>
x - 0x7ffd5f840050
I[0x7ffd5f840050] am dying.

替换注释<1>所在行,其他地方不变. 原因是对临时对象必须使用const引用

work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ g++ para.cc -o test
para.cc: In function ‘int main()’:
para.cc:34:9: error: invalid initialization of non-const reference of type ‘base&’ from an rvalue of type ‘int’
  foo2(90);
         ^
para.cc:28:5: error: in passing argument 1 of ‘int foo2(base&)’
 int foo2(base & x){
     ^

在刚刚的基础上把foo2的参数加上const,<2>:

work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
I[0x7fff2a5ac3f0] am init from <int>
I[0x7fff2a5ac400] am init from <int>
x - 0x7fff2a5ac400
I[0x7fff2a5ac400] am dying.
I[0x7fff2a5ac3f0] am dying.

这里没有对复制构造函数的调用.因为参数是引用类型. 在<3>处把复制构造函数加上explicit声明.编译成功,并且结果与上面一样.

附4:-fno-elide-constructors

-fno-elide-constructors
The C++ standard allows an implementation to omit creating a temporary which is only used to initialize another object of the same type. Specifying this option disables that optimization, and forces G++ to call the copy constructor in all cases.

大意就是强迫编译器每次在产生临时对象的时候,都通过复制构造函数来构造,测试一下,确实都明明白白出现了复制构造函数的调用.

我先给出一段代码.

#include <cstdio>
int seq;
class base{
    public:
        base();
        ~base();
        base(int);
        base(const base &);
        base& operator=(const base &);
        void value();
        void add();
    private:
        
        int val;
};

inline base::base() : val(seq){ 
    printf("I[%d,%p] am init from <default>\n", val, this); 
    seq++; 
}
inline base::~base(){ 
    printf("I[%d,%p] am dying.\n", val, this);
    val = -1;
}
inline base::base(const base & b) : val(seq){
    printf("I[%d,%p] am copied from [%d,%p]\n", val, this, b.val, &b);
    seq++;
}
inline base::base(int k):val(seq){
    printf("I[%d,%p] am init from <int>\n", val, this);
    seq++;
}
base& base::operator=(const base &rhs){
    printf("this:[%d,%p] | rhs:[%d,%p]\n",  this->val, this,rhs.val , &rhs);
    return *this;
}


void base::value(){
    printf("[%d,%p]\n", val, this);
}
void base::add(){
    val++;
}

base foo()
{
    printf("===foo()===\n");
    base x;
    x.value();
    return x;
}
base foo2(base x)
{
    return x;
}

int main()
{
    //base s = 100;//1 -->will do the follows: a. call base(int) to a tmp object; b. call base(const base &) to copy to s obj.
    base test = foo();
    printf("====in main()====\n");
    test.value();
    
    return 0;
}

这个在常规的编译下结果是这样:

work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ g++ ret.cc -o test
work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
===foo()===
I[0,0x7fff02e834d0] am init from <default>
[0,0x7fff02e834d0]
====in main()====
[0,0x7fff02e834d0]
I[0,0x7fff02e834d0] am dying.

观察:完全没有体现对复制构造的调用,甚至main里的对象与foo里的对象是同一个,我们很清楚已经遇到了RVO了.编译器把这部分优化掉了,并且你也见不到复制构造的调用.

同样的代码加上-fno-elide-constructors再来一次.

work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ g++ ret.cc -o test -fno-elide-constructors
work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
===foo()===
I[0,0x7fffc7200ad0] am init from <default>
[0,0x7fffc7200ad0]
I[1,0x7fffc7200b10] am copied from [0,0x7fffc7200ad0]
I[0,0x7fffc7200ad0] am dying.
I[2,0x7fffc7200b00] am copied from [1,0x7fffc7200b10]
I[1,0x7fffc7200b10] am dying.
====in main()====
[2,0x7fffc7200b00]
I[2,0x7fffc7200b00] am dying.

ok,代码的运行重新回到我们的三观之内了,可以看到,base test = foo();主要经历了:

  1. 进入foo,声明一个局部变量 0
  2. 到了foo要返回时,一个临时对象10那复制,然后局部变量0析构.
  3. 出foo,test从临时对象1那里复制,临时对象1析构.
  4. main结束前,test对象析构.

所以一共涉及3个对象,而很具有迷惑性的RVO从头到尾只有一个对象.

附5:对附4的扩展

#include <cstdio>
int seq;
class base{
    public:
        base();
        ~base();
        base(int);
        base(const base &);
        base& operator=(const base &);
        void value();
        void add();
    private:
        
        int val;
};

inline base::base() : val(seq){ 
    printf("I[%d,%p] am init from <default>\n", val, this); 
    seq++; 
}
inline base::~base(){ 
    printf("I[%d,%p] am dying.\n", val, this);
    val = -1;
}
inline base::base(const base & b) : val(seq){
    printf("I[%d,%p] am copied from [%d,%p]\n", val, this, b.val, &b);
    seq++;
}
inline base::base(int k):val(seq){
    printf("I[%d,%p] am init from <int>\n", val, this);
    seq++;
}
base& base::operator=(const base &rhs){
    printf("this:[%d,%p] | rhs:[%d,%p]\n",  this->val, this,rhs.val , &rhs);
    return *this;
}


void base::value(){
    printf("[%d,%p]\n", val, this);
}
void base::add(){
    val++;
}

base foo()
{
    printf("===foo()===\n");
    base x;
    x.value();
    return x;
}
base foo2(base x)
{
    printf("===foo2()===\n");
    return x;
}

void test_foo()
{
    seq = 0;
    base test;
    test = foo();
    printf("====in test_foo()====\n");
    test.value();
}
void test_foo2()
{
    seq = 0;
    base tt;
    base test = foo2(tt);
    printf("====in test_foo2()====\n");
    test.value();
}

int main()
{
    //base s = 100;//1 -->will do the follows: a. call base(int) to a tmp object; b. call base(const base &) to copy to s obj.

    
    printf("=====start foo() test============\n");
    test_foo();
    printf("=====end foo() test============\n");
    printf("\n");
    printf("=====start foo2() test============\n");
    test_foo2();
    printf("=====end foo2() test============\n");

    
    return 0;
}
work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ g++ ret.cc -o test
work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
=====start foo() test============
I[0,0x7fffeb2edb70] am init from <default>
===foo()===
I[1,0x7fffeb2edb80] am init from <default>
[1,0x7fffeb2edb80]
this:[0,0x7fffeb2edb70] | rhs:[1,0x7fffeb2edb80]
I[1,0x7fffeb2edb80] am dying.
====in test_foo()====
[0,0x7fffeb2edb70]
I[0,0x7fffeb2edb70] am dying.
=====end foo() test============

=====start foo2() test============
I[0,0x7fffeb2edb60] am init from <default>
I[1,0x7fffeb2edb80] am copied from [0,0x7fffeb2edb60]
===foo2()===
I[2,0x7fffeb2edb70] am copied from [1,0x7fffeb2edb80]
I[1,0x7fffeb2edb80] am dying.
====in test_foo2()====
[2,0x7fffeb2edb70]
I[2,0x7fffeb2edb70] am dying.
I[0,0x7fffeb2edb60] am dying.
=====end foo2() test============

test_foo();主要想与上面直接复制初始化对比,这里就不再从头到尾只有一个对象了,而是一个外部自己的对象与一个赋值操作符.
test_foo2()主要是涉及参数的复制和一个临时对象的复制.

下面演示的是关闭这些优化的结果,很好,很清楚展示什么时候调用了复制,什么时候出现了临时对象.

work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ g++ ret.cc -o test -fno-elide-constructors
work@vm1:~/share/toys/CSE274.me/02_Cpp_Intro$ ./test
=====start foo() test============
I[0,0x7ffeef8c3ac0] am init from <default>
===foo()===
I[1,0x7ffeef8c3a90] am init from <default>
[1,0x7ffeef8c3a90]
I[2,0x7ffeef8c3ad0] am copied from [1,0x7ffeef8c3a90]
I[1,0x7ffeef8c3a90] am dying.
this:[0,0x7ffeef8c3ac0] | rhs:[2,0x7ffeef8c3ad0]
I[2,0x7ffeef8c3ad0] am dying.
====in test_foo()====
[0,0x7ffeef8c3ac0]
I[0,0x7ffeef8c3ac0] am dying.
=====end foo() test============

=====start foo2() test============
I[0,0x7ffeef8c3aa0] am init from <default>
I[1,0x7ffeef8c3ac0] am copied from [0,0x7ffeef8c3aa0]
===foo2()===
I[2,0x7ffeef8c3ad0] am copied from [1,0x7ffeef8c3ac0]
I[3,0x7ffeef8c3ab0] am copied from [2,0x7ffeef8c3ad0]
I[2,0x7ffeef8c3ad0] am dying.
I[1,0x7ffeef8c3ac0] am dying.
====in test_foo2()====
[3,0x7ffeef8c3ab0]
I[3,0x7ffeef8c3ab0] am dying.
I[0,0x7ffeef8c3aa0] am dying.
=====end foo2() test============

附6. 引用作为返回值

由于引用作为返回值与operator重载十分相关,这部分移到另一篇《operator》专门讨论.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容