本文持续更新中……

注意:伪代码没有任何固定的格式和规范,本文提供的格式仅为一个参考格式!

代码

  在研究伪代码之前我们要先明白“代码”是什么东西,代码是由一系列带有特定含义的符号组成,它可以命令计算机去完成人想要完成的任务。计算机完成任务时是严格按照代码进行执行的,不会违背传递给它的代码。

  代码下还有很多细分:

源代码

  源代码通常简称为“源码”,顾名思义,“源代码”就是构成一个程序的最初始的代码,通常会使用文本形式来保存源代码,方便人对其进行阅读和修改。

  源代码通常具有严格的格式要求,必须按照预定的规则进行书写,否则将会出现错误,不同编程语言对源代码的要求各不相同。

  计算机无法直接理解源代码的含义。

二进制代码

  二进制代码通常由“源代码”编译而来(也有直接人手动写二进制代码的情况),“编译”指的是使用计算机程序将“源代码”按照预定的规则转换为计算机能够理解的代码的过程。

  计算机程序是由二进制代码构成的,计算机可以直接理解二进制代码的含义,但是二进制代码对于人类来说可读性非常低。

  注:此处说的计算机可以理解的二进制代码指的是机器码,部分语言会将源代码编译成字节码等非机器码的二进制代码,此处不讨论这种情况,读者可以暂时忽略字节码这个概念。

伪代码

  “伪代码”就是“假的代码”,它不用来编译成二进制代码,也不要求计算机能够理解它。伪代码没有任何格式要求,只需要能让人看懂即可。

  不过虽然伪代码没有固定的规则,但是为了能够尽可能让更多的人能够足够快地理解伪代码的含义,所以还是有一些墨守成规的内容的。

基本概念

  接下来我们来介绍编写伪代码时需要用到的一些概念。

变量

  变量用来存储代码运行过程中需要的一些值,这些值可以是提前写在纸面上的固定值,也可以是通过算式计算得到的非固定值。

  一个变量由变量名和变量值共同构成,变量名就是变量的名称,变量值就是这个变量代表的内容。

  比如:

1
2
3
a = 5
b = 3
c = a + b // c is 8

  上面这段伪代码中,ab就是写在纸面上的固定值所构成的变量,而c就是由算式计算得到的非固定值所构成的变量。

  同时变量是可以多次赋值的:

1
2
3
a = 5       // a is 5
a = a + 1 // a is 6
a = a + 1 // a is 7

数据类型

  在伪代码中我们经常需要使用变量来存储数据,一个变量有且只有一个类型信息,我们常用的类型有如下几种:

  • 数值类型(number):代表一个数字,可以是整数、小数、虚数……
    1
    2
    3
    a = 5       // 整数
    b = 5.6 // 小数
    c = 1i + 5 // 虚数
    上面三个变量都是数值类型。
  • 布尔类型(boolean/bool):布尔类型用来标识真(true)和假(false),其只有这两个可能的值,一个比较运算符返回的类型就是布尔类型。
    1
    2
    3
    a = true
    b = false
    c = 5 > 6
    以上三个变量都是布尔类型。
  • 数组类型(Array<T>):由若干个相同类型的值并列构成的数据结构,通常使用[]{}包裹,<T>中的T表示数组中包含的元素的类型。
    1
    2
    3
    4
    a = [0, 1, 2, 3]                  // 内容为数值的数组 - Array<number>
    b = ['this', 'is', 'a', 'array'] // 内容为字符串的数组 - Array<string>
    c = [true, true, false] // 内容为布尔类型的数组 - Array<boolean> / Array<bool>
    d = [[0, 1, 2], [3], [], [4, 5]] // 内容为数组类型的数组 - Array<Array<number>>
    以上四个变量都是数组类型。
    访问数组时使用数组名[下标]的格式,下标从 0 开始递增;可以使用数组名.length访问数组的长度。
  • 字符串类型(string):代表一个长度任意的字符串,字符串是由零个、一个或多个字符构成的数组(字符串使用成对的单引号或双引号括住,用来区分字符串的内容和变量名等信息)。
    1
    2
    3
    4
    a = 'this is a string'
    b = '5'
    c = "true"
    d = ''
    以上四个变量都是字符串类型。
  • 复合类型(object):该类型使用多个类型组合而成,复合类型可以相互嵌套。
    1
    2
    3
    4
    5
    a = {
    ia: 5
    ib: { a: ['hello'] }
    ic: true
    }
    上面的变量是复合类型。

  在编写伪代码时可以在声明变量时写明数据类型也可以不写。

作用域

  作用域是一个区域的概念,用于控制变量的可见范围,一个作用域中可以有零个、一个或多个子作用域,子作用域不能包含其父作用域。

  假设我们有 4 个作用域,其包含关系为A > B > C > D,则我们称:AB的直接父域,是C``D的间接父域;DC的直接子域,是A``B的间接子域。

  为了方便,下文中我们提及“父域”就表示“直接父域和间接父域”,“子域”表示“直接子域和间接子域”。

  在编写伪代码时我们为了方便起见通常使用缩进来表明作用域的包含关系。

  比如:

1
2
3
4
作用域 A
作用域 B
作用域 C
作用域 D

  上面的例子中A是其余所有作用域的直接或间接父域,CD的直接父域,也就是说缩进越大等级越低,顶格写的作用域被称为“全局域”,在其中声明的内容在当前伪代码的任意地方都能使用。

  在一个作用域中声明的变量、函数仅能在当前作用域及其子域中使用,在一个作用域中不能声明一个和当前作用域和其父域中已有变量名称相同的变量,下面这段伪代码就是不合理的:

1
2
3
4
5
6
7
8
9
a = 5
if a = 0:
a = 'text' // 非法的,a 已经存在不能声明第二个 a,也可以解释为 a 是数值类型,不能将其赋值为字符串类型
print a // 合法
else if a = 5:
a = a * 3 // 合法
else
b = 'else code' // 合法
print b // 非法的,b 不在 print 所在作用域或其父域当中,所以此处 b 是不存在的

  注:虽然这段代码被我们称为“不合理”的,不过由于伪代码并没有严格的规范,所以真非要这么写也不是不行,只是不推荐这么做。

布尔表达式

  布尔表达式指的是最终返回一个布尔类型的值的算式(通常所有返回布尔类型的语句我们都可以叫做布尔表达式),常见的有:

  • a == b - 双等号判断两个值是否相等
  • a != b - 不等号(或者写成感叹号后跟一个等号)判断a是否不等于b
  • a < b - 小于号判断a是否小于b
  • b > b - 大于号判断a是否大于b
  • a <= b - 小于等于号(或者写成一个小于号后跟一个等号)判断a是否小于等于b
  • a >= b - 大于等于号(或者写成一个大于号后跟一个等号)判断a是否大于等于b

  我们可以通过布尔运算符来连接两个布尔表达式,将其合并为一个表达式,常用的布尔运算符有:

  • a && b - 且操作,当ab的值都为true时返回true,否则返回false
  • a || b - 或操作,当ab的值至少有一个true时返回true,否则返回false
  • !a - 非操作,当atrue时返回false,否则返回true,使用时注意感叹号紧贴后面的表达式

  布尔运算符可以连用,以下用法都是合法的:

  • a && b && c && d
  • a || b || c
  • !a && b && !c
  • a || !b || !c

  当&&||一起使用时必须使用小括号表明运算顺序:

  • a && (b || c) - 表示a为真并且bc中至少有一个为真
  • (a && b) || c - 表示ab都为真或者c为真

  小括号可以无限嵌套,运算时先计算小括号内的表达式。

函数

  函数是一段有名称、有输入、有动作的代码,与普通代码不同的是函数的输入一般不使用input之类的语句从控制台输入,而是通过“函数参数”输入。

  函数由三部分组成:

  1. 函数名称和参数
  2. 函数体(即函数执行的代码)
  3. 返回值

  调用函数时代码将转移到函数中执行,函数执行完毕后携带返回值返回到调用位置。伪代码中常用的inputprint其实就是特殊的函数,它们可以从控制台输入内容或输出内容到控制台。

  比如我们可以使用类似以下的语法从控制台输入数字:

1
2
3
4
a = input as number
input number >> a
input >> a: number
// ...

  输入、输出的写法有很多种,只要能够清晰地表达出代码的含义即可。

自定义函数

  我们可以使用类似以下的语法来自定义一个函数:

1
2
3
// 求两个数字的和
fun sum(a: number, b: number): number
return a + b

  其中fun用来表明这个语句是在定义一个函数;sum是函数名称,可以使用任意数量的有实际含义的英文单词组合而成;冒号后面的是函数的返回值类型,可写可不写;下面缩进的内容就是函数体,其中的return语句就是向函数的调用处输出返回值的语句。

  函数参数的作用域为整个函数体,不得直接修改参数(参数类型为数组或复合类型时可以修改其中的值)。

  调用函数的方法也有很多种,常用的方法就是函数名(参数列表)的形式,参数之间使用逗号间隔,比如可以用a = sum(1, 5)来调用并存储sum函数的返回值,调用后a的值为6

  对于一些特殊的函数我们还可以使用其它的调用方法,比如我们想要表示将一个字符串转换为数字就可以使用类似a = '12345'.toNumber()的调用方法,调用后a的值为12345而不是'12345'

return

  return是函数中的一个非常特殊的控制语句,它用来设置函数的返回值,同时return语句执行后该函数就会结束执行,剩下的代码将被跳过,比如下面两个代码的效果就是完全等价的:

1
2
3
4
5
fun max(a: number, b: number):
if a > b:
return a
else:
return b
1
2
3
4
fun max(a: number, b: number):
if a > b:
return a
return b

内置函数

  有一些非常简单的功能我们可以直接假定该函数已经存在,不需要再自己手动编写一遍,比如说:

  • 使用sum函数求任意个数字的和
  • 使用max求任意个数字之间的最大值
  • 使用min求任意个数字之间的最小值
  • 使用floor表示对数字进行向下取整
  • 使用random生成一个随机数
  • ……

  只要功能足够简单且函数名字能够准确清晰地描述函数的功能,我们就可以完全省略函数的定义,假设其为一个已存在的函数。

常用结构

  接下来我们来介绍伪代码编写时常用的三种结构。

顺序结构

  顺序结构是最常见的结构,它随处可见。顺序结构就是代码按照顺序向下执行,比如最常见的:

1
2
3
4
a = input as number
b = input as number
c = a + b
print c

分支结构

  分支结构就是在顺序结构的基础上添加一个或多个条件分支,分支结构中通常包含if或者when语句,下面我们分别介绍这两种语句的作用。

if

  if语句是最常用的条件判断语句,其作用是判断指定条件是否成立,if语句的写法如下:

1
2
3
4
5
6
if expression0:
// do something...
else if expression1:
// do something...
else:
// do something...

  其中else if是当其前面所有的ifelse if条件都不成立且当前else if条件成立时会进入当前分支;else是当前面所有ifelse if条件都不成立时触发。

  else ifelse都是可选项,可有可无;else if可以有任意个,else只能有一个且必须放置在最后。

  expression是一个布尔表达式,表示当前分支的分支条件,仅有布尔表达式值为真时才会进入当前分支。

  比如下面两个伪代码的效果是完全相同的,现在我们假设有两个数值变量ab

1
2
3
4
if a == b:
print 'equals'
else if a <= b:
print 'a is less than b'
1
2
3
4
if a == b:
print 'equals'
if a != b && a <= b:
print 'a is less than b'

  不过虽然效果等价,但是为了减少冗余的内容,逻辑上需要连起来的if我们还是使用if-else-if结构将其连接起来。

when

  whenif的特化版,可以用来判断某个变量是否满足某个条件,比如下面的代码:

1
2
3
4
5
6
7
a = input as number
b = input as number
when a:
== b:
print 'equals'
<= b:
print 'a is less than b'

  这个代码和上面给的if-else-if的代码效果是完全等价的,在条件比较多时比较好用。

  when的条件语句中还可以放一些其它比较复杂的表达式,执行时从上向下判断,遇到第一个满足的表达式时执行其对应的分支内容然后退出when语句。

循环结构

  循环结构就是重复执行循环体中的代码,在循环条件不满足后退出循环。

  有两种常用的循环代码,forwhile

for

  for有两种写法,第一种也是最基本的是:

1
2
for (初始化语句; 循环条件; 循环变量更新表达式):
// 循环体

  其中“初始化语句”用于初始化循环变量,我们可以在这个表达式的位置声明变量或修改已有变量的值,在初始化表达式中声明的变量仅在for循环中可用。

  “循环条件”应当是一个布尔表达式,用来表示是否继续循环,当布尔表达式返回false时将自动退出循环。

  “循环变量更新表达式”用于更新循环变量,此处可以修改循环变量的值。

  for循环的执行顺序如下:

  1. 初始化循环变量,执行初始化语句
  2. 判断循环条件,如果为false则退出循环
  3. 执行循环体
  4. 执行循环变量更新表达式
  5. 回到 (2)

  如果我们想遍历一个数组,我们可以这么写:

1
2
3
array = ...     // 这里假设我们有一个数组
for (i = 0; i < array.length; ++i): // array.length 表示数组的长度
print array[i] // 打印数组下标为 i 的变量的值

  for循环还有另一种写法,用于遍历一个可迭代的结构(通常为数组、链表……),格式为:

1
2
for (变量名 in 可迭代结构):
// 循环体

  现在让我们使用这种写法来遍历数组(遍历指的是顺序访问可迭代结构中的所有值):

1
2
3
array = ...     // 这里假设我们有一个数组
for (item in array):
print item

  我们给出的这两个for循环的代码示例效果是完全相同的,很显然如果for循环用于遍历一个可迭代结构的话使用后者会更加简单便捷。

  如果我们需要在遍历过程中访问下标,可以使用下面的写法:

1
2
for (变量名, 下标变量名 in 可迭代结构):
// 循环体

  下标的变量名我们通常使用ijkindex

while

  while循环也有两种写法,第一种是先判断循环条件再执行循环体:

1
2
while 循环条件:
// 循环体

  第二种是先执行循环体再判断循环条件:

1
2
3
do:
// 循环体
while 循环条件

  while相比于for就是少了初始化语句和变量更新语句,其它没有任何区别。

控制语句

  我们可以在循环体中使用控制语句来控制循环的执行,使用break即可立即跳出循环,使用continue可以在本次循环中跳过循环体中剩余的代码。