C++对C语言的扩展--引用
1 引用
1.1 变量名
1.2 引用的概念
1.3 规则
1.4 引用作为函数参数
1.5 引用的意义
1.6 引用的本质
1.7 引用作为函数的返回值
1.7.1 若返回栈变量的引用
1.7.2 若返回静态变量或全局变量引用
1.8 数组的引用
1.9 指针的引用
1.10 const 引用
1.11 常量的引用
1.11 const 引用的原理
1.12 引用的注意事项
1 引用
1.1 变量名
变量名实质上是一段连续存储空间的别名,是一个标号
通过变量名来申请并命名内存空间
通过变量的名字可以使用内存空间
1.2 引用的概念
变量名,本身是一段内存的引用,即别名(alias)。引用可以看作一个已定义变量的别名。
引用的语法:Type &name = var;
用法如下:
int a = 10;// 编译器分配4个字节的内存空间,a为此内存空间的别名,可以通过a来修改内存空间的值
int &b = a;// b为a的引用,即b为a的别名,同样可以通过b来修改内存空间的值
1.3 规则
引用没有定义,是一种关系型声明。声明它和原有某一变量(实体)的关系。故而类型与原类型保持一致,且不分配内存。与被引用的变量有相同的地址。
声明的时候必须初始化,一经声明,不可变更。
可对引用,再次引用。多次引用的结果,是某一变量具有多个别名。
&符号前有数据类型时,是引用。其它皆为取地址。
int a, b;
int &r = a;// 正确,变量与引⽤用具有相通的地址
int &r = b;// 错误,不可以修改原有的引用关系
float &rr = b;// 错误,引用类型不匹配
int &ra = r;// 正确,可以对引用再次引用,此时a有两个别名r和ra
1.4 引用作为函数参数
普通引用在声明时必须用其它的变量进行初始化,引用作为函数参数声明时不进行初始化。
void test01(int &i)// 形参为引用,相当于对传入形参起别名
{
r = 100;// 相当于修改了主函数中a为100
return;
}
void test02(int j)
{
j = 1000;
}
int main()
{
int a = 10;
cout << a << endl;// a = 10
test01(a);// 当传a进去时,i为a的别名,i与a为相同的内存地址
cout << a << endl;// a = 100;
test02(a);
cout << a << endl;// a = 100;
}
1.5 引用的意义
引用作为其它变量的别名而存在,因此在一些场合可以代替指针
引用相对于指针来说具有更好的可读性和实用性
1.6 引用的本质
引用在C++中的内部实现是一个常指针
Type& name <===> Type const name*
C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同
从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这是C++为了实用性而做出的细节隐藏。
间接赋值的3各必要条件
定义两个变量 (一个实参一个形参)
建立关联 实参取地址传给形参
*p形参去间接的修改实参的值
引用在实现上,只不过是把:间接赋值成立的三个条件的后两步和二为一.
当实参传给形参引用的时候,只不过是c++编译器帮我们程序员手工取了
一个实参地址,传给了形参引用(常量指针) 。
1.7 引用作为函数的返回值
1.7.1 若返回栈变量的引用
不能成为其它引用的初始值(不能作为左值使用)
尽量不要返回栈变量作引用
#include
using namespace std;
int getA1()
{
int a;
a = 10;
return a;
}
int& getA2()
{
int a;
a = 10;
return a;
}
int main()
{
int a1 = 0;
int a2 = 0;
// 值拷贝
a1 = getA1();
// 将一个引用赋给一个变量,会有拷贝动作
// 理解:编译器类似做了如下隐藏操作,a2 = *(getA2());
a2 = getA2();
// 将一个引用赋给另一个引用作为初始值,由于是栈内存的引用(函数getA2中变量a为栈内存,出了作用域就栈内存就释放了),内存非法
//int &a3 = getA2();
cout << "a1 = " << a1 << endl;
cout << "a2 = " << a2 << endl;
// cout << "a3 = " << a3 << endl;
return 0;
}
1.7.2 若返回静态变量或全局变量引用
可以成为其他引用的初始值(可作为右值使用,也可作为左值使用)
#include
using namespace std;
int getA1()
{
int a;
static a = 10;
return a;
}
int& getA2()
{
int a;
static a = 10;
return a;
}
int main()
{
int a1 = 0;
int a2 = 0;
// 值拷贝
a1 = getA1();
// 将一个引用赋给一个变量,会有拷贝动作
// 理解:编译器类似做了如下隐藏操作,a2 = *(getA2());
a2 = getA2();
// 将一个引用赋给另一个引用作为初始值,由于是静态区域,内存合法
int &a3 = getA2();
cout << "a1 = " << a1 << endl;
cout << "a2 = " << a2 << endl;
cout << "a3 = " << a3 << endl;
return 0;
}
如果返回值为引用可以当左值。(返回变量本身)(全局/静态变量)
如果返回值为普通变量不可以当左值。(返回变量的值)
#include
using namespace std;
// 函数当左值
// 返回变量的值
int func1()
{
int a;
static a = 10;
return a;
}
// 返回变量本身
int& func2()
{
int a;
static a = 10;
return a;
}
int main()
{
int a1 = 0;
int a2 = 0;
// 函数当右值
a1 = func1();
cout << "a1 = " << a1 << endl;// a1 = 10
// 函数返回值是一个引用,并且当右值
a2 = func2();
cout << "a2 = " << a2 << endl;// a2 = 10
// 函数当左值
//func1 = 100;// 错误
func2 = 100;// 函数返回值是⼀一个引⽤用,并且当左值
int a3 = func2();
cout << "a3 = " << a3 << endl;// a3 = 100
return 0;
}
1.8 数组的引用
//1.直接建立引用
int arr[10];
int(&pArr)[10] = arr;
// 使用
pArr[0] = 1;// 相当于arr[0] = 1;
----------------------------------------------------------------
//2.先定义出数组类型,再通过类型 定义引用
int arr[10];
typedef int(ARRAY_TYPE)[10];// 创建出10个int类型的数组,叫ARRAY_TYPE,即ARRAY_TYPE为10个int类型的数组
ARRAY_TYPE & pArr2 = arr;
// 使用
pArr2[0] = 1;// 相当于arr[0] = 1;
1.9 指针的引用
#include
using namespace std;
struct Person
{
string name;
int age;
};
void getPerson01(Person **p)
{
(*p)->name = "张三";
(*p)->age = 18;
return;
}
void getPerson02(Person *&p)
{
p->name = "李四";
p->age = 20;
return;
}
int main()
{
Person *pP = new Person;
//1c语⾔言中的⼆二级指针
getPerson01(&pP);
cout << "姓名:" << pP->name << " 年龄:" << pP->age << endl;
//2c++中的引⽤用 (指针的引⽤用)
//引⽤用的本质 间接赋值后2个条件 让c++编译器帮我们程序员做了
getPerson02(pP);
cout << "姓名:" << pP->name << " 年龄:" << pP->age << endl;
delete pP;
return 0;
}
利用引用可以简化指针
可以直接用同级指针的引用,给同级指针分配空间
1.10 const 引用
const 引用有较多使用。它可以防止对象的值被随意修改。因而具有一些特性。
1.const 对象的引用必须是const的,将普通引用绑定到const对象是不合法的。
const int a =1;// const变量
int &b = a;// 普通引用b绑定到const对象不合法
------------------------------------------------------------
int x = 1;
const int &y = a;// 合法,常引⽤用是限制变量为只读 不能通过y去修改x了
// y = 2;// 错误
const 引用可使用相关类型的对象(常量,非同类型的变量或表达式)初始化。这个是const 引用与普通引用最大的区别。
const int a =1;// const变量
const int &b = a;// 合法
------------------------------------------------------------
double x = 3.14;
const int &y = x;// 合法 y = 3;
1.11 常量的引用
const int &ref = 10;
// 加了const之后,相当于写成 int temp = 10; const int &ref = temp;
//常量引用的使用场景:修饰函数中的形参,防止误操作
void test(const int &a)
{
a = 10;// 错误
}
1.11 const 引用的原理
const 引用的目的是,禁止通过修改引用值来改变被引用的对象。
const 引用的初始化特性较为微妙,可通过如下代码说明:
double val = 3.14;
const int &ref = val;// 使用相关类型的对象初始 相当于 int temp = val; const int &ref = temp;
double &ref2 = val;
cout << ref << " " << ref2 << endl;
val = 4.14;
cout << ref << " " << ref2 << endl;
上述输出结果为3 3.14和3 4.14。因为ref是const的,在初始化的过程中已经给定值,不允许修改.而被引用的对象是val,是非const的,所以val的修改并未影响ref的值,而ref2的值发生了相应的改变。
为什么非const的引用不能使用相关类型(常量,非同类型的变量或表达式)初始化呢???
实际上,const 引用使用相关类型对象初始化时发生了如下过程:
int temp = val;
const int &ref = temp;
如果 ref 不是const的,那么改变ref 值,修改的是 temp,而不是 val。期望对ref的赋值会修改val的程序员会发现val实际并未修改。
因此不允许使用相关类型初始化非const引用
// 1.⽤用变量 初始化 常引⽤用
int x1 = 30;
const int &y1 = x1;// 用x1变量初始化 常引用
// 2.用字面量初始 常量引用
const int a = 40;// c++编译器把a放在符号表中
// int &m = 41;// 错误
const int &m = 42;// c++ 会分配内存空间
// 相当于 int temp = 42; const int &m = temp;
1.12 引用的注意事项
引用必须引一块合法内存空间
不要返回局部变量的引用
当函数返回值是引用时候,那么函数的调用可以作为左值进行运算
结论:
const int & e 相当于 const int * const e
普通引用 相当于 int *const e
当使用常量(字面量)对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名
使用字面量对const引用初始化后,将生成一个只读变量