02|串讲:编程开发中,那些你需要掌握的基本概念
该思维导图由 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-24732 - 无名氏置顶虚表没有理解,虚表会存储在哪里
作者回复: 虚表相当于在运行时生成的一个涵盖了一系列函数指针的数据结构。有时候对于不同类型但是满足相同接口的数据,我们希望可以抹去他们的原始类型,让它们有相同的接口类型,以便于统一处理,这样更加灵活,但此时需要为每个数据构造他们各自对接口实现的虚表,这样可以依旧调用到属于该类型的实现。 虚表一般存储在堆上。Rust 下也有虚表的栈实现:https://github.com/archshift/dynstack
2021-08-23524 - Christian1. 函数放在代码段中,通常是只读的,往只读段写入数据会触发保护异常。 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-23685 - 🔥神山 | 雷神山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-24828 - 虾请问一个关于虚表的问题。虚表是每个类有一份,还是每个对象有一份,还是每个胖指针有一份?
作者回复: 好问题。这个在讲 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-0420 - 徐洲更这一篇是对编程语言的高度抽象呀,这一篇的知识完全可以应用到任何一门编程语言上。
作者回复: 嗯,前两篇都是每个程序员最好掌握的基础知识。
2021-08-2416 - Jason作为一个只写过脚本语言的前端,看虚表那部分基本上等于是在看天书,太菜了呜呜呜
作者回复: 虚表你可以理解成一张指向若干个函数地址的表。这样在运行时,可以通过这张表找出要执行的函数,进而执行。比如 w.write(),如果 w 是一个类型被抹去的引用,指向了一块地址,此时 w 如何能够执行到 write() 方法?只能通过运行时构造的虚表来解决。
2021-08-3039 - 太子长琴第一个题意不是特别理解 后面的倾向于用 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-2428 - Scott我想问问B站油管视频里那个vscode直接在出错的那一行显示错误信息怎么弄的,我装了ra,但是出错信息还是显示在底部。
作者回复: 对是 error lens。感谢 coer 的解答。https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens
2021-08-2327 - 核桃首先建议这里补充一下状态机的概念,这个也是很核心的但是平时不太重视。另外虚表和泛型那里的例子,rust编程之道的书上提到实现加法这个功能,那里数字有整型和浮点型,如果正常实现要一一列举出来,使用泛型就可以很抽象地弄出来了。而这个背后,编译器其实会有一个叫单态化的操作,也就是把具体类型实现编译出来,那样会导致编译文件变大一点,但不是不可接受。 另外虚表这里涉及到rust的动态分发概念,就是当没有使用where这些约束的时候,那么编译时无法知道具体类型就需要用到虚表这些了,而一般来说,静态抽象的成本还是低一点的,动态分发有性能成本存在,而且有时候代码不好理解?
作者回复: 嗯。限于篇幅,确实有好多东西无法都讲到。状态机在讲 async/await 的时候会具体介绍。 单态化在具体讲泛型时会介绍。 虚表会在讲 trait 时具体介绍。 动态分发的性能比静态分发差很多,可能是数量级的差别。静态分发单态化后直接跳转到方法执行;动态分发会先找到虚表,再找到虚表中对应的方法,再跳转到对应的方法执行。这个过程会大概率有两次 cache miss。 代码倒并不太会不好理解吧,尤其是现在不加 dyn 会有编译器 warning,dyn T 还是一目了然知道是哪个 trait 的 trait object。
2021-09-086