【C++初阶】第一站:C++入门基础(中)-2

简介: 【C++初阶】第一站:C++入门基础(中)-2

【C++初阶】第一站:C++入门基础(中)-1

https://developer.aliyun.com/article/1456970?spm=a2c6h.13148508.setting.29.2e124f0eXM1nTd




引用特性

🚩引用在定义时必须初始化


c6fca676a6d141cb9b3440b23df795fc.png

🚩一个变量可以有多个引用

int main()
{
  int a = 0;
  int& b = a;
  int& c = a;
  int& d = b;//给别名取别名,实际上是同一块空间。
    int x = 1;
  //赋值
  b = x;
  return 0;
}

代码执行变化:


17cd34bebb9d41ccb11db187849ea53e.png


🚩引用一旦引用一个实体,再不能引用其他实体

int main()
{
  int a = 0;
  int& b = a;
  int x = 1;
  int&b = x;
  return 0;
}

代码执行:

05040484a90740bcb36089df098f93be.png



使用场景

传引用返回和传值返回:


以下的两者返回的方式有什么区别呢?


       答:这两种情况的区别,在于传值调用在函数销毁时有寄存器,传引用调用没有寄存器保存值,因为是同一个空间,引用不同于传值和传址,既然直接传的就是这个空间本身,因此既不用创建临时拷贝,也不需要传变量地址。而是直接变量进行赋值。

553facae43df40658bba07d5a2a9aeea.png



💥先来看传值返回:


       用了一个全局的寄存器eax把返回值保存起来,待Count函数栈帧销毁后,回到主函数main,再将寄存器里面的值赋值给ret


706415a8a67a483d85a682cbeef81ff7.png


💨传引用返回:


       这个n的别名,出了作用域就销毁(这意味着返回的值是对已被销毁的变量的引用),还给操作系统了,在还给操作系统的时候,可能将这块空间里面的值给清理了,变成随机值了。由于Count函数的返回值是无效的(引用已被销毁),所以打印ret的值将导致未定义的行为。它可能打印1,也可能打印随机值,或者可能导致程序崩溃。


a717e78d3e2e42aaad16efdbfd17eccf.png


💢那如果对代码再修改一下呢:


       这段代码是非法的。当函数 Count() 执行完毕后,局部变量 n 将被销毁,引用 ret 将会成为悬空引用(dangling reference),它指向了已经归还给操作系统的空间,该空间里面的值可能已经被初始化为随机值。因此,将对n的引用返回给调用函数是无效的,导致未定义的行为。


b88dec608e324246aa1dd05ce825654e.png


总结:


传引用的第一个示例还是第二个示例中,代码都是非法的,并且会导致未定义的行为


       如果函数返回时,离开函数作用域后,其栈上空间已经还给系统,因此不能用栈上的空间作为引用类型返回。如果以引用类型返回,返回值的生命周期必须不受函数的限制(即比函数生命周期长)。


       只有当出了这个作用域,这个对象还在的情况下,才可以加引用比如便用static将变量设成静态变量,或是全局变量,函数生命周期就不会影响到引用

c58d5c62132c4519981e0c58acebd4b2.png



代码示例:


//传引用调用
int& Count()
{
  int n = 0;
  n++;
  return n;
}
int main()
{
  int& ret = Count();
  //这里打印的结果可能是1,也可能是随机值
  cout << ret << endl;
  cout << ret << endl;
  return 0;
}
//传值调用
int Count()
{
  int n = 0;
  n++;
  return n;
}
int main()
{
  int ret = Count();
  cout << ret << endl;
  cout << ret << endl;
  return 0;
}


当变量前面有很大一块空间被占用时,有可能不会被覆盖:


6f0317722d1e4e05bbb830b92e5cf9b2.png


       写一个相加两个变量值的代码:


058e5ff38bad45278840f46e20b57c4e.png


代码实现:


#include<iostream>
#include<assert.h>
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;
}

解析:


68aa0335923e4747a944bbf6cb9b0dd0.png


   

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

传值、传引用效率比较

          以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效

率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

传引用传参(任何时候都可以用)
1、提高效率
2、输出型参数(形参的修改,影响的实参)

     

#include <time.h>
#include<iostream>
struct A { int a[10000]; };
A a;//全局变量!!!
// 值返回
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 < 100000; ++i)
  TestFunc2();
  size_t end2 = clock();
  // 计算两个函数运算完成之后的时间
  cout << "TestFunc1 time:" << end1 - begin1 << endl;
  cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{
  TestReturnByRefOrValue();
  return 0;
}


值和引用的作为返回值类型的性能比较

传引用返回(出了函数作用域对象还在才可以用)-- static修饰的,全局变量,堆空间等等
 1、提高效率
 2、修改返回对象


965302fe79eb4502aa861787e3721e4f.png

#include <time.h>
struct A { int a[10000]; };
A a;
// 值返回
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 < 100000; ++i)
  TestFunc2();
  size_t end2 = clock();
  // 计算两个函数运算完成之后的时间
  cout << "TestFunc1 time:" << end1 - begin1 << endl;
  cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{
  TestReturnByRefOrValue();
  return 0;
}


关于顺序表的读取与修改

c语言接口

struct SeqList
{
  int a[10];
  int size;
};
//C的接口设计 
//读取第i个位置
int SLAT(struct SeqList* ps, int i)
{
  assert(i<ps->size);//防止越界
  //...
  return ps->a[i];
}
//修改第i个位置的值
void SLModify(struct SeqList* ps, int i, int x)
{
  assert(i< ps->size);
  // ...
  ps->a[i] = x;
}


       可以看待以上代码,C语言实现读取和修改结构体成员--数组元素时,是非常繁琐的,实现功能就要编写一个功能函数,但是如果换成c++的引用,那就可以一个函数实现两个功能


Cpp的接口设计:

bbd61b051601450ab7a0af5a687d1bf0.png


代码示例:


CPP接口设计
//读 or 修改第i个位置的值
#include<iostream>
#include<assert.h>
int& SLAT(struct SeqList& ps, int i)
{
  assert(i < ps.size);
  return(ps.a[i]);
}
int main()
{
  struct SeqList s;
  s.size = 3;
  SLAT(s, 0) = 10;
  SLAT(s, 1) = 20;
  SLAT(s, 2) = 30;
  cout << SLAT(s, 0) << endl;
  cout << SLAT(s, 1) << endl;
  cout << SLAT(s, 2) << endl;
  return 0;
}


特别注意:


2fa055fa930f406d9a326fdc6b299ca3.png



常引用

在引用的过程中:
1.权限可以平移
2.权限可以缩小
3.权限不能放大!!!


int main()
{
  const int a = 0;
  //权限的放大
  int& b = a;//这个是不行的!!!
  //权限的平移
  const int& c = a;
  //权限的缩小
//形象地理解: 
  int x = 0;//齐天大圣
  const int& y = x;//戴上紧箍咒的孙悟空
  return 0;
}

赋值:


int b = a;//可以的,因为这里是赋值拷贝,b修改不影响a

类型转换:


       在c/c++里面有个规定:表达式转换的时候会产生一个临时变量,具有常性


375eec0e16264a20a15759fdc5cbca2f.png


以及函数返回的时候也会产生一个临时对象



7135b0b71aae417f8333ca33644608c8.png

   

相关文章
|
29天前
|
安全 编译器 程序员
【C++初阶】C++简单入门
【C++初阶】C++简单入门
|
8天前
|
编译器 Linux C语言
C++基础入门
C++基础入门
|
1月前
|
安全 编译器 C++
C++入门 | 函数重载、引用、内联函数
C++入门 | 函数重载、引用、内联函数
24 5
|
1月前
|
存储 安全 编译器
C++入门 | auto关键字、范围for、指针空值nullptr
C++入门 | auto关键字、范围for、指针空值nullptr
47 4
|
1月前
|
编译器 C语言 C++
C++入门 | 命名空间、输入输出、缺省参数
C++入门 | 命名空间、输入输出、缺省参数
33 4
|
27天前
|
安全 编译器 C语言
C++入门-数组
C++入门-数组
|
27天前
|
存储 编译器 程序员
C++从遗忘到入门
本文主要面向的是曾经学过、了解过C++的同学,旨在帮助这些同学唤醒C++的记忆,提升下自身的技术储备。如果之前完全没接触过C++,也可以整体了解下这门语言。
|
29天前
|
C++ 容器
C++中自定义结构体或类作为关联容器的键
C++中自定义结构体或类作为关联容器的键
30 0
|
8天前
|
存储 编译器 C++
C ++初阶:类和对象(中)
C ++初阶:类和对象(中)
|
8天前
|
C++
C++(十六)类之间转化
在C++中,类之间的转换可以通过转换构造函数和操作符函数实现。转换构造函数是一种单参数构造函数,用于将其他类型转换为本类类型。为了防止不必要的隐式转换,可以使用`explicit`关键字来禁止这种自动转换。此外,还可以通过定义`operator`函数来进行类型转换,该函数无参数且无返回值。下面展示了如何使用这两种方式实现自定义类型的相互转换,并通过示例代码说明了`explicit`关键字的作用。