C++中的引用&

简介: 本文将讲述,C++中的引用&

前言


本文将讲述,C++中的引用&


🕺作者: 迷茫的启明星


😘欢迎关注:👍点赞🙌收藏✍️留言


🏇家人们,码字不易,你的👍点赞🙌收藏❤️关注对我真的很重要,有问题可在评论区提出,感谢阅读!!!


持续更新中~


引用&


引用的概念

引用并不是定义一个新的变量,而是给已经存在的变量取一个别名,相当于外号,编译器不会给它开辟内存空间,它和它所引用的变量共用一块空间。


比如说:宋江,江湖人称“及时雨”,本质都是宋江,不过是一个外号


引用的格式:


类型 & 引用变量名=引用实体;


void test1
{
int a=1;
int& b=a;//定义引用的类型
    cout<
    cout<
}
```
这里要注意的是,引用的类型必须和引用的实体是同种类型的。

引用的特性


引用在定义的时候必须初始化

也就是说它引用的是已经存在的变量(空间)

一个变量可以有多个引用

也就是说给一个实体取多个“外号”

引用一旦引用一个实体,就不能再引用其他的实体了

也就是说不能再把这个“外号”给别人了

void test2()
{
  int a=1;
  int x=2;
  //int &b;//这个引用没有初始化,编译会报错
  int &b=a;
  int &c=a;//多个引用(多个外号)
  //int &c=b//引用多个实体,不同实体有相同的外号,会报错
}


常引用

int main()
{
  // 权限放大  不可以
  const int a = 10;
  int& b = a;
  // 权限不变 可以
  const int a = 10;
  const int& b = a;
  // 权限的缩小 可以
  int c = 10;
  const int& d = c;
  return 0;
}



在引用时要注意,只能把自己有的给别人,自己都没有权限怎么给呢?


引用的应用

在之前我们实现链表是使用指针来做参数的,现在我们就可以利用引用的性质来实现它。


譬如:实现尾插


之前使用指针是这样的(仅展现部分代码,理解就好)


void SListPushBack(SLTNode** pphead, SLTDateType x)
{
  SLTNode* newnode = BuyListNode(x);
  if (*pphead == NULL)
  {
  *pphead = newnode;
  }
  else
  {
  // 找到尾节点
  SLTNode* tail = *pphead;
  while (tail->next != NULL)
  {
    tail = tail->next;
  }
  tail->next = newnode;
  } 
}
int main()
{
  SLTNode* plist = NULL;
  SListPushBack(&plist, 1);
  SListPushBack(&plist, 2);
  SListPushBack(&plist, 3);
  SListPushBack(&plist, 4);
  return 0;
}



现在我们就可以这样写


void SListPushBack(SLTNode*& pphead, SLTDateType x)
{
  SLTNode* newnode = BuyListNode(x);
  if (pphead == NULL)
  {
  pphead = newnode;
  }
  else
  {
  // 找到尾节点
  SLTNode* tail = pphead;
  while (tail->next != NULL)
  {
    tail = tail->next;
  }
  tail->next = newnode;
  } 
}
int main()
{
  SLTNode* plist = NULL;
  SListPushBack(plist, 1);
  SListPushBack(plist, 2);
  SListPushBack(plist, 3);
  SListPushBack(plist, 4);
  return 0;
}



pphead是plist的别名,改变pphead就是改变plist


做参数

使用引用的好处就在于,没有指针那么复杂,还要使用二级指针,难以理解。再就是,定义函数时,要么就是传值,要么就是传址,现在就有了第三种情况传引用。

void swap(int r1, int r2)   // 传值
{
  int tmp = r1;
  r1 = r2;
  r2 = tmp;
}
void swap(int* p1, int* p2) // 传地址
{
  int tmp = *p1;
  *p1 = *p2;
  *p2 = tmp;
}
void swap(int& r1, int& r2) // 传引用
{
  int tmp = r1;
  r1 = r2;
  r2 = tmp;
}

引用又有了另一个好处


它不需要开辟新的空间,是在原空间操作,这在数量级小的时候不起眼,可是在递归时就显得尤为可贵了,虽说传地址所需空间也少,但是在有时候,还是不如&方便。


#include <time.h>
struct A{ int a[10000]; };
void TestFunc1(A a){}
void TestFunc2(A& a){}
int main()
{
    A a;
    // 以值作为函数参数,每次都要拷贝40000byte
    size_t begin1 = clock();
    for (size_t i = 0; i < 1000000; ++i)
       TestFunc1(a);
    size_t end1 = clock();
    // 以引用作为函数参数,无需拷贝
    size_t begin2 = clock();
    for (size_t i = 0; i < 1000000; ++i)
       TestFunc2(a);
    size_t end2 = clock();
    // 分别计算两个函数运行结束后的时间
    cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
    cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
    return 0;
}



image.png


这里还要注意一个点,如果使用引用传参,函数中不改变参数的值,建议使用const &


做返回值

#include <time.h>
struct A{ int a[10000]; };
A a;
// 值返回 -- 每次拷贝40000byte
A TestFunc1() { return a; }
// 引用返回 -- 没有拷贝
A& TestFunc2(){ return a; }
int main()
{
  // 以值作为函数的返回值类型
  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 < 100000; ++i)
  TestFunc2();
  size_t end2 = clock();
  // 计算两个函数运算完成之后的时间
  cout << "TestFunc1 time:" << end1 - begin1 << endl;
  cout << "TestFunc2 time:" << end2 - begin2 << endl;
  return 0;
}



image.png


使用引用作返回值时,看上去是不是觉得效率提升了很多?但是这里有个大坑,虽说传值返回会生成拷贝,但是它在结束时会产生一个临时变量,临时变量再给调用的地方,至少是安全的


这里提一下临时变量存在哪里


如果比较小(4/8),一般是存在寄存器中

如果比较大,就会放在调用函数的栈帧中

接前面继续讲


引用做返回值,它引用的是什么呢?


是返回值,但是我们要知道,函数结束后,栈帧就销毁了,


它这个时候就相当于非法访问了,它访问的就可能是随机值(编译器不同而不同),


那么我们应该怎么使用它呢?


需要记住这个规则:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。


比如:


int& count()
{
  static int n;//静态
  n++;
  retuern n;
}


它就可以使用引用作返回值


引用和指针的区别


在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。


int main()
{
    int a = 10;
    int& ra = a;
    cout<<"&a = "<<&a<<endl;
    cout<<"&ra = "<<&ra<<endl;
    return 0;
}


但是,在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。


int main()
{
    int a = 10;
    int& ra = a;
    ra = 20;
    int* pa = &a;
    *pa = 20;
    return 0;
}


我们来看下引用和指针的汇编代码对比

image.png



引用和指针的不同点:


引用概念上定义一个变量的别名,指针存储一个变量地址。


引用在定义时必须初始化,指针不是必须


引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体


没有NULL引用,但有NULL指针


在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)


引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小


有多级指针,但是没有多级引用


访问实体方式不同,指针需要显式解引用,引用编译器自己处理


引用比指针使用起来相对更安全,因为不用像指针那样要考虑野指针、空指针等问题。


总结


本文讲述了,C++中的引用&,下一篇将讲述C++初识下篇。


respect !


下篇见!

相关文章
|
5月前
|
存储 安全 C++
C++中的引用和指针:区别与应用
引用和指针在C++中都有其独特的优势和应用场景。引用更适合简洁、安全的代码,而指针提供了更大的灵活性和动态内存管理的能力。在实际编程中,根据需求选择适当的类型,能够编写出高效、可维护的代码。理解并正确使用这两种类型,是掌握C++编程的关键一步。
71 1
|
6月前
|
C++
C++中的const指针与const引用
C++中的const指针与const引用
73 2
|
4月前
|
存储 安全 C++
浅析C++的指针与引用
虽然指针和引用在C++中都用于间接数据访问,但它们各自拥有独特的特性和应用场景。选择使用指针还是引用,主要取决于程序的具体需求,如是否需要动态内存管理,是否希望变量可以重新指向其他对象等。理解这二者的区别,将有助于开发高效、安全的C++程序。
31 3
|
4月前
|
存储 自然语言处理 编译器
【C++入门 三】学习C++缺省参数 | 函数重载 | 引用
【C++入门 三】学习C++缺省参数 | 函数重载 | 引用
|
5月前
|
存储 安全 编译器
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
65 5
|
5月前
|
C++
C++引用
C++引用
|
5月前
|
存储 安全 编译器
【C++入门】—— C++入门 (中)_引用
【C++入门】—— C++入门 (中)_引用
36 5
|
5月前
|
C语言 C++ 编译器
【C++语言】冲突-C语言:输入输出、缺省参数、引用、内联函数
【C++语言】冲突-C语言:输入输出、缺省参数、引用、内联函数
【C++语言】冲突-C语言:输入输出、缺省参数、引用、内联函数
|
5月前
|
安全 测试技术 C++
C++中的引用
C++中的引用
29 1