`###### 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)
- 关于函数返回值的方式 - 值/引用/地址(主要是对返回值的生命周期有些疑惑)
- 以值返回:情况与上面说的一样,会引发复制构造(?),会有浅拷贝的问题.
- 原理上来讲是需要一个临时变量来存返回值:
- 从返回值说起(以局部对象返回):返回值是函数体内部的变量,在函数返回后已经出作用域,需要析构.
- 临时对象,在返回值析构之前用一个临时对象暂存该返回值(调用复制构造),该临时对象的生命周期在调用该函数的行后结束.
- 如果这行存在赋值或复制初始化等情况,则会调用复制构造函数从临时对象复制.
- 实现上则存在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();
主要经历了:
- 进入foo,声明一个局部变量
0
- 到了foo要返回时,一个临时对象
1
从0
那复制,然后局部变量0
析构. - 出foo,
test
从临时对象1
那里复制,临时对象1
析构. - 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》专门讨论.