栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

c++(引用和内联)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

c++(引用和内联)

目录
  • 1.引用
    • 1.引用概念
    • 2.引用特性
    • 3.const和引用的搭配
    • 4.为什么const要和引用一起使用的使用场景
    • 5.引用的效率和比较
    • 6.引用和指针的不同
    • 7.引用和指针的汇编代码一样吗?
  • 2.内联函数
    • 1.内联概念
    • 2.为什么要用内联,不用宏
    • 3.什么时候用内联
    • 4.内联的特性

1.引用 1.引用概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
比如:英雄联盟的"亚索",打的不好叫"孤儿索",也有的人叫他"剑豪"等等…这些就是别名
我们接下来就用代码和调试来下了解引用吧

代码

#include
using namespace std;

int main()
{
	int a = 10;
	int& b = a;
	int* p = &b;
	int& d = b;
	return 0;
}

c++里面的引用和c的&共有一个符号,放在类型后的&代表引用

我们接下来用调试看看引用了有什么效果

根据图片我们看到取别名的和被取别名的地址是一样的,这样就代表着任意加减一个别名的值,其他的值也会被加减

代码

#include
using namespace std;

int main()
{
	int a = 10;
	int& b = a;
	int* p = &b;
	b += 1;
	int& c = b;
	return 0;
}

调试结果

打个比喻:我玩的亚索五杀了,那么别人说:我的孤儿索五杀了。那么我的亚索是不是五杀了。

2.引用特性

1.引用在定义时必须初始化

看图可以看到编译器给了红色警告

  1. 一个变量可以有多个引用

    这个就不用演示了,之前的代表就已经解释了
    
  2. 引用一旦引用一个实体,再不能引用其他实体

    代码

#include
using namespace std;

int main()
{
	int a = 10;
	int& b = a;
	int* p = &b;
	int& c = b;
	int d = 20;
	b = d;
	return 0;
}

运行结果

可以看到这里只是赋值,而不是改引用的地址,因为这里只是把d=20的值赋给abc而abc的地址不等于d的地址,所以引用一旦引用一个实体,再不能引用其他实体。这一点就和指针很大的区别,因为指针可能指向其他的地址而引用不能。

3.const和引用的搭配


在这图片可以看到编译器报错了,为什么加了const过后就不能这样子引用了呢
因为加了const代表可读,而报错的代码就是改成可读可写,根据取别名原则,权限是不能放大的,所以就报错了

再来验证下取别名原则

代码

#include
using namespace std;


int main()
{
	int a = 0;
	int& b = a;//权限不变
	//取别名原则:对于原引用变量,权限只能缩小,不能放大

	const int x = 20;
	//int& y = x;//权限放大
	const int& y = x;//权限不变

	int c = 40;
	const int& d = c;//权限缩小
	return 0;
}

运行结果

可以看到运行成功,取别名原则是没有问题的。

不能给常量取别名,看下面图片的报错

但是如果加上了const

可以看到没有问题
这个还不算奇怪最奇怪的是

这样子报错问题很明显,毕竟double和int的类型不同,报错很应该但是,加上了const

居然可以运行,虽然有警告。

那么为什么可以运行呢?是因为


e引用的是临时变量,而临时变量具有常性所以加了const可以运行。
而没加const等于之前的权限放大,有const修饰的变成没const修饰的这不就是权限放大
这个临时变量会一直存在,什么时候消失呢?e消失了临时变量也消失了,因为它的生命周期是和
e一样的

4.为什么const要和引用一起使用的使用场景

图片

可以看到f(a)后面的代码都有报错,而这些报错就是我们前面讲的权限放大问题,而加const就不会出现这些问题,而不加const就会出现不能全部值都接收只能接收部分值
当然有的人可能会问一个问题为什么要用引用,不用引用不行吗?
答案是可以,但是用了引用可以减少对参数的临时拷贝

1.引用做参数
代码

#include
using namespace std;


void swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

void swap(double& a, double& b)
{
	double tmp = a;
	a = b;
	b = tmp;
}

int main()
{
	int a = 10, b = 20;
	swap(a, b);
	double c =1.1, d = 2.2;
	swap(c, d);
	return 0;
}

可以看到用起来引用和重载,感觉很爽,不用解引用,也让人感觉二个swap调用的是同一个函数,虽然调用的不是同一个函数

5.引用的效率和比较

代码

#include
using namespace std;


#include 
struct A
{ 
	int a[10000];
};
void TestFunc1(A a) 
{

}
void TestFunc2(A& a) 
{

}
void TestRefAndValue()
{
	A a;
	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

int main()
{
	TestRefAndValue();
	return 0;
}

运行结果

可以看到用了引用的效率和没用引用的效率的区别

2.引用做返回值
c++返回值的作法

而为什么不直接用a返回要加个临时拷贝呢?
因为这是局部变量出了局部就会销毁
那么我们来证明一下使用了临时拷贝吧!

证明

可以看到用了引用报错了,这说明了用了临时变量,不过这样不严谨,因为没完全满足要求
我们来用上const,因为临时变量具有常性

没有报错这样子就完全证明了,返回值是由临时拷贝的

那么我们来讲引用返回的作用
代码

int& count()
{
	static int a = 0;
	a++;
	return a;
}


int main()
{
	int ret = count();
	return 0;
}

这样子相当于

返回了一个别名,怎么证明

报错了因为引用不能变成临时变量的别名
加上引用呢?

没有报错证明完毕
那么这样子就代表了ret=a的别名那么它们的地址应该是一样的

图片

那么用引用的好处一样是,用返回值会有一个拷贝,用引用没有,因为返回值就是返回变量的别名

上面的那个代码是有问题的,什么问题野指针访问的问题,上面的a都销毁返回空间了,但是你还在用着它

代码

#include
using namespace std;


int& Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int& ret = Add(1, 2);
	Add(3, 4);
	cout << "Add(1, 2) is :" << ret << endl;
	return 0;
}

打印结果

是7而不是3为什么,因为出栈后销毁,add(3,4)产生了新的栈帧所以打印7;


这里再打印一次就是随机值为什么,一样是栈帧销毁,重新调用栈帧所以是随机值

比较引用返回值效率的代码

#include
using namespace std;

#include 
struct A 
{ 
	int a[10000]; 
};
void TestFunc1(A a)
{

}
void TestFunc2(A& a)
{

}
void TestRefAndValue()
{
	A a;
	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

int main()
{
	TestRefAndValue();
	return 0;
}

运行结果

6.引用和指针的不同
  1. 引用在定义时必须初始化,指针没有要求
  2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型
    实体
  3. 没有NULL引用,但有NULL指针
  4. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占
    4个字节)
  5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  6. 有多级指针,但是没有多级引用
  7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  8. 引用比指针使用起来相对更安全
7.引用和指针的汇编代码一样吗?

代码和解释

int main()
{
	int a = 10;
	//从语法的角度:ra是a的别名
	//从底层的角度:它们用的是一样方式实现的
	int& ra = a;
	ra = 20;
	//从语法的角度:pa存储了a的地址,开辟了4/8个字节
	//从底层的角度:它们用的是一样方式实现的
	int* pa = &a;
	*pa = 20;
	return 0;
}

证明图片

可以看到是一模一样的

2.内联函数 1.内联概念

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,
内联函数提升程序运行的效率。

2.为什么要用内联,不用宏

为什么用内联,不用宏
宏的缺点
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。
而出来内联就是为了解决宏的问题宏可以调式,还是函数,不容易错,还有安全性的检查

3.什么时候用内联

在频繁使用使用短小的函数的时候可以使用,只要在函数前面加上inline就是内联
代码

#include
using namespace std;

inline int add(int left, int right)
{
	int z = left + right;
	return z;
}

int main()
{
	add(1, 2);
	add(1, 2);
	add(1, 2);
	add(1, 2);
	add(1, 2);
	add(1, 2);
	add(1, 2);
	add(1, 2);
	add(1, 2);
	return 0;
}

在汇编看到

这是debug模式下没有优化的inline ,注意看着这个call等下优化就没没有了
打开属性改成程序数据库

在改成只适用于inline(/ob1)


可以看到优化了call
为什么说内联是展开的,而不是进入函数里面运行
证明
自己调试用F11看能不能进去

4.内联的特性
  1. inline是一种以空间换时间的做法,省去调用函数额开销(建立栈帧)。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。(大概有多长建议10行左右,取决于编译器)
  2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
  3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/884147.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号