Vico Bill< 刘 利 波 > 的个人网站

记录关于学习、工作中的技术点滴

C,C++,Rust,Ruby爱好者;热衷于游戏开发、任务自动化与跨平台;沉迷于游戏引擎与图形表现;深信'简单、多元'哲学的力量。


访问主页

编程语言-如何选择编程语言

日常使用编程语言:Ruby(高抽象、语法糖), Rust(性能,宏) 工作=游戏开发中使用编程语言:Lua(热更新),C#/GDscript/js(引擎自身绑定语言), C/C++(引擎)

编程语言是编程过程中的首要之门,是编写软件过程中首要的选择项。如今编程语言遍地开花,选择项有多种,每种编程语言都有大量拥护者和狂热者。每种语言设计哲学都有区别,应对的领域也不相同,语言设计的性能、 开发效率也不相同,如何挑选适合自身的编程语言是一项技术活。

目标明确,选择才能清晰。

日常使用主要图开发效率,最好一句代码搞定大量内容,这需要极高的抽象,有简化代码编写工作的语法糖。

对于开发效率而言,富含其他附加的功能有:所见即所得/实时预览/热加载;尽早识别错误;大量现成的库能快速完成开发等。

其次是领域。区块链要求安全;云计算、机器学习、爬虫、大数据分析等追求速度;实时性命攸关系统,追求的是容错性。

然而,最主要的还是语言本身是可靠的!也就是说,语言本身不应出现 BUG。

针对游戏开发而言,依照现有库的选择标准,3D 引擎基本上都是 C++写成,选择显而易见。引擎有外扩脚本语言,用于编写逻辑和完成快速开发,每种引擎都有其官方的脚本绑定,官方的脚本语言的选择有官方自己的抉择标准,往往别无二选。对于需要热更新,引擎如有内置脚本引擎,则可实现热更新。

针对游戏日常开发而言,主要是写工具,简化外围工作,因此高开发效率的脚本语言成为不二之选。如果工具的性能是选择标准,那么选择只能限于编译式语言。

综上,选择编程语言的标准如下:

  • 高效开发
    • 丰富库,支持 FFI(使用现有库)
    • 辅助工具:IDE(快速定位错误)
    • 调试方式,调试工具(快速定位运行时错误)
    • 快速解析,实时预览(快速运行预览结果)
  • 性能,速度
    • 并发
  • 安全,容错性

各种编程语言,大同小异。选择优秀的编程语言标准,主要集中在:开发效率高,高性能,易维护,易扩展。

  • 开发效率高:

    • 语法简洁、清晰程度(快速编码,提高开发效率)

    可以减少代码量(开发效率),同时,使代码变得更为清晰易懂,更能理解与接受(可读性)。

    • 完善的字面量及类型推导
    • 高阶函数
    • 模板
    • 丰富的内置库。可以提高开发效率。
    • C语言FFI。可以利用现有库,或直接与操作系统操作。
  • 易维护:

    • 能否在开发中快速发现潜在问题(IDE支持)

      编码只是整个产品过程中的一部分,另外一部分是确保没有错误。快速发现问题也很重要。

    • 静态类型,类型推导

    • 测试驱动,自动生成

  • 高性能

    程序最终是要运行在计算机中,计算机资源是有限的。性能在很大程度上决定语言的应用范围。

    • 并行运算。并发是在性能上锦上添花的手段,也是未来发展趋势。简单明了的并行运算支持,可以简单、快速开发并行运算程序。
    • 惰性求值。也是作为性能的提升的手段。
  • 易扩展

    • 多态。可以快速进行扩展,并进行维护。
    • /包/模块/命名空间。可以使用其他模块,迅速扩展。

优秀特性

  • 丰富的内置类型:如bool,int,float,symbol,lambda;

  • 丰富的内置容器:如Array/Tuple,List,HashMap/map

  • 简洁、清晰的字面量表示。如0b10100、100_999、dead?、replace!,1..2,nil

  • Unicode编码。

  • 高阶函数,lambda表达式。函数和数据是同等重要的,都是编程语言中的核心组成部分。

    高阶函数可以以函数作为处理单元,简化代码量。

    函数作为第一等公民,可以简化很多代码的编写。如C++和JAVA将函数作为第二等公民,为了回调函数,需要平白无故地创建一个类型,实属事倍功半,徒增麻烦。函数和数据都是同等重要的,厚此薄彼必定产生不必要的问题。

  • 模块/包/命名空间/库管理

    先进的包管理,拿来即用,可以提高生产力,无需因配置库环境而消耗大部分时间。

  • 垃圾回收。(不适用于强实时应用)

    内存自动管理,可以简化程序员的心智负担,无需关心内存何时释放与否,而造成内存泄漏。

    • 自动作用域。

      特别对于文件等操作,在操作完后,很多时候会忘记关闭文件,造成问题。简单的作用域范围使用,可以自动关闭文件。与内存自动管理类似。

  • 惰性求值。

    一个比较重要的例子是:惰性求值瞬间可以打开非常大的文件而无需等待,但常规的求值表达式在打开文件时,需要占用非常大的内存空间,而需要等待。

  • 并发语句。

    并发是当今提高运算速度的可行方案。

    • async/await
    • Channel
    • yield,coroutine,thread,fiber
  • 模式匹配

    • 正则表达式

      正则表达式在文本处理中非常简练,可以简化很多代码编写。

  • 自动文档化注释

    • 属性标记

      align,depracate,等等。通过编译器辅助属性特征,帮助完成部分工作,而无需手动对这些属性做一些处理。

  • 具名参数。参数名称往往能包含很多内容,一个好的参数名,可以省略多余、繁杂的注释。

    • 默认参数值。
    • 不定参数处理。
    • 多返回值,及其解构。
  • 多态。

    多态在类型理论中,用于对多种不同类型实体,提供相同接口;或使用单个符号代表多个不同类型。可以以统一方式处理不同类型的数据。

    多态的实现方式主要有以下几种:

    • 特定同质法(Adhoc):应用多态函数到不同类型参数上。例如,加法可以应用在数值、数组、列表等
    • 参数多态:类型不以名称,而以抽象符号表示任意类型。(模版)
    • 子类型:通过共同超类,以不同名称表示不同类的实例。(继承)
    • 鸭子类型:类似于子类型,但以结构化类型决定。允许使用包含一些相同属性的类型,但不丢失类型信息。
    • 数据类型泛型(多型):它比多态更具一般性,对于多型函数,可提供不存在特殊组合的不同数据类型的固定几种特殊情况。
  • 跨平台

    编写一次代码,可在不同操作系统上顺利运行,而无需手动解决跨平台相关问题。

    • 操作系统抽象层
    • 与外部语言集成:C
  • 宏/ 同相性

    规则或模式:通过定义过程,指定输入序列如何映射到可替换的输出序列。

    代码即数据,数据即代码,可以非常便利地实现DSL。可以以较少的代码,自动生成复杂的代码块,或处理复杂的任务。简化代码量。

  • 尾调用优化

    当函数尾部调用另一函数时,只保存内层函数调用记录,从而优化掉不必要的一些中间变量存储状态,简化内存空间使用。通常在递归函数使用中,会大大增加内存开销,尾调用优化可以减少递归函数的内存空间开销。

  • 错误处理

    • 异常:运行时错误
    • 断言:开发时错误

静态语言

  • 类型推导。可以简化对于类型声明的代码量。
  • 泛型。简化函数逻辑所能应用的类型。
  • 宏。可以简化代码量。
  • 类型别名
  • 函数重载
  • 基本的类型组合手段:struct/class/record等

OOP

  • 接口,triat,鸭子类型

    类型只要响应对应的消息,皆可适用需求。

  • 类型扩展,开放类型

    类型可以附加新的操作,以适应于灵活的需求。

  • 操作符重载

    简化代码意义,节约命名。

  • 单继承,Mixin

  • 反射,自省

  • 异常处理

    可以简化错误处理流程。

不利的特性

  • 副作用

    副作用表示在函数处理过程中,改变了传入的值或其他外部的某个状态。

  • 状态

    状态意味着随时可能被更改,在并行环境下,状态的改变通常难以跟踪而引发问题。

最佳实践

  • 契约式编程

    遵循契约,则可正确执行,不遵循契约,则拒绝执行,直接报告错误。

  • 单元测试,测试驱动的开发过程

    每个大型的功能,需要确保有特定的测试用例。这样在迭代开发时,如果不满足测试用例,则可快速定位错误的地方。

  • RAII。

    资源在获得时,先初始化。

  • 无副作用编程。

    函数不改变传入的参数,也不改变外部的状态。即不依赖外部状态。无副作用的函数无论何时以何种方式调用,所得到的结果相同。

  • 可并行代码。

    对于耗时的操作,采用并行原语进行处理。

  • 100个函数操作一个数据结构比10个函数操作10个数据结构要优。

    函数是第一等公民,数据结构通常而言:列表、元组等即可满足常用。

  • 减少使用静态、全局变量

  • 边界检测

    对于边界检测,做好断言。

  • 一致性。

    相同名称的函数和类型,请保持一致的操作。

怎样写程序

使用编程语言写好程序是有技巧的。

编程风格

编程风格是编程的细节,比如变量名的选择方法,函数的写法等。

算法

算法是解决问题的方法。现实中各种算法都已广为人知,所以编程时的算法也是对这些技巧的具体应用。

数据结构

很多算法光靠自己想是很难想出来的。比如数组的排序,就有多种算法。如果对这些算法根本就不了解,那么像做出高速排序就很难。算法和特定的数据结构关系很大。所以:程序=算法+数据结构。

设计模式

设计模式是指设计软件时,根据以前的设计经验对设计方法进行分类。算法和数据结构从广义上说也是设计模式的一种分类。

开发方法

指开发程序时的设计方法,包括管理在内的整个开发工程。在大的软件项目中,随着开发人员的增加,整个软件工程的开发方法就很重要。

面向对象

面向对象编程是对结构化编程的延伸。面向对象的核心是:对功能扩展是开放的;保持接口稳定,在修改内部实现细节时,不会改变其他部分。是否数据和函数一体化,是否封装不充分等等,都不重要,因为面向对象只是抽象化的工具。

多态/动态绑定:最重要且必需的功能

多种形态,即不同种类的东西当做相同的东西来处理。如有3个箱子,一个木箱子,一个带锁的箱子,一个银行里层层加密的箱子。当发出“打开箱子”的命令时,每个箱子的打开方式都不一样,但它们“都是箱子,可以打开”。这就是多态的本质。“打开箱子”的命令,我们称之为消息,而打开不同箱子的具体操作,我们称之为方法。

多态的好处:

  • 各种数据可以统一处理。多态性可以让程序只关注要处理什么(what),而不是怎么去处理(how)。
  • 根据对象的不同自动选择最合适的方法,而程序内部不会发生冲突。不管打开什么箱子,它们都能自动处理,不用担心调用中会发生错误,这样就会减轻程序员的负担。
  • 如果有新数据需要对应处理,通过简单追加就可实现,而无需改动以前的程序,这就让程序具备了扩展性。

数据抽象/信息隐藏/封装:简化理解力的利器

人的记忆和理解能力是有限的,但计算机无论多少数据、运算多久都可以完成。在早期结构化程序思想中,人们把程序执行顺序限制为顺序、分支和循环这3种,把共通的处理归结为例程。结构化编程的限制抽象,是人类处理复杂软件的非常有效的方法。通过限制大大降低了程序的自由度,减少了各种组合,使得程序不至于太过复杂。但如果由于降低了额自由度而导致程序的实现能力低下,这是我们不愿看到的。结构化的顺序、分支和循环可以实现一切算法,虽然降低了程序的复杂性和灵活性,但程序实现能力并没有降低。

抽象化的目的是,我们只需要知道过程的名字,而不需要指导过程的内部细节,因此它也被称为“黑盒化”。我们只需要知道“黑盒子”的输入与输出,而过程的细节是隐藏的。

如果把黑盒的内部处理也考虑上,整个系统的复杂性并没有改变。但是如果不考虑黑盒内部的处理,系统复杂性就可以降低到人类的可控范围内。此外,黑盒内部的处理无论如何变化,如果输入和输出不发生变化,那么对外部就没有影响,所以这种扩展特性是我们非常希望获得的。

结构化编程采用限制和抽象化解决程序控制流复杂问题,结果证明它是非常成功的。所以现在几乎所有编程语言都支持结构化编程。

但是程序里不仅包括控制结构,还包括要处理的数据。结构化编程降低了程序流程的复杂性,但随着处理数据的增加,程序的复杂性也会上升。

数据抽象是数据和处理方法的结合。对数据内容的处理和操作,必须通过预定义好的方法来进行。数据和处理方法结合起来成为了黑盒子。如对栈进行操作,只有push和pop两种操作方法。通过简单的两种方法就能控制许多内部细节,但这些细节我们并不需要太过关注。利于变化是抽象数据的巨大优点。

有了数据抽象,程序处理的数据就不再是单纯的数值或文字这些概念性的东西,而变成人脑容易想象的具体事物,更易于理解和接受。

重复的程序是冗余的,暴露太多细节而难以修改,从而降低了程序的可靠性,且人们解读程序、理解程序意图的成本也会增加。对计算机而言,无论重复与不重复都没关系,但对人而言,却非常重要。重复冗长的程序会降低人的生产力。

对于同样对象大量存在时,为了避免重复,可采用两种方法:

  • 原型。用原始对象的副本来做作为新的相同的对象。
  • 模板。比如浇注东西时,往模板里注入材料就能出来相同的东西。这种模板在OOP语言中称为类(class)。操作方法和属性可以共享。类与对象是有区别的,如同蛋糕模具和蛋糕是有区别的。因此,类是模具,对象是实例。

继承:避免重复

随着软件规模的扩大,用到的类的数量也会增加,其中会有很多性质相似的类。重复这些相似性质会降低生产力,如果将这些相似的性质汇总在一起就好了。继承即是这种方式。

在动态语言中,允许调用没有继承关系的方法。但静态语言中,只能调用有继承关系的方法。

多重继承

单一继承的特点:

  • 继承关系单纯。但不能通过继承关系共享程序代码,导致需要复制程序。
  • 类的优先顺序明确了然

多重继承的特点:

  • 很容易扩展单一继承
  • 可以集成多个类的功能。但类关系变得复杂。
  • 结构复杂化,继承类的关系很复杂
  • 多重继承中的优先顺序模糊。
  • 功能冲突。如果不同基类中有相同方法时,就会产生冲突。

继承作为抽象化的手段,是需要实现多重继承功能的。在抽取类的共通功能时,如果一个类只允许抽出一个功能,那限制就太多了。既想利用多重继承的优点,又要回避它可能带来的问题,就需要找出解决方法:受限制的多重继承。

实现继承和规格继承(接口)

继承包含两种含义:1. 类都有哪些方法,即类都支持什么操作,即规格的继承。2. 类中都用了什么数据结构和算法,即实现的继承,指继承功能的实现方法。

在静态语言中,两者的区别很重要:类是用来指定对象实现的,而接口只是指定对象的规格(都有哪些方法)。接口对实现没有任何限制,即接口可以由跟实现的继承没有任何关系的类来实现,也就是实现这一接口的类可以继承任何其他类。在一些静态语言中,如JAVA或C#明确区分两种继承,实现的继承只能是单继承,却可以多重继承接口。

但接口并非解决多重继承完美的办法。Java推荐解决共享实现问题的方案是,在单一继承的前提下,使用组合模式来调用别的类实现的共通功能。本来只是为了跨越继承层次来共享代码,现在却需要另外一个独立对象,而且每次方法调用都要委派给那个对象,这实在是不太合理,而且执行的效率不高。

动态语言本来就没有规格的继承这种概念。动态语言需要解决的就是实现的多重继承。

mix-in:受限的多重继承

mix-in是降低多重继承复杂性的技术。它按以下规则来限制多重继承:

  • 通常的继承用单一继承。
  • 第二个以及两个以上的父类,必须是mix-in的抽象类。

mix-in类是具有以下特征的抽象类:

  • 不能单独生成实例。
  • 不能继承普通类(没有父类)。

以此,类层次单一,且可实现功能共享,避免复制代码。(mix-in类通常以-able结尾)

元编程

元编程是对程序进行编程。

元编程的应用范围:

  • 动态生成方法。在静态语言中,通常使用宏来实现。
  • 反射:在程序执行时取出程序的信息(自省)或改变程序的信息。在运行时,按需修改功能。
  • DSL:主要利用宏或块的功能。

闭包

闭包是可以捕获外部环境的变量,用于在函数之间共享信息。

闭包通常用于高阶函数。如map,reduce,each等。高阶函数把函数作为数据来处理。

闭包也能保证程序的后处理。如文件打开后,如果出现异常,不会关闭。利用块则很容易处理。在闭包中,捕获的参数在结尾会自动清除。

闭包也能用于回调函数。

设计模式

设计模式是反复出现、使用的模式。模式与库不同,模式不能以库的形式来共享。设计模式是软件抽象化的新工具。

把共通处理抽象化,产生了例程(函数)。把数据构造也包括在一起进行抽象化,产生了抽象数据类型;把抽象数据类型的共通部分再抽象,产生了继承(派生)。软件设计总是在不断导入新工具,以实现更高程度的抽象化。

  • Singleton:单个实例保证
  • Proxy:为某个对象提供代理对象。通常用于惰性操作(需要的时候,才真正出现)。假设有个生成代价非常大的对象。如果不知道是否真正需要该对象的时候就事先生成,可能会带来很大浪费。但如果不生成,什么也做不了,此时代理对象就有用武之地了。Proxy通常使用delegate来实现。
  • Iterator:顺序访问集合对象中的各个元素。
  • Prototype:重复使用既存对象。核心思想:复制并衍化(进化)。在面向对象中,类并非必不可少的。类最初只是用于模子(雏形),然后从类生成实际对象。对于原型来说,复制既存对象,给复制的对象增加方法或实例变量等功能。在静态的面向对象语言中,类的继承其实本质也是Clone+扩展。生成新的实例时,也是进行着类似的操作:复制雏形。在很多原型为基础的语言中,实例是可以自定方法的。
  • Template Method:编写抽象算法。父类定义算法框架,步骤的具体实现留给子类实现。它不涉及各种数据结构细节,而是只在抽象的水平上编写算法。此模式通常与继承成对讨论。
  • Observer:避免高度依赖性。当某个对象的状态发生改变时,依存于该状态的全部对象都自动得到通知,而且为了让它们都得到更新,定义了对象间一对多的依存关系。这是控制类与类之间依存关系的一种模式。通常用于MVC框架。观察者模式有两个对象:观察者(接受变更通知)和被观察者(发出变更通知)。通常在使用中,由被观察者添加观察者,被观察者在改变时,通知观察者。观察者模式是事件系统的核心。
  • Composite: 组合,用树结构来构成对象。
  • Facade:隐藏子系统的详细内容,提供统一的接口。
  • Decorator:给对象动态增加新的功能。
  • Flyweight:以共享的方式提高大量小对象的实现效率。
  • Visitor:对集合的元素进行操作。
  • Strategy:封装算法,使之具有可变换性
  • State: 把对象的内部状态独立出来,封装状态变化。
  • Mediator:封装对象之间的相互作用。
  • Adapter:变幻对象的接口
  • Bridge:分离类之间的实现
  • Builder:分离复杂对象的生成过程
  • Decorator:给对象动态增加新的功能
  • Factory Method:基类只定义生成对象的接口,具体的生成过程由派生类实现
  • Memento:记录对象的内部状态
  • 职责链:用多个对象来处理请求。

MVC框架

一般采用Observer模式和轮询两种方法。

  • 模型:内容(信息)的对象。模型只是信息,不能包含如何来显示这些信息的信息。
  • 视图:将模型中包含的信息在窗口中进行表示的对象。视图指导要表示的模型的信息,而模型一般不知道要表示自己的视图的信息。
  • 控制:从用户端接受输入,对视图和模型进行操作的对象。

通常对应的文件夹为:model,view,cntrl,helper.

但对于多数GUI工具箱来说,包括组件、事件和回调。在典型的GUI应用中,控制和视图紧密协作,共同实现功能。在此密切相关的情况下,对类进行分割的做法并不明智。所以多数GUI都是视图和控制相结合,称为组件(Component/Widget)。在GUI中,模型部分没有提供相当的东西,作为替代,提供了访问模型的入口:回调。回调可以将完成一定操作的程序块作为对象调出来。当用户输入特定事件时,GUI组件通过回调调出功能程序块。回调一半时较小的操作功能,发挥“启动”模型对象的功能。回调的功能就像时联结“视图+控制”组成的组件和模型之间的“胶水”。即:

框架部分 GUI中对应的概念
View+Controller GUI Component(组件)
Model Callback(回调)

虽然GUI工具箱(Toolkits)是以此构建,但把整体应用构成MVC模式,是很容易维护的。

杂牌军/猴子补丁:

开放类,不改变原先的代码,替换方法(打补丁)称为猴子补丁。有以下功能:

  • 功能追加:追加标准库或已有代码中不存在的功能。
  • 功能变更:替换方法让已有的类发挥完全不同的功能。
  • 修正程序错误:重新定义了有程序错误或有副作用的方法,不用修改原来的代码即可解决问题。这也是猴子补丁本来的目的。
  • 钩子:可在方法调用的同时增加一些其他处理。这些处理是原本调用此方法时没有的功能。
  • 缓存:在计算量很大,而且一次计算后,结果可反复使用的情况下,在一次计算完成后,对方法进行替换可提高处理速度。

性能与并发:

不要做优化。先不要做优化。过早的优化是万恶之源。

对于性能问题,通常执行时间都耗费在程序中不足4%的部分。

通常在优化之前,使用Profiler对性能剖析,有依可循,不可盲目猜测。

常用优化手段:

  • 削减对象。复杂对象的生成会占用一定的时间。
  • 减少方法调用。多态方法的调用,会占用一定的时间。
  • 利用立即值。对于对象不使用引用,不实际分配内存的值,称为立即值。它不生成对象,因此不担心对象生成成本。
  • 采用合适的数据结构。
  • 以空间换时间。

并行是以硬件为辅助,解决性能问题。在计算机,多个程序运行时,操作系统以进程为单位同时运行。进程之间相互通信,通过管道、套接字或共享内存等机制。进程之间的内存空间是相互独立的,用于保护进程不被其他进程干扰。

线程可在一个进程中同时运行多个处理,线程可以看作轻量级进程。与进程不同,同一进程的线程可以共享内存空间。

线程的执行状态:

  • run:执行。线程将处理细分为一段一段,交替执行。
  • stop:停止的理由有IO等待、时间等待和join等待。IO等待等待外部输入的状态;时间等待指到达指定时间为止,停止的状态(sleep等的结果)。join等待是指等待别的线程终止。
  • to_kill:终止处理中。线程被强制终止,完全终止之前,正在处理后处理程序等状态。
  • killed:已终止。

并行处理的问题:

  • 数据完整性的丧失。数据共享,可能无意间同时访问了同一变量或对象。例如银行账户,可能A和B同时访问同一账户。在并行处理中,访问一个变量时,其他处理不可中途插入,这样不可分割的处理称为原子操作。在原子操作中,需要将数据保护起来,不可由外部操作。
  • 死锁。多个线程互相争夺资源,互不相让,造成线程停止。这通常发生在某一瞬间。

对于并行,同时访问资源就会成为程序的陷阱。规避资源的同时访问,有几种方法,其中最简单的就是独占使用中的资源。为此,引入了锁(Mutex互斥锁)的手段。由锁锁起来的代码块,是别的线程不能侵入的领域(Critical Section)。对上锁的资源,在使用之前,需要检测是否有锁。

但很多问题并非简单锁可以解决,如数据库中的问题:

  • 可以同时引用
  • 禁止同时更新
  • 禁止更新中引用
  • 禁止引用中更新

这就需要使用二级锁。

对于并行同步,锁只是一种机制。问题的根源在于多线程访问同一资源。所以解决这个问题就可以解决并行同步问题。

无共享

给每个资源准备一个管理用的线程,各线程间只能交换某些限定的信息。县城建信息交换的方法有很多种,有代表性的有:消息存储、信道(Channel)、队列(Queue)。

在无共享队列中,一个线程制作数据,另一线程通过队列获得数据,然后处理。即生产者和消费者模型。

队列也可用于解决资源的竞争。准备一个用于管理资源的线程,对资源的请求(Request)通过队列发送给该线程,这样,直接操作资源的只有管理线程。

### 锁模型和队列模型比较:

锁模型:如果竞争足够少,多数情况下能保持高性能。但,对于资源竞争,不能忘记加锁,难以保证完美无缺。

队列模型:竞争少时,性能不及锁模型,但更容易 贯彻。

在Erlang中,使用与队列模型本质上等价的消息存储。

Actor:

Actor是仅通过消息进行通信的实体。对于普通方法调用,需要等待结果。但对Actor发送消息,不必等待返回结果。Actor通过信息交换而非直接共享空间,性能不及线程高。Actor没有消息以外的信息传递手段,所以不用担心资源竞争。多个消息同时到达时的竞争,已经由内联到系统中的排除机制处理了。

Actor更安全、易懂。Actor根据消息进行处理,必要的话会向其他Actor传递消息,或者向源Actor返回消息。

Erlang采用Actor模型。

Actor模型 底层可线程或进程等。

并发与并行

并发是同时应对多件事情的能力;并行是同一时间动手做多件事情的能力。

并发的目的,不仅仅是为了让程序并行运行从而发挥多核的优势。若正确使用并发,程序还将获得以下优点:及时响应、高效、容错、简单。

并发是系统及时响应的关键。比如文件下载可以在后台进行时,用户不必一直盯着鼠标沙漏而烦心。软件在非同步运行的多台计算机上分布式地运行,其本质是并发。

并发代码的关键是:独立性和故障检测。独立性是一个故障不会影响到故障任务以外的其他任务。故障检测指当一个任务失败时,需要通知负责故障处理的其他任务来处理。

Unicode:

Unicode(16位)的两大问题:

  • 字节顺序。Intel普遍采用小端,网络上普遍采用大端。因此在Unicode中,采用BOM(ByteOrerMark,0xfeff.如果头两个字节读取是0xff,0xfe则是小端)标志判别文本字节序。
  • NUL文字问题。传统C语言以’\0’(NUL)判断结束,但Unicode中有很多NUL字符。传统的C处理函数:strcmp,strcpy则无法使用。

随着文字被发现的越来越多,16位已经到达上限。现在Unicode采用21位表示一个文字。

现在表示Unicode文字主要推崇:

  • UTF-8: 可变长,与ASCII兼容。不含NUL文字。兼容C语言文字处理。

文字在显示时,需要使用对应的字体。如果字体不支持,则会发生乱码或豆腐块。

负数与小数的表示:

负数:-1:会经过以下变化:2的补数方式:将正数的每一位反转,然后加1所得。

  1. 数字1 : 00000001
  2. 位反转:11111110
  3. 加1: 11111111

浮点数:是真实数的近似。浮点数和整数永远不会绝对相等。如1.0与1只是近似相等,二进制也不相同。1/3后得到的值,连加3次却不是1。在计算机中1/10,结果值连加10次,却也并非1。

浮点数比较特殊的值:

  • Inf 无限。当浮点数运算溢出时,使用此值。
  • 0,正0和负0是有区别的。
  • NaN:非数。如0/0,表示未定义的结果的错误。

软件漏洞

  • DOS攻击:拒绝服务(DenialOfService),妨碍软件正常运行的攻击手段。能够引发软件异常终止的程序错误。
  • 信息泄漏。
  • 权限夺取。
  • 权限升级。

主要的程序错误的攻击:

  • 缓冲区溢出
  • 整数溢出
  • 跨站点脚本攻击(XSS)
  • SQL注入
  • 跨站点伪造请求(CSRF)

对于程序中的错误,有些严重错误需要中断执行,有些错误只需要记录日志,恢复执行即可。对于软件使用者而言,没有必要因为非严重错误而中断正在运行的工作。

函数式编程

函数式编程语言的特点:

  • 函数本身也作为数据来处理
  • 以函数为参数的高阶函数
  • 函数相同即可保证结果相同的引用透明性
  • 为实现引用透明性,禁止产生副作用的处理。

学习语言的步骤

  • 作为兴趣来学习。了解此语言及其背后的思想。
  • 作为个人工具使用,日常生活中使用此语言作为工具。
  • 工作中作为辅助开发工具。

自动代码生成

应用范围主要有:

  • 数据库访问。从数据结构定义自动生成数据库访问例程(包括SQL)。手工编写大量的文件当然不是聪明人应该做的事。代码生成可以帮我们解决这个问题。
  • 用户接口。
  • 单元测试。
  • 客户界面
  • 文档化

代码生成的作用:

  • 改进质量
  • 确保一致性
  • 集中知识
  • 增加用于设计的时间
  • 独立于程序实现的设计判断。

对语言的正确理解

争论语言孰好孰坏没有任何意义。每种语言都有其诞生的环境和应用范围,在语言特性的选择中,各有取舍。世界上没有完美的事物,总是有一些这样或那样的问题或缺陷。选用适合自己的、最满足自己需求的语言,从一而终即可。当此语言没有好用的特性时,自己动手添加或给创造者建议,对语言进行进化。

语言选定之后,就学着去适应它,使用它,掌握它。

想知道软件是如何执行的,就去阅读源代码。软件有错误的时候,就自己来修改。如果觉得自己行动受到限制的话,就阅读源代码,利用编程技巧排除这一限制。这些都是程序员的本能。

推荐的语言

  • System Language 系统语言 :C,Rust
    • 手动管理内存;高性能;能调用C库(调用系统API);能编译成C库; 能编写汇编(能编写驱动);
  • Server/Service Language 服务器(游戏、Web)语言:Go,Elixir
    • 并发;库多;简洁;快速;错误处理;较高性能
  • Scripting language 脚本(游戏、工具、自动化)语言:Python
    • 简单;快速;库多;解释型
  • Object-oriented Language 面向对象语言: Ruby
    • 面向对象编程特征;
  • Functional Language 函数式语言: Lisp-Scheme-Clojure

  • 最佳语言拍档:
    • 返璞归真,皈依太极:C+LISP
    • 游戏开发: C++ + 脚本语言(Lua/Python/Ruby/Lisp)
    • Web与网络容器: Go
    • 大数据与机器学习: C++(TensorFlow) + Python(PyTorch)
    • 人工智能: C++ + Lisp
最近的文章

游戏开发-开发知识汇总

CMakeXMakeC++ Treat warning as error: WX -> WX-库开发: 避免全局状态,否则其生命周期的管理会相当麻烦 不要在公共头文件中定义通用类型,否则不同编译器的字节对齐会出现问题 不要在公共头文件中包含windows.h 小心对待自己的命名空间,不要导出你不想公开的符号 尝试建立稳定的 ABI 不要对结构体太疯狂 允许别人自定内存分配器。如果不能对每个context这样做,至少在每个库中这样做 不要强迫用户使用你所喜欢的构建工具...…

继续阅读
更早的文章

笔记集锦-Unity-快捷键

3D视口 解释(中文) | 快键 — | — 飞行模式前进 | W 飞行模式后退 | S 飞行模式左移 | A 飞行模式右移 | D 飞行模式上移 | E 飞行模式下移 | Q主菜单解释 | 快键— | —复制资源路径 | Ctrl+Alt+C创建TMP字体资源 | Ctrl+Shift+F12刷新资源 | Ctrl+R组件 | |添加 | Ctrl+Shift+A编辑 | |编辑/复制 | Ctrl+C编辑/剪切 | Ctrl+X编辑/反选所有 | Shift+D编辑/克隆 | Ctrl...…

继续阅读