Linux C 编程一站式学习记录(一)- 编程思想

宋劲杉 老师的 Linux C 编程一站式学习 是国人当中写的非常好的计算机书籍,豆瓣评分 9.3 ,非科班出生的程序员拿来入门非常好,后面部分关于程序原理的内容也适合工作多年的人进行查缺补漏。书籍囊括了程序设计基本思想和开发调试方法,以 Linux 平台为载体介绍 C 语言基础及程序工作原理,Linux 系统编程,对计算机组成、C 语言、操作系统、编译原理等课程知识达到融汇贯通。很难得图书还是开源的,链接 http://akaedu.github.io/book/index.html, 第二版 一站式学习 C 编程 删掉了 Linux 系统编程相关的内容,其它变化不大,推荐第一版。这篇总结图书中提到的编程思想及相关思维方法。

编程学习

基本类型和复合类型

  • Primitive Type:最基本的的、不可再分的数据类型
  • Compound Type:根据语法规则组合而成的类型

学习编程要注意的三个方面(来自SICP)

  • 提供的了哪些 Primitive,如基本类型、基本运算符、表达式、语句。
  • 提供了哪些组合规则,如基本类型怎么组合成复合类型,简单表达式和语句怎么组成负杂的表达式和语句
  • 提供了哪些抽象机制,包括数据抽象和过程抽象。

思维方法

以概念为中心的阅读习惯

每读一节就总结一套概念之间的关系图。比如下面的概念:

程序指令组成,计算机只能执行低级语言的指令,高级语言执行前要先编译解释,好处是平台无关性,平台是一种体系结构,是一种指令集,就是一种机器语言

编程语言是一种形式语言,对应人类用的自然语言,形式语言有严格的语法 (Syntax) 规则,由符号 (Token) 结构(Structure) 的规则组成。关于 Token 的规则称为词法 (Lexical) 规则,关于结构的规则称为语法 (Grammer)规则。分析句子结构的过程称为解析 (Parse)。解析完句子,理解句子的上下文,暗示的内容,是**语义 (Semantic) **的范畴。形式语言和自然语言的区别:歧义性、冗余性、与字面意思的一致性。

最少例外原则 (Rules of Least Surprise)

一个容易被用户接受的设计应该遵循最少例外原则。C 语言的设计非常优美,C++ 的设计非常负责,充满例外,饱受争议。

必要条件 (Necessary Condition)和充分条件 (Sufficient Condition)

不要把必要条件当充分条件。

编程思想

组合规则 (Composition)

组合规则是理解语法规则的基础,根据语法规则任意组合,可以用简单的常量、变量、表达式、语句和声明搭建出任意复杂的程序。

递归 (Recursive) 和 迭代 (Iteration)

如果定义一个概念要用到这个概念本身,它的定义是递归的。需要定义一个最关键的基础条件 (Base Case),比如阶乘 (Factorial) 里 0 的阶乘等于 1。如果你相信你正在写的递归函数是正确的,并调用它,然后在此基础上写完这个递归函数,那么它就会是正确的,从而值得你相信它正确。

每次都有一点区别的重复工作称为迭代

递归和循环是等价的,用循环能做的事用递归都能做,比如 LISP 语言只有递归而没有循环,编译器的实现用了大量递归。

递归例子:两个正整数的 a 和 b 的最大公约数 (GCD, Greatest Common Divisor) 使用 Euclid 算法,如果 a 除以 b 能整除, 则最大公约数是 b,否则,最大公约数等于 b 和 a%b 的最大公约数。

无限循环 (Infinite Loop) 的例子,n 忽大忽小,著名的 3+1 问题:

while (n != 1) {
  if (n % 2 == 0) {
    n = n / 2;
  } else {
    n = n * 3 + 1;
  }
}

函数式编程 (Functional Programming) 和命令式编程 (Imperative Programming)

函数式思路:整个递归调用过程中,虽然分配和释放了很多变量,但所有变量都只在初始化时赋值。 命令式思路:通过循环对变量多次赋值来达到同样的目的。C 语言主要用 Imperative 的方式。

抽象 (Abstraction)

组合使得系统可以任意复杂,而抽象使得系统的复杂性是可以控制的,任何改动都局限在某一层,而不会波及整个系统。计算机科学家 Butler Lampson 说过:"All problems in computer science can be solved by another level of indirection."

避免硬编码 (Hard coding)

数据驱动的编程 (Data-driven Programming)

数据代替代码,以打印星期几的代码为例:通过下表访问字符串组成的数组可以代替一堆 case 分支判断,把每个 case 里重复的代码 (printf 调用)提取出来。写代码最重要的是选择正确的数据结构来组织信息,设计控制流程和算法尚在其次,只要数据结构选择正确,其它代码自然而然变得容易理解和维护。Show me your flowcharts and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won't usually need your flowcharts, they'll be obvious.

分而治之 (Divide-and-Conquer)

举例,归并排序,时间复杂度O(nlgn):

  • Divide: 把长度为 n 的输入序列分成两个长度为 n/2 的子序列
  • Conquer: 对两个子序列分别采用归并排序
  • Combine: 将两个排序好的子序列合并成一个最终的排序序列

折半查找 (Binary Search)

针对有序序列,每次把搜索范围缩小一半。

回溯 (Backtrack)

探索问题的解时走进了死胡同,则需要退回来从另一条路继续探索。举例:基于堆栈的深度优先搜索 (DFS, Depth First Search)。

调试方法

错误

编译时错误、运行时错误 (Run-time) 、逻辑和语义错误。

增量式 (Incremental) 开发

首先分析和分解问题,把大问题分解成小问题,再对小问题分别求解。这个过程在代码中体现函数的分层设计 (Stratify) ,底层函数解决小问题,上层函数通过调用底层函数解决更大的问题。

插入排序算法采用增量式的策略解决问题,每次添一个元素到已排序的子序列中,逐渐将整个数组排序完毕。时间复杂度O(n^2)。

打印语句

函数里的 printf 语句类似脚手架 (Scaffold),验证无误后就可以撤掉。把 Scaffolding 的代码注释掉。

Design by Contract (DbC)

如果每个函数的文档都非常清楚地记录 Precondition、Maintenance、Postcondition 是什么,每个函数都可以独立编写和测试。

断言 (Assertion)

发布时在包含 assert.h 之前定义一个 NDEBUG 宏,就可以禁用 assert.h 中的 assert 宏定义,或者编译时加上选项 -DNDEBUG

  • switch 的语句块和循环结构的语句块没有本质区别:Duff’s Device

  • 编码风格
    Thus, programs must be written for people to read, and only incidentally for machines to execute.

猜你喜欢

转载自blog.csdn.net/hgleagle/article/details/88380734