美文网首页C++探索之旅C/C++首页投稿(暂停使用,暂停投稿)
[cpp deep dive]引用与指针、引用传递与值传递

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

作者: Quasars | 来源:发表于2016-06-26 23:48 被阅读67次

    `###### 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》专门讨论.

    相关文章

      网友评论

        本文标题:[cpp deep dive]引用与指针、引用传递与值传递

        本文链接:https://www.haomeiwen.com/subject/hqnadttx.html