C++入门(二)

引用

引用是指对已经定义的变量起一个别名,并不是定义一个新的变量,因此编译器不会为引用变量开辟一个新的空间,这个引用变量和他引用的变量共用一个空间。

举一个简单的例子:对于电视剧西游记中孙悟空,有许多称号,如“弼马温”,“孙大圣”,“孙行者”等,这就是一个形象的例子。 

 如上图所示代码及运行结果:a是一个定义的变量,而sa就是a的引用变量(别名),通过cout打印可以得出a和sa的数值是一样的,在通过地址的打印可以得出a和sa的地址也是一样的。

对于引用需要注意一下几点:

  1. 引用变量必须初始化,必须在定义引用时明确引用的是哪个变量或者对象,否则语法错误;
  2. 同一个变量可以有多个引用(别名);
  3. 引用只能引用一个实体,不能引用其他的实体;
  4. 引用表面好像是传值,其本质也是传地址,只是这个工作有编译器来做;

常引用

常引用的规则是:引用变量的权限不能大于其引用的变量的权限。

如上图所示的一段代码中,a具有常属性,是无法被修改的,对于引用变量sa来说,sa的权限比a要大,因此就会报错; 而解决方法也很简单:

加上const修改sa的权限即可,这里a和sa的权限就一样大。从而也会存在另一种情况:引用的变量的权限比引用变量的权限大,如下图所示:

 可以看出c的权限要比其引用变量sc要大,这也是可行的。


常引用还有一个值得注意的点:

 如上图所示:类型不同的引用是不合法的。

但是,当我使用const修饰的时候,代码不会报错,这是因为:

 但是不难发现,这里会有精度的丢失,原因如下:在这期间会产生一个临时变量,这个临时变量存放的是f的整数部分,而这个临时变量具有常属性,因此需要加上const修饰,因此此时的sf引用的也是临时变量f,如下图所示: 

引用的使用场景 

场景一:做参数

场景二:做返回值

//交换数值

void Swap(int& a, int& b) {
	int tmp = a;
	a = b;
	b = tmp;
}
int main() {
	
	int x1 = 10;
	int x2 = 20;
	int& a = x1;
	int& b = x2;
	cout << "x1 = " << x1 << "  x2 = " << x2 << endl;
	Swap(a, b);
	cout << "x1 = " << x1 << "  x2 = " << x2 << endl;

	return 0;
}

介绍场景二前,先弄清楚传值返回:

如上图所示的两种不同的代码,左边的Add函数的x被static修饰,是静态变量;而右边的Add函数中的x是正常的变量,我们都知道静态变量出函数的作用域不会被销毁。而对于右边的Add函数来说,出函数作用域,x的值就会被销毁,那么main函数又是怎样接收到Add函数的返回值的呢???

原来,main函数和Add函数都有自己的函数栈帧,对于static修饰的静态变量,main函数调用Add函数,Add函数会创建相应的函数栈帧及变量,在调用完成后,编译器会根据返回值的大小创建一个临时变量(假设为n),此时Add函数的返回值就会拷贝到临时变量n中,而Add函数的栈帧也会被销毁,最后临时变量n的值会拷贝给main函数。对于这个临时变量,若临时变量较小,会保存在寄存器中;若临时变量过大,则再上一个函数(例如这里的main函数)就会开辟一块空间用来存放临时变量。

接下来,进入正题:


 如上图所示的一段代码,用引用变量引用Add的返回值时会发现编译器报错,正如我刚才所述,Add函数返回的是临时变量,而临时变量具有常属性,因此使用const修饰的引用变量b编译器就不会报错(此处详见引用规则篇幅)。


如上图所示的代码中,用引用变量引用Add函数的返回值,在取地址可以发现Add函数的返回值x和main函数的引用变量a地址相同,是同一个值。因此可以得出,

传引用返回不会出现临时变量的情况。但是需要注意的是,可能会出现由于引用变量造成的野指针访问情况,详见如下:

像这种的,就是野指针的访问,在main函数调用Add函数结束后,Add函数的栈帧理应立即就会销毁,但是部分编译器并不会立即销毁(就像我这个)。编译器没有立即销毁,使得Add这部分的栈帧空间依旧存在,如果接下来使用引用变量访问这块空间,得到的结果是正确的,因为这块空间还是存在的,没有被分配。而两次打印数值之间还存在一个地址打印,这个地址打印的操作可能就会使Add函数的空间被分配占用,因此第二次数值打印时出现了随机值。

总结:只有在静态变量的情况下才可以使用引用变量做返回值,因为调用函数的静态变量不会被销毁,就不会出现野指针的情况;或者说函数返回时,返回值还在,则可以使用传引用返回,否则只能是传值返回。

引用和指针的区别:

  • 引用是定义一个变量的别名,其实质还是这个变量(就像孔明和诸葛的区别);指针是用来存储变量的地址;
  • 引用在定义时需要初始化,指针不需要;
  • 引用在引用一个实体后不能再引用其他变量,指针可以指向其他变量;
  • 引用没有NULL,指针有NULL;
  • sizeof中,引用表示引用的变量的大小,指针是4/8个字节;
  • 引用自加表示变量自加,指针自加表示跳过一个该类型;
  • 引用没有二级引用,指针有二级指针;
  • 引用更安全;
  • 引用不需要解引用,指针需要解引用。

如上图,引用和指针在本质上是一样的,但是引用不占用空间,和引用变量共用一个空间。

内联函数

用inline修饰的函数就叫内联函数,在编译时c++编译器会在调用内联函数的地方展开,以省去建立函数的栈帧,从而提高程序整体的效率。inline是一种通过空间换时间的做法。

编译器一旦将一个函数作为内联函数处理,就会在调用位置展开,即该函数是没有地址的,也不能在其他源文件中调用,故一般都是直接在源文件中定义内联函数的。

一般来说,需要频繁调用且简短的函数可以使用内联函数。但其实,及时使用了内联函数,编译器也不一定会调用,就相当于一个建议的作用,是否展开取决于编译器及代码复杂度。

需要注意的是:
内联函数的声明和定义不要分开,否则链接会报错。

如上图,声明和定义不在同一个文件中,导致链接出错。

auto关键字

auto修饰的变量,该变量会根据定义的变量推导出自身的变量类型;

如上图,b、c、d根据后面的类型推导出自身的类型,但是需要注意的是:

  • 使用auto必须要初始化;
  • 同一行定义多个变量时,前后变量类型要一致;因为编译器只会根据第一个变量类型推导后面的类型;
  • auto*后面必须跟地址;

auto有两种场景不能使用:

  • auto不能作为函数的参数;
  • auto不能声明数组;

如上图,auto既不能做函数参数,也不能做数组的声明;

 基于范围的for循环

for循环后面有两个变量,第一个变量是范围内用于迭代的变量,第二个是被迭代的范围。


如上图,当我将arr数组元素扩大二倍后,打印却发现,数据并没有改变。这是因为:数组只是将数据拷贝给x,因此x只是拷贝的数据,数组的数据并没有改变。当我使用引用后即可解决,取引用实际上操作的数据就是数组本身的数据。


如上图,在使用数组传参的函数中使用范围for循环就会出错,这是因为范围for循环需要提供begin和end的方法(这就相当于范围for循环中的“范围”)。而数组传参其实传递的是数组的地址,此时范围for循环无法确定范围,就会报错,而我们刚才main函数中使用的数组begin和end就相当于数组的首元素和尾元素。因此范围for循环中,范围是必须是确定的。

  • 17
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值