概述
引用
引用的概念
引用不是定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它,引用的变量共用同一块内存空间
引用特性
1. 引用在定义时必须初始化
2. 一个变量可以有多个引用
类似于一个人有多个名字,多个代号
3. 引用一旦引用一个实体,再不能引用其他实体
引用的应用
引用1:
在数据结构单链表中,尾插函数在不传返回值的情况下,会用到二级指针来传地址来改变地址的值,这里可以用引用,对于不是很理解这里指针的人有很大帮助。
传二级指针是这样的
所以改变成引用就是:
在我们输出型参数也可运用
引用2:引用做返回值
传值返回是这个样子:
int Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int ret = Add(1, 2);
cout << ret << endl;
return 0;
}
当我们传引用返回是这个样子:
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
cout << ret << endl;
return 0;
}
传值返回的时候,函数会将数据拷贝到一个临时变量,再将值返回,生成的栈帧也随之销毁。
我们的临时变量存在哪里呢?
- 如果c比较小(4/8),一般是寄存器充当临时变量,会存到寄存器中
- 如果c比较大,临时变量放在调用Add函数的栈帧中
传引用返回将函数名字取一个别名,没有浪费空间,
当前代码的问题:
- 存在非法访问,因为Add(1,2)的返回值是c的引用,所以Add栈销毁了以后,会去访问c位置的空间
- 如果Add函数栈帧销毁,清理空间,那么取c值的时候取到的就是随机值,给ret就是随机值,当前这个取决于编译实现了
- ps:vs下销毁栈帧,没有清空间数据
那么什么时候可以用引用返回呢?
如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
int& Count()
{
static int n = 0;
n++;
//...
return n;
}
引用的价值体现
- 做参数
- 做返回值
做参数
- 提高效率(大对象+深层拷贝)
- 形参修改,可以影响实参(输出型参数)
引用传参
#include <time.h>
struct A
{
int a[10000];
};
A a;
//值返回--每次拷贝40000byte
A TestFunc1()
{
return a;
}
//引用返回--没有拷贝
A& TestFunc2()
{
return a;
}
void TestReturnByReforValue()
{
//值返回
size_t begin1 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc1();
size_t end1 = clock();
//引用返回
size_t begin2 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc2();
size_t end2 = clock();
//计算两个函数运算完成之后的时间
cout << "TestFunc1 time:" << end1 - begin1 << endl;
cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{
TestReturnByReforValue();
return 0;
}
a作为全局变量,栈帧没有被销毁,所以可以用引用返回
做返回值
- 提高效率
- 修改返回变量
a[i]是右值,临时变量具有常行。
ps:表达式的返回值,常量通常可以认为是右值,右值不能修改
所以改为
int& At(int i)
引用读写
使用引用传参,如果函数中不改变参数的值,建议使用const &
void StackPrint(const struct Stack& st){}
const读写权限
这里还有一个左右值的问题
结论:const Type& 可以接收各种类型的对象。
引用和指针的不同点:
- 引用概念上定义一个变量的别名,指针存著一个变量地址
- 引用在定义时必须初始化,指针最好初始化,但是不初始化也不会报错
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以再任何时候指向任何一个同类型实体
- 没有NULL引用,但有NULL指针
- 再sizeof中含义不同:引用结果为引用类型的大小,但指针始终时地址空间所占字节数--32位平台下4个字节
- 引用自加引用实体增加1,指针自加即指针向后偏移一个类型的大小
- 访问实体方法不同:指针需要显式解引用,引用编译器自己理
- 引用比指针使用起来相对更安全 有多级指针,但是没有多级用 后面两条总结起来 :指针更复杂一些,更容易出错一些
void f1(int* p) { *p = 10; } void f2(int& r) { r = 10; } int main() { //没报错但是调试会崩 f1(NULL); f1(0); //引用没有初始化,报错 /*f2(NULL); f2(0);*/ int a = 1; f1(&a); f2(a); return 0; }
内联函数
调用函数的时候需要建立栈帧,栈帧中要保存一些寄存器,结束后又要恢复,下面的函数可以看到多次调用,多次建立栈帧,有消耗。
可以看到这些都是有消耗的,对于频繁调用小函数,我们是否能优化一下呢?
C语言中提供了宏,宏是一种替换
#define ADD(x,y) ((x)+(y))
cout << ADD(1, 2) << endl;
C++中提供了一个新的方法:内联函数inline
有了inline,我们就不需要用C的宏,因为宏很复杂,很容易出错
概念
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。
用法
inline在release中不会建立栈帧,会在调用的地方展开
我们转到反汇编查看,release中将代码性能优化,以至于优化的太好了,这里面什么都看不到
在Debug中,能看开call函数
call Add函数就代表函数没有展开
那么如何在Debug下面也展开呢?
- 解决方案资源管理器->右键属性->C/C++->常规->调试信息格式->程序数据库
- 优化->内联函数扩展->只适用于_inline
- 应用->确定
此时我们再来调试一下
在Debug下展开成功
特性
- inline是一种以空间换时间的做法,省去调用函数的开销。所以代码很长(10行以上)或者有递归的函数不适合使用内联数 所以长函数和递归函数不适合展开 ,调用地方很多,展开后程序可能会一下变得很大
- inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数很长或者是递归函数等等,编译器优化时会忽略掉内联,像第一点提到。
- inline不建议声明和定义分离,分离会导致链接错误,因为inline被展开,就没有函数地址了,链接就会找不到
f.cpp->f.o符号表中不会生成f函数的地址,因为Inline函数是不需要地址的,都在调用的地方展开了// F.h #include <iostream> using namespace std; inline void f(int i); // F.cpp #include "F.h" void f(int i) { cout << i << endl; } // main.cpp #include "F.h" int main() { f(10); return 0; }
// 链接错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl f(int)" (?
f@@YAXH@Z),该符号在函数 _main 中被引用
结论:短小,频繁调用的函数建议定义成inline
最后
以上就是淡淡发夹为你收集整理的C++基础---引用和内联函数的用法引用内联函数的全部内容,希望文章能够帮你解决C++基础---引用和内联函数的用法引用内联函数所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复