《Java核心技术》笔记

《Java核心技术》笔记

《鸟哥的Linux私房菜》笔记

学习《鸟哥的Linux私房菜》时,
在印象笔记上手码的Linux笔记

python笔记

使用ipython notebook学习python
模仿教材敲代码并用注释markdown在旁写下注解

《python基础教程》笔记

模块学习笔记

Bottle框架

sh模块

sh

tornado框架

Tornado

指针&数组

指针和数组是C语言中常用的类型(数据结构)
《C Programming Language》一书中提到,指针访问比数组访问效率高一些,这是真的吗?
本着一个求知(ba gua)的心,我决定一探究竟=。=

指针和数组

数组和指针的关系、区别是什么?

简单地说,数组名是一个指针常量

  1. 数组名映射到程序常量区的一段数据,其中保存了数组的首地址
  2. 数组名的值不能改变,不能作为左值
  3. 通过下标访问数组元素是通过数组首元素地址(数组名)加上偏移量实现的
    因此,指针必须有类型限制(才能够正确的计算步长)
    另外,对于数组a,a[6]6[a]是等价的
    没错!6[a]是成立的,但是为了程序的可读性,尽量不要刻意这么用

C语言的多维数组

“数组的数组”和“多维数组”

  • 数组的数组:
    即为“有一个数组,它的元素也是数组”,诸如a[2][2]的形式
  • 多维数组:
    即为“有一个数组,它的元素都是整型(浮点数等)数据,但是它有多个维度”,诸如a[2,2]的形式

显然,C语言所说的“多维数组”,本质上是“数组的数组”

多维数组的内存布局

简单来说,C语言以“行主序”的规则布局内存,也就是说“最右边的下标先变化”
以二维数组为例,a[2][4]的内存布局如下:
a[0][0], a[0][1], a[0][2], a[0][3], a[1][0], a[1][1], a[1][2], a[1][3]

锯齿状数组的使用

锯齿状数组:某一维或某几维长短不一的数组
以二维数组为例,char a[5][50]
我可以在a[0]保存一个很长的字符串,在a[1]保存一个很短的字符串,但是这样做就浪费了许多的空间
这时候,我们就可以创建一个锯齿状数组来节约空间

char my_string1[] = "aaaaaaaaaaaaaaaaa";
char my_string2[] = "a";
char *my_strings[5] = { my_string1, my_string2 };

char a[3];char b[4];不是同种类型

虽然元素都是字符,而且都能通过char *指针访问,但它们的确是属于不同的类型
用二维数组来展示更加明显,假定char c[1][2];char d[3][4];
那么前者只能由指针char (*p)[2];来访问
后者只能由指针char (*q)[4];来访问
两者显然是不同的

char *p = "abc";char a[] = "abc"

前者的”abc”保存在常量区中,不可改变
后者的”abc”保存在栈中,是可变的
如果试图改变前者的元素,如p[1] = 'd';,编译能够通过,但运行时会导致程序异常(试图修改常量区内容)
char *p = "abc";这种写法存在着弊病,C++尝试否定这种写法,但这种写法过于普遍,最后为了兼容C而只好接受
更好的写法是认为的加上常量限定符,即const char *p = "abc";,来显式说明p指向的是一个常量
这样,当尝试去修改p所指向的值时,编译器就会报错

函数传值

无论函数的形参是char a[]还是char *a
a都是一个可变的指针变量,而不会作为一个常量

另外,假定有如下定义:

void main(){
    int a[6];
    example(a);
}
void example( int a[] ){
    /*empty*/
}

sizeof( a )得到的是数组的空间大小(而非长度)
sizeof( b )得到的是一个指针的空间大小(而不能取得数组的大小)
所以,无法通过sizeof运算符在函数内部获取到“传入”的数组的大小
另外,为了直观,应尽量使用形参char *a

那么,如何得知传入的数组的大小?

  1. 增加一个额外的参数,提示数组的长度
  2. 为数组的最后一个元素赋予特殊的值,来提示数组的结束

效率比较

《C专家编程》指出,
“使用现代的产品质量优化的编译器,一维数组和指针引用所产生的代码并不具有显著差别”

通过下标访问元素

这里以char a[] = "abcd";char *p = a;为例
比较a[3]p[3]的区别
(假设a数组的首地址为123,p的地址为789)

  • a[3]
    1. 计算目标地址,即123 + 3 * 1得到a的第四个元素地址为126
    2. 在地址126处提取一个字节的字符
  • p[3]
    1. 在地址789处提取四个字节的地址,即a数组的首地址123
    2. 计算目标地址,即123 + 3 * 1得到a的第四个元素地址为126
    3. 在地址126处提取一个字节的字符

可以看到,指针访问比数组访问多了一个步骤,从这一点看,通过下标的方式,数组访问的效率反而稍高一些

处理多个元素

这里以char a[] = "abcd";char *p = a;为例
通过两种方式来遍历数组中的所有元素
(假设a数组的首地址为123,p的地址为789)

  • 数组访问

    for(i=0; i<3; i++){
        t = a[i];
    }
    
  1. 访问第一个元素
    计算目标地址,即123 + 0 * 1得到a的第一个元素地址为123
    在地址123处提取第一个字符’a’
  2. 访问第二个元素
    计算目标地址,即123 + 1 * 1得到a的第二个元素地址为124
    在地址124处提取第二个字符’b’
  3. ……
  • 指针访问

    for(i=0; i<3; i++){
        t = *p++;
    }
    
  1. 访问第一个元素
    在地址789处提取四个字节的地址123
    在地址123处提取第一个字符’a’
    p自增即p + 1124
  2. 访问第二个元素
    在地址789处提取四个字节的地址124
    在地址124处提取第一个字符’b’
    p自增即p + 1125
  3. ……

可以看到,数组访问每次都要计算目标地址,而指针访问只需要直接提取数据
尽管指针需要偏移,但只作加减运算,而数组访问需要进行乘法运算,显然效率低一些
(实际上,现代计算机处理器的乘法运算得到很好的优化,其计算速度已经接近加减运算了,真正的瓶颈在于除法运算)

现代编译器

现代编译器常常会直接把数组转换成对应的指针形式,
在这种编译器下,无论是数组访问还是指针访问都会形成相同的机器指令

参考资料

#

《C专家编程》小记

书上底层的东西比较多,还有些东西不是很理解,等学习完更多的东西后得回头看看
知识点比较杂,这里只是简单的把自己觉得有用的东西罗列出来

  • C语言的基本数据类型与底层硬件相对应
    • 使用应适量,不宜过度使用
    • 宏名建议要大写
    • 不宜使用宏来改变C语言的基础结构
  • 不宜省略形参名
  • 合法的指针赋值形式
    1. 两个操作数都是指向有限定符或无限定符的相容类型指针
    2. 左边指针所指向的类型必须具有右边指针所指向类型的全部限定符
  • const并不能把一个变量变为一个常量
    只是限定它不可修改
    const常用于函数的形参
  • 算术转换
    当执行算术运算时,操作数的类型如果不同,就会发生转换。
    数据类型一般朝着浮点精度更高,长度更长的方向转换,
    整型数如果转换为signed不会丢失信息,就转换为signed,否则转换为unsigned
  • sizeof()不是函数,而是运算符,其结果为无符号整型
    • sizeof的操作数如果是类型,必须加括号,如sizeof(int)
    • sizeof的操作数如果是变量,有无括号都可以,如sizeof(a)sizeof a
  • 程序中应避免无符号数,以免增加不必要的复杂性
  • 编程语言的缺陷一般分为三类:
    不该做的做了,该做的没做,该做但做的不合适
  • NUL 和 NULL
    NUL用于结束一个字符串(即\0
    NULL表示一个空指针
  • 临时变量可声明于花括号的局部空间内(如if、while等语句的花括号内)
  • 两个相邻的常量字符串会自动合并
    注意:两个常量字符串作为函数的两个参数时,如果不小心漏掉了逗号,会出现错误
  • C语言中许多符号是被“重载”的,即在不同的上下文环境里有不同的意义
  • C语言中部分符号优先级是不符合常规逻辑的,如:
    1. ==和!=高于位操作符
      注意val & mask != 0
    2. ==和!=高于赋值符
      注意c = getchar() != EOF
    3. 算数运算符高于移位运算符
      注意msk << 4 + lsb
  • 逗号表达式的值为最右边操作数的值
    注意i = 1, 2
  • 一些比较老的程序存在诸如if( a == b & c == d )的语句
    由于==优先级高于&,所以与if( a == b && c == d)的执行效果是相同的
    历史原因:逻辑运算符是后来从&中分离出来的,但为了兼容旧的代码,只好使==优先级高于&
  • 一个表达式中各意群的执行顺序是未定义的
    如:x = f() + g() * h();,其中f()、g()、h()谁先调用是未定义的(注意f()不一定是在g()和h()之后)
  • 有些编程专家建议:只记住乘除法优先于加减法即可,其他运算都用括号显式地指定优先级
  • 结合性用于相同优先级的操作符之间消除歧义;
  • 优先级相同的操作符,其结合性也相同
  • 如果计算表达式的值时需要考虑结合性,最好将其一分为二或者使用括号
  • 函数调用中,各个参数的计算顺序是不确定的
    但是传参顺序是一定的(从右向左)
  • “在调用函数时,参数按照从右到左的次序压到堆栈里”的说法是错误的
    参数在传递时首先尽可能地放到寄存器中,以提高速度
    注意:int变量i结构变量s的int成员的传参方式是不同的
    前者一般会传递到寄存器中,后者一般会传递到堆栈中
  • gets()函数存在严重漏洞,官方手册强烈建议用fgets()函数取代
    即把gets(line)改为
    if( fgets(line, sizeof(line), stdin) == NULL ) exit(1);
  • C语言中读取argv的选项,其开头必须先判断首字符是否为“-”(有可能是文件名而不是参数)
  • 尽量避免用\转义回车来将代码分解为多行
    万一在\之后不小心加入了空格,将带来难以发现的bug
  • maximal munch strategy(最大一口策略)
    如果下一个标记有超过一种的解释方案,编译器将选取能组成最长字符序列的方案
    如:z=y+++x将被解释为z = (y++) + x(但是最好还是用括号显示表示出来)
  • 不应试图返回一个指向局部自动变量或数组的指针
    若为单一变量,会引起报错
    但如果为数组,有可能会因为某些间接形式而不会报错,该bug不易发现
  • 函数返回数组的方法
    1. 若为字符串常量,可以直接返回指针
    2. 使用全局声明的数组
    3. 使用静态数组
    4. 显示分配一些堆内存
    5. 最好的方法:由调用者分配堆内存,将内存的指针传入函数
  • lint程序已经从编译器中被分离的出来,但是源代码应尽可能通过lint和cstyle程序的检查
  • C语言声明形式的原则:与使用形式相似
    如:int *p;
    但是,这种原则并不直观,如指针声明形式改为int &p;更为合理(C++已采纳这种声明形式)
  • const和指针
    int const * grape;:指向的整型只读
    int * const grape;:指针只读
    记忆:把const作为类型后缀,接在*之后针对指针,接在int之后针对整型
  • 声明的结构
    1. 至少一个类型说明符(包含类型说明符、存储类型、类型限定符)
    2. 至少一个声明器(包括指针、直接声明器、初始化内容),多个声明器由逗号分隔开
    3. 有且只有一个分号
  • 声明中,与标识符最贴近的(按照优先级、结合性)的符号即为标识符的本质
    如:int (*foo[])();,最贴近的是[],即foo是一个数组
  • 结构的定义及其变量的声明最好分开
    “一行代码只做一件事”的C语言编程原则
  • 如果需要频繁的对整个数组进行赋值操作,可以把该数组放入结构中
    (结构变量可以整体赋值)
  • 联合的作用:
    1. 节省空间(但在大内存的今天,该作用不大)
    2. 将同一个数据解释成两种不同的东西
      (下面这个例子可以整体取得整个四字节变量,也可以分别取得各个字节)
union bits32_tag{
    int whole;
    struct { char c0, c1, c2, c3; } byte;
} value;
  • 可以巧用枚举类型来方便调试
    宏定义的名字在编译时会被丢弃,但枚举名字在调试器中会一直存在
  • C语言声明解析方法(见书P64-66)
  • typedef#define
    前者较后者,“封装”更为彻底
    后者在声明时能加入其他类型说明符进行扩展,前者声明时不行
    如将诸如int (*foo[])();的定义“封装”起来
    typedef int (*A[])();#define NEW(B) ( (*B[])() )
    1. A x;NEW(x);等价,均可行
    2. A x, y;NEW(x, y);不等价,只有前者可行
  • 不能给typedef加入多个声明器
    即不能企图通过typedef定义一个类型名,使之一次性声明多种类型的变量
  • 不能把“typedef”嵌入到声明当中
  • typedef和struct的标签名可以和变量名相同,但应尽量避免
  • typedef的用法:
    1. 数组、结构、指针、函数等的组合类型
    2. 可移植类型
  • typedef定义的标签名也可以用于强制类型转换
  • 赋值符左侧的操作数称为左值
    左值分为可修改左值和不可修改左值(如数组名)
  • 编译过程:C预处理器、前端(语法和语义分析)、后端(代码生成器)、优化器、汇编程序、链接-载入器
  • 静态链接(*.a):编译过程中,所用到的函数会被装入到可执行文件中(即每个程序对函数代码有一份单独的拷贝)
    动态链接(*.so):编译过程中,只把用到的库文件名或路径名装入可执行文件,需要时再实时调用(即每个程序对函数代码共用一份拷贝)
  • 动态编译运行速度稍慢,但可执行文件体积小,函数库版本升级更容易,而且部分函数库只能通过动态链接的形式使用
  • 函数库包含函数的定义,头文件包含函数原型的声明
    头文件的名字通常不和它所对应的函数库名相似
  • Interpositioning:
    自己编写的函数名如果与函数库函数同名,自定义的函数会取代函数库的函数而不会引发编译器的报错

  • 在UNIX中,段是一个二进制文件相关的内容块
    在Intel x86内存模型中,地址空间并非一个整体,而是分成若干64K大小的区域,这些区域称为段
  • a.out(assembler output,汇编程序输出,一种目标文件格式)依次包含以下内容:
    1. a.out的标志数字0407(即Kirk McKusick的生日,也是PDP-11无条件转移指令的二进制编码)
    2. a.out的其他内容
    3. BSS段所需的大小
      BSS即Block Started by Symbol(由符号开始的块),保存了没有值的变量
      这里只保存了变量的大小,但并不占据目标文件的任何空间
      如:C语言中未初始化的全局变量
      注意:局部变量并不进入a.out,而是在运行时创建
    4. 数据段
      包括初始化后的全局变量和静态变量
    5. 文本段
      包含可执行文件的指令
  • 进程的地址空间分布(由低到高)
    1. 未被映射的虚拟地址空间
      任何对它的引用都是非法的,一般包括几K字节,用于捕捉空指针和小整型值的指针引用内存的情况
    2. 文本段
    3. 数据段
      通常是进程中最大的段
    4. BSS段
      数据段和BSS段合称数据区
    5. 堆栈段
      • 主要有三个用途:
        1. 为函数内部声明的局部变量提供存储空间
        2. 进行函数调用时,保存相关的维护性信息(过程活动记录等)
        3. 作为暂时的存储区
      • 函数可以通过参数或全局指针访问它所调用的函数的局部变量,运行时系统维护一个用于提示堆栈当前顶部位置的指针(常位于寄存器中,称为sp)
  • C不支持函数嵌套的原因
    支持函数嵌套的语言一般采取上层引用的访问方式,需要活动记录包含一个指向它的外层函数的活动记录的指针(被称为静态链接),它允许内层过程访问外层过程的活动记录,因此也可以访问外层过程的局部数据;
    而C语言所用函数在词法层次中都是最顶层的、分别独立的
  • volatile类型修饰符
    • volatile本意为易变的
    • 用来告知编译器,程序每次使用该变量都要从内存中读取(出于优化目的,编译器有可能会直接在暂存的寄存器中读取)
    • 常用于多线程编程等情况
  • 线程控制
    • 头文件:<setjmp.h>
    • setjmp( jmp_buf j ):保存当前的过程活动记录到j,并返回0
    • longjmp( jmp_buf j, int i ):跳转到变量j所在的过程活动记录位置,并返回i
    • setjmp / longjmp{标签} / goto
      前者只能跳转回曾经到过的地方,后者可以任意设置标签的位置
      后者不能跳出函数,前者甚至可以跳转到其他文件的函数
    • 为了保证跳转过程中局部变量的可靠性,应使用volatile类型修饰符
    • 常用于错误修复(发现不可修复的错误即把控制转入主输入循环,重新开始)、从深层嵌套的函数中跳出等
    • 跟goto一样,这样控制线程会使程序难以理解和调试,应尽量避免使用
  • UNIX和MS-DOS中的堆栈段
    • UNIX的堆栈段是自动生长的
      当试图访问系统分配给堆栈的空间之外时,将会产生一个硬件中断,称为页错误
      一般,内核会通过向进程发送合适的信号(可能是段错误)来处理无效地址的引用
    • DOS的堆栈段是不可增长的,建立可执行文件时必须确定
  • 常用的C语言工具
    • 源代码检查:
      • cb:C程序美化器,使源文件有标准的布局和缩进格式
      • lint:C程序检查器
    • 可执行文件检查:
      • dis:(/usr/ccs/bin)目标代码反汇编工具
      • nm:(/usr/ccs/bin)打印目标文件的符号表
    • 调试:
      • debugger:交互式调试器
    • 性能优化:
      • time:(/usr/bin/time)显示程序所使用的实际使用和CPU时间
  • 内存媒介(速度、成本依次增加)
    磁带、硬盘、内存、cache存储器、CPU寄存器
  • 虚拟内存
    • 以“页”为单位(一般为几K)在磁盘和内存之间来回移动(page in为移入内存,page out为移到硬盘),也以“页”为单位对内存进行保护
    • 只有用户进程的内存在会被换进还出,系统内核进程常驻于物理内存当中
    • 进程只能操作位于物理内存中的页面
      当进程引用一个不存在于物理内存中的页面时,MMU(内存管理单元,负责支持虚拟内存的硬件)会产生“页错误”。内核将对事件作出响应,
      若判断有效,内核将从硬盘中取回对应的页,换入物理内存当中
      若判断无效,内核将向进程发出一个“segmentation violation(段违规)”信号
  • Cache存储器
    • 从MMU的角度看,不同机器Cache存储器的所属不同
      • 有些机器的Cache属于CPU一侧,Cache采用的是虚拟地址,每次切换进程时,它的内容必须进行刷新
      • 有些机器的Cache属于物理内存一侧,Cache采用的是物理地址,容易使多个CPU共享同一个Cache
    • 数据以“行”(一般为16字节或32字节)为单位从内存中读入Cache
    • 所有对内存的读写都要经过Cache
      当处理器需要从某个特定的地址提取数据时,这个请求首先递交给Cache
      如果数据已存在于Cache,它可以被直接提取;
      否则,Cache向内存传递这个请求,进行较缓慢的访问内存操作,并将数据读取到Cache
    • 常见的Cache类型
      • 全写法Cache:每次写入Cache时同时写入内存,保持两者内容一致
      • 写回法Cache:第一次写入时,只写入Cache;再次写入时,此时会把之前的数据写入到内存中保存起来;内核切换进程时,Cache数据都要写入内存中去
  • 拷贝整个数组
    用库函数memcpy( 目的数组, 源数组, 元素个数 )进行快速拷贝
    比循环拷贝快的多
  • malloc请求申请的内存大小为方便起见通常会被圆整为2的乘方
  • 生存时间长的程序需要管理动态内存的分配和回收,堆经常会出现两种问题:
    • 内存损坏:释放或改写仍在使用的内存
    • 内存泄漏:未释放不再使用的内存
  • alloca分配的动态内存,在离开函数时会被自动释放
    可以避免内存损坏和内存泄漏,但是不适用于创建比其所在函数生命周期更长的结构
  • 体积大的进程更有可能被系统换出,换进换出的时间也更长
  • 总线错误(bus error)
    • 主要是未对齐的读写引起的
      对齐:数据项只能存储在地址是数据项大小的整倍数的内存位置上,通过迫使每个内存访问局限在一个Cache行或一个单独的页面内,可以极大的简化并加速如Cache控制器和MMU等硬件
      出现未对齐的内存访问请求时,被堵塞的组件就是地址总线
    • 也有可能是引用物理上不存在的内存引起
  • 段错误(segmentation fault)
    • MMU异常导致的错误,通常由解除引用一个未初始化或非法值的指针引起
      但是,如果未初始化的指针恰好具有未对齐的值,它会引起总线错误但不引起段错误
    • 导致段错误的常见直接原因:
      1. 解除引用一个包含非法值的指针
      2. 解除引用一个空指针
      3. 在未得到正确的权限时进行访问(如往只读的文本段存储值)
      4. 堆栈或堆空间用完
    • 导致段错误的常见编程错误:
      1. 坏指针值错误
        对未初始化的指针解除引用,或向库函数传递一个坏指针,或解除引用已经被释放的指针
        建议养成指针释放后立即将其设置为NULL的习惯
      2. 改写错误
        越过数组边界写入数据,或在动态分配的内存之外写入数据,或改写一些堆管理数据结构
      3. 指针释放引起的错误
        多次释放同一块内存,或释放一块未分配的内存,或释放一个无效指针
  • 类型提升
    在所有表达式中,char都会被提升为int,float都会被提升为double
    在老式编译器中,传参的时候也会被提升类型,然后再被适当裁剪
    意义:简化编译器,压到堆栈中的参数都是同一长度的,运行时只需要知道参数数量,而不需要知道他们的长度
    但是,在新风格的函数定义下,已经声明过原型的函数,其参数不会进行类型提升,即char会直接以char类型进行传参
  • 如果库函数调用或系统调用出错,程序会设置全局变量errno的值来提示问题原因
    注意:只有确实出现问题时,errno的值才是有效的
    用法:在函数调用或系统调用前先设置全局变量errno = 0;,函数调用后再检查errno的值
    具体数值与其对应的错误类型参见errno_百度百科
  • C语言与有限状态机(Finite State Machine)
    • FSM用于基于输入的在几个不同的可选动作中进行循环的程序
    • 基本思路:用一张表保存所有可能的状态,并列出进入每个状态时可能执行的所有动作,其中最后一个动作就是计算(通常是在当前状态和下一次输入的基础上,再经过一次查询)下一个应该进入的状态
    • C语言实现:绝大多数基于函数指针数组,状态函数返回一个通用的函数指针(void *),再转换为适当的类型
    • 分析环(最简单的FSM)
      • 分析环用于绝大多数状态转换都是按连续的顺序进行的,与输入无关的程序
      • 不需要建立一个转换表用于匹配状态/输入以获得下次状态
    • 直接用switch语句和循环也可以实现简单的FSM
  • 如果函数的参数涉及多个不同的参数,可以考虑使用类似int argc, char *argv[]的参数计数器和一个字符串指针数组
  • 可调试性编码:
    把系统分成几个部分,先让程序总体结构运行,再逐步细化调试
  • 程序的提示信息应具有启发性,而非煽动性,要避免使用带有亵渎性、口语化、幽默(平时自己的小程序还是该幽默点的好QAQ)或者夸张的非专业用语
  • 使用realloc()函数时,不应直接把返回值直接赋给原指针
    否则,一旦realloc失败,返回NULL,那么将无法重新访问、或是释放原来的内存块
  • 指针&数组
  • 复合符号中间不允许嵌有空白
    a---b为例,
    • 根据C语言词法分析器的“贪心法”处理策略,
      该表达式应被视为( a-- ) - b
    • 在变量与符号之间添加空白,如a --- b是没有问题的,
    • 但如果在符号之间添加空白,会使符号失去原意,产生准二义性问题,
      a - -- b将被解释为a - ( --b )
    • 除了++--之外,/*等也可能产生准二义性问题
      y=x/*p/*会被解释为注释起始
      y = x / * p/*会分别被解释为 除法解除引用
  • else始终与 同一对括号内 最近未匹配 的if结合

《vim实用技巧》笔记

=。=最近在看《vim实用技巧》
对一些内容作点简单的记录,方便以后查阅

Vim解决问题的方式

  • 多用.命令(一个微型宏)来重复一些简单的操作
  • 减少无关的移动,形成合理的撤销块
  • 善用复合命令减少操作
    A == $a,插入在行末
    C == c$,替换行末字符并持续插入
    s == cl,替换当前字符并持续插入
    S == ^c,替换前一字符并持续插入
    I == ^i,从行首开始编辑
    o == A<CR>,在下方插入新行
    O == ko,在上方插入新行
    ……
  • 尽可能使修改、移动变得可重复
  • 常用的重复与回退操作
    • 一般的修改{edit}
      .重复,u回退
    • 行内的查找[f|F|t|T]{char}
      ;重复,,回退 下
    • 文档内的查找[/|?]{pattern}<CR>
      n重复,N回退
    • 执行替换:s/target/replacement
      &重复,u回退
    • 执行一系列修改qx{changes}q
      @x重复,u回退
  • .范式
    用一个键移动,用另一个键执行的可重复修改操作

普通模式

  • 停顿思考时切换到普通模式
  • 合理地切分撤销单元(模式间的切换)
  • 如果在插入模式中移动的光标,将会产生一个新的撤销单元
  • 删除单词daw,可以解读为”delete a word”,该命令可重复
  • 简单的算术运算{num}<C-a>加法,{num}<C-x>减法
    不需要把光标移动到数字上
    运算为num加减当前光标或光标以后的数字
    注意:默认情况下0开头的数字会被当作八进制,0x开头则为十六进制
    可以通过set nrformats=关闭进制识别(都作为十进制数)
  • 重复&次数
    删除多个单词有两种风格——
    1. dw+.的重复风格
      常用风格,使用灵活,方便回退,无需数单词个数
    2. d2w2dw的次数风格
      多用于删除一整块词组,如a couple of;可以提高撤销单元的连贯性
  • 操作 = 操作符 + 动作命令{motion}
    • 操作符
      • c:修改
      • d:删除
      • y:复制到寄存器
      • g~:字母大小写反转
      • gu:字母转换为小写
      • gU:字母转换为大写
      • >:增加缩进
      • <:减小缩进
      • =:自动缩进
      • !:用外部程序过滤{motion}跨越的行
    • 动作命令
      • [h|j|k|l]:左下上右
      • [-|+]:上一行(下一行)的非空白字符
      • 0(数字):行首(含空白)
      • ^:行首(不含空白),即本行第一个非空白字符
      • $:行末
      • gg:文首
      • G:文末
      • [f|F|t|T]{char}:右侧(左侧)的第一个char字符
      • [;|,]:下一个(上一个)”f|F|t|T”的char字符
      • :{num}:至第num行
      • [w|b]:后一个(前一个)或当前单词的头部(尾部)——含符号
      • [W|B]:后一个(前一个)或当前单词的头部(尾部)——跳过符号(仅字母和数字包括负号)
      • ............................
      • 动作命令可以待有修饰符前缀
        • a:即an,表示一个非空白对象
        • i:即inner,表示一个内含对象
    • 当一个操作符被连续调用两次,就表示作用于当前行
      特例:g~~guugUU
  • 操作符待决模式
    键入操作符后vim会进入操作符待决模式,此时vim会等待一个动作命令后执行操作
    在该模式下可以按<ESC>来退回普通模式
    因为该模式的存在,使得自定义操作符及动作指令能够存在

插入模式

  • 单词输入错误,应删除整个单词再重新输入
    可以减少以后输入出错
  • 删除
    • <C-h>:删除前一个字符,相当于退格键
    • <C-w>:删除前一个单词
    • <C-u>:删除至行首
    • 注意:只能删除本次插入模式下插入的内容
    • 这些命令不是插入模式独有,也不是vim独有,在命令行模式、shell中都能使用它们
  • 切换到普通模式使用<C-[>更加方便
  • 插入-普通模式:<C-o>
    执行一个普通模式命令后,马上就可以返回到插入命令
    在插入模式下,通过<C-o>zz迅速把当前行移动到屏幕正中
  • 插入模式下的粘贴几个单词
    <C-r>{register}:将register号寄存器的内容粘贴到当前位置
    普通模式下复制文本默认保存在0号寄存器中
    注意:当文本比较多的时候,应切换到普通模式下操作比较合适
  • 运算
    <C-r>={expr}<CR>:执行expr运算并且把结果插入到当前位置
  • 用字符编码插入字符
    • 十进制ASCII字符:<C-v>{code}
      只能插入三位十进制数的ASCII码,注意高位补0
    • 十六进制unicode字符:<C-v>u{code}
      插入四位十六进制数的unicode码,注意高位补0
    • 通过二合字母插入字符
      <C-k>{char1}{char2}
      二合字母集可以通过:digraphs查看
      :digraph-table可以获得二合字母集更详细的信息
  • 查看当前字符的编码
    ga:屏幕下方会显示当前字符的十进制、十六进制、八进制编码信息
  • 虚拟替换模式
    R:替换模式,<Tab>会作为一个字符被替换
    gR:虚拟替换模式,<Tab>会作为多个字符(一般为八个)被逐个替换(直到末尾才替换掉<Tab>
    rgr同理~
    应尽量使用虚拟替换模式

可视模式

  • 三种可视模式
    1. v:操作字符文本
    2. V:操作行文本
    3. <C-v>:操作块文本
  • 该模式与其他模式不同,是先选择文本后触发命令
  • 选择模式<C-g>
    类似与windows的选择模式,输入的可见字符会替换掉选中的文本,之后vim进入插入模式
    这个模式只是为了迎合windows用户,应尽量少用
  • gv:重选上次的高亮选区
  • 该模式下的.命令有时候会出现异常
    所以应尽可能使用操作命令而不是可视命令
  • 有时候修改文本的范围很难用动作命令表达出来,这时候才用到可视命令
  • 修改多行文本时,只有第一行发生变化,只有当返回普通模式后,其他行才会发生变化
  • <C-v>不仅可以选中矩形的区域,还可以选中长短不一的块
    当在该模式下选中行末部分,可以实现长短不一的块的选择

命令行模式

  • 三种形式的命令行模式
    1. 按下:的Ex命令
    2. 按下/的查找命令
    3. <C-r>=访问表达式寄存器
  • 指定命令作用范围
    • 指定行
      :{number}....
      只包含数字的Ex命令表示跳转
      可以使用特殊符号$表示最后一行,%表示当前文件的所有行
    • 用地址来指定一个范围
      :{start},{end}....,执行命令后光标将跳转到end行
    • 用高亮选取指定范围
      先用可视模式v,V,<C-v>选取高亮区
      再按下:,此时命令行会自动填充成:'<,'>,表示作用在高亮区上
    • 用模式指定
      :/{pattern1},/{pattern2}/....
      例如::/<html>/,/<\/html>/指定了html内的所有内容(包含<html></html>
      注意:斜杠/有特殊含义,需要用反斜杠\转义
    • 地址偏移
      直接对地址进行+-运算
      如::/<html>/+1,/<\/html>/-1则排除了<html></html>而只包含了其中间的内容
      如果不加数字,默认偏移量为1
    • 特殊符号总结
      • 1:第一行
      • 0:虚拟行,位于第一行上方,常用于插入到首行等功能
      • $:最后一行
      • .:当前行
      • %:整个文件,相当于:1,$
      • 'm:包含位置标记m的行
      • '<'>:高亮选取的起始和结束行
  • 复制命令::t:co:copy
    :{range}t {address}
    与普通模式下的y命令不同,该命令不把文本保存到寄存器
  • 移动命令::move:m
    :{range}m {address}
  • 在指定范围上执行普通模式命令
    :{range}normal {commands},常用于多行的处理
    常用的命令如下:
    • :{range}normal .:对多行重复同一操作
    • :{range}normal A;:对多行末尾补上分号
    • :{range}normal i//:将多行内容注释掉
  • 重复上一条命令:@:
  • 使用<C-o>进入插入-普通模式时,命令记录会跳转到上一条命令,可以借此进行命令记录的跳转
  • 自动补全
    • <Tab>自动补全命令
      多次按<Tab>会正向遍历补全列表的内容
    • <S-Tab>反向遍历补全列表,S表示<shift>按键
    • <C-d>显示可用的补全列表
    • 自定义补全行为
      • bash shell形式:set wildmode=longest,list
      • zsh形式:set wildmenuset wildmode=full
        • wildmenu为补全导航列表
  • 将当前单词插入到命令行中
    常用于替换命令和查看帮助文档
    在命令行模式下,<C-r><C-w>会复制当前单词到命令行中
  • 回溯历史命令
    • 回溯所有命令
      :下保持提示符为空按<up>(<C-p>)和<down>(<C-n>)
      • <C-p>``<C-n><up>``<down>的区别
        前者方便,但不能过滤命令
        解决方法:
        .vim文件中创建映射项
        cnoremap <C-p> <Up>
        cnoremap <C-n> <Down>
    • 过滤回溯的命令
      :help<up>回溯以”help”开头的命令
    • 命令历史容量设置
      缺省只有20
      推荐set history=200
    • 除了Ex命令,查找命令也会记录下来并保存在另一个文件中
  • 一次性执行多条命令
    |隔开命令即可
  • 命令行窗口
    像一个常规的vim缓冲区,其中每一行是命令历史中的一个条目
    可以在该窗口下修改命令历史记录,对之前使用过的命令做调整后方便重复利用
    当打开命令行窗口时,它始终拥有焦点,除非关闭它,否则无法切换到其他窗口
    • q::打开Ex命令的命令行窗口
    • q/:打开查找命令的命令行窗口
    • <C-f>:从命令行模式切换到命令行窗口(起始命令行模式下输入的内容仍然保留下来)
  • 运行shell命令
    !{command}
    • %代表当前文件名
    • :shell可以打开一个交互式的shell会话(通过exit退出)
      更好的方法是用<C-z>挂起vim所属进程,用fp唤醒挂起的作业
      shell下的jobs命令可以查看当前的作业列表
  • 大量读取或写入命令输入输出
    :read !{cmd}将命令输出读取到当前缓冲区中
    :write !{cmd}:将当前缓冲区内容作为命令的标准输入
  • 借助shell命令过滤指定范围
    :{range}!{filter}
    如用sort命令,:2,$!sort -t ',' -k 2表示第2行到最后一行之间,以逗号为分隔符的第二字段排序
  • 常见的和操作缓冲区文本的Ex命令
    • :{range}d {x}:删除指定内容(并保存到寄存器x中)
    • :{range}y {x}:复制指定内容到寄存器x中
    • :{line}put {x}:在指定行后粘贴寄存器x中的内容
    • :{range}t {address}:复制指定内容到address处
    • :{range}m {address}:移动指定内容到address处
    • :{range}join:连接指定行内容
    • :{range}normal {commands}:对指定范围执行普通模式命令
    • :{range}s/{pattern}/{string}/{flag}:替换
    • :{range}global/{pattern}/{cmd}:对指定范围内匹配pattern的所有行执行cmd命令(Ex)

管理多个文件

缓冲区列表

文件读取后在内存缓冲区中

  • :ls命令可以列出所有被载入到内存中的缓冲区列表
    • 每个条目开头的数字为系统自动分配而不可改变的缓冲区编号
    • %:当前窗口中可见的缓冲区
    • #:轮换文件
      <C-^>可以在两个文件之间快速轮换
  • 缓冲区切换
    • 遍历缓冲区列表:
      • 正向移动::bn:bnext
      • 反向移动::bp:bprevious
    • :buffer {N}:跳转到指定编号的缓冲区
    • :buffer {bufname}:跳转到可以被bufname唯一标识的缓冲区(若多个缓冲区具有同一标识,可以通过<Tab>选择)
  • :bufdo {commands}:对所有缓冲区执行Ex命令
  • 创建快速遍历缓冲区列表的键盘映射

    nnoremap <silent> [b :bp<CR>  
    nnoremap <silent> ]b :bn<CR>  
    nnoremap <silent> [B :bfirst<CR>  
    nnoremap <silent> ]B :blast<CR>  
    
  • 删除:bd:bdelect
    :bd {N1,N2,N3....}:删除列出的缓冲区
    :{N,M} bd:删除连续的缓冲区

参数列表

对一批文件进行分组,其文件顺序可调整*

  • 初始的参数列表为启动时vim的文件列表
  • :args:查看参数列表
    其中[]表明了当前的活动文件
  • :args {arglist}:填充参数列表
    arglist可以是文件名、通配符、shell命令的输出结果等
    • 用文件名指定文件
      :args {file1,file2,....}
    • 用Glob模式指定文件
      *:表示0到无穷多个字符,但不包含子目录
      **:表示0到无穷多个字符,包含子目录
    • 用反引号结构指定文件
      将shell命令用反引号括起来,其输出将填充到相应的位置
      如::args \cat .chapters``
  • 隐藏缓冲区
    缓冲区列表中,+代表缓冲区被修改过,此时切换缓冲区会弹出错误信息,可以用感叹号强制切换,此时列表中被标记为a的为活动缓冲区(active),被标记为h的为隐藏活动区(hidden)
    • 处理隐藏缓冲区
      • :w:write:写入磁盘
      • :e:edit:从磁盘中读入到缓冲区(回滚修改操作)
      • :qa:qall:关闭所有窗口,放弃所有修改
      • :wa:wall:全部写入磁盘
    • :argdo:bufdo修改一组缓冲区
      前提:打开hidden选项
      打开后,对已修改的缓冲区执行:next,:bnext,cnext等命令无需再加感叹号

窗口

  • 分割
    • <C-w>s:水平切分(同高度),新窗口仍为当前缓冲区
    • <C-w>v:垂直切分(同宽度),新窗口仍未当前缓冲区
    • :sp {file}:split {file}:水平切分,新窗口为file内容
    • :vsp {file}:vsplit {file}:垂直切分,新窗口为file内容
  • 切换
    • <C-w>w:轮换
    • <C-w>{h|j|k|l}:切换到左(下上右)侧的窗口
    • 若为GVIM,可以直接点击对用窗口进行切换
  • 关闭
    • :clo:close<C-w>c:关闭当前窗口
    • :on:only<C-w>o:关闭其他重口
  • 改变窗口大小和重排列
    • <C-w>=:使所有窗口等宽、等高
    • <C-w>_:使当前窗口高度最大化
    • <C-w>|:使当前窗口宽度最大化
    • {N}<C-w>_:使当前窗口高度调整为N行
    • {N}<C-w>|:使当前窗口宽度调整为N列
    • 若为GVIM,可以直接用鼠标拖动窗口的分界线

标签页

对窗口进行分组

  • :lcd {path}:设置工作路径
    限定标签页的工程范围
    • 只能改变当前窗口,而不是当前标签页
    • 如果要改变标签页的所有窗口,应用:windo lcd {path}
  • 打开和关闭
    • :tabe {file}tabedit {file}:在新标签页中打开file
    • <C-w>T:将当前窗口移动到一个新标签页
    • :tabc:tabclos:关闭当前标签页及其所有窗口
    • :tabo:tabonly:关闭其他标签页及其所有窗口
  • 切换
    • 标签页从1开始编号
    • :tabn {N}:tabnext {N}{N}gt:切换到N号标签页
    • :tabn:tabnextgt:切换到下一标签页
    • :tabp:tabpreviousgT:切换到上一标签页
  • 重排列
    • :tabmove {N}:将当前标签页移动到N号标签页之后
      • :tabmove 0:表示移动到开头
      • :tabmove:表示移动到末尾
    • 若为GVIM,可以直接用鼠标拖曳

打开及保存文件

简单的正则表达式解析器

这是一段《代码之美》中的代码,仅用了三十余行实现了^$.*的功能
代码紧凑、优雅,值得研究推敲

/*
逐个尝试把每个text的字符作为匹配项的开头
通过递归用regexp进行尝试匹配
*/


/*match :在text中查找regexp,匹配的入口*/
int match(char *regexp,char *text)
{
    /*如果regexp开头为^,从下一个字符开始匹配,而且只需要匹配一次*/
    if(regexp[0] == '^')
        return matchhere(regexp+1,text);
    do{ /*即使字符串为空也必须检查*/
        if (matchhere(regexp,text))
            return 1;
     }while (*text++!= '\0');
         return 0;
}

/*matchhere在text的开头查找regexp*/
int matchhere(char *regexp,char *text)
{
     if (regexp[0] == '\0')
         return 1;
    if (regexp[1] == '*')
        return matchstar(regexp[0],regexp+2,text);
    if (regexp[0] == '$' && regexp[1]=='\0')
          return *text == '\0';
      /*如果出现句点'.'则直接跳过regexp和text的当前字符*/
    if (*text!='\0' && (regexp[0]=='.' || regexp[0]==*text))
          return matchhere(regexp+1,text+1);
     return 0;
}

/*matchstar :在text的开头查找C*regexp*/
int matchstar (int c,char *regexp,char *text)
{
    /*尝试结束星号*的匹配*/
    do { /*通配符*匹配零个或多个实例*/
          if (matchhere(regexp,text))
               return 1;
     }while (*text!='\0' && (*text++ ==c || c== '.'));    
     /*如果星号前为句点'.',那么可以跳过任意字符*/
     return 0;
}

JS笔记

学习网站:
w3cschool-javascript
MDN-javascript

起步

javascript的语法很多都跟C差不多

浏览器兼容问题

不同浏览器在功能的实现上有很多不同,但是可以使用javascript API接口来解决兼容性问题
WIN8支持javascript来创建windows8 app

基本的语法(只讲和C不同的)

书写脚本的位置

脚本内容必须包含在script标签中
HTML中的head标签或body标签中
外部js文件中不能包含script标签
引用外部js文件:在headbody标签中插入<script src=''></script>

变量

变量类型

字符串,数值,布尔,数组,对象,null,Undefined

  1. 当一个变量被定义但却不赋值时,其类型为Undefined,不可引用
  2. 数值可采用科学计数法,如:var x=2e-2;
声明
  1. 一般变量无需指明类型,直接使用var语句,如:var x=0;
  2. 也可以指明变量类型,借助new关键词

    var carname=new String;
    var x=      new Number;
    var y=      new Boolean;
    var cars=   new Array;
    var person= new Object;
    
  3. 也可以不显式声明,直接对一个未声明的变量复制
对于对象
  • 声明方式
    与python的字典类似,但“键”只能使用字符串,且不加引号
    var person={firstname:"Bill", lastname:"Gates", id:5566};
对于数组
  • 声明方式与python的列表类似
    var cars=["Audi","BMW","Volvo"];
  • 也可以借助new_Array()
    var cars=new Array("Audi","BMW","Volvo");
    var cars=new Array();
    cars[0]="Audi";
    cars[1]="BMW";
    cars[2]="Volvo";  
    
变量命名规则
  1. 字母开头
  2. 支持$_符号开头,但不建议
  3. 大小写敏感
重新声明

重新声明并不影响变量的值

函数

定义
function myFunction(var1,var2)
{
    ......
}

运算符

字符串连接使用加号(+)
字符串与数字相加:数字将转化为字符串
全等运算符(===)表示值和类型都相同
其他算术、逻辑运算符与C完全相同

条件语句

与C完全相同

循环语句

包含C的所有循环语句
另外支持for/in循环语句,用于遍历,如:

var person={fname:"John",lname:"Doe",age:25};
for (x in person)
  {
  txt=txt + person[x];
  }

break与continue语句

与C相同

标签

与C相同,可用于语句或代码块
continue(带或不带标签)都只能跳出循环
break(不带标签)只能跳出循环和switch语句
break(带标签)能跳出所有代码块
示例:

cars=["BMW","Volvo","Saab","Ford"];
list:
{
document.write(cars[0] + "<br>");
document.write(cars[1] + "<br>");
document.write(cars[2] + "<br>");
break list;
document.write(cars[3] + "<br>");
document.write(cars[4] + "<br>");
document.write(cars[5] + "<br>");
}

错误

抛出错误

当出现问题时,Javascript引擎会停止,并生成一个错误信息

测试与捕捉

与python的try/except语句类似

try
  {
      //在这里运行代码
  }
catch(err)
  {
      //在这里处理错误
  }

catch括号的err内为一个变量,用于错误对象
并且可以在catch代码块中引用这个变量

抛出异常

throw语句,可以接错误信息
如:throw "empty";

JS HTML DOM

HTML DOM(文档对象模型)

当网页被加载时,浏览器会创建页面的文档对象类型(Document Object Model),该模型被构造为对象的树
HTML DOM树
每一个标签都作为对象的一个元素(就像BeautifulSoup的处理方式)
每个元素都包含“属性”和“文本”两部分内容

查找元素

通过id,document.getElementById()

var x=document.getElementById("intro");
若找到,返回对象;否则返回null
注意大小写

通过标签名,x.getElementsByTagName(“p”)

var x=document.getElementById("main");
var y=x.getElementsByTagName("p");

返回的是一个包含多个对象的数组,所以这里的“element”是复数,要注意

通过类名

由于IE不支持,所以最好不要用

改变HTML

改变HTML输出流

可以用document.write()创建动态的内容,但是绝对不要在文档加载后使用

改变HTML内容

直接修改对象的.innerHTML属性的值

改变HTML属性

直接修改对象的.ATTRIBUTE属性的值
这里的ATTRIBUTE为所要修改的“属性名” ,如:x.src="";

改变CSS

直接修改对象的.style.ATTRIBUTE属性的值
这里的ATTRIBUTE为所要修改的“CSS属性名”,如:x.style.color='red';

DOM事件

可以对标签加入事件属性,用来触发javascript语句(一般触发一个函数)
提供的事件属性如下:

  • onload:进入页面时触发
    通常用来检测访问者的浏览器类型、版本,或者处理cookie
  • onunload:离开页面时触发
    通常用来处理cookie
  • onchange:字段变化时触发
    通常配合input使用来进行验证
  • onmouseover:鼠标移动到元素上时触发
  • onmouseout:鼠标移出元素时触发
  • onmousedown:鼠标按住时触发
  • onmouseup:鼠标按住后释放时触发
  • onclick:鼠标点击时触发

DOM 节点

  • 创建元素节点:document.createElement(TAGNAME)
    返回对应标签的对象
  • 创建文本节点:document.createTextNode(CONTENT)
    返回对应文本节点的对象
  • 追加节点:x.appendChild(NODE)
    不返回值
  • 删除节点:x.removeChild(NODE)
    不返回值

示例:

<div id="div1">
<p id="p1">这是一个段落</p>
<p id="p2">这是另一个段落</p>
</div>

<script>
var para=document.createElement("p");    //创建<p>元素
var node=document.createTextNode("这是新段落。");    //创建文本节点
para.appendChild(node);        //把文本节点追加到<p>元素上

var element=document.getElementById("div1");    //查找id=div1的标签元素
element.appendChild(para);    //把<p>元素追加到id=div1的元素上
</script>

JavaScript对象

创建对象

直接定义并创建

person={firstname:"John",lastname:"Doe",age:50,eyecolor:"blue"};

通过函数创建

person=new Object();
person.firstname="Bill";
person.lastname="Gates";
person.age=56;
person.eyecolor="blue";
//这里同时也演示了属性的添加

对象构造器

其实就是一个用来构造对象的函数
示例:(this即为对象自身)

function person(firstname,lastname,age,eyecolor)
{
    this.firstname=firstname;
    this.lastname=lastname;
    this.age=age;
    this.eyecolor=eyecolor;
}
myFather=new person("Bill","Gates",56,"blue");
//注意这里的new运算符,而不是直接调用函数

添加方法

把对象的一个属性绑定到一个内部的一个函数上,这个函数就是该对象的方法
可以通过追加属性添加,也可以在构造器内添加

Javascript时基于prototype的面向对象的语言,并不基于类
也就是说,没有类

数字

所有数值都为8字节,整数精度为15位,小数精度为17位

属性

  • MAX_VALUE
  • MIN_VALUE
  • NEGATIVE_INFINITIVE:负无穷大,溢出时返回该值
  • POSITIVE_INFINITY:正无穷大,溢出时返回该值
  • NaN:指示值,表明这个数据不是数字
    通常用isNaN()函数来判断一个变量是否为数字

这些属性是构造函数Number()本身的属性,而不是某个Number对象的属性
所以只能这么用:big = Number.MAX_VALUE;

方法

  • toString(radix):以radix进制转化为字符串(默认十进制)
  • toLocaleString():以本地的格式转化为字符串(本地格式??)
  • toFixed(num):四舍五入转化为num位小数
  • toExponential(num):四舍五入转化为num位小数(科学计数法)

字符串

属性

这个是对象的属性,而不是构造函数的属性

  • length

方法

  • anchor(ANCHORNAME):创建HTML锚,即将该字符串转换为带属性name="ANCHORNAME"<a>标签
  • bold():用粗体显示
  • fontcolor(COLOR):用指定颜色显示
  • fontsize(SIZE):用指定字体大小显示,SIZE为1-7
  • indexOf(SEARCHVALUE, FROMINDEX):从FROMINDEX位置(可选)开始搜索SEARCHVALUE子字符串,并返回头部索引,否则返回-1
  • italics():用斜体显示
  • link(URL):显示为指向URL的超链接
  • match(SEARCHVALUE | REGEXP):直接搜索子字符串或通过正则表达式匹配,返回数组
  • replace(REGEXP/SUBSTR, REPLACEMENT):替换,返回结果字符串
  • search(REGEXP):indexof函数的正则匹配版本
  • split(SEPARATOR, HOWMANY):以指定的分隔字符串或正则表达式分割字符串,值返回HOWMANY个结果(可选)的数组
  • strike():以删除线显示
  • sub():以下标形式显示
  • toLowerCase():转换为小写
  • toUpperCase():转换为大写

String对象都是不可变的,因此其方法都不做原位操作

日期

定义,new Date()

var myDate=new Date();
自动使用当前日期时间作为初始值

方法

设置日期:myDate.setFullYear(2008,7,9);
获取时间(从1970/1/1至今的毫秒数):myDate.getTime();
转换为字符串:myDate.toUTCString()
获取本周为第几天(返回一个数字,注意每周第一天是周日):myDate.getDay()

日期比较

两个Date对象可以直接用比较运算符进行比较

算数

Math对象

属性

  • Math.E
  • Math.PI

方法

  • Math.round(NUM):四舍五入
  • Math.random():产生0-1的随机数
  • Math.floor(NUM):向下取整

示例:(产生0-10的随机数)
Math.floor( Math.random() * 11 )

正则表达式

定义

类似python的complie
var patt1=new RegExp(PATTERN),PATTERN为匹配模式
可添加第二个参数为"g",表明(global)

方法

  • test(STRING):检查是否匹配,返回布尔值true和false
  • exec(SRING):检索匹配项并返回第一个匹配项,否则返回null
    如果定义时指定了参数"g",那么再次执行exec()可以进行下一项的匹配
  • compile(PATTERN):修改匹配模式,也可以添加修改删除第二个参数

JS Window

浏览器对象模型(Browser Object Model, BOM)尚无正式标准,但现代浏览器几乎都已经实现了JS交互性方面的相同方法和属性

window对象

window对象表示浏览器窗口
任何JS全局对象、函数以及变量都作为window对象的成员
全局变量作为window对象的属性
全局函数作为window对象的方法
如:
document.getElementById("header");
实际上是
window.document.getElementById("header");
window一般省略

window尺寸

尺寸即浏览器的视口部分,不包括工具栏和滚动条
有以下三种方法:

  • 对于IE、Chrome、Firefox、Opera、Safari
    window.innerHeight
    window.innerWidth
  • 对于IE5、6、7、8
    document.documentElement.clientHeight
    document.documentElement.clientWidth

    document.body.clientHeight
    document.body.clientWidth

通常直接使用以下方法来兼容所有浏览器:

var w=window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth;

var h=window.innerHeight
|| document.documentElement.clientHeight
|| document.body.clientHeight;    

window方法

window.open():打开新窗口
window.close():关闭当前窗口
window.moveTo():移动当前窗口
window.resizeTo():调整当前窗口尺寸

window.screen对象

包含有关用户屏幕的信息,window前缀可以省略

属性

screen.availWidth:可用宽度
screen.availHeight:可用高度

window.location对象

用于获得当前页面的信息如URL,也可以把浏览器重定向到新的页面,window前缀可省略

属性

location.hostname:web主机域名
location.port:web主机端口
location.protocol:所用的web协议(http或https)
location.href:当前页面的完整的URL
location.pathname:当前页面路径和文件名

方法

location.assign(URL):加载新文档

window.history对象

包含浏览器的历史信息,window前缀可以省略
为了保护用户隐私,该对象只允许访问前一个页面和下一个页面(后退和前进)

方法

history.back():加载历史列表的前一个URL(后退)
history.forward():加载历史列表的后一个URL(前进)

window.navigator对象

不包含有关访问者浏览器的信息,window前缀可以省略

属性

appCodeNameappNameappVersioncookieEnabledplatformuserAgentsystemLanguage

注意

该对象信息具有误导性,一方面数据可以人为伪造;另一方面,浏览器无法报告晚于浏览器发布的新操作系统

正确的浏览器检测方法

尝试访问不同浏览器的特殊属性,如Opera浏览器的opera属性
if(window.opera){.....}

PopupAlert消息框

警告框

alert(MESSAGE):警告弹框,需要点击确定才能继续操作
如果信息内容需要换行,使用换行符\n

确认框

confirm(MESSAGE):确认弹框,需要用户点击确定或取消才能继续操作
确认返回true,取消返回false

提示框

prompt(MESSAGE, DEFAULT_VALUE):提示弹框,要求用户输入值(默认值为参数DEFAULT_VALUE),点击确定或取消
若点击确定,返回输入值;若点击取消,返回null

Timing计时

  • 若干毫秒后执行指定JS语句
    var t=setTimeout("JS语句", 毫秒);
    返回某个值,可用于取消setTimeout
    可以通过不断调用自己来实现无限循环
  • 取消setTimeout
    clearTimeout(t);
    t为setTimeout的返回值

document.cookie作为一个字符串去玩就可以

C语言图形化编程(WIN)

TC图形函数库:#include <graphics.h>
平台:watcom
参考资料:

本文多数例子来自上述材料,自己额外添加注释,方便理解

初始化

void far initgraph(int far *gdriver, int far *gmode, char *path);

far类型

这是早期的dos编程中使用的内存分段模式
far类型表示代码段采用远跳转
near类型表示代码段采用近跳转
但现在的win都采用平坦内存模式,不存在近跳转和远跳转,所以可以不用考虑

参数

  • gdriver为驱动器
    可以采用常量或数值
    • CGA(1)
    • EGA(3)
    • VGA(9)
    • DETECT(0)
  • gmode为模式
    不同的驱动器提供的不同的模式
    可以采用常量或数值
  • path为图形驱动程序所在的目录
    一般传入空字符串
  • 一般自动检测即可
    gdriver = 0
    此时gmode的参数将被自动检测,但还是得传递一个变量,可以不赋值
    如:

    int gdriver = 0, gmode;
    initgraph(&gdriver, &gmode, "");  //此时gmode并没有赋值
    
  • 注意:
    函数的形参都是指针,不要直接传递整型数据

错误捕捉示例

通过graphresult()函数返回图形模式初始化结果
与常量grOk进行比较
注意:graphresult()必须在initgraph()之后调用

int gdriver = 0, gmode;
initgraph(&gdriver, &gmode, "");
errorcode = graphresult();
if(errorcode != grOk)
{
    printf("Graphics error: %s\n", grapherrormsg(errorcode));
    printf("Press any key to halt:");
    getchar();
    exit(1);
}

图形的坐标

文本模式

屏幕一般分为25或40或50行,80或40列
以左上角为原点(1,1),向右为x正方向,向下为y正方向

图形模式

屏幕被划分为像素,每个像素为一个点,像素的个数与视频适配器的类型和适配器的工作方式有关
系统默认以左上角为原点(0,0),向右为x正方向,向下为y正方向
注意:文本模式跟图形模式的原点坐标不同

获取坐标最大值

getmaxx()函数返回当前图形模式下的最大x坐标(整型)
getmaxy()函数返回当前图形模式下的最大y坐标(整型)

坐标轴变换

选择一个原点,建立以右为x正方向,以上为y正方向的坐标系(符合我们的习惯)
假设选定新的坐标原点(origin_x, origin_y)
原先坐标系的点(before_x, before_y)与新坐标系上的对应点(later_x, later_y)存在以下关系
before_x = origin_x + later_x
before_y = origin_y - later_y

文本模式与图形模式的切换

模式间切换不需要重复关闭、初始化图形模式

获取当前图形模式

int far getgraphmode(voide);
注意:前提是已经成功调用过initgraph()函数

恢复为图形初始化前的模式(也就是文本模式)

void far restorecrtmode(void);

切换到文本模式(bhh)

closegraph();
text_mode();
textmode(C80);

将系统设置为指定的图形模式并清屏

void far setgraphmode(int mode);

示例:

//......省略图形初始化的代码
gmode = getgraphmode();  //获取当前图形模式
restorecrtmode();  //恢复为文本模式  
setgraphmode(gmode);  //设置(恢复)为之前的图形模式并清屏

关闭图形系统

void far closegraph(void);
所有的有关图形的程序段必须在initgraph()closegraph()之间(类型于文件操作)

背景色和作图色设置

背景色: setbkcolor(int color);
作图色: setcolor(int color);

参数color

可以使用常量或数值表示

  • BLACK(0)
  • BLUE(1)
  • GREEN(2)
  • CYAN(3) —>青色
  • RED(4)
  • MAGENTA(5)—>洋红
  • BROWN(6)
  • LIGHTGRAY(7)
  • DARKGRAY(8)
  • LIGHTBLUE(9)
  • LIGHTGREEN(10)
  • LIGHTCYAN(11)
  • LIGHTRED(12)
  • LIGHTMAGENTA(13)
  • YELLOW(14)
  • WHITE(15)

9-13为1-5对应的浅色

基本图形函数

画点函数

void far putpixel(int x, int y, int pixelcolor);

坐标位置函数

获取最大x坐标:int far getmaxx(void);
获取最大y坐标:int far getmaxy(void);
获取当前x坐标:int far getx(void);
获取当前y坐标:int far gety(void);
移动画笔到绝对坐标:void far moveto(int x, int y);
移动画笔到相对坐标:void far moverel(int dx, int dy);

图形模式下的文本输出函数

只支持英文输出,且文字大小固定,为8*16点阵
在当前位置输出文本:void far outtext(char far *textstring);
在指定位置输出文本:void far outtextxy(int x, int y, char *textstring);

画线函数

指定直线起止点:void far line(int x0, int y0, int x1, int x2);
以当前位置为直线起点,指定终点(绝对坐标),并移动画笔:void far lineto(int x, int y);
以当前位置为直线起点,指定终点(相对坐标),并移动画笔:void far linerel(int dx, int dy);

画圆函数,画圆弧函数

画圆:void far circle(int x, int y, int radius);
画圆弧:void far arc(int x, int y, int stangle, int endangle, int radius);
stangleendangle参数采用角度制(0-360),以x轴正方向为0度,以逆时针为正方向

画椭圆(弧)函数

void far ellipse(int x, int y, int stangle, int endangle, int xradius, int yradius);

画矩形函数

void far rectangle(int left, int top, int right, int bottom);
指定左上角的坐标为(left, top),右下角的坐标为(right, bottom)

线型设定函数

void far setlinestyle(int linestype, unsigned upattern, int thick);

  • linestype为直线类型(常量或数值)
    • 实线(缺省):SOLID_LINE(0)
    • 点线:DOTTED_LINE(1)
    • 中心线:CENTER_LINE(2)
    • 虚线:DASHED_LINE(3)
    • 用户自定义线型:USERBIT_LINE(4)
  • upattern为自定义线型(十六进制数)
    若linestype不为USERBIT_LINE4,则该参数取0即可
  • thick为直线粗细(单位:px)
    相关常量:
    NORM_WIDTH(1),缺省
    THICK_WIDTH(3)

填充函数

设置填充模式和颜色

void far setfillsytle(int pattern, int color);
pattern可以采用常量或数值:

  • 用背景色:EMPTY_FILL(0)
  • 用指定颜色:SOLID_FILL(1)
  • 用线:LINE_FILL(2)
  • 用斜线:LTSLASH_FILL(3)
  • 用粗斜线:SLASH_FILL(4)
  • 用粗反斜线:BKLASH_FILL(5)
  • 用反斜线:LTKLASH_FILL(6)
  • 用网格线:HATCH_FILL(7)
  • 用斜交线:XHATCH_FILL(8)
  • 用间隔线:INTERLEAVE_FILL(9)
  • 用稀疏点:WIDE_DOT_FILL(10)
  • 用密集点:CLOSE_DOT_FILL(11)
  • 用用户自定义模式:USER_FILL(12)
有界区域填充

void far floodfill(int x, int y, int bcolor);
(x, y)为所要填充的有界区域的任意点
bcolor不是填充色,而是边框颜色

椭圆填充

void far fillellipse(int x, int y, int xradius, int yradius);

椭圆扇区填充

void far sector(int x, int y, int stangle, int endangle, int xradius, int yradius);

多边形填充

void far fillpoly(int numpoints, int far *polypoints);
polypoints参数为各个点的坐标,存放规则如下:(假设一个poly数组)
(poly[0], poly[1]) 为第一个顶点
(poly[2], poly[3]) 为第二个顶点
…………以此类推

图片输出

8bit图片输出

void load_8bit_bmp(int x, int y, char *path);

#include <graphics.h>
#include <stdio.h>
main()
{
   int x, y;
   int driver=0, mode=VESA_1024x768x8bit;
   initgraph(&driver, &mode, "");

   load_8bit_bmp(0, 0, "pic256.bmp");
   load_8bit_bmp(200, 200, "hello.bmp");

   getchar();
   closegraph();
}

24bit图片输出(系统没有提供函数)

通过读取bmp文件的二进制数据输出图片

#include <graphics.h>
#include <stdio.h>
#include <mem.h>
int load_24bit_bmp(int x, int y, char *filename)
{
   FILE *fp = NULL;
   byte *p = NULL; /* pointer to a line of bmp data */
   byte *vp = _vp + (y*_width + x) * (_color_bits/8);
   dword width, height, bmp_data_offset, bytes_per_line, offset;
   int i;
   p = malloc(1024L * 3);  /* memory for holding a line of bmp data */
   if(p == NULL)  /* cannot allocate enough memory for drawing 1 line */
      goto display_bmp_error;
   fp = fopen(filename, "rb");
   if(fp == NULL) /* cannot open bmp file */
      goto display_bmp_error;
   fread(p, 1, 0x36, fp);     /* read BMP head */
   if(*(word *)p != 0x4D42)   /* check BMP signature */
      goto display_bmp_error; /* not a BMP file */
   if(*(word *)(p+0x1C) != 24)
      goto display_bmp_error; /* not a 24-bit-color BMP file */
   width = *(dword *)(p+0x12);
   height = *(dword *)(p+0x16);
   bmp_data_offset = *(dword *)(p+0x0A);
   fseek(fp, bmp_data_offset, SEEK_SET); /* skip BMP head */
   bytes_per_line = (width * 3 + 3) / 4 * 4; /* must be multiple of 4 */
   for(i=height-1; i>=0; i--)          /* draw from bottom to top */
   {
      fread(p, 1, bytes_per_line, fp); /* read a line of bmp data */
      offset = i * 1024 * 3;
      memcpy(vp+offset, p, width*3);
   }
   free(p);
   fclose(fp);
   return 1;
display_bmp_error:
   if(p != NULL)
      free(p);
   if(fp != NULL)
      fclose(fp);
   return 0;
}

main()
{
   int driver=0, mode=VESA_1024x768x24bit;
   initgraph(&driver, &mode, "");
   load_24bit_bmp(0, 0, "pic.bmp");
   getchar();
   closegraph();
}

中文输出

点阵字库

字库有固定的字体大小
读取字库文件中对应文字的16*16点阵共32字节的信息,若为1则在对应的位置上输出一个点,否则跳过
示例:(读取16*16的字库,并将其放大四倍打印)

#include <graphics.h>
void draw_hz(byte buf[16][2]);
main()
{
   int driver=0, mode=VESA_640x480x8bit;
   FILE *fp;
   byte hz[3]="我", buf[16][2];    //每个汉字占用两个字节 
   long offset;
   int x, y,  button;
   initgraph(&driver, &mode, "");
   fp = fopen("hzk16", "rb"); // 打开16x16点阵字库文件
   if(fp == NULL)
   {
      puts("Cannot open file hzk16.");
      exit(0);
   }
   // 计算"我"字点阵的偏移位置
   offset = ((hz[0] - 0xA1) * 94 + (hz[1] - 0xA1)) * 0x20;         //呃?这是啥?莫非是固定的计算公式? 
   fseek(fp, offset, SEEK_SET); // 移动文件指针到该偏移位置
   fread(buf, 1, 2*16, fp);     // 读取32字节点阵数据
   fclose(fp);
   draw_hz(buf); // 画"我"字
   getchar();
   closegraph();
}

void draw_hz(byte buf[16][2])
{
   word sixteen_dots;
   int x0=_width/2-32, y0=_height/2-32;  //打印的起始位置
   //居中显示,注意我们把字库的字体放大了四倍,也就是实际上打印出来是64*64的点阵汉字 
   int x, y;    //当前打印的“点”的坐标 
   int i, j;
   x = x0;
   y = y0;
   for(i=0; i<16; i++) // 16行
   {
      sixteen_dots = buf[i][0]<<8 | buf[i][1]; // 把两个一字节的word合并为一个两字节的int,共16位,代表1行16个点 
      for(j=0; j<16; j++) // 16列
      {
         if(sixteen_dots  &  1<<(15-j)) // 判断第(15-j)位是否为1, 为1表示有笔画
         //假设j=14,则1<<(15-j)的结果为0000 0000 0000 0010
         //假设sixteen_dots为...........0101 0101 0101 0101
         //取位“与”即可判断第2位是否为1 
         {
            setfillstyle(SOLID_FILL, RED);
            bar(x, y, x+3, y+3); // 画一个4*4的红色方块代替一个前景点
         }
         else // 第(15-j)位为0,为0表示没笔画
         {
            setfillstyle(SOLID_FILL, BLACK);
            bar(x, y, x+3, y+3); // 画一个4*4的黑色方块代替一个背景点
         }
         x += 4;    //把“画笔”移动到当前行的下一个4*4的“点”的位置 
      }

      //把“画笔”移动到下一行第一个4*4的“点”的位置
      x = x0;
      y += 4;     
   }
}

缺陷:当字体放大时,会出现明显的锯齿形

TTF曲线字库

有两种方式——
1、将多行字符串转换为多幅图片再输出,速度较快
2、逐行输出字符串

相关类型——

typedef struct _PIC      // 照片结构类型
{
   struct picture *img;  // 照片的图像
   struct picture *mask; // 照片的掩码
   int width, height;    // 照片的宽度及高度
} PIC;

相关函数——

  • 字符串转换为图片
    PIC * get_ttf_text_pic(char text[], char fontname[], int fontsize);
    text为要转换的字符串
    fontname为使用的曲线字库
    fontsize为字体大小
    返回指向图片(PIC)的指针

  • 绘制图片
    void draw_picture(int x, int y, PIC *p);
    p为指向所要绘制的图片的指针

  • 销毁图片,释放资源
    void destroy_picture(PIC *p);
    只有一个参数(指针),为指向所要销毁的图片的指针

  • 直接打印字符串
    dword out_ttf_text_xy(int x0, int y0, char text[], char fontname[], int fontsize);
    返回32位整型,高16位记录图片宽度,低16位记录图片高度

示例:

#include <stdio.h>
#include <graphics.h>

main()
{   
   int driver=0, mode=VESA_1024x768x24bit;
   short int x=0, y=100;
   int i;
   PIC *p[4];
   initgraph(&driver, &mode, "");

   /* TTF字体输出演示1: 先把多行字符串转化成多幅照片, 再输出, 速度较快 */
   setcolor(0xFF0000); // 红色
   p[0] = get_ttf_text_pic("A QUICK BROWN FOX JUMPS", "courier.ttf", 40);
          // 把字符串转化成照片, 其中"courier.ttf"是TTF字库名, 40是字体大小
          // p[0]用来接收函数返回的照片指针
   setcolor(0xFFFF00); // 黄色
   p[1] = get_ttf_text_pic("a quick brown fox jumps", "courier.ttf", 36);
   setcolor(0x00FF00); // 绿色
   p[2] = get_ttf_text_pic("0123456789", "courier.ttf", 32);
   setcolor(0x0000FF); // 蓝色
   p[3] = get_ttf_text_pic("`~!@#$%^&*()-=_+[]{}\\|;:'\",.<>/?", "courier.ttf", 28);
   for(i=0; i<4; i++)
   {  
      draw_picture(x, y, p[i]); // 在(x,y)坐标输出照片p[i]
      y += p[i]->height;        // 计算下一行的y坐标
      destroy_picture(p[i]);    // 销毁已画照片占用的资源
   }

   getchar();

   /* TTF字体输出演示2: 逐行输出字符串, 速度较慢 */
   driver=0;
   mode=VESA_1024x768x8bit;
   initgraph(&driver, &mode, "");
   y = 100;
   setcolor(LIGHTRED);
   y += out_ttf_text_xy(x, y, "夏天的飞鸟,飞到我窗前唱歌,又飞去了。", "simyahei.ttf", 40);
   // 函数的返回值 = (照片宽度<<16) | 照片高度
   // 故类型为short int的y实际仅加上照片高度值, 从而得到下行的y坐标
   y += out_ttf_text_xy(x, y, "Stray birds of summer come to my window", "courier.ttf", 32);
   y += out_ttf_text_xy(x, y, "to sing and fly away.", "courier.ttf", 32);
   setcolor(YELLOW);
   y += out_ttf_text_xy(x, y, "秋天的黄叶,它们没什么可唱,", "simyahei.ttf", 36);
   y += out_ttf_text_xy(x, y, "And yellow leaves of autumn", "courier.ttf", 28);
   y += out_ttf_text_xy(x, y, "which have no songs", "courier.ttf", 28);
   setcolor(LIGHTGREEN);
   y += out_ttf_text_xy(x, y, "只叹息一声,", "simyahei.ttf", 32);
   y += out_ttf_text_xy(x, y, "Flutter and fall there", "courier.ttf", 24);
   setcolor(LIGHTBLUE);
   y += out_ttf_text_xy(x, y, "飞落在那里。", "simyahei.ttf", 28);
   y += out_ttf_text_xy(x, y, "with a sigh.", "courier.ttf", 20);

   getchar();
   closegraph();
}

声音输出

扬声器输出

过于机械麻烦,这里跳过

声卡输出

只支持midi音乐文件和wav波形文件

相关类型——
知道有这两个类型即可,具体成员不必深究

typedef struct midi // MIDI file data structure
{                 
   word format;                       // 0 or 1
   word num_tracks;                   // 1 - 32
   word divisions;                    // number ticks per quarter note
   byte  *track[16];                  // Track data pointers
   word repeat;
   struct midi *chain;
   void  (*chainfunc)(struct midi *);
} MIDI;

typedef struct wave 
{
   dword id;
   dword sample_rate;
   dword byte_rate;
   byte *data;
   dword chunk_size;                   // size of this chunk in bytes
   dword sample_size;                  // size of entire linked sample
   struct wave  *next;                 // points to next link.
   struct wave  *head;                 // points to top link.
} WAVE;

相关函数——

  • 初始化
    int initsound(void);
  • 载入
    WAVE * load_wave(char *filename);
    MIDI * load_midi(char *filename);
  • 播放
    void play_wave(WAVE *p, int repeat);
    void play_midi(MIDI *p, int repeat);
    若repeat为-1,表示无限循环
  • 释放
    void free_wave(WAVE *p);
    void free_midi(MIDI *p);
  • 关闭
    void closesound(void);

示例:

#include <graphics.h>

main()
{
   MIDI *pmidi;
   WAVE *pwave;

   initsound(); // 初始化声卡
   pmidi = load_midi("bach.mid");  // 载入midi文件, 返回MIDI指针
   pwave = load_wave("sheep.wav"); // 载入wav文件, 返回WAVE指针

   play_midi(pmidi, -1); // repeat forever 播放midi音乐
   play_wave(pwave, 3);  // repeat 3 times 播放wav声音

   puts("Press a key to continue.\n");
   getchar();

   free_midi(pmidi); // 释放midi音乐占用的资源
   free_wave(pwave); // 释放wav声音占用的资源
   closesound(); // 关闭声卡
   return 1;
}

详细函数——(包括一些控制函数)

int  initsound(void);                  // 声卡初始化
void closesound(void);                 // 关闭声卡
WAVE * load_wave(char *filename);      // 载入wav文件
void play_wave(WAVE *p, int repeat);   // 播放wav, repeat为循环次数, repeat=-1为无限循环
void stop_wave(WAVE *p);               // 停止播放wav
void pause_wave(WAVE *p);              // 暂停播放wav
void resume_wave(WAVE *p);             // 继续播放wav
void free_wave(WAVE *p);               // 释放wav占用的资源
MIDI * load_midi(char *filename);      // 载入midi文件
void play_midi(MIDI *p, int repeat);   // 播放midi, repeat为循环次数, repeat=-1为无限循环
void stop_midi(MIDI *p);               // 停止播放midi
void pause_midi(MIDI *p);              // 暂停播放midi
void resume_midi(MIDI *p);             // 继续播放midi
void free_midi(MIDI *p);               // 释放midi占用的资源  

键盘输入:bioskey()

  • bioskey(0)
    若键盘缓存区为空,将等待用户输入
    若键盘缓存区不为空,将直接读走其中的键
  • bioskey(1)
    若键盘缓存区为空,返回0
    若键盘缓存区不为空,返回非零

什么是键盘缓冲区?
CPU能在运行程序时处理外部中断(interrupt)。当用户按下某个键时,键盘会向CPU发出一个中断请求,此时CPU会暂停正在运行的程序而转去处理键盘中断,处理的结果是把当前按住的键读走并存放到键盘缓冲区中。键盘缓冲区是一个队列(queue),相当于一个数组。

鼠标输入

int x, y, b;
union REGS r;
r.w.ax = 0x0003;    /*调用0003号功能*/
int86(0x33, &r, &r);
x = r.w.cx; /* 当前鼠标的x坐标 */
y = r.w.dx; /* 当前鼠标的y坐标 */
b = r.w.bx; /* 当前鼠标的按键状态 */

其他常见功能:

  • 0000
    检测鼠标驱动是否安装
  • 0004
    把鼠标定位到(x,y)
  • 0007
    设置水平方向的鼠标移动范围
  • 0008
    设置竖直方向的鼠标移动范围

详见中断大全

watcom平台提供的VESA图形库(bhh)

通过直接给坐标对应的内存赋值画图

一些程序可能会出现byte, word, dword类型
其实这都是自定义类型
byte对应char
word对应short
dword对应int

相关的全局变量

  • _vp: 指向坐标(0,0)的指针
  • _width: 屏幕宽度
  • _height: 屏幕高度
  • _color_bits: 每个像素占用内存的位(bit)数
  • _visual_page: 当前显示画面(前景)对应的页号
  • _active_page: 当前输出画面对应的页号
  • _back_page: 后台画面(背景)对应的页号
  • _page_gap: 后台画面与显示画面的距离
  • _mode_index: 当前视频模式的编号
  • _mode: 当前视频模式的内部编号
  • _draw_color: 前景色
  • _back_color: 背景色
  • _fill_color: 填充前景色
  • _fill_mask_index: 填充掩码
  • _x: 当前x坐标
  • _y: 当前y坐标

指向(x,y)的指针p,指向char类型数据公式:
p = _vp + ( y * _width + x ) * ( _color_bits / 8 )
解释:((x, y)的指针) = (原点指针) + (跳过的元素总数) * (每个元素占用的字节)

文本模式

每个元素占用两个字节——

  1. 第一个字节为文本内容
  2. 第二个内容为文本样式
    高四位为背景色,低四位为前景色

每个指针指向一个一字节(8位)的数据
示例(输出一个蓝底红字的A):

char *p = _vp;
*p = 'A';  
*(p+1) = ( BLUE << 4 ) + RED;  //或*(p+1) = 0x14;

图形模式

gmode常量

watcom平台提供的VESA图形库中相关常量:
包含8bit,24bit, 32bit三种颜色模式

  • VESA_320x200x8bit
  • VESA_320x200x24bit
  • VESA_320x200x32bit
  • VESA_640x480x8bit
  • VESA_640x480x32bit
  • VESA_800x600x8bit
  • VESA_800x600x24bit
  • VESA_800x600x32bit
  • VESA_1024x768x8bit
  • VESA_1024x768x24bit
  • VESA_1024x768x32bit 以上为图形模式
  • TEXT_80x25x16bit 文本模式
8bit颜色模式

每个点占用显卡一个字节,每个点有2^8(256)种颜色,前十六种(0~15)颜色定义如下:

enum COLORS
{
   BLACK,       /* dark colors */
   BLUE,
   GREEN,
   CYAN,
   RED,
   MAGENTA,
   BROWN,
   LIGHTGRAY,
   DARKGRAY,    /* light colors */
   LIGHTBLUE,
   LIGHTGREEN,
   LIGHTCYAN,
   LIGHTRED,
   LIGHTMAGENTA,
   YELLOW,
   WHITE
};
24bit颜色模式

每个点占用显卡三个字节,分别代表RGB的蓝色(B),绿色(G),红色(R),(没错,就是逆序的),每个点有2^24种颜色。
示例:

char *p;
p = _vp;
*p = 0x56;    //Blue
*(p+1) = 0x34;    //Green
*(p+2) = 0x12;    //Red
32bit颜色模式

每个点占用显卡四个字节,前三个字节表示RGB(同24bit),第四个字节通常为0x00

前景页和背景页

显示页面存在两个层次,前景和背景
_active_page为当前输出的页面
_visual_page为当前显示的页面,非显示页面将被隐藏
_back_page为当前的背景页
setviualpage()函数能够设置显示页面
setactivepage()函数能够设置输出页面
示例:

setvivualpage(_back_page);    //表示将背景页设置为当前显示页面  
stactivepage(_back_page);    //表示将背景页设置为当前输出页面

python爬虫初探

  • 主要涉及的模块和工具
    • 正则表达式(re模块)
      主要用来匹配筛选所需要的字符串
      基本的使用方法见python笔记:re模块、正则表达式
    • requests模块
      主要用来对网页发送请求,并处理响应的数据
      参考资料:requests官方文档
    • BeautifulSoup(bs4模块)
      用来简化数据筛选的过程
      参考资料:beautifulsoup官方文档
    • ie、firefox、chrome的开发者工具
      用来分析网页上的数据,包括页面内容、headers、form data等

requests模块

参考资料:requests官方文档
嗯……据说requests是基于urllib3的第三方库
简单的使用如下:

requests.get获取页面

返回一个response对象
第一个参数为页面的url,可以加入关键词参数cookies

requests.post发送数据

也是返回一个response对象
第一个参数为页面的额url,可以加入关键词参数headers和data
headers即为Requests Headers的内容,参数形式是一个字典
data即为form data的内容,参数形式也是一个字典

response对象的属性

  • encoding:页面编码
    一般requests会自动推测出编码
  • text:以文本的方式存放的页面内容
  • content:以字节形式存放的页面内容
  • status_code:页面的状态码
    可以用来判断是否正常打开页面
  • headers:页面的响应头即Response Headers
  • cookies:当前的cookies

BeautifulSoup(bs4模块)

参考资料:beautifulsoup官方文档

  • BeautifulSoup函数:将文档转换为一个BeautifulSoup对象并返回
    参数为一个字符串
    文档内容会自动转换为unicode编码

BeautifulSoup对象

一般情况下可以直接当作它的子对象tag对象来使用
其中包含以下子对象

tag对象

对象的名称与html和xml的标签名都相同

  • name:即标签的名字
  • attrs:即标签内包含的属性
    比如class、id、name、type等
    可以通过has_attr()方法判断对象内(也就是标签内)是否包含这些属性
  • contents:即始末标签间的显示内容
  • tag对象内常常还会有tag对象,称为子节点
    • children:一个生成器,依次生成直接子节点的内容,可用于迭代
    • descendants:一个生成器,一次生成最里层的子孙节点的内容,可用于迭代
      比较:children和descendants
      假设有一个标签<head><title>aaa</title></head>
      通过soup.children将会获取<title>aaa</title>整个直接子节点
      通过soup.descendants则会获取aaa这样的子孙节点
  • 与子节点对应的为父节点
    • parents:与children类似
    • parent:得到某个父节点
  • 既然有父有子,那么也会有兄弟节点
    • next_sibling:获取下一个同级节点
    • next_siblings:迭代获取之后的所有节点
    • previous_sibling:获取上一个同级节点
    • previous_sibling:迭代获取之前的所有节点

string对象

即尖括号<>之外的内容

其他

  • next_element和previous_element:获取下(上)一个对象,包括tag和string
  • next_elements和previous_elements:迭代获取下(上)一个对象
  • find_all方法
    查找标签
    直接访问tag对象只能得到第一个匹配结果,如果需要得到所有结果需要借助find_all函数
    函数返回一个包含所有该标签的内容的列表

    • 单个指定
      第一个参数为标签名,可以指定关键词参数,筛选属性
      如:soup.find('a', id='link3')
    • 正则匹配
      将一个re.compile的对象作为参数代替标签名
      BeautifulSoup将通过match的方式来查找匹配的标签
    • 多个指定
      将多个标签(字符串)放入列表作为参数传入
    • True
      传入True将表示匹配任何值
  • find方法
    用法与find_all类似

  • prettify方法
    将文档返回为一个有缩进的字符串,便于print输出
    无参数
  • UnicodeDammit函数(啧啧……这函数名)
    自动检测编码,如果能导入chardetcchardet可以大大提高自动检测的准确率
    可直接用于任何字符串
    返回一个具有以下属性的对象
    unicode_markup:自动解码的结果
    original_encoding:检测到的编码形式

从网页上爬下所需信息

这个比较简单,从源码中找到所需要的信息,观察它的规则,用正则表达式表达出来并且借助re模块的函数进行匹配提取即可
比如:我要爬下十大的标题——
查看源码:

<a href="dispbbs.asp?boardid=736&id=4508354" target="_blank"><font color=#000066>[CIE创道人生大讲堂]世界那么大,出去创业吧——为你讲述众筹之王的创业之道(抢楼有大礼哦~)</font></a>
<a href="dispbbs.asp?boardid=114&id=4508053" target="_blank"><font color=#000066>真是哔狗了,刚加的妹子被我吓跑了</font></a>
<a href="dispbbs.asp?boardid=100&id=4508066" target="_blank"><font color=#000066>[微创业联盟]创业,就问你约不约!——抢楼有精美礼品相赠哦!</font></a>
......(这里仅列出三条)  

可以找到其中的规律,并且用以下正则表达式表示:

<a href="dispbbs\.asp\?boardid=[0-9]+&id=[0-9]+" target="_blank"><font color=#000066>(.+?)</font></a>
  1. 要对特殊字符“.”和“?”进行转义
  2. 可以看到“boardid”和“id”接的是一串数字,用[0-9]+进行匹配
  3. 所要提取的部分内容为任意数量的任意字符,而且要防止贪婪匹配,用(.+?)进行匹配

然后用re.findall函数把匹配项都找出来就可以了
接着我们把内容写入文件,比如把包含所有匹配项的titles写到/home/zk/TopTen文件中去

import codecs, os
f = codecs.open('/home/zk/TopTen', 'w', 'utf-8')
for i, title in zip( range(1,11), titles ):
    f.write( str(i) + title + os.linesep )
f.close()

因为写入的内容包含中文,所以我们需要使用codecs模块的open函数打开文件,指定编码为utf-8

模拟登录

收集信息

借助chrome的开发者工具,可以捕捉到网页的相关信息
登录后,我们只要找到POST了包含用户名信息的页面,就可以找到处理登录的页面
cc98是post到sign.asp中去
查看这个页面的post信息,主要关注以下的信息

General

Request URL:http://www.cc98.org/sign.asp

#

Request Headers
Accept:text/plain, /; q=0.01
Accept-Encoding:gzip, deflate
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:60
Content-Type:application/x-www-form-urlencoded; charset=UTF-8
Cookie:ASPSESSIONIDQSCRDADB=NMLCLJEDIAGPHCHGOKDBHPGN; upNum=0; aspsky=username=zkkzkk&usercookies=3&userid=509527&useranony=&userhidden=2&password=6cc9bed70c3c80e1; BoardList=BoardID=Show; owaenabled=True; autoplay=True
Host:www.cc98.org
Origin:http://www.cc98.org
Referer:http://www.cc98.org/
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36
X-Requested-With:XMLHttpRequest

#

Form Data
a:i
u:zkkzkk
p:*
userhidden:2

  • Request URL:
    是这个请求发送的url
  • Request Headers:
    是我们发送的请求头,其中
    Host不是自己指定和设置的,http请求会自动处理
    Content-TypeUser-AgentAccept-Encoding必须有,值照抄即可
  • Form Data:
    就是我们提交的表单
    这里的auserhidden不知道是什么意思,先照搬
    u显然是用户名
    p则是密码,这里安全起见,我把他用星号代替了,这里其实是一个用hashlib.md5加密了的密码

模拟

利用requests.post函数即可模仿post

#headers主要照搬Requests Headers的信息,删去Host和Cookie即可
headers = {
    'Accept':'text/plain, */*; q=0.01',
    'Accept-Encoding':'gzip, deflate',
    'Accept-Language':'en-US,en;q=0.8',
    'Connection':'keep-alive',
    'Content-Length':'60',
    'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',
    'Origin':'http://www.cc98.org',
    'Referer':'http://www.cc98.org/',
    'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36',
    'X-Requested-With':'XMLHttpRequest'
}
#payload为一些表单信息
payload = {
    'a' : 'i',
    'u' : 'zkkzkk',
    'p' : '***********', 
    'userhidden' : '2'
}
r = requests.post('http://www.cc98.org/sign.asp', data=payload, headers=headers)

函数返回一个包含登陆信息cookies的对象r
接下来我们只要用这个cookies去访问cc98上的页面即可

rr = requests.get('http://www.cc98.org', cookies=r.cookies)

练(wan)习(wan):模拟登录10.71.45.100并爬下C大程的课件

先上源码——

# coding: utf-8
import requests,sys,bs4,re,urllib,os,codecs

#设置编码为utf8,不然后边对中文进行url编码的时候会出现奇怪的错误
reload(sys)
sys.setdefaultencoding('utf8')

#登录URL
URL = 'http://10.71.45.100/cstcx/web/login/check.asp?tn=xjc&Rnd.7055475=.533424'

#HEADERS
    #这里只去掉了Request Headers中的Host和Cookie,其他照搬
headers = {
'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Encoding':'gzip, deflate',
'Accept-Language':'en-US,en;q=0.8',
'Cache-Control':'max-age=0',
'Connection':'keep-alive',
'Content-Length':'32',
'Content-Type':'application/x-www-form-urlencoded',
'Origin':'http://10.71.45.100',
'Referer':'http://10.71.45.100/cstcx/web/index.asp?tid=27&tt=xjc&tn=%D0%EC%BE%B5%B4%BA&Rnd.5751838=.1000522',
'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36'
}

#FORM DATA
payload = {
'txtUser':'3140100805',
'txtPwd':'123456'
}

#模拟登录
check = requests.post(URL, data=payload, headers=headers)

#“参考资料”URL
rsrcURL = 'http://10.71.45.100/cstcx/web/courceinfo/resource.asp?coid=18&flag=0&coname=C%D7%A8%CC%E215&clsid=25&clsname=C%D7%A8%CC%E215&techid=27&loc=%BF%CE%B3%CC%D0%C5%CF%A2%3E%3E%3E%B2%CE%BF%BC%D7%CA%C1%CF'

#“讲稿”URL
docuURL = 'http://10.71.45.100/cstcx/web/courceinfo/jianggao.asp?coid=18&loc=%BF%CE%B3%CC%D0%C5%CF%A2%3E%3E%3E%BD%B2%B8%E5'

#“参考资料”本地存放目录
rsrcDIR = '/home/zk/C大程资料/Resources/'

#“讲稿”本地存放目录
docuDIR = '/home/zk/C大程资料/Documents/'

def GetHref(URL):
    '''
    利用模拟登录获得的cookies访问“参考资料”或“讲稿”的页面
    获取所需资源的名称和url并返回(字典)
    '''
    resources = requests.get(URL, cookies=check.cookies)
    resources.encoding = 'gb2312'   #requests识别出来的是iso,需要手动修改
    rsrc_soup = bs4.BeautifulSoup(resources.text)    #用BeautifulSoup处理页面内容
    rsrc_a = rsrc_soup.find_all('a')    #找出页面内容中的a标签
    rsrc = {}    #初始化一个字典,用于储存name-href键值对
    for a in rsrc_a:    #逐个处理每个a标签
        href = re.findall('href="(.+?)"', str(a))[0]    #用正则表达式筛选出文件的url
        href = href.replace('../../', 'http://10.71.45.100/cstcx/')    #由于页面上写的是相对路径,我们需要将其恢复为绝对路径,以便后边的正常访问
        name = href.split('/')[-1]    #截取文件名
        rsrc[name] = href    #存入字典
    return rsrc

def Download(rsrc, DIR):
    '''
    依据本地文件名判断文件是否已经下载过
    如果是新文件则下载到本地、打印下载信息
    注释掉的代码为原先采用文件记录的形式判断是否重复
    '''
    #lst = open(DIR + 'list','r')
    #lst_name = lst.readlines()
    #lst.close()
    #lst = codecs.open(DIR + 'list', 'a','utf8')
    lst_name = os.listdir(DIR)    #获取本地目录的文件名
    for name,url in rsrc.items():    #逐个处理所获取的name-href键值对
        if name not in lst_name:    #如果这个文件不在本地?
            #lst.write(name + os.linesep)
            urllib.urlretrieve(url, DIR + name)    #下载该文件
            print 'New file: ' + DIR + name    #打印信息
        #lst.close()

def main():
    rsrc = GetHref(rsrcURL)
    Download(rsrc, rsrcDIR)    
    docu = GetHref(docuURL)
    Download(docu, docuDIR)
    print 'Finished~'


if __name__ == '__main__':
    main()

信息的抓取和登录模拟就跟玩CC98差不多
在这个玩这个练习的时候呢,主要遇到的是页面的编码问题

  1. requests编码判断错误
    requests自带自动识别页面编码的功能,所以没过多的关注页面的编码问题
    但是在保存本地文件的时候发现文件名都是乱码
    最后发现是它的识别出错了,识别的结果是ISO8859-1,这是一个英文的编码
    查看页面的源码后可以看到head标签下的meta子标签说明了charset=gb2312
    所以我们需要手动修改requests对象的encoding属性为gb2312
  2. python编码问题
    原先的默认编码是啥我倒是不知道
    但是我把取出来的带中文的页面内容作为url去下载文件时会出错,说是不存在页面
    后来百度了一下,在程序最前端加入以下代码修改python默认编码即可——

    import sys
    reload(sys)
    sys.setdefaultencoding('utf8')
    

另外一个问题是如何下载文件,有以下三种方法:
参考资料:python下载文件的三种方法

  1. urllib.urlretrieve直接下载
    第一个参数为url,第二个参数为保存的本地路径
    如这次的代码

    urlli.urlretrieve(url, DIR + name)
    
  2. urllib2.urlopen获取文件内容,以二进制的形式写入本地文件

    url = '.....'
    f = urllib2.urlopen(url)
    data = f.read()
    with open('demo2.zip', 'wb') as code:
        code.write(data)
    
  3. requests.get获取文件内容,以二进制的形式写入本地文件

    url = '....'
    r = requests.get(url)
    with open('demo3.zip', 'wb') as code:
        code.write(r.content)
    

显然urllib模块的方法最简单