一文搞懂引用、指针、const、参数传递的关系

一文搞懂引用、指针、const、参数传递的关系


前言

在《剑指offer》上看到一个问题:如果写的函数需要传入一个指针,则面试官可能会问是否需要为该指针加上const、把const加在指针不同的位置是否有区别;如果写的函数需要传入的参数是一个复杂类型的实例,则面试官可能会问传入值参数和传入引用参数有什么区别、什么时候需要为传入的引用参数加上const。
摸摸鼻子,自问自己是一脸懵逼,答不上来的。关于引用、指针、const以及参数传递这方面的知识是混淆不堪的!


一、const限定符

1、 const对象一旦创建后其值就不能再改变,const对象必须初始化。

2、 对象的类型决定了其上的操作,与非const类型所能参与的操作相比,const类型的对象能完成其中大部分,但也不是所有操作都适合。主要限制就是只能在const类型的对象上执行不改变其内容的操作。

3、 在不改变const对象的操作中还有一种是初始化,利用一个对象去初始化另外一个对象,它们是不是const都无关紧要。


二、引用是什么?

1、引用即别名: 引用并非对象,相反的,它只是为一个已经存在的对象所起的另外一个名字。

2、 定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。初始化完成后,引用将和它初始值对象一直绑定在一起。

3、 引用必须初始化,因为无法令引用重新绑定到另外一个对象。

4、 引用类型都要和与之绑定的对象严格匹配(有两种例外)。且引用只能绑定在对象上,不能与字面值或者某个表达式计算结果绑定在一起。

5、const的引用:

5.1、常量引用: “常量引用” 是 “对const的引用” 的简称。
5.2、 可以把引用绑定到const对象上,与普通引用不同的是:对常量的引用不能用作修改它所绑定的对象。

    const int ci = 123;
	const int &r1 = ci;   // 正确:引用及其对应的对象都是常量
	r1 = 42;              // 错误:r1是对常量的引用
	int &r2 = ci;         // 错误:试图让一个非常量引用指向一个常量对象

5.3、第一种例外: 初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。尤其允许为一个常量引用绑定非常量的对象、字面值、甚至是一般表达式。

	int i = 123;
	const int &r1 = i;       // 允许将const int&绑定到一个普通int对象上
	const int &r2 = 123;     // 正确:r1是一个常量引用
	const int &r3 = r1 * 2;  // 正确:r3是一个常量引用
	int &r4 = r1 * 2;        // 错误:r4是一个普通的非常量引用

原因: 当一个常量引用 ri 被绑定到另外一种类型的对象 ti 上时,编译器会生成一个临时量对象 temp,此时,ri 就被绑定到这个临时量对像 temp 上,这个临时量对象 temp 的类型与 ri 类型是一致的,即 ti 的类型会被转换成引用的类型。当 ri 不是一个常量时,就允许对 ri 赋值,这样就可以改变引用对象的值(本意),但此时 ri 绑定的对象实际上是一个临时量对象 temp,也就与本意相违背。

5.4、 对const的引用可能引用一个并非const的对象。常量引用仅对引用可参与的操作做出了限定,对引用的对象本身是不是一个常量未作限定。通俗理解就是:对象本身是大名,常量引用是小名,用小名不能修改对象的值,但是大名可以。

6、参数传递——引用传递:

6.1、传引用调用:当形参是引用类型时,称它对应的实参被引用传递,引用形参就是它绑定对象(对应实参)的别名。当实参的值被拷贝给形参时,形参实参是两个相互独立的对象,称为传值调用,即值传递。

6.2、传引用参数:通过使用引用形参,允许函数改变一个或多个实参的值。

// 该函数接受一个int对象,然后将对象的值设置为0
void reset(int &i)        
{
	i = 0;               // 改变了i所引对象的值
}

// 直接传入对象,无需传递对象的地址
int j = 3;
reset(j);               // j使用传引用,值被改变

6.3、使用引用的好处:
好处一:避免拷贝。 参数传递时,使用值传递,可能会拷贝大的类类型或者容器对象,比较低效,甚至有的类型(IO类型等)不支持拷贝操作。只能通过引用参数的方式访问。

好处二:返回额外信息。 一个函数只能返回一个值,当需要同时返回多个值,引用形参为我们返回多个结果,提供了有效途径。

// find_char函数,返回string对象中某个指定字符第一次出现是位置,以及出现次数
// 返回s中c第一次出现的位置索引
// 引用形参occurs复杂统计c出现总次数
string::size_type find_char(const string& s, char c, string::size_type& occurs)
{
	auto ret = s.size();       // 第一次出现位置(有的话)
	occurs = 0;                // 出现次数的形参值
	for (decltype(ret) i = 0; i != s.size(); ++i)
	{
		if (s[i] == c)
		{
			if (ret == s.size())
			{
				ret = i;       // 记录第一次出现位置
			}
			occurs++;          // 出现次数+1
		}
	}
	return ret;                // 隐式返回次数
}

7、尽量使用常量引用

7.1、形参的初始化方式和变量的初始化方式是一样的一个普通的引用必须用同类型的对象初始化。如6.2中引用版本的reset,只能使用int类型的对象,不能使用字面值、求值结果为int的表达式、需要转换的对象、const int 类型的对象。使用引用而不是常量引用会极大的限制函数所能接受的实参类型。记住:初始化常量引用时允许用任意表达式作为初始值


三、指针是什么?

1、与引用的异同:
相同点:与引用类似,也实现了对其他对象的间接访问
不同点:a、指针本身也是一个对象,允许对指针赋值和拷贝,并且可以先后指向不同的对象;b、指针不需要在定义是赋初值。

2、引用不是对象,不能定义指向引用的指针。

3、指针的类型要和它指向的对象严格匹配,除了两种例外情况。(和引用类似)

4、指向指针的引用:指针是对象,存在对指针的引用。

int i = 22;
int *p;         // p是一个指针
int *&r = p;    // r是对指针p的引用

r = &i;         // r引用了一个指针,给r赋值&i就是令p指向i
*r = 0;         // 解引用r得到i,也就是p指向的对象,将i的值改为0

判断 r 类型到底是什么,从右向左阅读 r 的定义。离变量名最近的符号对变量的类型有直接的影响。如上, r 是一个引用,声明符的其余部分用以确定 r 引用的类型是什么,此例中 * 说明 r 引用的是一个指针。最后,声明的基本数据类型部分指出 r 引用的是一个 int 型指针。

5、const 和指针

5.1、指向常量的指针(例外一),与常量引用类似,不能用于改变其对象的值。想要存放常量对象的地址,只能使用指向常量的指针。指向常量的指针可以指向一个非常量对象。(指向常量的指针可以记为:const在左,* 在右,简称const*)

5.2、const指针: 指针是对象而引用不是,与其他对象类型一样,指针可以把自身定为常量。常量指针(C++Primer这么定义常量指针,与我们常用定义相反)必须初始化 ,初始化完成后,它的值(存放的指向对象的地址)就不可以改变了。(常量指针可以记为:* 在左,const在右,简称*const)

5.3、指针常量还是常量指针,相信每个人被它俩所迷惑。中文是它俩具有迷惑性的主要原因!!!建议直接记const和 * 两者谁在左谁在右比较好。它俩的作用最好的办法是从右往左阅读去判定。

int num = 12;
int *const p = #   // *const,距离变量p最近的是const,即p是一个常量,p保存的地址不可更改,
                       // 再往左就是 *,即const p是一个指针。因此p的值(指向对象的地址)不可以更改,p指向对象的值可以更改。

const int *p = #   // const*,距离变量p最近的是 * ,即p是一个指针,可以更改p的值,
                       // 再往左就是const,即 *p 是一个常量。因此,p的值(指向对象的地址)可以更改,p指向对象的值不可以更改。

6、传递参数——值传递

6.1、在引用传递部分,已经介绍了引用传递和值传递的区别,指针传递本质就是值传递:它传递的是一个地址,将实参的地址拷贝给形参,对形参(形参存的是地址)的更改不会影响实参。(指针的行为和其他非引用类型一样,当执行指针拷贝操作时,拷贝的是指针的值,拷贝之后,两个指针是不同的指针。)

6.2、因为指针是我们可以间接的访问它所指的对象,所以通过指针可以修改它所指对象的值。

int i = 42;
reset(&i);

void reset(int* ip)
{
	*ip = 0;            // 改变指针ip所指对象的值
	ip = 0;            // 只改变了ip的局部拷贝,实参未改变
}

7、例外二(引用&指针)

7.1、存在继承关系的类是一个重要的例外:可以将基类的指针或引用绑定到派生类对象。
当使用基类的引用(指针)时,实际上我们并不清楚该引用(或指针)所绑定对象的真实类型。该对象可能是基类的对象,也可能是派生类的对象。


总结

记录的过程也是学习的过程。还有很多遗漏之处,暂时的就记录到这里。如有错误还请包涵和指正!感谢!!!

  • 7
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值