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*,最容易误用!
注意,出现了名字共享的现象。
结构体可以嵌套
实时效果反馈
1. 关于结构体struct的说法,正确的是:____
A 必须通过 typedef 才能使用
B 指针可以指向结构体,结构体中也可以包含指针
C 声明结构体时,必须赋给初始值
D 结构体只能在栈中分配
2. 在结构体中包含char 类型,==不会==引发的问题是:____*
A 结构体还在工作中,而它的 char*所指向的数据却被别人释放了
B 结构体赋值时,两个指针公用了同一片数据
C 释放结构体内存时,忘记了释放 char* 指针,造成内存泄漏
D 这样的结构体无法放在栈中
答案
1=>B 2=>D
指针入门
指针就是存放其它变量地址的小盒子
所说的地址,是虚拟内存的地址,本质上就是整数,默认以十六进制表示
指针的定义
类型 * 指针名
初始化:指针 = &某变量通过指针,访问被指向的变量
可以读取
myxx = *p;
也可以写入*p = ...
要十分谨慎 通过指针写入是危险的,这是 C++ 强大的原因,也是 bug 发源地
可以在参数中使用指针
形参是指针类型时,可以改变实参的值 我们可以利用这个特性,间接地实现多个返回值。
【示例】 交换连个变量
【示例】 改写三数居中的例子,使用
冒泡排序法
冒泡排序: 如果相邻的连个元素逆序,则交换它们 多次重复这个动作 伪代码如下:可以返回指针类型
避免返回局部变量的地址
当我们拿到返回值的时候,变量 a 已经被释放了。我们的指针指向了已经无效的内存,产生了野
指针
。这是很常见的bug之源实时效果反馈
1.对指针类型,说法正确的是? ____
A 虚拟内存的地址,本质上就是整数
B 对 cpu 的一种特殊的指令
C 程序的特殊区域
D c++语言独有的秘密武器
2. **悬挂指针是怎么产生的? **
A 一个指针长时间没用,被回收了
B 一个指针指向了无效的内存
C 一个指针,其值为0
D 一个指针,长夜漫漫,黯然神伤
答案
1=>A 2=>B
引用类型
引用在语义上看,是:
别名机制
在实现上,被转换为对指针的操作。变量可以看作是:内存中小盒子,变量名可以看作是盒子的标签 引用则可以想象为盒子上的另一个标签 就是说,一个盒子有多少个名字
有了指针,为什么还要引入别名?
- 为了语义上更清晰,更接近人的思考习惯。 【改写】 swap 函数
- 引用是不能运算的(比如++,--),只能去引用原来的值 相当于给“指针”这匹野马,套上约束,使用上更安全。
- 不存在空引用,但空指针很普遍,是bug之源
- 创建时初始化,不存在随机垃圾内存现象
- 引用绑定了一个目标,不能中途更换,指针随便
引用可以作为函数参数,也可以是返回值
当作形参时,可以看作实参的别名 当作返回值时,注意不要引用已经释放的变量
返回不存在的对象的引用,与返回不存在的对象的指针,都是典型的错误。
实现多返回
表面上看,c++的函数只能返回一个值,无法实现多值返回。
实际上,我们可以变通。
【示例】 通过引用实现返回多个值的效果
编写一个函数,传入a,b,返回a除以b的商,和余数
提示:可以用参数接收返回值。
实时效果反馈
1.引用和指针的关系:
A 引用比指针操作更快
B 引用比指针有更多的操作,功能更强大
C 引用总是比指针更省内存
D 引用比指针更安全,更人性化
2. **哪个==不是==使用引用的优点 **
A 必须初始化,不可能有垃圾值
B 无法更换绑定的变量,防止误操作
C 不能进行++ --的运算,更安全
D 看起来更酷,更唬人
答案
1=>D 2=>D
函数进阶
- 数学上的函数是:
输入数据 ===> 函数(加工机器) ===>吐出一个值
- 程序中的函数行为更复杂
主要表现在函数执行时,可能会影响调用者
也叫“副作用”
被调方(callee)如何影响主调方(caller)
- 返回值
这是最正经的渠道,调用方自己来决定如何使用返回值
可以:
也可以:丢弃
- 指针参数
参数类型为指针的话,就复杂了,callee 直接操刀。。。
- 形参是:指针的指针
比如:
int** p
这个就更恐怖了,实质与前述相同
- 形参是:引用
这个看着唬人,实际上最后,还是转换为指针的方案
指针形参
这种方式,好比调用方准备了一个盒子,把盒子地址传过去,被调方往里塞东西
讨饭的例子。。。
其本质是:两个指针指向同一个东西
【示例】数组排序
冒泡排序法
每一趟排序,把最大的归位
核心代码:
指针的指针形参
这是为了修改调用方指针本身(不是指针指向的值),使得它指向所需的内容
【示例】
准备一个整数数组,准备2个指针 p 和 q
调用函数 f 后,使得p指向==第一个==负数,q指向==最后一个==负数
来看调用方
有的人晕车,有的人晕船,。。。
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
数组与指针进阶
子曰:了解事物本质的方法是:观察事物的行为
数组名字的本质
- 准备两个数组 a, b,一个指针 p
- 观察下面的实验:
- 观察如下实验:
结果相同,这三者==等同==不?
- 观察如下实验:
- 思考与结论:
数组不会作为整体来==比较==,或者==赋值==,输出的时候,与指针无二
数组名不能被赋值,它只是编译器记录的信息,不是变量。
数组名取大小,包含了数组长度信息;赋值给指针,则==丢掉==了这个信息
数组名取地址,得到一个指向整个数组类型的==指针==
重点:
- 指针的==值==,决定了它指向哪块数据
- 指针的==类型==,决定了它的运算行为(比如 +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++ 中本没有数组,它只是指针的幻影
拓展:数组引用(用作参数的时候,有区别)
实时效果反馈
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
有关这节课的一切问题,欢迎在会客群中交流~
- 作者:DrimTech
- 链接:https://drim.cc/online-class-8
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章