跳转至

03. 程序设计语言基础

程序设计语言、基本成分、函数调用与语言处理程序基础。

配套阅读:程序设计语言基础:课件笔记,含课程配图、例题和参考解析。

本章目录

语言分类、编译与形式语言

--- markmap: height: 760 initialExpandLevel: 2 autoFit: true colorFreezeLevel: 2 maxWidth: 280 spacingHorizontal: 80 spacingVertical: 12 nodeMinHeight: 24 paddingX: 10 --- # 编程语言与执行方式 ## 语言层次与范式 - 机器语言:直接以机器指令编码表示操作 - 汇编语言:用助记符和符号地址描述机器指令,由汇编器翻译 - 高级语言:用更抽象的数据与控制结构表达程序 - 常见范式:过程式、面向对象、函数式、逻辑式;一种语言可支持多种范式 ## 编译、解释与混合实现 - 编译:先把源程序翻译为目标代码 - 目标可以是机器码,也可以是字节码或其他中间表示 - 提前编译的本机程序通常不需要编译器参与执行,但可能依赖运行库 - 解释:运行时由解释器执行源程序或中间表示 - 解释执行不等于逐行读取源代码,也不排斥预先生成字节码 - 混合方式:先编译为字节码,再解释执行或进行即时编译 JIT - 编译和解释属于实现方式,不能把语言永久划为互斥的两类 - 性能取决于实现、优化、运行环境与工作负载 - 可移植性取决于语言规范、平台依赖及工具链;机器码通常依赖目标架构 ## 常见语言 - Fortran:科学与数值计算,擅长数组和高性能计算 - Pascal:强调结构化程序设计;Delphi 使用 Object Pascal 并提供开发环境 - C:过程式语言,支持指针与底层内存操作,常用于系统开发 - Lisp:以符号和列表处理见长,支持函数式编程,常用于人工智能研究 - C++:支持过程式、面向对象和泛型编程,兼顾抽象能力与底层控制 - Java:通常先编译为字节码,由 JVM 执行;跨平台依赖兼容运行时与库 - C#:主要面向 .NET,通常生成中间语言,再由运行时编译或解释执行 - Prolog:用事实、规则和查询进行逻辑推理,适合知识表示与专家系统 - Python:支持多种范式,常用于脚本、自动化和系统集成 - CPython 通常先编译为字节码,再由虚拟机执行
--- markmap: height: 760 initialExpandLevel: 2 autoFit: true colorFreezeLevel: 2 maxWidth: 280 spacingHorizontal: 80 spacingVertical: 12 nodeMinHeight: 24 paddingX: 10 --- # 编译过程与程序错误 ## 词法分析 - 输入:字符流;输出:单词符号 Token 流 - 识别关键字、标识符、字面量、运算符和分隔符 - 处理空白与注释,报告非法字符或不完整字面量等问题 ## 语法分析 - 按文法把 Token 组织成表达式、语句等语法结构 - 构造语法树或抽象语法树 AST - 检查括号配对、结构和语句形式等语法约束 ## 语义分析 - 检查名字是否声明、作用域、类型与调用参数是否符合语言规则 - 静态类型检查能发现的错误取决于语言的类型系统与编译器能力 ## 中间代码与目标代码 - 中间表示:三地址码、字节码等,便于分析与转换 - 优化:在保持程序规定语义的前提下改进代码 - 目标代码生成:选择指令、分配寄存器,生成目标平台代码 - 链接:解析外部符号并组合目标模块与库 - 装入:把可执行映像映射到内存,完成运行所需的准备 ## 符号表 - 保存名字、类型、作用域、存储位置等信息 - 支持插入、查找、更新与作用域退出时的处理 - 为语义检查、存储分配和代码生成提供依据 ## 错误分类 - 静态错误:在运行前通过词法、语法、语义等检查发现 - 动态错误:在执行过程中暴露,如某些越界访问或除零 - 具体表现由语言规则决定,可能是异常、终止或未定义行为 - 逻辑错误:程序可以运行,但结果不符合预期;不一定自动报错
--- markmap: height: 760 initialExpandLevel: 2 autoFit: true colorFreezeLevel: 2 maxWidth: 280 spacingHorizontal: 80 spacingVertical: 12 nodeMinHeight: 24 paddingX: 10 --- # 形式语言、文法与自动机 ## 文法的组成 - 终结符、非终结符、产生式与开始符号 - 从开始符号反复应用产生式,推导出语言中的句子 ## Chomsky 文法层次 - 0 型:无限制文法,对应图灵机可识别语言 - 对不属于语言的输入,识别过程不保证停机 - 1 型:上下文有关文法,对应线性有界自动机 - 2 型:上下文无关文法,对应非确定性下推自动机 - 产生式左部是单个非终结符;常用于描述程序语法 - 3 型:正规文法,对应有限自动机 - 常用于描述标识符、数值字面量等词法模式 - 表达能力:正规语言 ⊂ 上下文无关语言 ⊂ 上下文有关语言 ⊂ 图灵可识别语言 ## 正则表达式与正规集 - 正则表达式描述字符串集合,ε 表示空串 - `ab`:先出现 a 再出现 b,即集合 {ab} - `a|b`:选择 a 或 b,即集合 {a, b} - `a*`:零次或多次重复 a,包含 ε、a、aa 等 - `(a|b)*`:由 a、b 构成的所有有限字符串,包含 ε - 基本运算:连接、选择、闭包;括号改变结合范围 - 此处指形式语言中的正则表达式;带反向引用等扩展的正则引擎能力可能更强 ## 有限自动机 - 组成:有限状态集合、输入字母表、转移、初态与接受状态 - DFA:每个状态对每个输入符号有唯一的转移结果 - NFA:可有多个候选转移,也可使用 ε 转移 - DFA、NFA 与正规文法在识别能力上等价 - 处理完全部输入后处于接受状态,才接受该字符串

基本成分、函数调用与表达式

--- markmap: height: 760 initialExpandLevel: 2 autoFit: true colorFreezeLevel: 2 maxWidth: 280 spacingHorizontal: 80 spacingVertical: 12 nodeMinHeight: 24 paddingX: 10 --- # 程序的基本成分 ## 数据成分 - 常量与变量:表示程序处理的值,类型约束可用操作 - C 的常见类型 - 整数、浮点、字符;数组、指针、结构体、联合体、枚举等 - 数组连续存储;指针保存地址,使用时受对象边界与生命周期约束 - Python 的常见对象 - 数值、字符串、元组:通常不可变;元组中的元素仍可能引用可变对象 - 列表:可变、有序、允许重复元素 - 元组与字符串:有序、允许重复元素 - 集合 set:可变、不保证迭代顺序、元素唯一;frozenset 不可变 - 字典 dict:可变、键唯一;Python 3.7 起保证保留插入顺序 - 集合元素与字典键须可哈希;不可变与可哈希不是完全相同的概念 ## 运算成分 - 算术、比较、逻辑、位运算与赋值 - 优先级与结合性决定表达式如何分组,括号可明确分组 - 分组顺序不必等于操作数的求值顺序;后者取决于语言规则 - Python 序列操作 - `+` 连接;`*` 重复;下标访问;切片;`in` 成员判断 - 列表重复复制的是元素引用,嵌套可变对象可能被多个位置共享 - Python 的 `/` 为真除法,`//` 为向下取整除法;不同语言的整数除法规则可能不同 ## 控制成分 - 顺序:依次执行语句 - 选择:根据条件执行分支,如 if、switch、match - 循环:重复执行,如 for、while - break 退出循环,continue 进入下一轮,return 结束函数调用 ## 传输成分 - 输入:从键盘、文件、网络等获取数据 - 输出:把数据写入屏幕、文件、网络等 - 输入输出常伴随格式转换、缓冲和错误处理
--- markmap: height: 760 initialExpandLevel: 2 autoFit: true colorFreezeLevel: 2 maxWidth: 280 spacingHorizontal: 80 spacingVertical: 12 nodeMinHeight: 24 paddingX: 10 --- # 函数调用与递归 ## 函数与调用过程 - 定义:名称、形参、函数体以及返回值约定 - 实参:调用时提供的表达式或对象;形参:被调用函数中接收参数的名字 - 调用栈帧通常保存返回地址、参数、局部变量和需恢复的状态 - 局部变量的作用域与对象的生命周期是不同概念 ## 参数传递 - 传值:形参获得实参值的副本 - 给形参重新赋值,一般不改变调用者变量 - 若复制的是指针或对象引用,仍可能通过它修改所指对象 - 传引用:形参成为调用者变量的别名 - 对形参代表的对象赋值,可以改变调用者的值 - C:参数按值传递;传递指针值可间接修改目标对象 - Java:始终按值传递,包括对象引用的值 - Python:传递对象引用,常称对象共享传递 - 修改可变对象的内容可能被调用者观察到 - 在函数内重新绑定形参,不会重新绑定调用者变量 ## 递归调用 - 函数直接或间接调用自身 - 基本情形:无需继续递归即可得到结果 - 递归步骤:把问题转化为规模更小的同类问题 - 终止性:每次递归须朝基本情形推进 - 每次调用拥有独立的局部执行状态,共享对象仍可被共同访问 - 空间开销与最大递归深度有关;过深可能造成栈溢出 - 尾递归能否消除栈增长,取决于语言和实现是否提供尾调用优化
--- markmap: height: 760 initialExpandLevel: 2 autoFit: true colorFreezeLevel: 2 maxWidth: 280 spacingHorizontal: 80 spacingVertical: 12 nodeMinHeight: 24 paddingX: 10 --- # 表达式的表示与求值 ## 中缀表达式 - 运算符位于操作数之间,如 `(a + b) * c` - 依靠优先级、结合性和括号确定结构 ## 前缀表达式 - 运算符在前,如 `* + a b c` - 对应表达式树的先序遍历 - 从右向左扫描:操作数入栈,运算符取出操作数计算 ## 后缀表达式 - 运算符在后,如 `a b + c *`,又称逆波兰式 - 对应表达式树的后序遍历 - 从左向右扫描:操作数入栈,遇运算符计算并把结果入栈 - 二元运算符先弹出右操作数,再弹出左操作数 - `a b -` 表示 a 减 b,不能颠倒顺序 ## 中缀转后缀 - 操作数直接输出;运算符用栈暂存 - 根据优先级与结合性弹出应先执行的栈顶运算符 - 左括号入栈,右括号触发弹出直到匹配的左括号 - 扫描结束后输出剩余运算符;括号本身不进入结果 ## 结构关系 - 前缀与后缀式在运算符元数明确时,无需括号即可确定结构 - 表达式树叶结点通常为操作数,内部结点为运算符