C++【引用】——串讲

【引用】——串讲(视频89-94)

Note:
i.视频为黑马程序员C++视频,系列文章为视频听课笔记;
ii.引用不仅包含定义及简单应用,在类与对象…中也有涉及;
iii.难度指数:+++
iv.不论变量、函数名、标识符形式怎样复杂,只要我们考虑编程的本质是对内存的操作,对内存进行分析,一切逻辑都会变得清晰。

1.引用的基本语法
1)含义:给变量起别名
2)code格式:数据类型 &别名 = 变量名
例如:int &b = a,则b为变量a的引用,也是对引用b的初始化
3)内存图解:
定义引用,则建立内存关系
由图可见,若定义b为变量a的引用,则b可对变量a中存储的值进行操作。

2.引用的注意事项
1)引用定义则必须进行初始化
2)引用在第一次初始化后不可更改

3.引用做函数参数(重点)
之前提到某封装函数被调用,传参时的传递方式分为按值传递或按地址传递,前者形参的改变不能改变实参的值,后者形参的改变可以改变实参的值;或者说前者形参不能修饰实参,后者形参可以修饰实参。

实际上,按地址传递实际上是传递的是指针,其记录实参的地址,因此可以实现对实参的修饰。引入“引用”的概念之后,可利用其作为函数参数实现对实参的修饰。

至此,函数被调用时,参数的传递方式有以下几种:
1)值传递
2)地址传递
3)引用传递
举例:以完成实参值的交换为例
1)值传递

#include<iostream>
#include<string>
using namespace std;
//定义值传递交换函数
void swap01(int a,int b)
{
	//值传递交换函数无法实现实参的交换
	int temp = a;
	a = b;
	tmep = b;
	cout << "值传递形参a=" << a << endl;//a=20
	cout << "值传递形参b=" << b << endl;//b=10
}
int main()
{
	int a = 10;
	int b = 20;
	swap01(a, b);
	cout << "值传递交换后实参a=" << a << endl;//a=10
	cout << "值传递交换后实参b=" << b << endl;//b=20
}

依靠值传递交换函数,无法实现实参的交换。

2)地址传递

#include<iostream>
#include<string>
using namespace std;
//定义地址传递交换函数
void swap02(int *a,int *b)
{
	//地址传递交换函数可以实现实参的交换
	int temp = *a;
	*a = *b;
	tmep = *b;
	cout << "地址传递形参a=" << a << endl;//a=20
	cout << "地址传递形参b=" << b << endl;//b=10
}
int main()
{
	int a = 10;
	int b = 20;
	swap02(&a, &b);
	cout << "地址传递交换后实参a=" << a << endl;//a=20
	cout << "地址传递交换后实参b=" << b << endl;//b=10
}

依赖地址传递交换函数,可以实现实参的交换。

3)引用传递

#include<iostream>
#include<string>
using namespace std;
//定义引用传递交换函数
void swap03(int &c,int &d)//定义形参c为a的引用,d为b的引用
{
	//引用传递交换函数可以实现实参的交换
	int temp = c;
	c = d;
	tmep = d;
	cout << "引用传递形参a=" << c << endl;//a=20
	cout << "引用传递形参b=" << d<< endl;//b=10
}
int main()
{
	int a = 10;
	int b = 20;
	swap03(a, b);//注意这里的实参,不需要加任何东西符号
	cout << "引用传递交换后实参a=" << a << endl;//a=20
	cout << "引用传递交换后实参b=" << b << endl;//b=10
}

依赖引用传递交换函数,可以实现实参的交换,形参作了实参的引用。

【总结】:
1)封装函数在没有返回值的情况下,要想实现对main函数中实参的操作,必须在封装函数中对其内存进行操作。
2)引用类似于指针的简化版本,它不像指针一样类似于一种数据类型,需要定义,有固定的含义,即存储的是内存地址。它更像是变量的分身,代替变量完成不能完成的对内存的操作。

4.引用做函数返回值(重点、难点)
Note:
1)不能返回局部变量的引用,但可以返回静态变量的引用。
【碎碎念】:总之,所有的注意事项都跟内存息息相关。

代码示例:

#include<iostream>
#include<string>
using namespace std;

int& test01()//注意这里定义的函数返回值类型为引用
{
	int a = 10;//a为局部变量,存储在栈区,函数调用完之后,变量内存就被释放。
	return a;
}
int& test02()
{
	static int a = 100;//a为静态变量,存储在全局区,程序全部执行完毕后释放
	return a;
}
int main()
{
	//创建局部变量引用
	int &ref = test01();//ref为局部变量a的引用,但是由于a在函数执行完后即被释放,因此ref并没有可以操纵的内存
	cout << "作局部变量引用ref的值=" << ref << endl;//10,之所以输出10,是由于编译器对a保留一次的操作
	cout << "作局部变量引用ref的值=" << ref << endl;//乱码
	//创建静态变量引用	
	int &ref2 = test02();
	cout << "作静态变量引用ref的值="<< ref2 << endl;//100
	cout << "作静态变量引用ref的值="<< ref2 << endl;//100
}

2)函数的调用可以作为左值

代码示例:

#include<iostream>
#include<string>
using namespace std;

int& test02()
{
	static int a = 100;//a为静态变量,存储在全局区,程序全部执行完毕后释放
	return a;
}
int main()
{
	//创建静态变量引用	
	int &ref2 = test02();
	cout << "ref2="<< ref2 << endl;//100
	cout << "ref2="<< ref2 << endl;//100
	//函数的调用作为左值
	test02() = 1000;//test02()可以看成a的引用
	cout << "ref2="<< ref2 << endl;//1000
	cout << "ref2="< ref2 << endl;//1000
}

从上面的例子中可以看到,函数test02()作为左值可以实现对静态变量的赋值操作。

5.引用的本质

1)定义:引用的本质是一个指针常量。
涉及到指针常量:指针的指向不可更改,但指向的内存中的值可更改。
易混淆的常量指针:指针的指向可以更改,但指向的值不可更改。
2)前面在引用的注意事项中提到,引用一旦初始化就不能更改,这也是跟引用的本质有关系,引用初始化时就确定了其指向,而其指向是不可更改的。
3)之所以引用的定义形式等跟指针不同,是因为在C++中,编译器对引用进行了包装,代码示例如下:

#include<iostream>
#include<string>
using namespace std;


void func(int &ref)//等价于int * const ref = &a
{
	//等价于*ref = 20
	ref = 100;
}
int main()
{
	int a = 10;
	int &ref = a;//等价于int * const ref = &a
	ref = 20;//ref为引用,内部编译器转换为*ref = 20
	cout << "ref:" << ref << endl;//20
	cout << "a:" << a << endl;//20

	func(a);
	cout << "ref:" << ref << endl;//100
	cout << "a:" << a << endl;//100
	return 0;
}

6.引用在类与对象中的应用(拓展、难点)

1)背景:创建类person,已知其中包含age属性以及personaddperson成员函数,该成员函数可以实现两个实例对象age属性的求和。
2)要求:创建实例对象p1p2,实现p2.age与若干个p1.age的连续相加。
3.1)示例介绍:

如代码示例一所示,成员函数personaddperson的函数类型为person,从函数的输出结果中可以看到,每次调用成员函数时,this指针都指向不同的实例对象(从输出的地址可以看出),因此,代码示例一不能实现p2.age与若干个p1.age的连续相加,仅且只能实现一次相加,下一次调用时,this指针即指向由成员函数创建的新的实例对象(非p2)。

//代码示例一
class person 
{
public:
	//有参构造函数
	person(int age)
	{
		this->age = age;
	}
	person personaddperson(person &p)//返回为“类”
	{
		this->age += p.age;
		cout << "this指针" << this << endl;//输出每次成员函数调用时,this指向的对象地址
		cout << "this指向的年龄为:" << this->age << endl;//输出每次成员函数调用时,this指向对象的年龄
		return *this;
	}
	int age;
};

void test01()
{
	person p1(10);
	person p2(10);
	p2.personaddperson(p1).personaddperson(p1).personaddperson(p1);
	cout << "p2对象年龄为:" << p2.age << endl;
}

int main()
{
	test01();
}

当成员函数返回为类,输出结果为:
在这里插入图片描述

3.2)示例介绍

如代码示例二所示,成员函数personaddperson的函数类型为person&,从函数的输出结果中可以看到,每次调用成员函数时,this指针均指向实例对象p2(从输出的地址可以看出)。这可以理解为:当利用类的引用作为函数返回类型时,所有创建的示例对象都是p2的别名,因此实现了对p2内存的连续操作。因此,示例二可以实现p2.age与若干个p1.age的连续相加。return *this 实际上返回的就是this指针指向的实例对象。

//代码示例2
	person& personaddperson(person &p)//返回为“类”
	{
		this->age += p.age;
		cout << "this指针" << this << endl;//输出每次成员函数调用时,this指向的对象地址
		cout << "this指向的年龄为:" << this->age << endl;//输出每次成员函数调用时,this指向对象的年龄
		return *this;
	}

当成员函数返回为“类”的引用(代码示例如上),输出结果为:
在这里插入图片描述
【一点思考】:
1)在刚开始学习引用时,把引用单纯的勉强理解为变量的别名,当接触了引用在“类与对象”中的应用后,发现,引用可以是任何事物的别名,可以是类的别名,好像也可以是函数的别名等等。在本文6)的案例中,其作为p2实例对象的别名实现了对p2中age属性的连续操作。
2)之前我将指针可以看作一种数据类型,记为int/char... *,即可套用其他数据类型定义的模板。
同理,引用也可以看作一种数据类型,记为int/char/person &,这样可以帮助我们更好的理解代码。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值