目录
引用的应用
做参数
#include<iostream>
using namespace std;
void Swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int c = 0, d = 1;
cout << c <<'\n' << d << endl;
Swap(c, d);
cout << c <<'\n' << d << endl;
return 0;
}
在C语言中的写法为
void Swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int c = 0, d = 1;
printf("%d %d ", c, d);
Swap(&c, &d);
printf("%d %d ", c, d);
return 0;
}
可以看出,C语言在实现Swap函数的时候,需要考虑Swap的参数类型,如果要传入地址就需要让参数类型变成int*类型,然后在传参时需要将参数的地址传进去
而C++则要简单一点,如果想要修改函数外面的变量,那么就让参数类型变成别名,这样就不需要考虑要不要传地址了
做返回值
int func()
{
int a = 0;
return a;
}
int main()
{
int ret = func();
return 0;
}
这段代码中不是把a的值给ret,这里需要再理解一下函数栈帧
main函数调用时会建立一块栈帧,局部变量ret会在里面开一块空间,当函数结束的时候局部变量就会销毁
main函数中调用了函数func,然后func函数会建立一块新的栈帧,a是func函数中的一个局部变量,所以回走func函数建立的栈帧里开一块空间
当func函数结束后,他开辟的空间就会销毁,而a也是会跟着销毁的,如果将a对值返回给ret,那么ret接受的可能是一个随机值
而不同的平台输出的值不同,VS中输出的是a的值,但并不代表a把值返回给了ret
因为VS会开辟一块空间,里面有一个临时变量,临时变量的值等于函数的返回值,当func函数结束后临时变量的空间是不会被销毁的,然后将临时变量的值返回给ret
注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用
引用返回,如果已经还给系统了,则必须使用传值返回
野引用
当我们强行让函数的返回值变成别名的时候,就会报错
int& func()
{
int a = 0;
int b = 1;
return b;
}
int main()
{
int ret = func();
cout << ret << endl;
return 0;
}
这种情况和指针类似,指针中的野指针是指向一块内存被释放或者没有访问权限的指针,这里的引用是取了一个变量a的别名,而a的空间已经被释放掉了,但是仍然要返回a的别名,导致出现了野引用
(实质就是a的空间被释放掉了,但是最后还要去访问a的空间,而空间里的值是一个随机值,导致输出结果不确定)
不同的平台结果不一样,VS中没有将a的栈帧销毁,所以ret接收到是a的值
如果我们让ret是func返回值的别名会怎么样?
using namespace std;
int& func()
{
int a = 0;
return a;
}
int main()
{
int &ret = func();
cout << ret << endl;
return 0;
}
ret为a的别名,所以ret的空间就是a的空间,而a的空间在func函数新开辟的空间中,所以ret在main函数中没有空间,最后因为a的空间会被释放,导致ret也成了也引用
扩展
int& func()
{
int a = 0;
return a;
}
void fx()
{
int b = 3;
}
int main()
{
int &ret = func();
cout << ret << endl;
fx();
cout << ret << endl;
return 0;
}
ret明明是a的别名,但是在调用一个函数fx后就变了
后面再调用一次func后,ret的值又变回来了
如果让fx函数的返回值后面加个引用符号会怎么样?
最终结果没有变化
上面的情况是因为空间可以互用的,当func函数调用完后,会销毁空间,而调用fx函数后,原来的那块空间就会给到fx
因为ret是野引用,ret的地址仍然指向原来空间的地址,但是由于后面那块空间被分给了fx,所以ret的值又变成了fx中b的值
如果fx中没有定义b会怎么样
这个例子更能体现出ret为野引用后的随机性
总结:传值返回是返回 返回值的拷贝,传引用返回是返回 返回值的别名,返回变量出了函数作用域后生命周期(局部变量)就结束了,不能引用返回而像全局变量 静态变量(static修饰) 堆上变量等这些就可以用引用返回
传值、传引用效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直
接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效
率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
int main()
{
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;
return 0;
}
这段代码是为了比较传值、传引用的效率,为了更容易看出他们直接到效率差距,所以定义了一个比较大的结构体,然后分别调用10000次函数,size_t begin = clock()是记录刚开始调用函数的时间, size_t end = clock()是记录调用结束后的时间,二者相减就可以得到调用函数所耗的时间
这里的0毫秒是接近于0,通过这段代码可以看出,引用传参效率更高
引用和指针的区别
引用的语法和底层是有一些不一样的
举个例子鱼香肉丝语法角度看有鱼有肉丝,而底层实现只有肉丝
语法角度引用是别名,不开空间,而指针是地址需要开空间存地址
而通过下面的代码调试看汇编可以得出引用也是会开空间的,事实上引用的底层是通过指针实现的
int main()
{
int a = 10;
int& ra = a;
ra = 20;
int* pa = &a;
*pa = 20;
return 0;
}
这里补充一下汇编的查找方法
先进行调试,然后点击鼠标右键,就可以找到汇编了
引用和指针的不同点:
- 引用概念上定义一个变量的别名,指针存储一个变量地址。
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
一个同类型实体(引用不能改变指向,指针可以) - 没有NULL引用,但有NULL指针(引用相对更安全,没有空引用,但是有空指针,容易出现野指针,但是不容易出现野引用)
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节) - 引用自加即引用的实体增加1(i++…),指针自加即指针向后偏移一个类型的大小( * a+1)
- 有多级指针(int ** a),但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
C++对比C语言实现顺序表
C语言实现顺序表可以查看顺序表详解
下面简单的用C++实现顺序表的一些功能
struct SeqList
{
int* a;
int size;
int capacity;
void Init()
{
a = (int*)malloc(sizeof(int) * 4);
//..
size = 0;
capacity = 4;
}
void PushBack(int x)
{
//扩容
a[size++] = x;
}
int SLGet(int pos)
{
assert(pos >= 0);
assert(pos < size);
return a[pos];
}
};
C语言是数据和函数分离,而C++可以将函数写在结构体里,并且可以看到,许多函数传入的参数都非常少,甚至没有传参的
因为这些函数是在结构体内部,而结构体里又定义了int a int size,int capacity这些参数,结构体内部的函数是可以直接用这些参数的,而像x和pos这些结构体里没有的参数就需要我们传参,这样就使C++实现顺序表变得很简单,很容易看懂*
对比C语言中因为数据和函数是分离的,导致像int* a int size,int capacity这些作为参数必须要写明是哪个结构体中的数据,否则就会分不清,而C++因为函数在结构体内部,这些数据都不需要写明就知道是在哪个结构体中
我们用代码测试一下功能
因为函数在结构体内部,所以调用函数的时候就可以直接当成结构体中的成员访问
打印顺序表中的数据
int main()
{
SeqList sl;
sl.Init();
sl.PushBack(1);
sl.PushBack(2);
sl.PushBack(3);
sl.PushBack(4);
for (int i = 0; i < sl.size; i++)
{
cout << sl.SLGet(i) << "";
}
return 0;
}
修改顺序表中的数据
int main()
{
SeqList sl;
sl.Init();
sl.PushBack(1);
sl.PushBack(2);
sl.PushBack(3);
sl.PushBack(4);
for (int i = 0; i < sl.size; i++)
{
if (sl.SLGet(i) % 2 == 0)
{
sl.SLGet(i) *= 2;
}
}
for (int i = 0; i < sl.size; i++)
{
cout << sl.SLGet(i) << endl;
}
}
当想修改顺序表中的数据时,却出现表达式必须是可修改的左值,说明sl.SLGet(i) *= 2中的sl.SLGet(i)为一个常量,比如1=2,1就是一个常量,我们修改不了他
要想解决就先得看一下int SLGet(int pos)的实现
int SLGet(int pos)
{
assert(pos >= 0);
assert(pos < size);
return a[pos];
}
显然SLGet函数返回类型为int类型,也就是说返回的是一个临时变量,C++规定临时变量具有常性,也就是说这个临时变量就是一个常量
为了解决这个问题,就需要用到引用int& SLGet(int pos),由于pos位置的值是用malloc开辟出来的,当出了作用域后不会被销毁,所以用引用取别名,仍然可以访问到pos位置的值