【C++学习】C++入门 | 引用 | 引用的底层原理 | auto关键字 | 范围for(语法糖)

简介: 【C++学习】C++入门 | 引用 | 引用的底层原理 | auto关键字 | 范围for(语法糖)

写在前面:

上一篇文章我介绍了缺省参数和函数重载,


探究了C++为什么能够支持函数重载而C语言不能,


这里是传送门,有兴趣可以去看看:http://t.csdn.cn/29ycJ


这篇我们继续来学习C++的基础知识。


目录


写在前面:


1. 引用


2. 引用的底层


3. auto 关键字


4. 范围for(语法糖)


总结:


写在最后:


1. 引用

引用就是起别名。


举一个经典的例子:周树人给自己起了一个笔名叫鲁迅,


那鲁迅和周树人是同一个人吗?答案是肯定的。(你找鲁迅跟我周树人有什么关系。。。)


那引用的语法是怎么样的呢:


#include 
using namespace std;
int main()
{
  int a = 10;
  int& b = a; //b就是a的别名
  cout << a << endl;
  cout << b << endl;
  return 0;
}

引用的符号是&,在C语言中这个符号是取地址,


引用的符号也是这个,他共用了这个符号。


上面这段代码其实我们就能直接理解成 a 是周树人,b 是他的别名鲁迅。


所以他们实际上是一样的,来看输出:


10

10

所以 b 和 a 其实就是一样的,所以 a = 10,b 当然也等于10。


所以我们再看:


#include 
using namespace std;
int main()
{
  int a = 10;
  int& b = a; //b就是a的别名
  cout << &a << endl;
  cout << &b << endl;
  return 0;
}

输出:


005EFE28

005EFE28

他们的地址也是一样的。


再来看:

#include 
using namespace std;
int main()
{
  int a = 10;
  int& b = a; //b就是a的别名
  int& c = b;
  int& d = c;
  cout << &a << endl;
  cout << &b << endl;
  cout << &c << endl;
  cout << &d << endl;
  return 0;
}


输出:


00AFF8B4

00AFF8B4

00AFF8B4

00AFF8B4

这样子当然也是一样的。


现在你大概就知道引用是什么样子了。


另外,引用是不能这样写的:


#include 
using namespace std;
int main()
{
  int& a;
  return 0;
}

使用引用的时候你一定要告诉编译器,你是谁的别名。


这里我们马上来一个场景,


当我们学了引用之后,指针一下子就不香了,很多地方我们就直接使用引用了:


比如说经典的Swap函数:

#include 
using namespace std;
// 实际上这里x就是a的引用,y就是b的引用
void Swap(int& x, int& y) {
  int tmp = x;
  x = y;
  y = tmp;
}
int main()
{
  int a = 10;
  int b = 20;
  Swap(a, b);
  cout << a << endl;
  cout << b << endl;
  return 0;
}


我们就能根据引用的特性实现,不需要用繁杂的指针操作,


让 x 是 a 的引用,y 是 b 的引用,


这样我们在函数里面操作 x 和 y 的时候其实就是在操作 a 和 b 。


这里我总结了引用的特性作为补充:


1. 一个变量可以有多个引用(就好像一个人可以有多个别名)


2. 引用必须在定义时初始化(前面演示过了)


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


(说人话就是如果你是 a 的引用,你就不能改成是 b 的引用,只能一直是 a 的引用)


这里给出例子:


#include 
using namespace std;
int main()
{
  //一个变量可以有多个引用
  int a = 0;
  int& b = a;
  int& c = a;
  //引用在定义时必须初始化
  //int& d;
  int x = 10;
  c = x; //这里是赋值操作,c依旧是a的引用(别名)
  return 0;
}


这是引用的基本特性,一定要熟悉好。


这里我继续介绍引用的作用:


1. 引用作为函数参数(输出型参数)(前面举了Swap函数的例子)


引用作为函数参数还能提高效率(之后学深浅拷贝的时候会再介绍)


2. 引用做返回值


我们来看这样一个例子:



当一个有返回值的函数返回值的时候,


他会将返回值拷贝生成一个临时变量,再将临时变量赋值给 ret 。


学过C语言,我们都知道,当这个函数结束的时候他的函数栈帧就销毁了,


所以他才需要生成一个临时变量,这样就能将返回值成功赋给主函数中的 ret 。


而且无论返回值是个什么变量,就算是静态变量,在返回的时候也会生成临时变量,


这个时候该引用出场了,


使用引用作为返回值,就不会生成临时变量:


#include 
using namespace std;
int& Count() {
  static int n = 0;
  n++;
  return n;
}
int main()
{
  int ret = Count();
  return 0;
}


所以这个时候我们又能理解前面所说的引用的作用,


使用引用能够提高效率,减少拷贝。


但是要注意,我们上面那段代码用引用返回没有问题,


那如果变量 n 不是一个静态变量呢?

#include 
using namespace std;
int& Count() {
  int n = 0;
  n++;
  return n;
}
int main()
{
  int ret = Count();
  return 0;
}

这样就出问题了,


ret 的值是不确定的,因为出了作用域 n 就不在了。


如果Count函数结束,栈帧销毁,没有清理栈帧,那么ret的结果侥幸是正确的,


如果Count函数结束,栈帧销毁,清理了栈帧,那么ret的结果就是随机值。


总结:


1. 基本任何场景都可以用引用传参,


2. 谨慎使用引用返回,避免出现上面的情况。


这里还有一种情况,常引用:


比如说这段代码:(这是一段错误代码)

#include 
using namespace std;
int main()
{
  //引用的过程中,权限不能放大
  const int a = 0;
  int& b = a;
  return 0;
}

再来看这一段代码:

#include 
using namespace std;
int main()
{
  //引用的过程中,权限不能放大
  //const int a = 0;
  //int& b = a;
  //引用的过程中,权限可以平移或者缩小
  int a = 0;
  int& b = a;
  const int& c = b;
  return 0;
}


这个时候问题来了,


我们能不能修改 a 的值呢?

#include 
using namespace std;
int main()
{
  //引用的过程中,权限不能放大
  //const int a = 0;
  //int& b = a;
  //引用的过程中,权限可以平移或者缩小
  int a = 0;
  int& b = a;
  const int& c = b;
  a++; 
  return 0;
}


答案是可以的,const int& c 修改的是这个别名的权限,


而 a 是不受影响的。


我们再来看一个例子:

#include 
using namespace std;
int main()
{
  double a = 1.1;
  int b = a; //隐式类型转换
  int& c = a;// ?
  return 0;
}

我们都知道,第二个语句是隐式类型转换,


那 int& c = a ,能编译通过吗? 答案是不能:



难道是因为不同类型的变量不能用引用吗?


我们再来看:


加了一个 const 在前面然后就编译成功了。。。


这又是为什么?


实际上,在学习C语言阶段我们曾经学习过,


在进行类型转换的时候,会生成一个临时变量,如图:



而临时变量具有常性,具有常性是什么意思呢?其实就类似用 const 修饰过一样。


这样就会出现权限的放大,所以导致报错。


这个时候我们结合前面的例子来看:

#include 
using namespace std;
int cnt() {
  static int n = 0;
  return n;
}
int main()
{
  int& a = cnt(); //这里会出错
  return 0;
}

为什么这段代码会出错?


其实这也是一个道理,函数返回值的时候会创建一个临时变量,


而临时变量具有常性,所以这里也会出现权限放大导致的错误。


只要在 int& a 前面加上一个const就行了,这样就达成了权限的平移。、


2. 引用的底层

那么引用又是怎么实现的呢?他的底层是什么样的?


我们还是得从汇编的角度来观察,


先来看这段代码:

#include 
using namespace std;
int main()
{
  int a = 0;
  int& ra = a;
  int* pa = &a;
  return 0;
}

这段代码里面分别使用了引用和指针来对 a 进行操作,


来看看他的汇编代码是怎么样的:



哦~,看看我们发现了什么,引用和指针的底层怎么一模一样?


在语法的层面:


引用不开空间,是对 a 取别名,


而指针开空间,是取 a 的地址,


但是,从底层汇编的角度来看,引用是以类似指针的方式实现的。


不过我们平时牢记引用在语法层的效果就行,底层就简单了解一下。


补充:引用之后还会有许多的场景,这些我会之后遇到具体的场景在做总结和分析。


3. auto 关键字

auto 能根据右边的表达式自动推导类型,


来看一个例子:

#include 
using namespace std;
int main()
{
  int a = 0;
  auto c = a;
  //这个操作能够查看变量的类型
  cout << typeid(c).name() << endl;
  return 0;
}

输出:


int

当然,右边不一定要是变量,也可以使表达式:

#include 
using namespace std;
int main()
{
  auto c = 1 + 1;
  //这个操作能够查看变量的类型
  cout << typeid(c).name() << endl;
  return 0;
}


输出:


int

现在这样看来,auto好像价值不大,


但是如果以后遇到非常复杂的类型的时候,直接使用auto会非常方便。


这里补充一点:auto是不能作为函数参数或者用来声明数组的。


4. 范围for(语法糖)

这里再基于我们刚刚学的auto,介绍一下范围for。


C语言阶段,我们平时遍历一个数组都是这样遍历的:

#include 
using namespace std;
int main()
{
  int arr[] = { 1, 2, 3, 4, 5 };
  for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
  cout << arr[i] << " ";
  }
  cout << endl;
  //使用范围for
  for (auto e : arr) {
  cout << e << " ";
  }
  return 0;
}

以上面的代码为例:


范围for 其实就是依次取数组中的数据赋值给e,


自动迭代,自动判断结束。


你就说范围for 方不方便,甜不甜?不甜又怎么会被叫做语法糖呢。


这里补充一点,修改e 是不会修改到数组的,因为e 的值是取数组的数据赋值过来的,


如果想要通过修改e 来修改数组,需要加个引用:

#include 
using namespace std;
int main()
{
  int arr[] = { 1, 2, 3, 4, 5 };
  //使用范围for
  for (auto& e : arr) {
  e++;
  cout << e << " ";
  }
  return 0;
}

输出:


2 3 4 5 6

当然,不止int 类型的数组,其他什么类型的数组都可以用范围for,


还有一些我们之后要学的容器,因为auto 会自动推导类型。


总结:

C++入门铺垫的知识学的差不多了,准备要开始类和对象了。


写在最后:

以上就是本篇文章的内容了,感谢你的阅读。


如果感到有所收获的话可以给博主点一个赞哦。


如果文章内容有遗漏或者错误的地方欢迎私信博主或者在评论区指出~


相关文章
|
4月前
|
缓存 算法 程序员
C++STL底层原理:探秘标准模板库的内部机制
🌟蒋星熠Jaxonic带你深入STL底层:从容器内存管理到红黑树、哈希表,剖析迭代器、算法与分配器核心机制,揭秘C++标准库的高效设计哲学与性能优化实践。
C++STL底层原理:探秘标准模板库的内部机制
|
8月前
|
存储 安全 编译器
c++入门
c++作为面向对象的语言与c的简单区别:c语言作为面向过程的语言还是跟c++有很大的区别的,比如说一个简单的五子棋的实现对于c语言面向过程的设计思路是首先分析解决这个问题的步骤:(1)开始游戏(2)黑子先走(3)绘制画面(4)判断输赢(5)轮到白子(6)绘制画面(7)判断输赢(8)返回步骤(2) (9)输出最后结果。但对于c++就不一样了,在下五子棋的例子中,用面向对象的方法来解决的话,首先将整个五子棋游戏分为三个对象:(1)黑白双方,这两方的行为是一样的。(2)棋盘系统,负责绘制画面。
123 0
|
12月前
|
存储 缓存 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 的奥秘,从入门到高效编程
|
11月前
|
存储 分布式计算 编译器
C++入门基础2
本内容主要讲解C++中的引用、inline函数和nullptr。引用是变量的别名,与原变量共享内存,定义时需初始化且不可更改指向对象,适用于传参和返回值以提高效率;const引用可增强代码灵活性。Inline函数通过展开提高效率,但是否展开由编译器决定,不建议分离声明与定义。Nullptr用于指针赋空,取代C语言中的NULL。最后鼓励持续学习,精进技能,提升竞争力。
|
11月前
|
C++
|
算法 网络安全 区块链
2023/11/10学习记录-C/C++对称分组加密DES
本文介绍了对称分组加密的常见算法(如DES、3DES、AES和国密SM4)及其应用场景,包括文件和视频加密、比特币私钥加密、消息和配置项加密及SSL通信加密。文章还详细展示了如何使用异或实现一个简易的对称加密算法,并通过示例代码演示了DES算法在ECB和CBC模式下的加密和解密过程,以及如何封装DES实现CBC和ECB的PKCS7Padding分块填充。
318 4
2023/11/10学习记录-C/C++对称分组加密DES
|
C++ 开发者
C++学习之继承
通过继承,C++可以实现代码重用、扩展类的功能并支持多态性。理解继承的类型、重写与重载、多重继承及其相关问题,对于掌握C++面向对象编程至关重要。希望本文能为您的C++学习和开发提供实用的指导。
197 16
|
12月前
|
安全 C语言 C++
彻底摘明白 C++ 的动态内存分配原理
大家好,我是V哥。C++的动态内存分配允许程序在运行时请求和释放内存,主要通过`new`/`delete`(用于对象)及`malloc`/`calloc`/`realloc`/`free`(继承自C语言)实现。`new`分配并初始化对象内存,`delete`释放并调用析构函数;而`malloc`等函数仅处理裸内存,不涉及构造与析构。掌握这些可有效管理内存,避免泄漏和悬空指针问题。智能指针如`std::unique_ptr`和`std::shared_ptr`能自动管理内存,确保异常安全。关注威哥爱编程,了解更多全栈开发技巧。 先赞再看后评论,腰缠万贯财进门。
513 0
|
12月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
10月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
412 12