当前位置: 首页 > article >正文

C/C++ | 每日一练 (5)

💢欢迎来到张胤尘的技术站
💥技术如江河,汇聚众志成。代码似星辰,照亮行征程。开源精神长,传承永不忘。携手共前行,未来更辉煌💥

文章目录

  • C/C++ | 每日一练 (5)
    • 题目
    • 参考答案
      • 引用
      • 引用和指针的区别
        • 语法
        • 使用方式
        • 安全和易用性
      • 底层差异
      • 注意事项
        • 引用的生命周期
        • 常量引用
        • 引用和函数参数

C/C++ | 每日一练 (5)

题目

什么是引用?引用和指针的区别是什么?

参考答案

引用

c++ 中,引用是为变量提供了一个别名,使得对引用的操作等同于对原始变量的操作。

需要注意的是,引用必须在声明时初始化,并且一旦初始化后,就与它所引用的变量绑定在一起,不能改变引用的目标。

给出引用的定义,如下所示:

int a = 10;
int& ref = a;  // ref 是 a 的引用

此时,refa 是同一个变量的两个名字,对 ref 的操作等同于对 a 的操作。

注意:本文章中的 “引用” 只讨论作为左值引用,不讨论右值引用的概念和差异。

引用和指针的区别

引用和指针虽然都可以用来操作变量,但它们在具体的使用方式上确实有很大的不同。

下面从:语法、使用方式、安全和易用性这几个方面进行说明。

语法

引用

  • 引用在其声明时必须初始化,且不能改变引用的目标。

  • 另外引用的语法类似于变量的声明,但需要在类型后面添加 & 符号。

  • 引用没有自己的内存地址,它和所引用的变量共享同一个内存地址。

指针

  • 指针可以不初始化(nullptr),也可以随时改变指向的目标。
  • 指针的声明需要在类型前加 *,并且需要通过解引用操作符 * 来访问指针所指向的内容。
  • 指针有自己的内存地址,存储的是目标变量的地址。
使用方式

引用

引用常用于函数参数传递(避免拷贝)和返回值(返回对象的别名)。

void increment(int &x)
{
    x++; // 直接操作引用
}

int main()
{
    int a = 10;
    increment(a); // 传递引用
    return 0;
}

指针

指针常用于动态内存分配(如 newdelete)、链表等数据结构。

#include <iostream>

int main(int argc, char const *argv[])
{
    int *a = new int();
    *a = 10;
    std::cout << *a << std::endl; // 10
    delete a;

    return 0;
}
安全和易用性

引用

  • 引用更安全,因为它不能为 nullptr,并且不能改变引用的目标。
int main()
{
    int *ptr = nullptr; // 合法
    // int &ref = nullptr; // 错误:引用不能为nullptr

    int a = 10;
    int b = 20;
    int &ref = a;
    ref = b;	// 注意:这句话的含义并不是将ref重新指向b,而是将b的值赋值给a,因为ref是a的引用

    return 0;
}
  • 引用的使用方式更直观,代码更简洁。

指针

  • 指针更为灵活,但同时也更容易出错,例如野指针(指向无效内存的指针)、空指针解引用。
  • 指针使用时需要注意更多的边界检查,例如检查是否为 nullptr

底层差异

为了从底层更为深入的了解引用和指针的差异,下面给出一段代码,将这段代码编译成会汇编,从汇编的角度观察两者之间的区别,如下所示:

void increment(int& x) {
    x = x + 1;
}

int main() {
    int a = 10;
    increment(a);
    return a;
}

汇编代码如下所示:

$ cat test.s
_Z9incrementRi:	
        pushq   %rbp		   # 建立_Z9incrementRi函数栈帧
        movq    %rsp, %rbp
        movq    %rdi, -8(%rbp) # 通过rdi寄存器传递,保存参数x的地址
        movq    -8(%rbp), %rax # 将x的地址加载到%rax
        movl    (%rax), %eax   # 通过地址加载x的值
        leal    1(%rax), %edx  # x + 1,结果存储在%edx
        movq    -8(%rbp), %rax # 再次加载x的地址
        movl    %edx, (%rax)   # 将结果写回到x的地址
        nop
        popq    %rbp		   # 恢复栈帧
        ret					   # 返回
main:
        endbr64		
        pushq   %rbp		   # 建立main函数栈帧
        movq    %rsp, %rbp
        subq    $16, %rsp
        movq    %fs:40, %rax
        movq    %rax, -8(%rbp)
        xorl    %eax, %eax
        movl    $10, -12(%rbp)	# 初始化变量a = 10
        leaq    -12(%rbp), %rax	# 获取a的地址
        movq    %rax, %rdi		# 将a的地址传递给increment
        call    _Z9incrementRi	# 调用increment函数
        movl    -12(%rbp), %eax # 将a的值加载到返回寄存器eax
        movq    -8(%rbp), %rdx	# 检查栈
        subq    %fs:40, %rdx
        je      .L4
        call    __stack_chk_fail@PLT
.L4:
        leave
        ret						# 返回

以上汇编代码并不完整,只保留核心代码逻辑。

从以上的代码中可以看出,引用在底层是通过指针实现的。c++ 中的引用本质上是一个“隐藏的指针”,它通过地址直接操作变量。

注意事项

从之前的分析可以看出,引用是一个非常强大且实用的特性,但是在平时使用过程中也有一些使用上的注意事项和限制。

引用的生命周期

引用的生命周期必须与其绑定的对象一致,否则可能导致未定义行为。

#include <iostream>

int& getRef() {
    int a = 10;
    return a;  // warning: reference to local variable ‘a’ returned [-Wreturn-local-addr]
}

int main() {
    int &b = getRef();
    std::cout << b << std::endl;	// Segmentation fault (core dumped)
    return 0;
}

在上面的代码中,a 是局部变量,函数返回后 a 的生命周期结束,返回的引用 b 此时指向了一个已经销毁的对象,打印的 b 会导致未定义的行为,从而报错段错误。

常量引用

Best Praetices:如果函数无须改变引用形参的值,最好将其声明为常量引用,以提高代码的安全性和效率。

#include <iostream>

void printVal(const int &x)
{
    std::cout << x << std::endl;
}

int main(int argc, char const *argv[])
{
    printVal(10);
    return 0;
}

使用 const 引用可以避免不必要的拷贝,同时又保证在函数内部不会修改传入的对象。

引用和函数参数

使用引用作为函数参数时,需要确保传递的参数是有效的对象,不能传递字面量或临时对象(除非是 const 引用或者是右值引用)。

本文章不对右值引用进行讨论。

#include <iostream>

void printVal(int &x)
{
    std::cout << x << std::endl;
}

int main(int argc, char const *argv[])
{
    // printVal(10);	// error: cannot bind non-const lvalue reference of type ‘int&’ to an rvalue of type ‘int’
    return 0;
}

🌺🌺🌺撒花!

如果本文对你有帮助,就点关注或者留个👍
如果您有任何技术问题或者需要更多其他的内容,请随时向我提问。

在这里插入图片描述


http://www.kler.cn/a/570082.html

相关文章:

  • Leetcode 662: 二叉树最大宽度
  • 【江科协-STM32】6. TIM编码器接口
  • Oracle数据库安全防护体系构建与核心技术解析
  • Artec Leo+Ray II 三维扫描仪成功为VR展数字化30吨重设备-沪敖3D
  • 网络安全架构三明治
  • 水仙花数(华为OD)
  • 最新版AI大模型面试八股文
  • 基于Asp.net的高校社交学习交流平台
  • 表关联查询:utf8mb4_general_ci和utf8mb4_0900_ai_ci两种不同的校对规则在操作符‘=‘时混用了
  • DeepSeek掘金——DeepSeek-R1驱动的房地产AI代理
  • OLMo OCR:让文字从图片里“跳”出来的魔法工具
  • 本地部署大数据集群前置准备
  • 多任务学习MTL+多任务损失
  • 机械视觉组成模块-相机选型
  • 从零开始用react + tailwindcss + express + mongodb实现一个聊天程序(九) 消息接口
  • 【含文档+PPT+源码】基于SpringBoot电脑DIY装机教程网站的设计与实现
  • 面试常问的压力测试问题
  • 实时金融信息搜索的新突破:基于大型语言模型的智能代理框架
  • 前端项目打包生成 JS 文件的核心步骤
  • OpenCV计算摄影学(12)色调映射(Tone Mapping)的一个类cv::TonemapMantiuk