C++探索之旅C/C++首页投稿(暂停使用,暂停投稿)

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

2016-06-26  本文已影响67人  Quasars

`###### stage_0 基本

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

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

        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; //同上,常量指针

    //编译通过,但不代表这就是重新指代
    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);
 //确定数组元素个数的宏:
    #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]));

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

stage_1 引用的一些其他

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

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

上一篇 下一篇

猜你喜欢

热点阅读