<<零基础C++第一期,C++入门基础之引用知识点>>
目录
引用
引用的概念和定义
引用的特性
引用的使用
const引用
指针和引⽤的关系
引用
引用的概念和定义
在生活中,我们可能也会给一些同学起外号,以“张磊”同学为例,我们可以叫他“张三石”,当我们叫到这个外号的时候就会自然而然的想到“张磊”同学,”张三石”就是张磊的别名,而引用也可以这样简单理解:在语法层面上,引用就是取别名。
引⽤不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引⽤变量开辟内存空间, 它和它引⽤的变量共⽤同⼀块内存空间。
来看语法:
类型& 引⽤别名 = 引⽤对象;
#include <iostream>
using namespace std;
int main()
{
int a = 10;
//引用,b 和 c 是a的别名
int& b = a;
int& c = a;
//也可以给别名b取别名,d相当于还是a的别名
int& d = b;
//a,b,c,d的地址都是一样的
cout << "&a = " << &a << endl;
cout << "&b = " << &b << endl;
cout << "&c = " << &c << endl;
cout << "&d = " << &d << endl;
return 0;
}
注:引用类型必须和引用实体是同种类型。
引用的特性
一、引⽤在定义时必须初始化
正确的示例:
int a = 10;
int& ra = a;
错误示例:
//编译会报错,ra必须初始化
int a = 10;
int& ra;
二、⼀个变量可以有多个引⽤
int a = 10;
int& b = a;
int& c = a;
int& d = a;
这里,b、c、d都是变量a的引用。
三、引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体
例如:
int a = 10;
int& b = a;
此时,b已经是a的引用了,b不能再引用其他实体。
int a = 10;
int& b = a;
int c = 20;
//这里并非让b引用c,因为c++引用不能改变指向
//这里是一个赋值
b = c;
引用的使用
一、引⽤在实践中主要是于引⽤传参和引⽤做返回值中减少拷⻉提⾼效率和改变引⽤对象时同时改变被 引⽤对象。
- 1.首先来讲一下引用传参
#include <iostream>
using namespace std;
//引用传参,实际的地址和x,y是一样的
void swap(int& rx, int& ry)
{
int tmp = rx;
rx = ry;
ry = tmp;
}
int main()
{
int x = 0, y = 1;
cout << x << " " << y << endl;
swap(x, y);
cout << x << " " << y << endl;
return 0;
}
引⽤传参跟指针传参功能是类似的,引⽤传参相对更⽅便⼀些。并且减少拷贝,不用开辟空间。
有人可能会说:引用定义不是需要初始化吗?其实形参初始化是实参传参的时候,当调用函数的时候才开始传参 。(传参的时候就相当于初始化)
注意:
改变引用别名,就改变了实参。本质上指向的都是同一个人。
- 2.引用做返回值
在用引用做返回值之前,我们先来看传值返回
//这是取栈顶元素
int STTop(ST& rs)
{
return rs.a[rs.top-1];
}
int main()
{
STTop(s) += 10; //这里会报错,不是左值
return 0;
}
这边写一部分代码,重点理解传值返回 和 引用返回值
传值返回其实是一个拷贝,当这个值比较小的话放在临时对象里,数据大的话放在寄存器。而临时对象(临时在栈帧开辟一块空间) 和 寄存器 都不是左值,相当于被const修饰,具有常性,不能被修改,属于右值。
再来看引用做返回值
int& STTop(ST& rs)
{
return rs.a[rs.top-1];
}
int main()
{
STTop(s) += 10; //这里不会报错,并且会加上10
return 0;
}
引用做返回值;用函数调用表达式,返回值是返回对象的别名。
这其实就是给返回的值,定义了一个引用--> int&__ 。通过这个引用找到它的内容。
有人可能会问出了这个局部函数,rs不是销毁了吗?确实是这样的。但是实际的空间还在就行。用新的引用去找内容不过分把!!!
引⽤返回值的场景相对⽐较复杂,我们在这⾥简单讲了⼀下场景,还有⼀些内容后续类和对象章节 中会继续深⼊讲解。
二、⼀些主要⽤C代码实现版本数据结构教材中,使⽤C++引⽤替代指针传参,⽬的是简化程序,避开 复杂的指针,但是很多同学没学过引⽤,导致⼀头雾⽔。
例子:
typedef struct ListNode
{
int val;
struct ListNode* next;
}LTNode, * PNode;
//下面的写法让大家更好理解
//typedef struct ListNode LTNode;
//typedef struct ListNode* PNode;
//指针写法
//void ListPushBack(LTNode** pphead, int x);
//写法一
//void ListPushBack(LTNode*& phead, int x);
//写法二
void ListPushBack(PNode& phead, int x);
int main()
{
PNode plist = NULL;
ListPushBack(plist, 1);
return 0;
}
首先它把struct ListNode 重定义了typedef struct ListNode LTNode 和 typedef struct ListNode* PNode(PNode是结构体的一级指针);当你传给函数一级结构体指针,需要改变内容你就需要传二级指针--》void ListPushBack(LTNode** pphead, int x);
而书上使用了引用,那么就是给一级指针变量取别名,有俩种写法:
1.void ListPushBack(LTNode*& phead, int x);
2.void ListPushBack(PNode& phead, int x);
原本作者是觉得指针太难,用的引用。其实你还是得理解指针,这样才能灵活运用。
const引用
一、引用const对象,是必须⽤const引⽤。因为有const对象的具有常性,不能被修改;可以理解为只能读,不能改。
正确示例:
权限平移
//权限平移,都只能读取
const int a = 10;
const int& ra = a;
权限缩小
//权限缩小,原本可读可写,变成只读也可以
int a = 10;
const int& ra = a;
错误示例:
//这里的引用是堆a的访问权限放大
const int a = 10;
int& ra = a;
举个栗子:你叫小鹏,当你下定决心减肥的时候,实行5减2,指的是5天有俩天不吃饭。这天,同学说小鹏去吃饭,你说在减肥。这时,你同学喊你鹏哥,说跟小鹏没关系,你觉得可能吗?
在这里也一样,原本只读的,引用之后还能写。这样就会出错。
注意:
const引⽤也可以引⽤普通对象,因为对象的访 问权限在引⽤过程中可以缩⼩,但是不能放⼤
二、比如int& rb = a*3 ; double d = 12.34 ; int& rd = d; 像这些运算表达式、类型转换、传值返回,都是存在临时变量 或者 寄存器中, 必须用const修饰
例子:
//运算表达式
const int& rb = a * 3;
//类型转型
double d = 2.2;
const int& rc = d;
//。。。
const int& rra = 30;
所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象, C++中把这个未命名对象叫做临时对象。
指针和引⽤的关系
C++中指针和引⽤就像两个性格迥异的亲兄弟,指针是哥哥,引⽤是弟弟,在实践中他们相辅相成,功 能有重叠性,但是各有⾃⼰的特点,互相不可替代。
- 语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
- 引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
- 引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。
- 引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象
- sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下 占4个字节,64位下是8byte)
- 指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些。
注意:
指针的使用会更麻烦一些,所以功能重复的部分,建议使用引用 。