http://philoscience.iteye.com/blog/1124950
赋值行为的差异:给引用赋值修改的是该引用所关联的对象的值,而并不是使引用与另一个对象关联。引用一经初始化,就始终指向同一个特定对象。
- int *ip[4]; //包含4个指向int的指针数组 //指针数组,数组的元素是指针
- int (*ip) [4]; //指向包含有4个int元素的数组的指针 //数组的指针
可以用下面任何一种方法为int apricot[2][3][5]在内存中定位:

http://www.zyfforlinux.cc/2014/11/01/C++%E5%BC%95%E7%94%A8%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E7%B2%BE%E9%80%9A/
引用就是为对象起一个别名,两个别名都代表内存的中的用一个地址,但是引用和指针不同的是,引用不是一个对象,指针本身是一个对象。
#include <iostream>
int main()
{
int num(5);
int *p(&num);
int &r(num);
std::cout <<"num的地址:" <<&num <<"指针p的地址:" << &p<<std::endl;
std::cout <<"num的地址:" <<&num <<"引用r的地址:" << &r<<std::endl;
}
输出结果:
num的地址:0x7fffd21299e4指针p的地址:0x7fffd21299d8
num的地址:0x7fffd21299e4引用r的地址:0x7fffd21299e4
可见引用是不占用内存空间的,引用不是一个对象,引用和引用的实际对象地址是相同的,指针本身是一个对象,这个对象所在内存里面存的值是num的地址
使用引用的时候需要注意一些东西:
- 引用在声明的时候必须要进行初始化
- 引用声明后不能再作为其他变量的引用
- 不能直接对数组引用(需要变换形式对数组进行引用,引用高级部分会详细介绍)
- 引用不能对字面值建立引用(因为在C++中子面值都是const类型,需要使用常量引用才可以对字面值进行引用)
看下面这个例子
#include <iostream>
int main()
{
int &a = 4; //不可以 4是const类型 左边引用是非const类型,类型不匹配
const int &b = 4; //可以
}
报错信息:
invalid initialization of non-const reference of type ‘int&’ from an rvalue of type ‘int’
用引用作为函数的返回值最大的好处是在内存中不产生返回值的一个副本
#include <iostream>
float temp;
float fn1(float r)
{
temp = r*r*3.14;
return temp;
}
float& fn2(float r)
{
temp = r*r*3.14;
return temp; //temp是全局变量,因此不是返回临时变量的引用
}
void main()
{
float a=fn1(5.0); //1 在栈中创建临时变量,将temp的值赋给这个临时变量,然后再将这个临时变量的值复制给a
float& b=fn1(5.0); //2:warning 过程和上面一样,但是这里是利用临时变量给引用初始化,因为临时变量的在赋值语句执行
//结束后就宣告结束了其值是不确定的。所在这样使用C++编译器会报错
float c=fn2(5.0); //3 不产生临时变量直接将temp的值赋值给c,避免产生临时变量和产生多余的赋值开销
float& d=fn2(5.0); //4 不产生临时变量,直接对temp产生一个引用d,完全避免了赋值和产生临时变量的开销
cout<<a<<endl;
cout<<b<<endl;
cout<<c<<endl;
cout<<d<<endl;
}
-
引用进阶 - 不能返回局部变量的引用,即使是局部的智能指针也不行
对于这个问题我就不做分析了,如果对程序的内存的结构清楚的化就很明细了,如果返回局部变量的引用,那么当函数执行结束局部变量的
生成周期就结束了,局部变量的值也就是一个未定的了。
-
不能返回函数内部new分配的内存的引用
这点主要是从工程实践的角度考虑(参考Effective C++),如果你在函数内部使用new进行分配,并返回一个引用,然后这个引用只是作为临时
变量出现,并没有实际的赋值给一个引用这就导致内存空间无法释放导致内存泄露问题。
1.如何引用一个数组
#include <iostream>
int main()
{
int data[4] = {1,2,34,4};
// int &rd = data; //错误,无法进行引用,需要用另外一种方式
int (&rc)[4] = data; //可以,这才是引用数组的方法;数组的引用
for(auto num : data)
std::cout << num;
//对二维数组的引用
int num[2][3]={0};
int (&rf)[2][3] = num;
}
2.引用函数指针
#include <iostream>
int add(int a,int b)
{
std::cout << a+b <<std::endl;
}
int main()
{
int(*p)(int,int)(add);//定义一个函数指针p,指向add函数
int(* &r)(int,int)(p); //定义一个函数指针的引用 引用p
r(4,5);
}
引用高级
看下面这个例子:
C++11中引入了右值引用这个概念,首先要明确下什么是右值什么是左值
C++的表达式要不然是右值,要么是左值, 左值可以位于赋值语句的左侧(也可以位于右侧),右值则不能。
当一个对象用作右值的时候用的是对象的值(这个值是要放在寄存器中的), 当对象被用作左值的时候用的是对象的身份(在内存中的位置)
左值和右值最大的区别是是否可以取地址。
对于常规的引用(我们称为左值引用)我们不能将左值引用绑定得到要求转换的表达式,字面常量,或是返回右值表达式
例如下面这个例子:
1
|
#include <iostream>
|
//把右值赋给左值引用,错误,除非使用const &万能引用,既可以用左值赋值,又可以用右值赋值
右值引用则和左值引用有着相反的特性,可以将绑定在子面值、表达式; 但是却不能绑定到一些左值上。
例如下面这个例子:
1
|
#include <iostream>
|
看了那么多,总而言之记住左值引用不能进行引用的集中场合(将左值引用绑定得到要求转换的表达式,字面常量,或是返回右值表达式)
右值引用有着相反的特性就OK了。剩下的我们就需要关注左值引用和右值引用最重要的区别:
=====================
一、大概区别
就像指针的数组和数组的指针一样耐人寻味
- //引用的数组,数组的元素是引用
int &array[], 相当于(int&) array[], 不支持,且错误
- //array首先和&结合,所以array是引用,引用的对象是数组
//应该叫作数组的引用
int (&array)[10]
二、引用的数组
首先,可以明确的说明,引用的数组是不能当函数的参数的。再者要说明,这种方式是非法的。
原因:从引用的本质说吧
首先,引用必须被初始化,这没得说,而数组并不能被另一个数组初始化或被另一个数组赋值这是其一,并且其中的每一个引用,你没有办法给他一下子初始化。
再次,引用它不支持传统意义的复制,它不占用新的空间
再再次,他没有意义,它可以用指针数组实现,完全可以代替
int i,j;
int *a[5] = {&i, &j};
a[0]; // point to i
a[1]; // point to j
再再再次,不好实现,因为编译器不知道应该分多大的内存给引用的数组。
三、数组的引用
如果不好理解,可以这样理解(从网上看到的)
int n3[3] = {2, 4, 6};
int (&rn3)[3] = n3; //数组的引用
typedef一下
int n3[3] = {2, 4, 6};
typedef int Int3[3];
Int3& rn3 = n3;
四、数组的引用的应用
1、如上所说,可以当函数参数,并且可以传递维数过去。
2、维度的大小可以用一个模板的方式直接表示,这样更清楚明确
template<size_t SIZE> void fun(int (&arr)[SIZE] );
code:
1: // --------------------------------------------------------------------------
2: // 说明:数组引用学习
3: // 环境:vs2008
4: // 创建:熊哥 (http://pppboy.blog.163.com)
5: // 时间:2013.9.13
6: // --------------------------------------------------------------------------
7:
8: //固定大小,很难受
9: void ArrRef(int (&arr)[5])
10: {
11: for (int i = 0; i < 5; ++i)
12: {
13: cout << arr[i] << " ";
14: }
15: cout << "\n";
16: }
17:
18: //使用模板通用化一些,对数组通用
19: template<size_t SIZE>
20: void ArrRefTemplete(int (&arr)[SIZE])
21: {
22: for (int i = 0; i < SIZE; ++i)
23: {
24: cout << arr[i] << " ";
25: }
26: cout << "\n";
27: }
28:
29: int main(int argc, char* argv[])
30: {
31: const int MAX_INDEX(5);
32: int aIndex[MAX_INDEX] = {1,3,4,5,6};
33:
34: //如果改了数组大小MAX_INDEX的值,两个大小对不上就会有问题
35: ArrRef(aIndex);
36:
37: //如果改了数组大小MAX_INDEX的值,也无所谓
38: ArrRefTemplete(aIndex);
39:
40: system("pause");
41: return 0;
42: }
out:
1 3 4 5 6
1 3 4 5 6
请按任意键继续. . .
end
==================================
https://blog.csdn.net/fukaibo121/article/details/76473333
C++的数组元素为什么不能是引用类型
先讲一下引用的某些特点:
1.引用是对象的另一个名字(这里的对象指内存的区域,如int所占内存4个字节)。定义一个对象的引用,该引用并不占内存空间,它只是这个对象的别名。
2.引用必须绑定到相应的对象。引用在定义时必须初始化,绑定到相应的对象,且之后不能更改。
再说一下为什么数组的元素不能为引用:
- 1.大数组初始化麻烦。引用在定义时必须初始化。若数组元素是引用的话,则必须对每个元素进行初始化。比如int a[100000000]这样的大数组,一一赋值很麻烦。就算你可以一一初始化每个元素,那么往下看。
- 2.破坏数组元素的内存存放连续性。数组的一大优点就是可以随机快速访问某个元素,这是因为数组不仅在逻辑上连续,在物理上也连续。如对数组int a[10],给其第4个元素赋值可以写为:*(a+3) = 15;。
能这么写是因为数组元素在内存中是连续存放的,如果数组a的首地址为0x00000000,那么a[0]的地址是0x00000000,a[1]的地址是0x00000004,a[2]的是0x00000008,以此类推。
a中每个元素都占用4个字节空间,每个元素在逻辑上是连续的,即第2个元素紧接着第1个元素;同样,数组在物理内存中的存放也是连续的,即第2个元素的地址紧挨着第1个元素。
那么,数组元素为引用会是什么情况?上面引用第1个特点中提到,引用只是标签,需要手动绑定到某个对象(地址区域)才行。那么在给数组中的引用初始化时,不能保证逻辑上连续的引用元素所绑定的对象也正好物理上连续。
举个例子:
假如引用数组是合法的,编译可以通过,那么根据语法来说,定义语句应该是这样的:int &a[4],a是一个数组,有4个元素,每个元素都是引用。当然,我们还需要对每个元素进行初始化,所以写全的话是这样:
//首先定义4个对象,内存地址、标签和初值如下:
int i1 = 10; //内存地址是0x00000000-0x00000003,初值是10
int i2 = 11;//内存地址是0x00000014-0x00000017,初值是11
int i3 = 12;//内存地址是0x0000000c-0x0000000f,初值是12
int i4 = 13;//内存地址是0x00000020-0x00000023,初值是13
int &a[4] = {i1, i2, i3, i4};//a的4个元素分别绑定到4个对象。
这样看来,既然a是数组,那么逻辑上第1个元素紧挨着第2个元素,物理上也应该如此。但是这个例子告诉我们,a数组的元素在物理上并不连续,a[0]的地址是i1的地址,a[1]的地址是i2的地址,以此类推。甚至a[2]的地址比a[1]的地址还要靠前(本应该靠后才对)。
正是由于引用只是别名,不占用内存空间,才导致了上述的这个问题,使得数组在物理上不连续了。这样的话类似*(a+3)=15;的语句就行不通了。
因为a+3是这样计算的:a的地址+3×元素所占字节数。这么计算的根基就是必须保证物理连续,否则无法通过数组首地址推出其余元素的地址。
3.操作系统无法为引用数组分配空间。对象是要占用内存空间的,如int a[10];这条定义语句定义了一个有10个整型元素的数组,因此操作系统在运行时从内存划出40字节留给数组a,然后访问a就会访问这40字节的地址。
但是引用是不占用内存的,它只是名字,所以如果数组元素是引用的话,如int &ar[10]={….};,这个数组到底占用多少内存?不知道!因为引用不占用空间,操作系统无法给数组分配内存空间,也就无法读写这个数组。无法读写的数组还有意义吗?
综上所述,数组的元素不能是引用。
=============引用的引用,引用折叠==========
https://www.zhihu.com/question/28023545
“因为引用本身不是一个对象,所以不能定义引用的引用”。
int main()
{
int ival=1024;
int &refVal=ival;
int &&refVal2=refVal;
return 0;
}
main.cpp:32:16: error: cannot bind rvalue reference of type 'int&&' to lvalue of type 'int'
int &&refVal2=refVal;
间接定义引用的引用有两种方式:
a)类型别名。
例如:
using intRef = int &; // 用typedef亦可
int a = 1;
intRef & rrefa = a; // rrefa是引用的引用
b)模板。
例如:
template<typename T>
void func(T &&t){}
int a = 1;
func(a);
/***************** 注意 *****************/
/* 如果func是普通函数而非函数模板,则这段代码会报错 */
/* 因为左值不能绑定到右值引用上 */
/* void func(int && t){} ---func(a) --- ERROR!!! */
/* 函数模板是个特例,此处会触发引用折叠 */
引用折叠规则:
X& &(引用的引用)、X& &&(右值引用的引用)、X&& &(引用的右值引用)均折叠为X &。
X&& &&(右值引用的右值引用)折叠为X &&。
上面的类型别名和函数模板均触发了引用折叠。
注意:引用折叠的前提必须是类型别名或者模板参数。标准禁止直接定义引用的引用。
为什么需要引用折叠:
引用折叠是std::move、std::forward等的工作基础。
所以题主千万不要觉得“引用的引用”是一件“很正常”的事情。如果有人问你:c++能定义引用的引用吗?答案是:不能。不过你可以补充说:不过有两个例外:类型别名和模板参数时可以间接定义引用的引用。
事实上,所谓“间接定义引用的引用”的说法并不十分正确,正确的说法是“引用折叠”而其实引用折叠后依旧是普通的引用或者右值引用,所以其实“引用的引用”严格来说是不存在的。
c++需要引用折叠的原因是为了实现std::move。也就是说,所谓“引用的引用”的存在价值,只是为std::move、std::forward等而服务的,仅此而已;它并不是让程序员去用的。
在你自己写代码的时候,一定不要“引用的引用”---因为凡是需要用“引用的引用”即引用折叠才能实现的代码,你都可以在std里找到,例如std::move、std::forward。
说到这里,可能会有朋友突然灵机一动:如果我把所有的函数模板的参数类型都定义成T &&,那么这个模板就既可以接受左值实参又可以接受右值实参,岂不是很好吗?
这个想法看似很不错但是是不可行的,比如:
template<typename T>
void func(T && param)
{
T t = param; // 普通的赋值拷贝还是引用?
t = changeValue(t); // param的值也会被改变吗?
if (t == param) // 是不是永远为true?
{//...}
}
在个例子里,如果给func一个左值,比如int a,那么T就会被推断成int &,参数展开后为int & && param, param的类型经过引用折叠后是int &。此时,T t就等效于int &t,因此t是引用;
而如果给func一个右值,那么T就会被推断为int。此时,T t等效于int t,因此t是赋值拷贝后生成的T类型的一个普通值。
所以,在大多数情况下,写模板还是老老实实按部就班地写,千万不要滥用引用折叠。再说一次,引用折叠是服务于std::move、std::forward等的,而并不是服务于程序员的。
不过,在少数情况下,程序员确实需要使用引用折叠来完成一些代码的设计。
“少数情况”指:
case1)模板转发实参
case2)模板重载
这里就不展开说了。
关于这部分内容的推荐:
《c++ primer》第五版(中文版)---P608至P612
另:如果题主是在学习c++,那么请你务必尽快使用靠谱的编译器,你截图显示出你在用一款非常粗糙的编译器(本来应该报错的但是没有),这是很可怕的因为这些编译器会篡改c++的标准。
如果你实在找不到合适的编译器,你甚至可以找个靠谱点的在线c++编译器---即使是这种编译器都要比你现在使用的编译器要强得多得多。这里给你推荐一个,但不知是否需要FQ:
Compile and Execute C++ Online
5 There shall be no references to references
Effective Modern C++, Chapter 5, Item 28: Understand reference collapsing
You are forbidden from declaring references to references, but compilers may produce them inparticular contexts.
When compilers generate references to references, reference collapsing dictates what happens next.
Reference collapsing occurs in four contexts: template instantiation, auto type generation, creation and use of typedefs and alias declarations, and decltype.
When compilers generate a reference to a reference in a reference collapsing context, the result becomes a single reference. If either of the original references is an lvalue reference, the result is an lvalue reference. Otherwise it’s an rvalue reference.
浙公网安备 33010602011771号