【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

   

相关文章
|
2月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
42 2
C++入门12——详解多态1
|
2月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
84 1
|
2月前
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
24 0
|
2月前
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
34 0
|
2月前
|
分布式计算 Java 编译器
【C++入门(下)】—— 我与C++的不解之缘(二)
【C++入门(下)】—— 我与C++的不解之缘(二)
|
2月前
|
编译器 Linux C语言
【C++入门(上)】—— 我与C++的不解之缘(一)
【C++入门(上)】—— 我与C++的不解之缘(一)
|
2月前
|
编译器 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
32 0
|
15天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
25 2
|
21天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
54 5
|
27天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
56 4