陈天 · Rust 编程第一课
陈天
Tubi TV 研发副总裁
23195 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 65 讲
基础篇 (21讲)
陈天 · Rust 编程第一课
15
15
1.0x
00:00/00:00
登录|注册

02|串讲:编程开发中,那些你需要掌握的基本概念

编程范式
运行方式
代码
数据
编程开发中的基本概念

该思维导图由 AI 生成,仅供参考

你好,我是陈天。
上一讲我们了解了内存的基本运作方式,简单回顾一下:栈上存放的数据是静态的,固定大小,固定生命周期;堆上存放的数据是动态的,不固定大小,不固定生命周期。
今天我们来继续梳理,编程开发中经常接触到的其它基本概念。需要掌握的小概念点比较多,为了方便你学习,我把它们分为四大类来讲解:数据(值和类型、指针和引用)、代码(函数、方法、闭包、接口和虚表)、运行方式(并发并行、同步异步和 Promise / async / await ),以及编程范式(泛型编程)。
希望通过重温这些概念,你能够夯实软件开发领域的基础知识,这对你后续理解 Rust 里面的很多难点至关重要,比如所有权、动态分派、并发处理等。
好了,废话不多说,我们马上开始。

数据

数据是程序操作的对象,不进行数据处理的程序是没有意义的,我们先来重温和数据有关的概念,包括值和类型、指针和引用。

值和类型

严谨地说,类型是对值的区分,它包含了值在内存中的长度对齐以及值可以进行的操作等信息一个值是符合一个特定类型的数据的某个实体。比如 64u8,它是 u8 类型,对应一个字节大小、取值范围在 0~255 的某个整数实体,这个实体是 64。
值以类型规定的表达方式(representation)被存储成一组字节流进行访问。比如 64,存储在内存中的表现形式是 0x40,或者 0b 0100 0000。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文介绍了编程开发中需要掌握的基本概念,包括数据、代码、运行方式和编程范式。在数据方面,重点涉及值和类型、指针和引用的重要性;代码方面包括函数、方法、闭包、接口和虚表;运行方式包括并发、并行、同步、异步以及Promise/async/await等概念;编程范式方面涉及泛型编程。文章通过串讲这些概念,旨在帮助读者加深对软件开发基础知识的理解,为后续技术学习打下坚实基础。此外,还提出了思考题,引导读者思考并巩固对基本概念的理解。文章内容涵盖了并发与并行、同步与异步、泛型编程等技术特点,对于想要深入理解软件开发基础知识的读者具有重要参考价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《陈天 · Rust 编程第一课》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(60)

  • 最新
  • 精选
  • Geek_5b5ca4
    置顶
    Python 是强类型动态语言

    作者回复: 这里我写的不太严谨。强类型和弱类型的定义一直不太明确,wikipedia 上也没有一个标准的说法。https://en.wikipedia.org/wiki/Strong_and_weak_typing。我一般是看类型在调用时是否会发生隐式转换,所以说 python 是弱类型。不过 wikipedia 在介绍 python 时确实说它是 strongly typed:https://en.wikipedia.org/wiki/Python_(programming_language)。 但如果按照类型是否会隐式转换,Rust 是强类型,Python 和 C 是弱类型: ```rust fn main() { let a = 42u8; let b = 42.0f64; // 不会做隐式转换 println!("a+b = {}", a+b); } ``` Python 会做类型的隐式转换: ```python def add_numbers(a, b): return a + b if __name__ == '__main__': a = 42 b = 42.0 print(add_numbers(a,b)) ``` C 也会: ```C #include <stdio.h> int add_numbers(int a, int b) { int result = a + b; return result; } int main() { char c = "42"; int n = 42; // 为什么说 C 是 weakly typed int result = add_numbers(c, n); printf("%d\n", result); return 0; } ```

    2021-08-24
    7
    32
  • 无名氏
    置顶
    虚表没有理解,虚表会存储在哪里

    作者回复: 虚表相当于在运行时生成的一个涵盖了一系列函数指针的数据结构。有时候对于不同类型但是满足相同接口的数据,我们希望可以抹去他们的原始类型,让它们有相同的接口类型,以便于统一处理,这样更加灵活,但此时需要为每个数据构造他们各自对接口实现的虚表,这样可以依旧调用到属于该类型的实现。 虚表一般存储在堆上。Rust 下也有虚表的栈实现:https://github.com/archshift/dynstack

    2021-08-23
    5
    24
  • Christian
    1. 函数放在代码段中,通常是只读的,往只读段写入数据会触发保护异常。 2. 使用 enum: ```rust enum Shape { Rectangle(Rectangle), Circle(Circle), Triangle(Triangle), } ``` 3. 定义一个 trait 并为三种结构都实现该 trait: ```rust trait SomeInterface { fn area(&self) -> f64; fn circumference(&self) -> f64; } impl Rectangle for SomeInterface { fn area(&self) -> f64 { ... } fn circumference(&self) -> f64 { ... } } impl Circle for SomeInterface { ... } impl Triangle for SomeInterface { ... } ```

    作者回复: 正确!非常棒!

    2021-08-23
    6
    85
  • 🔥神山 | 雷神山
    1. 有一个指向某个函数的指针,如果将其解引用成一个列表,然后往列表中插入一个元素,请问会发生什么? 对于强类型语言(如:rust),无法解引用成一个列表,rust会提示类型不匹配错误。 对于弱类型语言(如:python, javascript),解引用成一个列表后,可以正常插入元素。 ```javascript let fn = function (title) { console.log(title); } fn("test"); fn = [1, 2, 3, 4, 5, 6, 7, 8]; fn.push(9); console.log(fn); ``` ```rust let func = |x| x; func(1); func = vec![1, 2, 3]; println!(func); ``` 2. 要构造一个数据结构 Shape,可以是 Rectangle、 Circle 或是 Triangle,这三种结构见如下代码。请问 Shape 类型该用什么数据结构实现?怎么实现? ```rust use std::f64::consts; trait Calculator { fn perimeter(&self) -> f64; fn area(&self) -> f64; } struct Rectangle { a: f64, b: f64, } struct Circle { r: f64, } struct Triangle { a: f64, b: f64, c: f64, } #[derive(Debug)] enum EShape { Rectangle(f64, f64), Circle(f64), Triangle(f64, f64, f64), } #[derive(Debug)] struct Shape { shape: EShape, } impl Shape { fn new(shape: EShape) -> Shape { Shape { shape } } } impl Calculator for Shape { fn perimeter(&self) -> f64 { match self.shape { EShape::Rectangle(a, b) => (a + b) * 2.0, EShape::Circle(r) => 2.0 * consts::PI * r, EShape::Triangle(a, b, c) => a + b + c, } } fn area(&self) -> f64 { match self.shape { EShape::Rectangle(a, b) => a * b, EShape::Circle(r) => consts::PI * r * r, EShape::Triangle(a, b, c) => { let p = (a + b + c) / 2.0; (p * (p - a) * (p - b) * (p - c)).sqrt() } } } } fn main() { let shape = Shape::new(EShape::Triangle(3.0, 4.0, 5.0)); println!("shape:{:#?}", shape); println!("perimeter: {}", shape.perimeter()); println!("area: {}", shape.area()); } ``` 3. 对于上面的三种结构,如果我们要定义一个接口,可以计算周长和面积,怎么计算? 只需要将上述代码中的Rectangle ,Circle ,Triangle 三个结构体分别实现Calculator trait即可。

    作者回复: 非常棒!

    2021-08-24
    8
    28
  • 请问一个关于虚表的问题。虚表是每个类有一份,还是每个对象有一份,还是每个胖指针有一份?

    作者回复: 好问题。这个在讲 trait 的那一课有讲到。虚表是每个 impl TraitA for TypeB {} 时就会编译出一份。比如 String 的 Debug 实现, String 的 Display 实现各有一份虚表,它们在编译时就生成并放在了二进制文件中(大多是 RODATA 段中)。 所以虚表是每个 (Trait, Type) 一份。并且在编译时就生成好了。 如果你感兴趣,可以在 playground 里运行这段代码(这是后面讲 trait 时使用的代码):https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=89311eb50772982723a39b23874b20d6。限于篇幅,代码就不贴了。

    2021-09-04
    20
  • 徐洲更
    这一篇是对编程语言的高度抽象呀,这一篇的知识完全可以应用到任何一门编程语言上。

    作者回复: 嗯,前两篇都是每个程序员最好掌握的基础知识。

    2021-08-24
    16
  • Jason
    作为一个只写过脚本语言的前端,看虚表那部分基本上等于是在看天书,太菜了呜呜呜

    作者回复: 虚表你可以理解成一张指向若干个函数地址的表。这样在运行时,可以通过这张表找出要执行的函数,进而执行。比如 w.write(),如果 w 是一个类型被抹去的引用,指向了一块地址,此时 w 如何能够执行到 write() 方法?只能通过运行时构造的虚表来解决。

    2021-08-30
    3
    9
  • 太子长琴
    第一个题意不是特别理解 后面的倾向于用 match,每个 struct 重复 impl 看着就觉得烦 pub enum ShapeEnum { Rectangle(f64, f64), Circle(f64), Triangle(f64, f64, f64), } #[derive(Debug)] pub struct Shape { pub shape: ShapeEnum, } impl Shape { pub fn new(shape: ShapeEnum) -> Shape { Shape { shape: shape } } } pub trait Calculate { fn perimeter(&self) -> f64; fn area(&self) -> f64; } impl Calculate for Shape { fn perimeter(&self) -> f64 { match self.shape { ShapeEnum::Rectangle(a, b) => 2.0 * (a + b), ShapeEnum::Circle(r) => 2.0 * 3.14 * r, ShapeEnum::Triangle(a, b, c) => a + b + c, } } fn area(&self) -> f64 { match self.shape { ShapeEnum::Rectangle(a, b) => a * b, ShapeEnum::Circle(r) => 3.14 * r * r, ShapeEnum::Triangle(a, b, c) => { let p = (a + b + c) / 2.0; (p * (p - a) * (p - b) * (p - c)).sqrt() } } } }

    作者回复: 1 的题意可以看这个代码: ```C #include "stdio.h" void hello() { printf("Hello world!\n"); } int main() { char buf[1024]; void (* p)() = &hello; (*p)(); int *p1 = (int *) p; p1[1] = 0xdeadbeef; } ``` 在 C 语言中,这种操作是允许的,但会造成内存访问越界,导致崩溃;在 Rust 中,编译器会拒绝这样的操作。 2/3 正确!

    2021-08-24
    2
    8
  • Scott
    我想问问B站油管视频里那个vscode直接在出错的那一行显示错误信息怎么弄的,我装了ra,但是出错信息还是显示在底部。

    作者回复: 对是 error lens。感谢 coer 的解答。https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens

    2021-08-23
    2
    7
  • 核桃
    首先建议这里补充一下状态机的概念,这个也是很核心的但是平时不太重视。另外虚表和泛型那里的例子,rust编程之道的书上提到实现加法这个功能,那里数字有整型和浮点型,如果正常实现要一一列举出来,使用泛型就可以很抽象地弄出来了。而这个背后,编译器其实会有一个叫单态化的操作,也就是把具体类型实现编译出来,那样会导致编译文件变大一点,但不是不可接受。 另外虚表这里涉及到rust的动态分发概念,就是当没有使用where这些约束的时候,那么编译时无法知道具体类型就需要用到虚表这些了,而一般来说,静态抽象的成本还是低一点的,动态分发有性能成本存在,而且有时候代码不好理解?

    作者回复: 嗯。限于篇幅,确实有好多东西无法都讲到。状态机在讲 async/await 的时候会具体介绍。 单态化在具体讲泛型时会介绍。 虚表会在讲 trait 时具体介绍。 动态分发的性能比静态分发差很多,可能是数量级的差别。静态分发单态化后直接跳转到方法执行;动态分发会先找到虚表,再找到虚表中对应的方法,再跳转到对应的方法执行。这个过程会大概率有两次 cache miss。 代码倒并不太会不好理解吧,尤其是现在不加 dyn 会有编译器 warning,dyn T 还是一目了然知道是哪个 trait 的 trait object。

    2021-09-08
    6
收起评论
显示
设置
留言
60
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部