type
status
date
slug
summary
tags
category
icon
password
🧑‍🏫
这节课是信息学奥赛系列的第 5 讲,主要内容是结构体和指针。

📝 课程大纲

结构体

  • 结构体的定义和操作
  • 结构体的使用

指针

  • 指针变量的定义、赋值
  • 指针的引用与运算
  • 指针与数组
  • 指针与字符串

🤗 整理归纳

结构体

结构体是一种复合类型,也是一种用户定义类型
通俗地说,它捆绑多个变量,使之成为一个整体
比如,平面上点坐标:
C++ 的结构体统一了 class 和 struct,不再需要 typedef 才能定义结构体的类型
struct 与 class 的唯一区别是:struct 内默认都是 public
用 sizeof 可以查看结构体大小

Struct 初始化

结构体是值语义

a = b;的结果是拷贝了 b 中所有项的数据到 a;a、b 数据是独立的
结构体作为参数传递时,值语义。
当结构体较大时,有时为了效率,我们可以传递引用
如果不需要修改结构体,最好用 const 修饰,增加安全性

访问结构体的成员

data.x或者p->x

结构体中的指针和数组

结构体中使用指针类型数据,一定要当心,尤其是 char*,最容易误用!
注意,出现了名字共享的现象。
notion image

结构体可以嵌套

实时效果反馈
1关于结构体struct的说法,正确的是:____
A 必须通过 typedef 才能使用
B 指针可以指向结构体,结构体中也可以包含指针
C 声明结构体时,必须赋给初始值
D 结构体只能在栈中分配
2在结构体中包含char 类型,==不会==引发的问题是:____*
A 结构体还在工作中,而它的 char*所指向的数据却被别人释放了
B 结构体赋值时,两个指针公用了同一片数据
C 释放结构体内存时,忘记了释放 char* 指针,造成内存泄漏
D 这样的结构体无法放在栈中
答案
1=>B 2=>D

指针入门

指针就是存放其它变量地址的小盒子
所说的地址,是虚拟内存的地址,本质上就是整数,默认以十六进制表示
notion image

指针的定义

类型 * 指针名初始化:指针 = &某变量

通过指针,访问被指向的变量

可以读取myxx = *p;也可以写入*p = ...
要十分谨慎 通过指针写入是危险的,这是 C++ 强大的原因,也是 bug 发源地

可以在参数中使用指针

形参是指针类型时,可以改变实参的值 我们可以利用这个特性,间接地实现多个返回值。
【示例】 交换连个变量
【示例】 改写三数居中的例子,使用冒泡排序法 冒泡排序: 如果相邻的连个元素逆序,则交换它们 多次重复这个动作 伪代码如下:

可以返回指针类型

避免返回局部变量的地址
当我们拿到返回值的时候,变量 a 已经被释放了。我们的指针指向了已经无效的内存,产生了野指针。这是很常见的bug之源
实时效果反馈
1.对指针类型,说法正确的是? ____
A 虚拟内存的地址,本质上就是整数
B 对 cpu 的一种特殊的指令
C 程序的特殊区域
D c++语言独有的秘密武器
2. **悬挂指针是怎么产生的? **
A 一个指针长时间没用,被回收了
B 一个指针指向了无效的内存
C 一个指针,其值为0
D 一个指针,长夜漫漫,黯然神伤
答案
1=>A 2=>B

引用类型

引用在语义上看,是:别名机制 在实现上,被转换为对指针的操作。
变量可以看作是:内存中小盒子,变量名可以看作是盒子的标签 引用则可以想象为盒子上的另一个标签 就是说,一个盒子有多少个名字
notion image

有了指针,为什么还要引入别名?

  1. 为了语义上更清晰,更接近人的思考习惯。 【改写】 swap 函数
    1. 引用是不能运算的(比如++,--),只能去引用原来的值 相当于给“指针”这匹野马,套上约束,使用上更安全。
    1. 不存在空引用,但空指针很普遍,是bug之源
    1. 创建时初始化,不存在随机垃圾内存现象
    1. 引用绑定了一个目标,不能中途更换,指针随便

    引用可以作为函数参数,也可以是返回值

    当作形参时,可以看作实参的别名 当作返回值时,注意不要引用已经释放的变量
    返回不存在的对象的引用,与返回不存在的对象的指针,都是典型的错误。

    实现多返回

    表面上看,c++的函数只能返回一个值,无法实现多值返回。
    实际上,我们可以变通。
    【示例】 通过引用实现返回多个值的效果 编写一个函数,传入a,b,返回a除以b的商,和余数
    提示:可以用参数接收返回值。
    实时效果反馈
    1.引用和指针的关系:
    A 引用比指针操作更快
    B 引用比指针有更多的操作,功能更强大
    C 引用总是比指针更省内存
    D 引用比指针更安全,更人性化
    2. **哪个==不是==使用引用的优点 **
    A 必须初始化,不可能有垃圾值
    B 无法更换绑定的变量,防止误操作
    C 不能进行++ --的运算,更安全
    D 看起来更酷,更唬人
    答案
    1=>D 2=>D

    函数进阶

    notion image
    • 数学上的函数是:
    输入数据 ===> 函数(加工机器) ===>吐出一个值
    • 程序中的函数行为更复杂
      • 主要表现在函数执行时,可能会影响调用者
        也叫“副作用”

    被调方(callee)如何影响主调方(caller)

    1. 返回值
      1. 这是最正经的渠道,调用方自己来决定如何使用返回值
        可以:
        也可以:丢弃
    1. 指针参数
      1. 参数类型为指针的话,就复杂了,callee 直接操刀。。。
    1. 形参是:指针的指针
      1. 比如: int** p
        这个就更恐怖了,实质与前述相同
    1. 形参是:引用
      1. 这个看着唬人,实际上最后,还是转换为指针的方案

    指针形参

    这种方式,好比调用方准备了一个盒子,把盒子地址传过去,被调方往里塞东西
    notion image
    讨饭的例子。。。
    其本质是:两个指针指向同一个东西
    notion image
    【示例】数组排序
    冒泡排序法
    每一趟排序,把最大的归位
    notion image
    核心代码:

    指针的指针形参

    这是为了修改调用方指针本身(不是指针指向的值),使得它指向所需的内容
    【示例】
    准备一个整数数组,准备2个指针 p 和 q
    调用函数 f 后,使得p指向==第一个==负数,q指向==最后一个==负数
    notion image
    来看调用方
    有的人晕车,有的人晕船,。。。
    c/c++程序员==不能==晕指针,那好比美丽的鸟儿有==恐高症==。
    秘诀:
    只要对照这两个例子,找共性。
    • 要回填的值是什么类型? 比如是 A
      • 。。。我们要修改A类型的东西。。。那么,要通知别人什么信息?。。。。
    • 传递的参数是什么类型?必然是:A*
    • 当 A = int* 时,我们得到了 int**
    实时效果反馈
    1.下列关于冒泡排序,说法正确的是____
    A 100个元素冒泡排序,共需要比较99次
    B 100个元素冒泡排序,需要比较99 + 98 + 97 + ... + 1 次
    C 一趟冒泡排序可以找到最大值和最小值
    D 完全逆序的数据,需要更多的比较次数
    2.示例题目中,我们使用了指针的指针,目的是____
    A 让一个指针,指向另一个指针
    B 取得指针的地址
    C 回填一个指针类型的数据
    D 炫耀一下指针的颜值
    答案
    1=>B 2=>C

    数组与指针进阶

    notion image
    子曰:了解事物本质的方法是:观察事物的行为

    数组名字的本质

    1. 准备两个数组 a, b,一个指针 p
    1. 观察下面的实验:
      1. 观察如下实验:
        1. 结果相同,这三者==等同==不?
      1. 观察如下实验:
        1. 思考与结论:
          1. 数组不会作为整体来==比较==,或者==赋值==,输出的时候,与指针无二
            数组名不能被赋值,它只是编译器记录的信息,不是变量。
            数组名取大小,包含了数组长度信息;赋值给指针,则==丢掉==了这个信息
            数组名取地址,得到一个指向整个数组类型的==指针==
        notion image
        重点:
        • 指针的==值==,决定了它指向哪块数据
        • 指针的==类型==,决定了它的运算行为(比如 +1 的行为)

        数组作为参数传递

        子曰: 真知来源于实践
        做下面的实验:
        实参
        形参
        现象
        int a[3]
        int * p
        可操作,丢失size信息
        int a[3]
        int p[ ]
        与前项同
        int a[3]
        int p[3]
        同前
        int a[3]
        int p[4]
        竟然也可以??

        指针的运算

        探究:p + 1 的含义
        结论:
        p + n 真实的增量是 n * sizeof(单个元素)

        指针运算与[ ]运算的等效性

        • (p + n) === p[n]
        • p === p[0]
        请反复验证:
        指针可以当数组用;数组也可以当指针用
        子曰:C/C++ 中本没有数组,它只是指针的幻影
        notion image

        拓展:数组引用(用作参数的时候,有区别)

        实时效果反馈
        1.a是整型数组,int p = a+1 的含义是____*
        A 与 int* p = &a[1]; 等同
        B 把数组 a 的第二个元素赋值给 p
        C 把表示数组的首地址的整数加 1,给了p
        D 把数组中的每个元素都加 1, 并把数组地址给 p
        2.对于函数 void f(int p[3]), 说法正确的是____
        A 调用时,必须传入一个含有三个元素的整型数组
        B 函数内部:sizeof(p) 会返回 12
        C 函数内部用 *(p+1) = ... 是错误的写法,应该: p[1] = ...
        D 调用时,传入任何 int* 类型的实参,都能编译通过
        答案
        1=>A 2=>D
         
        💡
        有关这节课的一切问题,欢迎在会客群中交流~
        相关文章
        线上课 #18:研究性学习开题仪式 & 如何科学地提问
        Lazy loaded image
        线上课 #17:栈与队列
        Lazy loaded image
        线上课 #16:Git & GitHub 教程
        Lazy loaded image
        线下课 #5:研究性学习动员
        Lazy loaded image
        线下课 #4:氵课
        Lazy loaded image
        线下课 #3:Web 前端“速成”
        Lazy loaded image
        社长在有道小图灵 10 月线上赛中斩获佳绩数字创想社进入“双课”平行推进阶段
        Loading...
        DrimTech
        DrimTech
        一群热爱信息技术,善于创造的羊羔
        最新发布
        线上课 #18:研究性学习开题仪式 & 如何科学地提问
        2025-1-14
        线上课 #17:栈与队列
        2025-1-13
        『林桛杨高』Demo 带你漫游杨高
        2025-1-9
        直面挑战,追求卓越——DrimTech 2024
        2024-12-31
        DrimTech 祝大家 1024 程序员节快乐
        2024-12-31
        邮箱添加别名教程
        2024-12-29
        公告
        🎉 DrimTech 研究性学习正式开题! 🎉
        2025 年 1 月 11 日,DrimTech 线上课 #18:研究性学习开题仪式 & 如何科学地提问顺利开展,宣告首次研究性学习拉开帷幕。