Compound Statements

复合语句

复合语句:包含其他语句(语句组)的语句

  • 复合语句由一个、多个子句组成,子句包含句头、句体

    • 子句头
      • 都处于相同的缩进层级
      • 以作为唯一标识的关键字开始、冒号结束
    • 子句体
      • 在子句头冒号后、与其同处一行的一条或多条分号分隔 的多条简单语句
      • 或者是在其之后缩进的一行、多行语句,此形式才能 包含嵌套的复合语句
  • 其会以某种方式影响、控制所包含的其他语句执行

1
2
3
4
5
6
7
8
9
10
11
12
13
compound_stmt ::=  if_stmt
| while_stmt
| for_stmt
| try_stmt
| with_stmt
| funcdef
| classdef
| async_with_stmt
| async_for_stmt
| async_funcdef
suite ::= stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT
statement ::= stmt_list NEWLINE | compound_stmt
stmt_list ::= simple_stmt (";" simple_stmt)* [";"]
  • 语句总以NEWLINE结束,之后可能跟随DEDENT
  • 可选的后续子句总是以不能作为语句开头的关键字作为开头, 不会产生歧义

关键字

if

if语句:有条件的执行

1
2
3
if_stmt ::=  "if" expression ":" suite
("elif" expression ":" suite)*
["else" ":" suite]
  • 对表达式逐个求值直至找到真值,在子句体中选择唯一匹配者 执行
  • 若所有表达式均为假值,则else子句体如果存在被执行

while

while语句:在表达式保持为真的情况下重复执行

1
2
while_stmt ::=  "while" expression ":" suite
["else" ":" suite]
  • 重复检验表达式
    • 若为真,则执行第1个子句体
    • 若为假,则else子句体存在就被执行并终止循环
  • 第1个子句体中break语句执行将终止循环,且不执行else 子句体
  • 第1个子句体中continue语句执行将跳过子句体中剩余部分, 直接检验表达式

for

for语句:对序列(字符串、元组、列表)或其他可迭代对象中元素 进行迭代

1
2
for_stmt ::= "for" target_list "in" expression_list ":" suite
["else" : suite]
  • 表达式列表被求值一次

    • 应该产生可迭代对象
    • python将为其结果创建可迭代对象创建迭代器
  • 迭代器每项会按照标准赋值规则被依次赋值给目标列表

    • 为迭代器每项执行依次子句体
    • 所有项被耗尽raise StopIteration时,else子句体 存在则会被执行
  • 目标列表中名称在循环结束后不会被删除

    • 但若序列为空,其不会被赋值
  • 序列在循环子句体中被修改可能导致问题

    • 序列的__iter__方法默认实现依赖内部计数器和序列长度 的比较
    • 若在子句体中增、删元素会使得内部计数器“错误工作”
    • 可以对整个序列使用切片创建临时副本避免此问题
  • 第1个子句体中break语句执行将终止循环,且不执行else 子句体
  • 第1个子句体中continue语句执行将跳过子句体中剩余部分, 转至下个迭代项执行

try

try语句:为一组语句指定异常处理器、清理代码

1
2
3
4
5
6
7
try_stmt  ::=  try1_stmt | try2_stmt
try1_stmt ::= "try" ":" suite
("except" [expression ["as" identifier]] ":" suite)+
["else" ":" suite]
["finally" ":" suite]
try2_stmt ::= "try" ":" suite
"finally" ":" suite

except子句

except子句:指定一个、多个异常处理器

  • try子句中没有异常时,没有异常处理器执行

  • 否则,依次检查except子句直至找到和异常匹配的子句

    • 无表达式子句必须是最后一个,将匹配任何异常
    • 有表达式子句中表达式被求值,求值结果同异常兼容则匹配 成功
      • 若在表达式求值引发异常,则对原异常处理器搜索取消
      • 其被视为整个try语句引发异常,将在周边代码、 主调栈中为新异常启动搜索
    • 若无法找到匹配的异常子句,则在周边代码、主调栈中继续 搜索异常处理器
    • 兼容:是异常对象所属类、基类,或包含兼容异常对象元组
  • 当找到匹配except子句时

    • 异常将被赋值给as子句后目标,若存在as子句
    • 对应子句体被执行(所有except子句都需要子句体)
    • as后目标在except子句结束后被清除

      1
      2
      3
      4
      5
      6
      7
      8
      except E as N:
      foo
      # 被转写为
      except E as N:
      try:
      foo
      finally:
      del N
      • 避免因异常附加回溯信息而形成栈帧的循环引用,使得 所有局部变量存活直至下次垃圾回收
      • 则异常必须赋值给其他名称才能在except子句后继续 引用
  • except子句体执行前,有关异常信息存放在sys模块中, 参见cs_python/py3std/os_sys.md

else子句

else子句:在以下情况将被执行,若存在

  • 控制流离开try子句体没有引发异常
  • 没有执行returncontinuebreak语句

finally子句

finally子句:指定清理处理器,子句体在任何情况下都被执行

  • 执行期间程序不能获取任何异常信息

    • tryexceptelse子句中引发的任何未处理异常 将被临时保存,执行完finally子句后被重新引发

    • 但若finally子句中执行returnbreak语句,则临时 保存异常被丢弃

    • finally子句引发新的异常,临时保存异常作为新异常 上下文被串联

    • 显式异常串联参见cs_python/py3ref/simple_stmt
  • try子句中执行returnbreakcontinue语句时, finally子句在控制流离开try语句前被执行

    • 函数返回值由最后被执行return语句决定,而 finally子句总是最后被执行

      1
      2
      3
      4
      5
      6
      7
      8
      def foo():
      try:
      return "try"
      finally:
      return "finally"

      foo()
      # 返回"finally"

with

with语句:包装上下文管理器定义方法中代码块的执行

1
2
with_stmt ::=  "with" with_item ("," with_item)* ":" suite
with_item ::= expression ["as" target]
  • with句头中有多个项目,被视为多个with语句嵌套处理多个 上下文管理器

    1
    2
    3
    4
    5
    6
    with A() as a, B() as b:
    suite
    # 等价于
    with A() as a:
    wiht B() as b:
    suite

执行流程

  • 对表达式求值获得上下文管理器
  • 载入上下文管理器__exit__以便后续使用
  • 调用上下文管理器__enter__方法
  • 若包含as子句,__enter__返回值将被赋值给其后目标
    • with语句保证若__enter__方法返回时未发生错误, __exit__总会被执行
    • 若在对目标列表赋值期间发生错误,视为在语句体内部发生 错误
  • 执行with语句体
  • 调用上下文关管理器__exit__方法
    • 若语句体退出由异常导致
      • 其类型、值、回溯信息将被作为参数传递给__exit__ 方法;否则提供三个None作为参数
      • __exit__返回值为假,该异常被重新引发;否则 异常被抑制,继续执行with之后语句
    • 若语句体由于异常以外任何原因退出
      • __exit__返回值被忽略

Function

函数定义:对用户自定义函数的定义

1
2
3
4
5
6
7
8
9
10
11
12
funcdef                 ::=  [decorators] "def" funcname "(" [parameter_list] ")"
["->" expression] ":" suite
decorators ::= decorator+
decorator ::= "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE
dotted_name ::= identifier ("." identifier)*
parameter_list ::= defparameter ("," defparameter)* ["," [parameter_list_starargs]]
| parameter_list_starargs
parameter_list_starargs ::= "*" [parameter] ("," defparameter)* ["," ["**" parameter [","]]]
| "**" parameter [","]
parameter ::= identifier [":" expression]
defparameter ::= parameter ["=" expression]
funcname ::= identifier
  • 函数定义是可执行语句

    • 在当前局部命名空间中将函数名称绑定至函数对象(函数 可执行代码包装器)
    • 函数对象包含对当前全局命名空间的引用以便调用时使用
  • 函数定义不执行函数体,仅函数被调用时才会被执行

Decorators

装饰器:函数定义可以被一个、多个装饰器表达式包装

  • 函数被定义时将在包含该函数定义作用域中对装饰器表达式求值 ,求值结果须为可调用对象
  • 其将以该函数对象作为唯一参数被调用;返回值将被绑定至函数 名称
  • 多个装饰器会以嵌套方式被应用
1
2
3
4
5
6
7
8
@f1(arg)
@f2
def func():
pass
# 大致等价,仅以上不会临时绑定函数对象至名称
def func():
pass
func = f1(arg)(f2(func))

Parameter Types

形参类型

  • POSITIONAL_OR_KEYWORD:之前没有VAR_POSITIONAL类型的 参数

    • 可以通过位置关键字传值
  • KEYWORD_ONLY:之前存在VAR_POSITION类型、或*的参数

    • 只能通过关键字传值
  • VAR_POSITIONAL*args形式参数

    • 只能通过位置传值
    • 隐式默认值为()
  • VAR_KEYWORD**kwargs形式参数

    • 只能通过关键字传值
    • 隐式默认值为{}
  • POSITIONAL_ONLY:只能通过位置传值的参数

    • 某些实现可能提供的函数包含没有名称的位置参数
    • 唯一不能使用关键字传参参数类型
    • CPython:C编写、PyArg_ParseTuple()解析参数的函数

Default Parameters Values

默认参数值:具有parameter = expression形式的形参

  • 具有默认值的形参,对应argument可以在调用中可被省略

  • 默认形参值将在执行函数定义时按从左至右的顺序被求值

    • 即函数定义时的预计算值将在每次调用时被使用
    • 则被作为默认值的列表、字典等可变对象将被所有未指定该 参数调用共享,应该避免
      • 可以设置默认值为None,并在函数体中显式测试
  • POSITION_OR_KEYWORD、有默认值形参必须位于无默认值者后 ,即若形参具有默认值,后续所有在*前形参必须具有默认值
  • KEYWORD_ONLY、有默认值形参可位于无默认值者前

Annotations

  • 形参标注:param:expression
  • 函数返回标注:-> expression
  • 标注不会改变函数语义
  • 标注可以是任何有效python表达式
    • 默认在执行函数定义时被求值
    • 使用future表达式from __future__ import annotations ,则标注在运行时被保存为字符串以启用延迟求值特性
  • 标注默认存储在函数对象__annotation__属性字典中
    • 可以通过对应参数名称、"return"访问

Class

类定义:对类的定义

1
2
3
classdef    ::=  [decorators] "class" classname [inheritance] ":" suite
inheritance ::= "(" [argument_list] ")"
classname ::= identifier
  • 类定义为可执行语句

    • 继承列表inheritance通常给出基类列表、元类
    • 基类列表中每项都应当被求值为运行派生子类的类
    • 没有继承类列表的类默认继承自基类object
  • 类定义语句执行过程

    • 类体将在新的执行帧中被执行
    • 使用新创建的局部命名空间和原有的全局命名空间
    • 类体执行完毕之后
      • 丢弃执行帧
      • 保留局部命名空间
    • 创建类对象
      • 给定继承列表作为基类
      • 保留的局部命名空间作为属性字典__dict__
    • 类名称将在原有的全局命名空间中绑定至该类对象
  • 类可以类似函数一样被装饰

    • 装饰器表达式求值规则同函数装饰器
    • 结果被绑定至类名称
  • 类中属性、方法参见#todo
  • 类属性可以作为实例属性的默认值,但注意使用可变类型值可能 导致未预期结果

Coroutine

协程函数

1
2
async_funcdef ::=  [decorators] "async" "def" funcname "(" [parameter_list] ")"
["->" expression] ":" suite
  • 协程函数可以在多个位置上挂起(保存局部状态)、恢复执行
  • 协程函数体内部
    • awaitasync是保留关键字
    • await表达式、async forasync with只能在协程 函数体内部使用
    • 使用yield from表达式将raise SyntaxError

async for语句

1
async_for_stmt ::= "async" for_stmt
  • async for语句允许方便的对异步迭代器进行迭代

    1
    2
    3
    4
    async for TARGET in ITER:
    ...BLOCK1...
    else:
    ...BLOCK2...

    在语义上等价于

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    iter = (ITER)
    iter = type(iter).__aiter__(iter)
    running = True
    while running:
    try:
    TARGET = await type(iter).__anext__(iter)
    except StopAsyncIteration:
    running = False
    else:
    BLOCK1
    else:
    BLOCK2

async with语句

1
async_with_stmt ::= "async" with_stmt
  • async with语句允许方便使用异步上下文管理器

    1
    2
    async with EXPR as VAR:
    BLOCK

    语义上等价于

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    mgf = (EXPR)
    aexit = type(mgr).__aexit__
    aenter = type(mgr).__aenter__(mgr)

    VAR = await aenter
    try:
    BLOCK
    except:
    if not await aexit(mgr, *sys.exc_info()):
    raise
    else:
    await aexit(mgr, None, None, None)

编码问题

Abstract Charater Repertoire

抽象字符集:ACR

字符

字母、数字、标点、表意文字(汉字)、符号或其他文本形式的 “原子”

抽象字符

抽象的字符,包括空白、不可打印的字符

  • 对于某些语言中,抽象字符应该还包括发音字符

  • 如:印度语中单词“नमस्ते”

    • 有6个字符[‘न’, ‘म’, ‘स’, ‘्’, ‘त’, ‘े’],
    • 其中4、6两个字符在单词不出现,是发音字符

Abstract Charater Repertoire

抽象字符集:ACR,抽象字符的集合

  • 集合表明无序性
  • 有时也简称为字符集(charater set)
  • 有开放(字符不会改变)和封闭之分(会扩张)

Coded Character Set

编码字符集:CCS

Code Point

码位:抽象字符集中与字符关联的数字编号

  • 一般是非负整数
  • 习惯上有16进制表示

Coded Character Set

编码字符集:CCS,每个所属字符都分配了码位的抽象字符集

  • 经常简称为字符集(charater set),同ACR弄混
  • 字符与码位一一映射
  • 可以更加方便的引用字符集中的某个元素
  • 可以类比于dict

字符集(抽象、编码)举例

US-ASCII

ACSII字符集

  • 128个抽象字符,封闭字符集

  • 主要包括

    • 控制字符:回车、退格、换行
    • 可显示字符:英文大小写、阿拉伯数字、西文符号
  • 一般字符集都是兼容ascii编码字符集,即相同字符的码位相同

ISO-8859-X

扩展的ASCII字符集

  • 涵盖了大多数西欧语言字符、希腊语

GBXXXX

国标系列字符集:每个标准包含的字符数量不同、对应的编码方案 也不不完全相同

  • GB2312:信息交换用汉字编码字符集基本集

    • 包含汉字覆盖99.75%的使用频率
    • 人名、古汉语等罕用字不能处理
  • GBK:汉字内码扩展规范

    • 包括21003个汉字
    • 支持国际标准ISO/IEC10646-1和国家标准GB13000-1中 全部中日韩汉字
    • 包含了BIG5编码中的所有汉字
    • 兼容GB2312
  • GB18030:信息技术中文编码字符集

    • 其中收入汉字70000余个
    • 以汉字为主并包含多种我国少数民族文字(如藏、蒙古、 傣、彝、朝鲜、维吾尔文等)的超大型中文编码字符集 强制性标准
    • 兼容GBK

Big5

Big5字符集:主要包含台湾、香港地区繁体字

Universal Character Set/Unicode/UCS

统一字符集/Unicode字符集:ISO/IEC 10646定义的编码字符

  • 开放字符集,码位无上限,容纳一切字符,包括emoji等

  • UCS中码位不是连续分配的

    • 目前为止,分为0x0000~0x10FFFF共17个平面

    • 其中0平面0x0000~0xFFFF称为 basic multilingual plane

    • BMP中码位只有16bit长度,能够节约大量存储空间,有 战略意义

    • 因此“常用”语言的常用字符放在BMP,其他不常用的字符 只能放在其他平面

  • unicode本身是指一系列用于计算机表示所有语言字符的标准

Character Encoding Form

字符编码表:CEF,将码位映射为码元序列

  • fixed-length-encoding:定长编码,对每个码位(字符) 赋予长度同为m的码元(位串)

    • 封闭字符集符号有限,可以直接确定一一对应编码表
  • variable-length-encoding:变长编码,允许对不同码位 赋予不同长度的码元

    • 开放字符集包括符号无上限,无法定长码元表示码位, 必须有某种方式将码位一一映射为码元序列
    • 封闭字符集出于节约成本考量,也可能使用变长编码 如:Huffman编码
  • 码元:能用于处理或交换编码文本的最小比特组合(位串)

Unicode定义的CEF

本质思想:预留标记位值使码元序列的长度实现变长

UTF-8

  • 码元为1B
  • BMP中字符一般需要1~3BBMP外需要4B
  • 兼容ASCII编码表(方案)
    • 不同于编码字符集兼容的意义,基本上编码字符集都 兼容ASCII编码字符集,即对应字符码位相同
    • 兼容编码表指,“ASCII编码方案”可以使用UTF-8解码 方案直接解码

UTF-16

  • 码元为2B
  • BMP中字符一般需要2B,BMP外需要4B

UTF-32

  • 码元为4B

Prefix-Free Code

(自由)前缀码:所有代码码元都不是另一个字符码元的前缀

  • 可以简单扫描位串直到得到一组有效码元,转换为相应字符

  • 这样编码表可以很容易用一棵(编码)二叉树表示

    • 树左向边标记为0、右向边标记为1
    • 叶子节点表示字符,根节点到叶子节点路径为其码元
    • 树中叶子节点到其他叶子节点的简单路径不存在,即码元 不可能为其他码元前缀
    • 所以任何二叉树对应一套编码表
  • 这种编码方案一般用于产生平均长度最短的位串

    • 因此这类编码方案以bit为单位,而不是以byte为单位

Huffman Encoding

哈夫曼编码:prefix-free code的一种

  • 根据字符出现频率进行编码

    • 需要事先知道字符出现概率
    • 可以事先扫描给定文本,对文本中字符出现字符计数
    • 将较短位串分配给高频字符、较长位串分配给低频字符
  • 若字符独立出现,则哈夫曼编码是最优编码(最短长度编码)

  • 需要把编码树信息包含在编码文本中才能正确解码

  • 构造哈夫曼树的贪婪算法参见组合问题

Adaptive Huffman Encoding

利用已经处理字符串动态更新编码

Lempel-ziv

对字符串编码

Character Encoding Schema

字符编码方案:CES字符编码表+字节序列化方案,将码位 映射为字节流

  • 大小端序问题:码元高位还是低位字节在前
  • 字节序标记问题:不同程序之间端序交流
  • 通常所说编码、解码就是指使用CES

应用场合

  • CES是真正的应用层面,需要给出具体存储方案实现, 前述都是理论上protocol

  • 所有字符串存储介质中,磁盘、内存都采用某种具体CES 实现存储

    • Java、Python3这样的偏上层语言,字符串对象在内存中 通常采用UTF-16

    • C这样偏底层语言,基本上按照源文件的编码确定,即将 源文件中对应字符串对应字节,但现在C/C++中还有一种 宽字符w_char类型

  • 以上仅对Unicode而言,对于ASCII来说没有区分必要

内存CSE说明

  • 内存中如果不使用某种CES实现,直接使用码元,一样会出现 长度问题,所以显然会使用某种CES方案

  • 虽然在内存中,字符仍然使用某种编码方案得到字节流存储,但 这个字节流并不是这个字符,码位才“是”这个字符

    • 大部分提供Unicode处理语言会自动处理字符,不仅仅 是字节

    • 在考虑字符串问题时,可以“抽象的”忽略具体存储方式, 认为存储的就是“码位”本身

Byte Order Mark

字节序标:BOM,放置于编码字节开始处的特殊字节序列,表明 序列大小端序

  • 0xFFFE:小端序,低位在前
  • 0xFEFF:大端序,高位在前

Unicode族CES方案

UTF

unicode transfromation format:历史上是指CES,而UTF-X 现在可以同时指代CES和CEF,Unicode族标准CEF方案

  • UTF-8:utf-8编码表码元为1B,不存在字节序问题

    • 指代CES和CEF没有什么区别,CEF只有一种
  • UTF-16:指代CES和CEF时有歧义,需要明确指明是 UTF-16 encoding form(码元序列)、 UTF-16 encoding schema(字节流)

    • UTF-16-le:utf-16编码表小端版本
    • UTF-16-be:yutf-16编码表大端版本
    • UTF-16:utf-16编码表带BOM版本,大小端均可
    • UTF-16 CES表示BMP(包含大部分常用字符)只需要2B, 权衡了内存占用、处理便捷,适合作为内存中字符串的 编码方案
  • UTF-32

    • UTF-32le
    • UTF-32be
    • UTF-32

UCS

Unicode还有两种非标准CES

  • UCS-2:使用2B定长序列化码位
    • 可以视为UTF-16的子集
    • 不支持BMP外的字符表示
  • UCS-4:使用4B定长序列化码位
    • 可以视为UTF-32的子集

其他字符集CES方案

US-ASCII、GBK都有自己的编码方案,只是编码方案太简单,以至于 CCS、CEF、CES三层合一

  • ASCII编码方案:1B的低7位表示一个字符
  • ISO-8895-1编码方案:1B表示一个字符
  • GB2312编码方案:2B表示一个汉字
    • 第一个字节:区字节,高位字节
    • 第二个字节:位字节,低位字节
  • GBK编码方案:2B表示一个汉字
    • 兼容GB2312方案
    • 编码范围:0x8140~0xFEFE,剔除0xxx7F
  • GB18030编码方案:变长字节1B、2B、4B
    • 兼容GBK方案
  • Big5编码方案:2B表示一个汉字
    • 字节顺序类似GB2312

ANSI编码

  • 各个国家、地区独立制定、兼容ASCII编码,但彼此之间不兼容 的编码方案,微软统称为ANSI编码

  • ANSI编码一般代表系统(仅win)默认编码方式,在不同系统中 指不同的编码方案

    • 英文操作系统:ISO-8859-1
    • 简体中文:GBxxxx编码
    • 繁体中文:Big5编码
    • 日文:Shift JIS编码
  • 默认ANSI编码可以通过设置系统Locale更改
    • win下系统Locale决定代码页,用户Locale决定数字、 数字、货币、时间与日期格式

Transfer Encoding Syntax

传输编码语法:TES,有时候需要对字节流再次编码以进行传输

  • 如:某些字符不允许出现在传输流中

举例

  • base64编码:将字节流映射成64个安全字符集组成的字符流

输入、输出辨析

输入

所有的输入都是经过CES编码的字节流(包括数字)

  • 文件输入流:文件编码方案决定
  • 标准输入流(terminal):terminal编码方案决定
  • 管道传输流:由管道输入端的编码方案决定

处理

这里应该有两种处理方式

  • 将输入视为字节流,不做任何处理,直接按字节存储在 内存中

    • 将输入字节流视为其自身编码方案字节流,直接储存
  • 将输入视为字符串,尝试解码

    • 若解码发现无法解释位串

      • strict:报错
      • replace:将违规字符替换为有效字符
        • 替换为某种?:很多应用采用此方式,是乱码 发生的主要原因
        • 有些也替换为Unicode码位
      • ignore:忽略该位串
    • 而解码后字符串的在内存中的存储,取决于解释器、编译器 、系统等处理主体的编码方案

  • 以上只是对真正有字符串类型的语言Python、Java有这样区分
  • 对于没有字符串类型的语言C并没有真正意义上的字符串,只有 字节串,不涉及解码、自身字符串内存存储的问题,仅有换行符 转义问题(若处理换行符被视为解码)

输出

所有输出都是编码后字节流(包括数字)

  • 文件输出流:write
  • 标准输出流(terminal):print
  • 管道传输流
  • 需要注意的是,输出的字节流编码方案和处理主体在内存中编码 方案不一定相同,和编程实现、平台等因素都有关,比如很多 默认输出编码方案为utf-8
  • 同样的,此输出流是对于其接收者而言仅仅是字节流,需要自行 决定如何处理字节流
  • 此输出是指传递给直接输出外部的输出,有些语言在输出前 会隐式调用函数生成字符串

其他常见问题

乱码

乱码主要有以下3种可能

  • 读取乱码:真正的乱码
    • 读取时没有正确解码,内容被替换,打印输出
  • 存储乱码:保存时已经是“乱码”,其实也不能算是乱码
    • 读取时没有正确解码,内存中内容已经被错误,保存后内容 保持错误
    • 内存中数据正确,但保存使用的编码方案不支持某些字符, 内容被替换
  • 缺少字体库
  • 乱码不是其实已经是将不能打印的字符剔除、替换,能看到的 乱码已经是程序认为“正确解码的字符串”

换行符处理

  • 鉴于以下两种行为

    • win下换行需要两个字符\r\n标识,linux下只需要\n 即可
    • 编程语言往往类似Linux只需要\n即标识换行
    • Vim中在内存中以<CR>标识换行
  • 在win下很多语言以字符串模式:读取字节流时会自动将\r\n 替换为\n、写出字节流时替换回\r\n

    • Python这种原生支持字符串语言,这个特性可以看作字符串 解码的行为
    • C这种原生只支持字节串的语言,这个特性可能是二进制、 字符串读写的唯一区别

例子

以UTF-8编码方案为例

  • 输入的所有的内容都是由UTF-8编码方案编码的字节流

  • 处理主体获取字节序列,根据指令处理字节序列

    • 比如字节序列编码和处理主体编码不同,将其解码主体编码方案
    • 比如按照约定将字节序列转变为不同类型的数据
  • 输出则是将需要输出的内容(包括数字等)转换字节流传给底层 函数

Vim 内建函数、变量

文件、路径相关函数

  • expand(option):根据参数返回当前文件相关信息
  • fnamemodify(file_name, option):返回当前文件夹下文件 信息
  • globpath(dir, type):返回的dir下符合type的文件 列表值字符串,使用,分隔,type**时将递归的列出 文件夹及文件

特殊变量

command-line模式的特殊变量,在执行命令前会将其替换为相应的 变量

  • <cword>:光标处单词
  • <cWORD>:光标处单词大写形式

寄存器

寄存器相关快捷键、命令

  • <c-r><reg>:insert模式下直接输入<reg>中的值

一般寄存器

Readonly Register

Expression Register("=

"=实际上并不是一个寄存器,这是使用命令表达式的一种方法, 按下=之后,光标会移动到命令行,此时可以输入任何表达式, (不只有"=才会激活命令行,<c-m>"也能激活) 输入表达式之后

  • 按下<esc>,表达式值被丢弃
  • 按下<cr>,表达式值计算后存入"=
    1
    2
    :nnoremap time "=strftime("%c")<cr>p
    :inoremap time <c-r>strftime("%c")<cr>

之后:put或者按下p将粘贴"=中的值

寄存器中的值一定是字符串,如果是其他类型变量,会被强制 转换之后存入"=寄存器中

Vim特殊

换行

  • \0:空转义序列(ASCII码位0)<Nul>

    • <c-v> 000:输入<Nul>
    • Vim在内存中使用<NL>存储<Nul>,在读、写文件时即 发生转换
    • Vi无法处理<Nul>,应该是为了兼容Vi
  • \n:换行转义序列<NL>

    • <c-v><c-j>:输入<NL>,会被替换为输入<Nul>, 等同于<c-v> 000
    • 在搜索表达式中:字面意义的newline序列被匹配
    • 在替换表达式中:在内部被替换为<Nul>被输入,即 不再表示newline
  • \r:回车转义序列<CR>

    • 被Vim视为为换行,可在替换表达中表示<NL>
    • <c-v><c-j>:输入<CR>字符本身
  • vim在内存换行应该同一使用<CR>,在读、写时,根据当前 fileformat设置自动转换换行字符(序列)

IO

  • C++中数据输入/输出操作是通过I/O流库实现

  • 流:数据之间的传输操作

    • 输出流:数据从内存中传送到某个载体、设备中
    • 输入流:数据从某个载体、设备传送到内存缓冲区
  • C++中流类型

    • 标准流I/O流:内存与标准输入、输出设备之间信息传递
    • 文件I/O流:内存与外部文件之间信息传递
    • 字符串I/O流:内存变量与表示字符串流的字符数组 之间信息传递

<ios>

class ios

ios:流基类

  • 所有流的父类
  • 保存流状态、处理错误

方法

  • .fail():判断流是否失效

    • 尝试超出文件的结尾读取数据时
    • 输入流中字符串无法被正确解析
  • .eof():判断流是否处于文件末尾

    • 基于C++流库语义,.eof方法只用在.fail调用之后, 用于判断错故障是否是由于到达文件结尾引起的
  • .clear():重置与流相关状态位

    • 故障发生后,任何时候重新使用新流都必须调用此函数
  • if(stream):判断流是否有效

    • 大部分情况下等同于if(!stream.fial())
  • .open(filename):尝试打开文件filename并附加到流中

    • 流方向由流类型决定:输入流对于输入打开、输出流对于 输出打开
    • 可以调用.fail判断方法是否失败
  • .close():关闭依附于流的文件

.[un]setf
1
2
UKNOWN setf(setflag, unsetfield);
UKNOWN unsetf(unsetflag);
  • 用途

    • .setf:设置某个流操纵符
    • .unsetf():取消某个流操纵符
  • 参数

    • setflag:需要设置的操纵符
    • unsetflag:取消设置的操纵符
    • unsetfield:需要清空的格式设置位组合
  • 不能像<<>>中省略操纵符ios::前缀
.rdbuf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <class Elem, class Traits>
class basic_ios: public ios_base{
basic_streambuf <_Elem, _Traits> *_Mystrbuf,

_Mysb * rdbuf() const{
return (_Mystrbuf);
}

_Mysb * rdbuf(_Mysb * _Strbuf){
_Mysb * _Oldstrbuf = _Mystrbuf;
_Mystrbuf = _Strbuf;
return (_Oldstrbuf);
}
}
  • 用途:获得输入、输出流对象中指向缓冲区类streambuf指针
    • >><<操作符对其有重载,可以方便读取、写入

class istream

istream:输入流基类

  • 将流缓冲区中数据作格式化、非格式化之间的转换
  • 输入

方法

  • .unget():复制流的内部指针,以便最后读取的字符能再次 被下个get函数读取
.get
1
2
3
4
5
6
int_type get();
basic_istream& get(E& c);
basic_istream& get(E *s, streamsize n);
basic_istream& get(E *s, streamsize n, E delim);
basic_istream& get(basic_stream<E, T> &sb);
basci_istream& get(basci_stream<E, T> &sb, E delim);
  • 用途:从输入流中获取字符、字符串

  • 参数

    • delim:分隔符,缺省\n
    • n

(友元)函数

getline
1
2
3
4
5
6
7
8
9
10
11
12
template<class E, class T, class A>
basic_istream<E, T>& getline(
basic_istream<E, T>& is,
basic_string<E, T, A>& str,
);

template<class E, class T, class A>
basic_istream<E, T>& getline(
basic_istream<E, T>& is,
basic_string<E, T, A>& str,
E delim,
);
  • 用途:从流is读取以delim为界,到字符串中

    • 保留开头空白字符、丢弃行尾分割符
    • 读取字符直到分隔符,若首字符为分隔符则返回空字符串
  • 参数

    • delim:分隔符,缺省为换行符\n

class ostream

ostream:输出流基类

  • 将流缓冲区中数据作格式化、非格式化之间的转换,输出

方法

  • .put(ch):将字符ch写入输出流

class iostream

iosstream:多目的输入、输出流基类

Operator

Insertion Operator

<<:插入操作符,将数据插入流中

  • 左操作数是输出流

  • 右操作数是需要插入流中的数据

    • 基本类型:<<会将其自动转换为字符串形式

      • 整形:默认10进制格式
      • [unsigned ]char类型:总是插入单个字符
    • streambuf类型指针:插入缓冲区对象中所有字符

Extraction Operator

>>:提取操作符,从输入流中读取格式化数据

  • 左操作数为输入流

  • 右操作数存储从输入流中读取的数据

    • 缺省

      • skipws:忽略开头所有空白字符
      • 空白字符分隔:读取字符直到遇到空白字符
    • streambuf类型指针:把输入流对象中所有字符写入该 缓冲区

  • 几乎不提供任何支持检测用户输入是否有效的功能
    • 数据格式由变量类型控制

缓冲

缓冲类型

  • ISO C要求
    • 当且仅当不涉及交互设备时,标准输入、输出全缓存
    • 标准错误绝不是全缓存
  • 无缓冲:不缓冲字符

    • 适用情况:标准错误

    • 标准库不缓冲不意味着系统、设备驱动不缓冲

  • 行缓冲:在输入、输出遇到换行符时才会执行I/O操作

    • 适用情况:涉及交互设备,如标准输入、输出
  • 全缓冲:I/O操作只会在缓冲区填满后才会进行

    • 适用情况:大部分情况,如驻留在磁盘的文件

    • flush描述I/O缓冲写操作

      • 标准I/O函数自动flush
      • 手动调用对流调用死fflush函数
  • 缓冲区一般是在第一次对流进行I/O操作时,由标准I/O函数调用 malloc函数分配得到

文件自定义缓冲区

  • 文件必须已打开、未做任何操作
setbuf
1
void setbuf(FILE * restrict fp, char * restrict buf);
  • 用途:打开或关闭缓冲区
    • 打开:buf必须为大小为BUFSIZ的缓存
      • BUFSIZ:定义在stdio.h中,至少256
    • 关闭:将buf设置为NULL
setvbuf
1
2
int setvbuf(FILE * restrict fp, char * restrict buf,
int mode, size_t size);
  • 用途:设置缓冲区类型

流自定义缓冲区

setbuf
1
virtual basic_streambuf * setbuf(E *s, streamsize n);

Manipulator

(流)操纵符:控制格式化输出的一种特定类型值

输出

  • 短暂的:只影响下个插入流中的数据
  • 持久的:直到被明确改变为止

  • 双操纵符条目中,前者为默认

  • setwsetprecisionsetfill还需要包含<iomanip>

组合格式

  • adjustfield:对齐格式位组合
  • basefield:进制位组合
  • floatfield:浮点表示方式位组合

位置

  • endl:将行结束序列插入输出流,确保输出字符被写入目的流
  • setw(n):短暂的
  • setfill(ch):持久的,指定填充字符,缺省空格
  • left:持久的,指定有效值靠左
  • right:持久的,指定有效值靠右
  • internal:持久的,指定填充字符位于符号、数值间

数值

  • showbase:为整数添加表示其进制的前缀
  • fixed:持久的,完整输出浮点数
  • scientific:持久的,科学计数法输出浮点数
  • setprecision(digits):持久的,精度设置依赖于其他设置

    • fixed/scientific:指定小数点后数字位数
    • 其他:有效数字位数
  • hex:持久的,16进制输出无符号整形

  • oct:持久的,8进制输出无符号整形
  • dec:持久的,10进制输出整形

  • noshowpoint/showpoint:持久的,否/是强制要求包含 小数点

  • noshowpos/showpos:持久的,要求正数前没有/有+
  • nouppercase/uppercase:持久的,控制作为数据转换部分 产生任意字符小/大写,如:科学计数法中的e
  • noboolalpha/boolalpha:持久的,控制布尔值以数字/ 字符形式输出

控制

  • unitbuf:插入、提取操作之后清空缓冲
  • stdio:每次输出后清空stdout、stderr

输入

  • skipws/noskipws:持久的,读取之前是/否忽略空白字符
  • ws:从输入流中读取空白字符,直到不属于空白字符为止

<iostream>

  • ifstream_withassign:标准输入流类

    • cin:标准文件stdin
  • ofstream_withassign:标准输出、错误、log流

    • cout:标准文件stdout
    • cerr:标准文件stderr
    • clog:标准文件stderr

<fstream>

  • ifstream:文件输入流类

    • 默认操作:ios::in
  • ofstream:文件输出流类

    • 默认操作:ios::out|ios::trunc
  • fstream:文件流输入、输出类

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <fstream>

int main(){
ifstream infile;
ofstream outfile;
// 声明指向某个文件的流变量

infile.open(filename)
// 打开文件:在所声明变量和实际文件间建立关联

infile.close()
// 关闭文件:切断流与所关联对象之间联系
}

流操作复制文件

  • 逐字符复制

    1
    2
    3
    4
    5
    6
    7
    #include<fstream>
    std::ifstream input("in", ios::binary);
    std::ofstream output("out", ios::binary);
    char ch;
    while(input.get(ch)){
    output << ch;
    }
    • 使用input >> ch默认会跳过空白符,需要使用 input.unsetf(ios::skipws)取消
  • 逐行复制

    1
    2
    3
    4
    5
    #include<string>
    std::string line;
    while(getline(input, line)){
    output << line << "\n";
    }
    • 若文件最后没有换行符,则复制文件会末尾多\n
  • 迭代器复制

    1
    2
    3
    4
    5
    6
    #include<iterator>
    #include<algorithm>
    input.unsetf(ios::skipws);
    copy(istream_iterator(input), istream_iterator(),
    ostream_iterator(output, "")
    );
  • 缓冲区复制

    1
    output << input.rdbuf();
    • 丢失\n

标准输出文件内容

  • <<操作符

    1
    2
    3
    4
    5
    #include<iostream>
    #include<fstream>

    ifstream input("in");
    cout << input.rdbuf();
  • .get方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    while(input.get(*cout.rdbuf()).eof()){
    // 读取一行
    if(input.fail()){
    // `get`遇到空行无法提取字符,会设置失败标志
    input.clear();
    // 清除错误标志
    }
    cout << char(input.get());
    // 提取换行符,转换为`char`类型输出
    }
  • .get方法2

    1
    input.get(*cout.rdbuf(), EOF);

<sstream>

  • 基于C类型字符串char *编写

    • istrstream:串输入流类
    • ostrstream:串输出流类
    • strstream:串输入、输出流类
  • 基于std::string编写:推荐

    • istringstream:串输入流类
    • ostringstream:串输出流类
    • stringstream:串输入、输出流类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <sstream>

int string_to_integer(string str){
instringstream istream(str)
// 类似`ifstream`,使用流操作符从字符串中读取数据
int value;

istream >> value >> ws;
// `>>`忽略流开头空白字符,`ws`读取尾部空白
if(stream.fail() || !stream.eof()){
// 如果字符串不能作为整数解析,`.fail`返回`true`
// `.eof`返回`false`,说明字符串包含其他字符
error("string to integer: illegal integer format");
}
return value;
}

string integer_to_string(int n){
ostringstream ostream;
ostream << n;
return stream.str();
//
}

Postgre SQL笔记

安装

  • 交互式客户端:postgresql
  • 服务器:postgres-server
  • 额外功能:postgresql-contrib
  • 开发工具:postgresql-devel

OpenSuSe

1
2
$ sudo zypper in postgresql postgresql-server \
postgresql-contrib postgresql-devel

CentOS

1
2
$ sudo yum install postgresql postgresql-server \
postgresql-contrib postgresql-devel

其他版本

  • 从中选择合适版本下载:Postgres Yum repositories

    1
    $ wget https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-centos96-9.6-3.noarch.rpm
  • 安装下载的RPM(依赖EPEL repo)

    1
    $ sudo yum install pgdg-centos96-9.6-3.noarch.rpm
  • 更新Yum、安装指定PG版本

    1
    2
    $ sudo yum update
    $ sudo yum install postgresql96-sever postgresql96-contrib
  • 安装的PG带有版本后缀,初始化、启动时注意

配置

  • postgres安装完成后,默认创建Linux用户

    • 用户密码为空,要为其设置密码以切换到其
      1
      2
      $ sudo passwd postgres
      $ su - postgres
    • 用户目录默认是/var/lib/pgsql
    • 很多命令可以切换到用户postgres直接执行
  • 初始化数据库簇后,默认创建数据库角色postgres、数据库 postgres

初始化

  • 创建新PostgreSQL数据库簇

    1
    2
    3
    $ sudo postgresql-setup initdb
    #
    $ sudo inidb -D /var/lib/pgsql/data
    • 默认数据库存储路径为/var/lib/pgsql/data
  • 开启PG密码认证:修改host-based authentication设置

    1
    2
    3
    4
    # /var/lib/pgsql/data/pg_hba.conf
    # TYPE DATABASE USER ADDRESS MEHTOD
    host all all 127.0.0.1/32 md5
    host all all ::1/128 md5
    • 替换默认identmd5开启密码认证
    • 修改之后需要重启PG
  • 修改postgres用户密码,以可以通过密码作为postgres连接 数据库

    1
    2
    3
    $ su - postgres
    $ psql -d template1 -c "ALTER USER postgres with password '<passwd>'"
    # 也可以在数据库prompt中执行

启动数据库

  • 作为服务:startenablePG

    1
    2
    $ sudo systemctl start postgresql
    $ sudo systemctl enable postgresql
  • 作为普通程序启动:pg_ctl

    1
    2
    $ su - postgres
    $ pg_ctl start -D /var/lib/pgsql/data

Roles

PG使用概念roles处理认证、权限问题

  • 角色相较于区分明显的用户、组概念更加灵活

  • create usercreate role几乎完全相同

    • create user:创建角色默认带LOGIN属性
    • create role:创建角色默认不带LOGIN属性

权限

  • SUPERUSER/NOSUPERUSER:数据库超级用户
  • CREATEDB/NOCREATEDB:创建数据库
  • CREATEUSER/NOCREATEUSER
  • CREATEROLE/NOCREATEROLE:创建、删除普通用户角色
  • INHERIT/INHERIT:角色可以继承所属用户组权限
  • LOGIN/NONLOGIN:作连接数据库初始角色名
  • REPLICATION/NOREPLICATION:流复制时用到
  • CONNECTION LIMIT connlimit
  • [ENCRYPTED/UNENCRYPTED]PASSWORD '<passwd>'
  • VALID UNTIL '<timestamp>'
  • IN ROLE <role_name>[, ...]:角色所属用户组
  • IN GROUP <role_name>[, ...]
  • ROLE <role_name>[, ...]
  • ADMIN <role_name>[, ...]
  • USER <role_name>[, ...]
  • SYSID uid

角色赋权

1
2
3
4
5
psql> create role/user <name> [[with] <option> [...]];
# 创建角色时直接赋权
psql> alter role/user <name> [[with] <option> [...]];
psql> grant connect on database <db_name> to <name>;
# 修改已创建角色权限

组赋权

  • 把多个角色归为组,通过给组赋权、撤销权限实现权限管理
  • PG中角色赋权是通过inherit方式实现的
1
2
3
4
5
6
7
psql> create role father login createdb createrole;
# 创建组角色、赋权
psql> create role son1 inherit;
psql> grant father to son1;
# 创建有`inherit`权限的用户、赋权
psql> create role son2 inherit in role father;
# 创建用户时直接赋组权

认证方式

Peer Authentication

peer:从内核中获取操作系统中用户名,作为数据库角色名连接

  • 默认连接同名数据库
  • 信任Linux用户身份(认证),不询问密码
    • 即使-W强制输入密码,也不会检查密码正确性
  • 只有local连接支持此方法

Trust Authentication

trust:允许任何数据库角色名的连接

  • 信任任何连接、不询问密码

    • 只应该在操作系统层面上能提供足够保护下情况下使用
      • 文件系统权限:限制对Linux域套接字文件的访问
    • 适合单用户、本地连接
  • 数据库、用户权限限制仍然存在

Ident Authentication

ident:从ident服务器中获取操作系统中用户名,用于连接数据库

  • 仅在TCP/IP连接情况下支持

    • 若被指定给local连接,将使用peer认证
  • 数据库服务器向客户端ident服务器询问“连接数据库的”用户, 据此判断连

    • 此流程依赖于客户端完整性,若客户端机器不可信,则 攻击者可以在113端口执行任何程序返回任何用户名
    • 故此认证方法只适合封闭网络,所以客户端机器都被严格 控制
    • 有些ident服务器开启非标准选项导致返回的加密用户名, 此选项应该关闭
    • 基本每个类Unix操作系统都带有ident服务器,用于监听 113端口TCP
涉及配置
  • map:运行系统、数据库用户名之间映射

Password Authentication

Password认证:基于密码的认证方式

  • password:明文传输密码验证
  • md5:MD5-hashed传输密码o
  • md5可以避免密码嗅探攻击

  • password总应该尽量避免使用

    • 启用db_user_namespace特性时无法使用md5
    • SSL加密连接下password也能安全使用
  • 每个数据库的密码存储在pg_authid系统表中

    • 若用户没有设置密码,则存储的密码为null,密码验证 也总是失败
    • 使用create useralter role等SQL语句修改密码

GSSAPI Authentication

GSSAPI:定义在RFC 2743中的安全认证产业界标准协议

  • GSSAPI为支持其的系统自动提供认证
    • 认证本身是安全的,但是通过数据库连接的数据默认没有 加密,除非使用SSL
  • PG中GSSAPI需要编译时启用支持

SSPI Authentication

negotiate:windows的安全认证技术

  • PG将尽可能使用Kerberos,并自动回滚为NTLM
  • 仅服务器、客户端均在windows下或GSSAPI可用的情况下才能 工作
  • 使用Kerberos情况下,SSPI、GSSAPI工作方式相同
涉及配置
  • include_realm
  • map
  • krb_realm

Kerberos Authentication

Kerberos:适合公共网络上分布式计算的产业界标准安全认证系统

  • Kerberos提供不加密的语句、数据安全认证,若需要加密则使用 SSL
  • PG支持Kerberos第5版,需要在build时开启Kerberos支持
涉及配置
  • map
  • include_realm
  • krb_realm
  • krb_server_hostname

LDAP Authentication

LDAP:类似password,只是使用LDAP作为密码认证方法

涉及配置
  • ldapserver
  • ldapport
  • ldaptls
  • ldapprefix
  • ldapsuffix
  • ldapbasedn
  • ldapbinddn
  • ldapbindpasswd
  • ldapsearchattribute

RADIUS Authentication

RADIUS:类似password,只是使用RADIUS作为密码认证方法

涉及配置
  • radiusserver
  • radiussecret
  • radiusport
  • radiusidentifier

Certificate Authentication

Certificate:使用SSL客户多证书进行认证

  • 所以只在SSL连接中可用
  • 服务器要求客户端提供有效证书,不会向客户端传递密码prompt
    • cn属性(common name)将回和目标数据库的用户名 比较
    • 可通过名称映射允许cn属性和数据库用户名不同
涉及配置
  • map:允许系统、数据库用户名之间映射

PAM Authentication

PAM:类似password,只是使用 PAM(Pluggable Anthentication Modules)作为密码认证机制

涉及配置
  • parmservice:PAM服务名
    • 默认postgresql

pg_ctl

pg_ctl:用于控制PostgreSQL服务器的工具,此工具基本需要在 postgres用户下才能使用

  • 查看状态:$ pg_ctl status -D /var/lib/pgsql/data

psql

Shell

连接数据库

1
$ psql [-h <host>] [-p <port>] [-U <user_name>] [[-d] <db_name>]
  • -h:缺省为local类型连接本地数据库
    • localhost连接类型对应不同认证方式
    • -h localhost和缺省对应不同hba.conf条目
  • -p:缺省端口5432
  • -U/--user_name=:缺省linux用户名
  • [-d]/--database=:当前linux用户名
  • -W:密码,peertrust模式下无价值

Shell命令

  • postgres不仅仅提供psql交互环境作为shell命令,还提供可以 直接在shell中运行的数据库命令

    • 当然前提是当前linux登陆用户在数据库中存在、有权限
1
2
3
4
$ createdb <db_name> [-O <user_name>]
# 创建数据库,设置所有权为`user_name`
$ dropdb <db_name>
# 删除数据库

元命令

元命令:反斜杠命令,\开头,由psql自己处理

  • \后:跟命令动词、参数,其间使用空白字符分割

  • 冒号::不在引号中的冒号开头的参数会被当作psql变量

  • 反点号:参数内容被当作命令传给shell, 输出(除去换行符)作为参数值

  • 单引号':参数包含空白时需用单引号包o,其中包含的参数 的内容会进行类C的替换

    • \n(换行)、\digits(八进制)
    • 包含单引号需要使用反斜杠转义
  • 双引号\”<\code>

    • 遵循SQL语法规则,双引号保护字符不被强制转换为 小写,且允许其中使用空白
    • 双引号内的双引号使用双双引号""转义

帮助

  • \?

    • [<commands>]:元命令帮助
    • <options>:psql命令行选项帮助
    • <variables>:特殊变量帮助
  • \h [<clauses>]:SQL命令语法帮助(*表示全部)

展示信息

  • \du:查看用户权限
  • \c <db_name> <name>:以身份name访问数据库db_name
  • \l[ist]:查看数据库
  • \dt:展示当前数据库中表

变量

  • \set foo bar:设置变量
    • 可以像php设置“变量 变量”:\set :foo bar
  • \unset foo:重置变量

数据库变量

内部变量

特殊变量

特殊变量:一些选项设置,在运行时可通过改变变量的值、应用的 表现状态改变其,不推荐改变这些变量的用途

  • AUTOCOMMIT:缺省为on,每个SQL命令完成后自行提交,此时 需要输出BEGINSTART TRANSACTION命令推迟提交
  • DBNAMW:当前所连接数据库
  • ENCODING:客户端字符集编码
  • 详情查询手册

环境变量

  • PGDATA:指定数据库簇(存放数据)目录

    1
    $export PGDATA=/var/lib/pgsql/data
    • 默认/var/lib/pgsql/data
    • -D命令行参数指定

二进制文件格式

IDX

IDX:MNIST数据集独创的数据格式

  • 用于存储多维数组

  • 后可以跟数字表示存储数组的维度

    • idx1:存储1维数组
    • idx3:存储3维数组

格式

  • 2bytes:格式版本号

    • 一直是0x0000
  • 1bytes:数组中每个元素的数据类型

    • 0x08unsigned byte
    • 0x09signed byte
    • 0x0Bshort(2bytes)
    • 0x0Cint(4bytes)
    • 0x0Dfloat(4bytes)
    • 0x0Edouble(8bytes)
  • 1bytes:数组维度d

  • d * 4bytes(int):数组各维度长度

  • 数据部分

    • 数据类型已知、长度已知
    • 若元素格式符合文件头要求,表明解析正确,否则文件损坏

class

类:包含值、相应操作集的模板,将相关信息片段组织成复合值, 使得可以整体对其进行操作

概念

实体

  • object:对象,属于一个类的所有值

  • instance:实例,单个对象

  • field/instance variable:域/实例变量,类的分量

  • method:应用于类实例的操作

    • C++中方法的使用和传统函数类似,新名称只是为强调其同 所属的类紧密联系
    • 传统函数称为free function,不被约束于特定类

权限

  • private:私有,声明在private section中的域仅对该类 本身可见(默认)

  • public:公有,声明在public section中的域对所有用户 可见

    • 现代面向对编程不鼓励在类中声明public实例变量
  • protected:受限,声明在protected section中的成员对 所有子类都可以访问,但用户不能访问

类&结构体

C++中:结构类型和类基本上以同样方式实现,类是struct的扩展

  • 结构体:默认访问权限是public,类默认为private
  • 类:允许高级继承、方法,成员默认private
  • 对一般成员变量,类和结构体在内存布局上完全一致,如: 顺序、内存布局

消息传递模型

面向对象程序设计中,对象间通过信息发送、请求实现对象间通信, 将传递的这些信息称为消息

1
receiver.name(arguments)
  • 对象间消息发通常理解为一个对象调用另一个对象的方法
  • sender:发送方,初始化方法的对象
  • receiver:接收方,消息的目标对象

接口实现分离

  • C++中类接口、实现相分离时,类自身定义仅存在与其.h文件

  • 实现放在.cpp中作为独立方法定义,需要以类名作为限定符、 ::作为分隔的方式表明自己所属的类

类继承

1
2
3
class subclass: public superclass{
...code...
}
  • 子类subclass继承父类superclass所有publicprotected成员,private成员仍然保持私有特性

  • 可以继承模板类

    • 子类甚至可以不包含任何代码,仅为简化类型名
      1
      2
      class StringMap: public Map<string, string>{}
      // 创建不包含代码的子类,简化代码

子类局限

子类对象是其所属父类的实例,但是有其局限

  • 将子类对象赋值给父类对象会导致子类特有实例变量值被丢弃 (因为父类对象在栈中空间较小)

  • 常用解决方法是使用指针,指针变量大小相同

    • 但使用指针会使内存管理变得复杂
    • 为类定义析构函数可以管理内存,但是指针越界时,不能 保证析构函数会在合适的时间调用
  • 最好方法是避免使用类继承,并创建独立的类管理自身堆内存, 否则

    • 完全禁止拷贝:定义私有拷贝构造函数、重载赋值操作符, 但这会使得对象难以嵌入大型数据结构
    • 执行深拷贝:用户需要承担内存管理,重载拷贝构造函数、 重载赋值操作符

Multiple Inheritance

多重继承:类可以继承自多个父类

  • 多重继承在实际编程过程中可能导致程序复杂、模糊不清
    • 多重继承的多个父类可能拥有多个相同方法名、数据域名
  • C++中单继承已经足够复杂,最好避免使用多重继承

final

final:指定虚函数不能被派生类覆盖、类不能被继承

  • 语法

    • 虚函数:在声明符之后
    • 类:紧跟类名后
    1
    2
    3
    4
    5
    6
    7
    struct A final{}
    struct B{
    virtual void foo();
    }
    struct C: B{
    void foo() final;
    }

override

override:指定派生类虚方法覆盖基类虚方法

  • 语法:在成员函数声明之后 (其他情况下override甚至不是关键字)

  • 用途:有助于防止代码出现意外继承行为

    • 即不是强制性关键字,是辅助性、可选
    • 非虚函数可以通过类型转换调用基类方法,不算覆盖???

Iterator

迭代器:指向集合中一个特定元素,每次可以通过单步递进方式访问 其他元素

  • 迭代器使用*操作查找其指向的值

迭代器层次结构

iterator_hierarchy

  • InputIterator:允许读值
  • OutputIterator:允许给解析的迭代器赋新值
  • ForwardItertor:结合InputIteratorOutputIterator ,允许读写值
  • BidirectionIterator:在ForwardIterator基础上允许向后 迭代,增加--操作符
  • RandomAccessIterator:在BidirectionIterator基础上 允许向前、向后移动任意元素,包含全部关系符

指针作为迭代器

  • 指针类型已经实现了RandomAccesIterator提供的所有操作符 ,所以可以使用指针作为迭代器

  • 很多实现已经将iterator作为类中嵌套迭代器的类型,要用 typedef重命名指针类型为iterator

    1
    typedef ValueType * iterator;
    • 如:编译器内部利用迭代器,将基于范围for展开
  • 编译器内部iterator类型指针变量参见 cppc/basics/intro

类方法

Constructor

构造函数:创建对象

  • 构造函数名与类名完全一样

  • 构造函数无法被子类继承,只能在初始化列表中调用

    • 缺省编译器调用父类默认构造函数
    • 若没有构造函数,则必须显式在初始化列表中初始化

执行阶段

  • 初始化阶段:初始化列表

    • 调用父类构造函数初始化父类成员变量

      • 缺省调用默认构造函数
      • 若父类没有默认构造函数,必须在初始化列表中显式 调用构造函数
    • 初始化类自身数据域

      • 对一般类型,缺省类似于普通未赋值初始化
      • 对类类型成员变量,缺省没有显式初始化则调用默认 构造函数
  • 计算阶段:执行构造函数体

    • 在函数体中执行的都是赋值,不是初始化

Initializer List

初始化列表:初始化父类、自身数据域

  • 位于的构造函数体花括号前、参数列表之后,用:和参数列表 分隔
初始化父类
  • 父类名后用括号括起来的参数列表,参数列表必须和父类 某一构造函数原型相匹配

    1
    Foo(string name, int id):(name, id){};
  • 父类数据域后用括号括起的该数据域的初始化值(此方法 也可以用于初始化类自身数据域)

    1
    Foo(string name, int id): name(name), id(id){};
  • 调用父类构造函数

    1
    Derived(string name, int id):Base(name, id){};
初始化自身数据域
  • 类类型成员变量最好使用初始化列表初始化,可以避免 构造函数初始化阶段调用成员变量默认构造函数

  • 必须放在初始化列表中成员变量

    • 常量成员:常量只能初始化,不能赋值
    • 引用类型:引用必须在定义时初始化,不能重新赋值
    • 没有默认构造函数的类类型
  • 成员变量按照其在类中声明顺序初始化,而不是在初始化 列表中的顺序

    • 为避免因成员变量初始化依赖导致的未定义,应该按照成员 变量声明的顺序初始化

Default Constructor

默认构造函数:没有参数的构造函数

  • 若类没有默认构造函数,编译器会自行定义

Copy Constructor

1
Foo(const Foo &f){};

拷贝构造函数:使用同类实例初始化创建新对象

  • 用途

    • 复制对象
    • 传值调用时,隐式调用拷贝构造函数构造新对象传参
  • 类中没有定义拷贝构造函数时,编译器会自行定义一个

    • 若类中有指针变量、并有动态内存分配,则必须定义拷贝 构造函数

explicit

epxplicit关键字:声明为explicit构造函数不能在隐式转换中 使用

  • 用途
    • 希望函数参数只能是给定类型,可以禁止隐式类型转换

Destructor

析构函数:类的对象消亡时,析构函数被自动调用

  • 析构函数可以完成各种清理操作
    • 最重要:释放对象所创建的所有堆内存
    • 关闭对象打开的任何文件
  • 析构函数名称:类名前加上~
  • 析构函数特点
    • 没有返回类型
    • 不能重载
    • 每个类只有一个无参的析构函数
  • 良好设计的C++应用中,每个类都需要堆其对内存负责

Operator Overloading

操作符重载:扩展标准操作符以适应新的数据类型

  • 编译器遇到操作符时,会根据操作符操作数的类型确定其 操作语义

  • 重载操作符函数名由关键字operator后跟操作符构成

    • 操作符的左右操作数类型、传值方式
    • 操作符返回值
      1
      2
      3
      ostream & operator<<(ostream & os, Point pt);
      // 流对象不能拷贝,必须引用传递
      // 返回依然是流引用
  • 有些类型根本没有定义过某些操作符,虽然实际上是扩展该类型 操作符,但是也成为重载操作符

  • overload:重载,使用相同名字的不同版本类方法

重载方式

C++提供两种机制用以重载内置操作符使得可以适用于新定义类

  • 类方法:在类中用方法重载操作符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // .h
    Class Point{
    Public:
    bool operator==(Point rhs);
    // 类中声明操作符重载方法
    }

    // .c
    bool Point::operator==(Point rhs){
    // 实现中限定为`Point`方法
    }
    • 左操作数为该类型对象(声明、实现省略)
    • 右操作数作为形参传递
    • 编译器把左操作数视为接收者,将右操作数作为形参传递
  • free function在类外使用自由函数重载定义操作符

    1
    2
    3
    4
    5
    6
    // .h
    bool operator==(Point pt1, Point pt2);

    // .c
    bool operator==(Point pt1, Point pt2){
    }
    • 二元操作符的两个操作数都作为形参传递
    • 操作符重载自由函数一般需要声明为类的友元函数,使其 能够访问类的私有实例变量

++/--

重载++/--时,必须指明是重载前缀还是后缀形式

  • 额外传入无意义整形参数说明重载后缀形式
  • 常用于枚举类型,方便遍历枚举类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Direction operator++(Direction & dir){
// 重载前缀形式
dir = Direction(dir+1);
return dir;
}

Direction operator++(Direction & dir, int){
// 重载后缀形式
// 这也说明,C++中若不需要使用形参值,可以省略形参名,
// 即使是在函数实现中
Direction old = dir;
dir = Direction(dir + 1);
return old;
}

=

  • 赋值操作符被定义为返回其左值,所以重载赋值操作符时注意 返回值

  • 赋值操作符开头往往检查左、右操作数地址是否一样

    • 避免不必要拷贝操作、逻辑错误

()

  • 重载函数调用操作符()将可以像函数一样调用对象
1
2
3
4
5
6
7
8
9
10
11
class AddFunction{
Public:
AddFunction(int k){
this->k = k;
}
int operator(int x){
return x+k;
}
private:
int k;
}
  • 重载此方法类称为function class(函数类),其实例称为 function object/functor(函数对象、函数子)

const方法

C++允许在方法参数表后增加关键字const指定方法,声明 其不改变其对象的状态

  • 在接口原型、实现中都应该使用const关键字约束
1
2
3
4
5
6
7
// .h
int size() const;

//.cpp
int CharStack::size() const{
return count;
}

Friend

友元:允许访问类中私有实例变量的元素(不是类方法)

  • 友元函数:允许访问类中私有变量的自由函数

    1
    2
    3
    4
    5
    friend prototype;
    // 在类定义中声明友元函数
    class Point{
    friend bool operator==(Point p1, Point p2);
    }
  • 友元类:允许访问类中私有实例变量的类

    1
    2
    friend class cls_name;
    // 在类中定义中声明友元类
    • 友元类的声明不是双向的,需要两个类都显式声明另一个类 为友元类,才能相互对方私有变量

典型方法

  • Accessor/Getter:访问器/读取器:获取实例变量值的函数

    • 为方便,读取器的命名通常以get为前缀
  • Mutator/Setter:设值方法/设值器,为特定实例变量 设置值的方法

    • 为方便,设置器的命名通常以set为前缀
    • 将实例变量设为私有就是为了阻止用户不受限制访问变量, 所以读取器的设置更为常见
    • 事实上,immutable设计风格将类设置为完全不可变

类内存结构

  • 类的方法地址不会存储在实例的内存空间中

非虚函数

  • 编译时

    • 编译器根据变量类型(指针、引用类型)在调用函数 处写入类方法的地址
    • 编译时即确定执行的函数
    • 可以认为是普通函数,只是默认包含参数this指针
  • 运行时

    • 访问方法相应内存地址,传入this指针调用方法

(Pure)Virtual Method

  • 虚方法/虚函数:virtual修饰的方法,基类中有定义
  • 纯虚方法:没有函数体的虚函数,基类中无定义,实现只能由 其子类提供
  • 派生类中覆盖方法总为虚方法,无论是否有virtual关键字
    • 建议后代虚函数加上virtual以提升可读性
1
2
3
4
virtual double getpay();
// `virtual`:普通虚函数
virtual double getPay() = 0;
// `=0`:纯虚函数,没有函数体
  • 编译时

    • 编译器根据类的声明创建虚表,每个类维护一张虚表
    • 对象被构造时,虚表地址在实例内存的首个字
    • 编译器在调用函数处不直接写入函数地址(因为不确定 调用的函数)
  • 执行时

    • 从对象首读取虚表,在虚表中查询需要执行方法地址
    • 访问相应方法地址,传入this指针调用方法
    • 实现多态:subtyping polymorphism,同一语句具体调用 方法动态决定
  • 多态:参见program/program_design/language_design
  • vTable:虚表,存储一个类中所有虚函数地址
    • 虚表是依次存储当前类中所有虚方法,不是继承序列 中同一个虚方法

Abstract Class

抽象类:包含纯虚方法的类

  • 抽象类主要作用

    • 将有关类组织在继承层次结构中

    • 刻画一组子类的操作接口的通用语义,只描述派生类共同 操作接口,完整实现留给子类

  • 抽象类只能作为基类派生新类使用,不能创建抽象类对象

    • 所以抽象类也不能做参数类型、函数返回类型、显式转换 类型

    • 可以定义指向抽象类的指针、引用,可以指向其派生类, 进而实现多态

    • 此特性本身就是为了解决基类生成对象不合理的问题, 抽象类就是为了抽象设计目的而建立

    • 实际中,为了强调类是抽象类,可以将类的构造函数 放在protected区域

    • 其派生类必须实现基类中所有纯虚函数,才能成为非抽象类 ,否则仍然是抽象类

  • 构造/析构函数内不能使用纯虚函数(一般成员方法内可以)

    • 因为派生类构造/析构函数内会调用基类构造/析构函数

    • 纯虚函数没有函数体,不能被调用

Override

覆盖/重置:在派生类中签名一样的方法会覆盖父类的方法

  • 非虚方法:调用方法取决于指针、引用类型

    • 将派生类对象赋给基类指针、引用后,调用方法是基类方法 ,表现父类的行为
  • 虚方法:动态检查,调用方法取决于对象实际类型

    • 将派生类对象赋给基类指针、引用后,调用方法是派生类 方法
    • 即指针(引用)根据指向值决定行为,实现多态

Template

模板:parameterized class参数化类,包含base type规格说明 的类

  • 模板是C++多态的一种实现

  • 编译器遇到函数模板调用,会自动生成相应版本函数拷贝

    • 因此模板必须能实现,即不能将模板接口、实现分开, 否则编译器无法生成相应版本函数拷贝 (当然可以通过#include强行将接口、实现分开)
    • 模板不能节省空间

模板使用

在函数、类前添加

1
template <typename ValueType>
  • template关键字:表示此行后整个语法单位是模板模式一部分
  • ValueType:类型占位符,生成函数拷贝时被替换为相应类型

模板函数

1
2
3
4
template <typename ValueType>
ValueType max(ValueType x, ValueType y){
return (x > y) ? x: y;
}

模板类

1
2
3
4
5
6
7
8
9
10
11
// .h
template <typename T1, typename T2>
class Stack{
T1 d1;
T2 d2;
}

template<typename ValueType>
Stack<ValueType>::Stack(){
}
// 模板实现、接口在同一文件中

Template Specialization

模板特化:指定一个或多个有具体模板参数的模板

  • 全特化:给出所有模板参数
  • 偏特化:给出部分模板参数
  • 模板实例化时,优先使用模板参数最匹配的模板版本

  • 通过特化模板,可以对特定模板参数集合自定义当前模板

    • 最好特化模板的接口和普通模板一致

类模板特化

类模板可以全特化、偏特化

  • 特化的模板参数从模板参数列表中移除
  • 在类名后给出完整参数列表
1
2
3
4
template <typename T2>
class Stack<int, T2>{
...
}

函数模板特化

函数模板只能全特化

  • 若编译器可以通过返回值类型推断模板实参类型,可以省略 函数后模板参数列表,否则引起歧义报错
  • 函数模板“偏特化”可以通过函数模板重载实现
1
2
3
4
template <>
int max(int x, int y){
...
}

常见分布

离散

连续

P-stable Distributions

p_stable distribution:随机变量 $\sum_i v_i X_i$ 、随机变量 $(\sum_i |v_i|^p)^{1/p} X$ 具有相同的分布

  • $v_1, v_2, \cdots, v_n$:任意实数
  • $X_1, X_2, \cdots, X_n$:独立同分布$D$随机变量
  • $X$:服从分布$D$随机变量
  • $\forall p \in (0, 2]$,稳定分布存在,但仅$p=1,2$时,有解析解

    • $p=1$:柯西分布

    • $p=2$:高斯分布

  • 可以从$[0,1]$上均匀分布获得稳定分布

    • 但是概率分布、密度函数没有解析解

性质、用途

  • 若向量 $a$ 中每个元素独立从 p-stable 分布中抽取,则 $|v|_p X = (\sum_i |v_i|^p)^{1/p} X$ 和 $$ 同分布
    • 可用较好计算的内积估计 $|v|_p$
    • 考虑到 $a(v_1 - v_2) = av_1 - av_2$,将内积和点之间 $L_p$ 范数距离 $|v_1 - v_2|_p$ 相联系

Exponential Family of Distributions

单变量指数分布概率密度/分布

  • $\eta(\theta)$:nutural parameter,自然参数
  • $h(x)$:underlying measure,底层观测值
  • $T(x)$:sufficient statistic,随机变量X的充分统计量
  • $A(\theta)$:log normalizer,对数规范化
  • $\eta(\theta), T(x)$:可以是向量,其内积仍为实数

  • $\eta(\theta) = \theta$时,称分布族为canonical形式

    • 总是能够定义$\eta = \eta(\theta)$转为此形式
  • 对数规范化$A(\theta)$使得概率密度函数满足积分为1

性质

  • 充分统计量$T(x)$可以使用固定几个值,从大量的独立同分布 数据中获取信息

    todo

Bernoulli分布

  • $h(x) = 1$
  • $T(x) = x$
  • $\eta = log \frac \theta {1 - \theta}$
  • $A(\theta) = ln(1+e^{\theta})$

Possion

  • $\theta = \lambda$
  • $h(x) = \frac 1 {x!}$
  • $\eta(\theta) = ln\lambda$
  • $T(x) = x$
  • $A(\theta) = \lambda$

Normal

  • $h(x) = \frac 1 {\sqrt{2\pi\sigma^2}} e^{-\frac {x^2} {2\sigma^2}}$
  • $T(x) = \frac x \sigma$
  • $A(\theta) = \frac {\mu^2} {2\sigma^2}$
  • $\eta(\theta) = \frac \mu \sigma$

RFC

局域网协议

Identification Protocal

Ident协议

  • 如果客户端支持Ident协议,可以在TCP端口113上监听ident请求

    • 基本每个类Unix操作系统都带有Ident协议
  • Ident在组织内部可以很好工作,但是在公共网络不能很好工作

    • 很多PC客户端没有运行Ident识别协议守护进程
    • Ident协议会使HTTP事务处理产生严重时延
    • 很多防火墙不允许Ident流量进入
    • Ident协议不安全,容易被伪造
    • Ident协议不支持虚拟IP地址
    • 暴露客户端用户名涉及隐私问题
  • Ident协议不应用作认证、访问控制协议

Internet协议