【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

   

相关文章
|
6月前
|
存储 安全 编译器
c++入门
c++作为面向对象的语言与c的简单区别:c语言作为面向过程的语言还是跟c++有很大的区别的,比如说一个简单的五子棋的实现对于c语言面向过程的设计思路是首先分析解决这个问题的步骤:(1)开始游戏(2)黑子先走(3)绘制画面(4)判断输赢(5)轮到白子(6)绘制画面(7)判断输赢(8)返回步骤(2) (9)输出最后结果。但对于c++就不一样了,在下五子棋的例子中,用面向对象的方法来解决的话,首先将整个五子棋游戏分为三个对象:(1)黑白双方,这两方的行为是一样的。(2)棋盘系统,负责绘制画面。
99 0
|
10月前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
9月前
|
存储 分布式计算 编译器
C++入门基础2
本内容主要讲解C++中的引用、inline函数和nullptr。引用是变量的别名,与原变量共享内存,定义时需初始化且不可更改指向对象,适用于传参和返回值以提高效率;const引用可增强代码灵活性。Inline函数通过展开提高效率,但是否展开由编译器决定,不建议分离声明与定义。Nullptr用于指针赋空,取代C语言中的NULL。最后鼓励持续学习,精进技能,提升竞争力。
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
133 0
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
179 0
|
分布式计算 Java 编译器
【C++入门(下)】—— 我与C++的不解之缘(二)
【C++入门(下)】—— 我与C++的不解之缘(二)
|
10月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
6月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
191 0
|
6月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
281 0