915 ℉

14.11.06

rust入门教程(0.12)-1-安装与基本概念

1 安装(linux-ubuntu-14.04)

安装rustc 与cargo
$ curl -s https://static.rust-lang.org/rustup.sh | sudo sh
卸载
$ curl -s https://static.rust-lang.org/rustup.sh | sudo sh -s -- --uninstall

cargo 使用:

新建项目
$ cargo new hello_world --bin
$ cd hello_world
项目结构
$ tree .
.
├── Cargo.toml
└── src
    └── main.rs
$ cat Cargo.toml:
[package]

name = "hello_world"
version = "0.0.1"
authors = ["Your Name <you@example.com>"]

$ cat src/main.rs:

fn main() {
    println!("Hello, world!")
}
运行项目
$ cargo build
   Compiling hello_world v0.0.1 (file:///path/to/project/hello_world)

$ ./target/hello_world
Hello, world!
$ cargo run
     Fresh hello_world v0.0.1 (file:///path/to/project/hello_world)
   Running `target/hello_world`
Hello, world!

2 Hello World

新建项目
$ mkdir ~/projects
$ cd ~/projects
$ mkdir hello_world
$ cd hello_world
编辑代码
$ editor main.rs
fn main() {
    println!("Hello, world!");
}
编译运行
$ rustc main.rs
$ ls
main main.rs
$ ./main # or main.exe on Windows
Hello, world!

3 变量

声明基本
let x = 5i; // i表示整形
let (x, y) = (1i, 2i); // 多个变量一起赋值
let x: int = 5; // 类型在前面声明
更改变量
let x = 5i;
x = 10i;
// 会报错如下
error: re-assignment of immutable variable `x`
     x = 10i;
     ^~~~~~~
// let 声明的变量不允许改变

// 可改变变量的声明方法
let mut x = 5i;
x = 10i;

// 不允许如下声明,即不声明类型不赋值(推到类型)的情况会报错
let x; //会报错如下
src/main.rs:2:9: 2:10 error: cannot determine a type for this local variable: unconstrained type
src/main.rs:2     let x;
                      ^

// 允许这样:
let x: int;

// 变量声明而不用的warning:
fn main() {
    let x: int;

    println!("Hello world!");
}
$ cargo build
  Compiling hello_world v0.0.1 (file:///home/you/projects/hello_world)
src/main.rs:2:9: 2:10 warning: unused variable: `x`, #[warn(unused_variable)] on by default
src/main.rs:2     let x: int;
                         ^

// 不赋值的使用报错例子:
fn main() {
    let x: int;

    println!("The value of x is: {}", x);
}

$ cargo build
   Compiling hello_world v0.0.1 (file:///home/you/projects/hello_world)
src/main.rs:4:39: 4:40 error: use of possibly uninitialized variable: `x`
src/main.rs:4     println!("The value of x is: {}", x);
                                                    ^
note: in expansion of format_args!
<std macros>:2:23: 2:77 note: expansion site
<std macros>:1:1: 3:2 note: in expansion of println!
src/main.rs:4:5: 4:42 note: expansion site
error: aborting due to previous error
Could not compile `hello_world`.

注释:

注释分为行注释和文档注释

行注释:
// Line comments are anything after '//' and extend to the end of the line.

let x = 5i; // this is also a line comment.

// If you have a long explanation for something, you can put line comments next
// to each other. Put a space between the // and your comment so that it's
// more readable.
文档注释:
/// `hello` is a function that prints a greeting that is personalized based on
/// the name given.
///
/// # Arguments
///
/// * `name` - The name of the person you'd like to greet.
///
/// # Example
///
/// ```rust
/// let name = "Steve";
/// hello(name); // prints "Hello, Steve!"
/// ```
fn hello(name: &str) {
    println!("Hello, {}!", name);
}

###### 区别: 文档注释可以利用rustdoc命令生成文档

jack.zh 标签:rust 继续阅读

795 ℉

14.11.04

C语言基础(3-1)-关键字

C语言基础(3-1)-关键字

1 数据类型关键字
  • A.基本数据类型(5个)
    • void :声明函数无返回值或无参数,声明无类型指针,显式丢弃运算结果
    • char :字符型类型数据,属于整型数据的一种
    • int :整型数据,通常为编译器指定的机器字长
    • float :单精度浮点型数据,属于浮点数据的一种
    • double :双精度浮点型数据,属于浮点数据的一种
  • B .类型修饰关键字(4个)
    • short :修饰int,短整型数据,可省略被修饰的int。
    • long :修饰int,长整形数据,可省略被修饰的int。
    • signed :修饰整型数据,有符号数据类型
    • unsigned :修饰整型数据,无符号数据类型
  • C .复杂类型关键字(5个)
    • struct :结构体声明
    • union :共用体声明
    • enum :枚举声明
    • typedef :声明类型别名
    • sizeof :得到特定类型或特定类型变量的大小
  • D .存储级别关键字(6个)
    • auto :指定为自动变量,由编译器自动分配及释放。通常在栈上分配
    • static :指定为静态变量,分配在静态变量区,修饰函数时,指定函数作用域为文件内部
    • register :指定为寄存器变量,建议编译器将变量存储到寄存器中使用,也可以修饰函数形参,建议编译器通过寄存器而不是堆栈传递参数
    • extern :指定对应变量为外部变量,即标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。
    • const :与volatile合称“cv特性”,指定变量不可被当前线程/进程改变(但有可能被系统或其他线程/进程改变)
    • volatile :与const合称“cv特性”,指定变量的值有可能会被系统或其他进程/线程改变,强制编译器每次从内存中取得该变量的值
2 流程控制关键字
  • A .跳转结构(4个)
    • return :用在函数体中,返回特定值(或者是void值,即不返回值)
    • continue :结束当前循环,开始下一轮循环
    • break :跳出当前循环或switch结构
    • goto :无条件跳转语句
  • B .分支结构(5个)
    • if :条件语句,后面不需要放分号
    • else :条件语句否定分支(与if连用)
    • switch :开关语句(多重分支语句)
    • case :开关语句中的分支标记
    • default :开关语句中的“其他”分支,可选。
  • C .循环结构(3个)
    • for:for循环结构,for(1;2;3)4;的执行顺序为1->2->4->3->2…循环,其中2为循环条件。在整个for循环过程中,表达式1只计算一次,表达式2和表达式3则可能计算多次,也可能一次也不计算。循环体可能多次执行,也可能一次都不执行。
    • do :do循环结构,do 1 while(2); 的执行顺序是1->2->1…循环,2为循环条件
    • while :while循环结构,while(1) 2; 的执行顺序是1->2->1…循环,1为循环条件
3 C99新增关键字
  • _Bool:布尔类型,用来表示真或假,零表示假,非零表示真。所有非零的数赋值给布尔型变量,最终的值还是1。
  • _Complex:复数类型,C99提供了三种复数类型:float _Complex,double _Complex,long double _Complex复数类型包括一个实部和一个虚部。
  • _Imaginary:虚数类型,C99提供了三种虚数类型float _Imaginary,double _Imaginary,以及 long double _Imaginary,虚数类型没有实部,只有虚部。
  • restrict:用来限定指针,表明指针是访问一个数据对象的唯一且初始化对象。作用是告诉编译器除了该指针,其他任何指针都不能对所指向的数据进行存取,以便编译器优化代码。
  • inline:内联函数,是为了解决C 预处理器宏存在的问题所提出一种解决方案,用来提高函数使用效率。内联函数使用inline关键字定义,并且函数体和申明必须结合在一起, 否则编译器将他作为普通函数对待。

jack.zh 标签:C 继续阅读

798 ℉

14.11.04

C语言基础(2)-C语言简介

C语言简介

C语言的演化

  • C语言的第一次发展在1969年到1973年之间。C之所以被称为C是因为C语言的很多特性是由一种更早的被称为B语言的编程语言中发展而来的,而B语言的前身是BCPL。
  • BCPL语言由Martin Richards开发;B语言是Ken Thompson为第一个UNIX系统而于1970年在DEC PDP-7计算机上开发的。
  • BCPL和B语言都是“无类型”的语言,而C语言提供了很多的数据类型。
  • 到了1973年,C语言已经可以用来编写Unix操作系统的内核。这是第一次用C语言来编写操作系统的内核。丹尼斯·里奇(Dennis M.Ritchie 和Brian Kernighan在1978年出版了《C程序设计语言》(The C Programming Language,经常简称为“白皮书”或“K&R”)。
  • 1980年以后,贝尔实验室使得C变得更为广泛的流行,使得C一度成为了操作系统和应用程序编程的首选.

C语言的版本

  • K&R C: 1978年Kernighan和Dennis M.Ritchie的《C程序设计语言》第一版出版。它介绍了下面的有关C语言版本的特性:
    • struct数据类型
    • long int数据类型去!
    • unsigned int数据类型
    • 把运算符=+改为+=,依次类推。因为=+使得编译器混淆。
    • K&R C通常被作为C编译器所支持的最基本的C语言部分。虽然现在的编译器并不一定都完全遵循ANSI标准,但K&R C作为C语言的最低要求仍然要编程人员掌握。但是无论怎样,现在使用广泛的C语言版本都已经与K&R C相距甚远了,因为这些编译器都使用ANSI C标准。
  • ANSI C和ISO C:1989年,C语言被ANSI标准化。(ANSI X3.159-1989)。标准化的一个目的是扩展K&R C。这个标准包括了一些新的特性。在K&R出版后,一些新的特征被“非官方”的加到C语言中。
    • void函数
    • 函数返回structunion类型
    • void *数据类型
    • 在ANSI标准化自己的过程中,一些新的特征被加了进去。ANSI也标准了函数库。ANSI C标准被ISO(国际标准化组织)采纳成为ISO 9899。ISO的第一个版本文件在1990年出版。《The Programming Language》第二版是基于ANSI C写的。
  • C99:在ANSI标准化后,C语言的标准在一段相当的时间内都保持不变,尽管C++继续在改进。(实际上,Normative Amendment1在1995年已经开发了一个新的C语言版本。但是这个版本很少为人所知。)标准在90年代才经历了改进,这就是ISO9899: 1999(1999年出版)。这个版本就是通常提及的C99。它被ANSI于2000年三月采用。在C99中包括的特性有:
    • 对编译器限制增加了,比如源程序每行要求至少支持到 4095 字节,变量名函数名的要求支持到 63 字节 (extern 要求支持到 31)
    • 预处理增强了。例如:
    • 宏支持取参数 #define Macro(...)__VA_ARGS__
    • 使用宏的时候,参数如果不写,宏里用 #,## 这样的东西会扩展成空串。(以前会出错的)
    • 支持 // 行注释(这个特性实际上在C89的很多编译器上已经被支持了)
    • 增加了新关键字 restrict, inline, _Complex, _Imaginary, _Bool
    • 支持 long long, long double _Complex, float _Complex 这样的类型
    • 支持 <: :> <% %> %: %:%: ,等等奇怪的符号替代,D&E 里提过这个
    • 支持了不定长的数组。数组的长度就可以用变量了。声明类型的时候呢,就用 int a[*] 这样的写法。不过考虑到效率和实现,这玩意并不是一个新类型。所以就不能用在全局里,或者 struct union 里面,如果你用了这样的东西,goto 语句就受限制了。
    • 变量声明不必放在语句块的开头,for 语句提倡这么写 for(int i=0;i<100;++i) 就是说,int i 的声明放在里面,i 只在 for 里面有效。
    • 当一个类似结构的东西需要临时构造的时候,可以用 (type_name){xx,xx,xx} 这有点像 C++ 的构造函数
    • 初始化结构的时候现在可以这样写: struct {int a[3], b;} hehe[] = { [0].a = {1}, [1].a = 2 }; struct {int a, b, c, d;} hehe = { .a = 1, .c = 3, 4, .b = 5} // 3,4 是对 .c,.d 赋值的
    • 字符串里面,/u 支持 unicode 的字符
    • 支持 16 进制的浮点数的描述
    • 所以 printf scanf 的格式化串多支持了 ll / LL (VC6 里用的 I64) 对应新的 long long 类型。
    • 浮点数的内部数据描述支持了新标准,这个可以用 #pragma 编译器指定
    • 除了已经有的 __line__ __file__ 以外,又支持了一个 __func__ 可以得到当前的函数名
    • 对于非常数的表达式,也允许编译器做化简
    • 修改了对于 / % 处理负数上的定义,比如老的标准里 -22 / 7 = -3, -22 % 7 = -1 而现在 -22 / 7 = -4, -22 % 7 = 6
    • 取消了不写函数返回类型默认就是 int 的规定
    • 允许 struct 定义的最后一个数组写做 [] 不指定其长度描述
    • const const int i; 将被当作 const int i; 处理
    • 增加和修改了一些标准头文件, 比如定义 bool<stdbool.h> 定义一些标准长度的 int<inttypes.h> 定义复数的 <complex.h> 定义宽字符的 <wctype.h> 有点泛型味道的数学函数 <tgmath.h> 跟浮点数有关的 <fenv.h><stdarg.h> 里多了一个 va_copy 可以复制 ... 的参数。<time.h> 里多了个 struct tmxstruct tm 做了扩展
    • 输入输出对宽字符还有长整数等做了相应的支持
    • 但是各个公司对C99的支持所表现出来的兴趣不同。当GCC和其它一些商业编译器支持C99的大部分特性的时候,微软和Borland却似乎对此不感兴趣。

C语言哲学

  • 相信程序员(Trust the programmer)
  • 不妨碍程序员行需行之事(Don’t prevent the programmer from doing what needs to be done)
  • 保持语言简洁精炼干(Keep the language small and simple)
  • 为操作提供唯一方式(Provide only one way to do an operation)
  • 速度优于可移植性(Make it fast, even if it is not guaranteed to be portable)

jack.zh 标签:C 继续阅读

1288 ℉

14.11.03

Linux 工具图表

linux_benchmarking_tools

linux_benchmarking_tools

linux_observability_sar

linux_observability_sar

#### linux_observability_tools linux_observability_tools

linux_tuning_tools

linux_tuning_tools

Linux-io-stack-diagram_v1.0

Linux-io-stack-diagram_v1.0

Linux-storage-stack-diagram_v3.17

Linux-storage-stack-diagram_v3.17

jack.zh 标签:Linux tool 继续阅读

908 ℉

14.10.31

C语言基础(1)-Linux下的基本编译

1:跑起来

先把hello world跑起来

#include <stdio.h>

int main()  
{  
     printf("hello world!/n");  
     return 0;  
}  

保存为 ex1.c linux终端中敲入

~$:make ex1  
~$:cc -Wall -g    ex1.c   -o ex1 

你会发现 生成了一个ex1文件

~$:./ex1  
   hello world! 

2:怎么跑起来的

但是 它到底做了什么 ? 各个段落都干了什么? 这段我不知道怎么说了 直接命令说话

~$:gcc -E ex1.c -o ex1.i   

-E 这是一个预处理选项,其实生成的.i文件还是C语言文件,只是把include 还有宏之类的东西做了替换 替换

比如 ex2.c如下:

#include "stdio.h"  
#define  HELLOWORLD "hello world!"  

 int main()  
{  
     printf("%s\n",HELLOWORLD);  
     return 0;  
}  

运行一下

~$:gcc -E ex2.c -o ex2.i

HELLOWORLD 做了相应的的替换 接着下面,如像下面这样:

int main()
{
 printf("%s\n","hello world!");
 return 0;
}

进行下一步

~$:gcc -S ex2.i -o ex2.s  

这个会把.i 文件编译成汇编文件 如果熟悉汇编语言 你可以看一下ex2.s文件

继续下一步

~$:gcc -c ex2.s -o ex2.o

上面是把汇编代码 转换成机器代码,最后:

~$:gcc ex2.o -o ex2  

最后,在连接阶段将输入的机器代码文件*.o(与其它的机器代码文件和库文件)汇集成一个可执行的二进制代码文件。

这个是整个的编译过程

3:链接

然而 就如上面所说,与其它的机器代码文件和库文件,这个其他代码和库文件是怎么链接过来的呢?

我们先看一下这个命令

~$:ldd ex2  
 libc.so.6 => /lib64/libc.so.6 (0x0000003195800000)  
 /lib64/ld-linux-x86-64.so.2 (0x0000003195400000) 

这个命令可以看得到上面的C文件所用到的动态连接库是什么

现在问题来了,如果我们自己想写一个自己的动态或是静态的链接库,怎么来写?

OK 我们下面解决这个问题 比如 我们的printf函数是哪里实现的呢 是怎么来链接此函数实现的呢?通过上面的ldd,可以分析到原来是程序连接到了libc.so.6

4:实现自己的链接库

实现自己的链接库

test1.c:

#include "stdio.h"  
#define  HELLOWORLD "hello world!"  

 int test1()  
{  
     printf("%s\n",HELLOWORLD);  
     return 0;  
} 

test2.c:

#include "stdio.h"  

void test2(int test2)  
{  
     printf("test2:%d\n",test2);  
} 

test.h:

void test1(int);  
void test2(int);   

把它们编译成.o文件

~$:cc -c test1.c  
~$:cc -c test2.c  

会生成test1.o 和test2.o

写一个他们的调用函数 test.c:

#include "stdio.h"  
#include "test.h"  

int main(void)  
{  
     test1(1);  
     test2(2);  
     return 0;  
}  

编译一下:

~$:cc -c test.c  
~$:cc -o test test.o test1.o test2.o  

这样会生成一个可执行文件test,运行它

~$:./test  
    test1:1  
    test2:2  

我们可以运行了,然后我们开始做自己的静态库 libself.a

~$:ar crv libself.a test1.o test2.o  
    a - test1.o  
    a - test2.o 

我们试用一下自己的库

~$:cc -o test test.o libself.a  
~$: ./test 
    test1:1  
    test2:2  

成功 看一下用参数

如果不行 记得生成内容表

~$:ranlib libself.a

这个可以运行了 但是呢,我还是很郁闷 因为静态库有以下缺点:

当需要多次调用静态库的时候,内存中会存在多个静态库 占用资源

这个时候就需要共享库了 它成功的解决了这个问题:

  • 链接方式:程序本身不包含函数代码,而是引用运行时可访问的共享代码
  • 对于一个共享库 他是这样的 :程序本身不包含函数代码,在磁盘上和内存中也仅保存一份,更新也更方便。
  • 一个共享库的典型的名字为libNAME.so.N NAME为共享库的名字 N为版本号码。

书上说linux负责装载共享库并解析客户程序函数应用的程序(动态装载器)是ld.so.N s搜索共享库使用的,是配置/etc/ld.so.conf 如果修改了这个文件,需要 ldconfig来处理它
我对动态连接库的了解也就这么多了.

试着自己做一下

~$:gcc test12.c test2.c -fPIC -shared -o libtest.so  
~$:gcc test.c -L. -ltest -o test  
~$:./test
    test1:1  
    test2:2  

成功,解释一下

  • -shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件
  • -fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过 代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
  • -L.:表示要连接的库在当前目录中
  • -ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字 前面加上lib,后面加上.so来确定库的名称
  • LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。
  • 当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。

注意:

调用动态库的时候有几个问题会经常碰到,有时,明 明已经将库的头文件所在目录 通过 -I include进来了, 库所在文件通过 -L参数引导,并指定了-l的库名, 但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。

5: 附 gcc以及相关使用

1:-I 增加头文件路径
2:-L 增加链接库路径
3:-c 只激活预处理,编译,和汇编,也就是他只把程序做成obj文件
4:-S 汇编文件
5:-E 只激活预处理,但是不输出为文件,需要定向
6:-l -lm引入数学库
7:ldd 库依赖
8:ar 归档
9:ranlib 函数库生成内容表

jack.zh 标签:CLinuxGcc 继续阅读

799 ℉

14.10.28

Simple-javascript

// 注释方式和C很像,这是单行注释
/* 这是多行
   注释 */

// 语句可以以分号结束
doStuff();

// ... 但是分号也可以省略,每当遇到一个新行时,分号会自动插入
doStuff()

// 我们在这里会去掉分号,但是否添加最后的分号取决于你个人的习惯
// 及你所在团队的编程风格

///////////////////////////////////
// 1. 数字、字符串与操作符

// Javascript 只有一种数字类型 (即 64位 IEEE 754 双精度浮点).
3 // = 3
1.5 // = 1.5

// 所有基本的算数运算
1 + 1 // = 2
8 - 1 // = 7
10 * 2 // = 20
35 / 5 // = 7

// 包括无法整除的除法
5 / 2 // = 2.5

// 位运算也和其他语言一样。当你对浮点数进行位运算时,
// 浮点数会转换为至多 32 位的无符号整数
1 << 2 // = 4

// 括号可以决定优先级
(1 + 3) * 2 // = 8

// 有三种非数字的数字类型
Infinity //  1/0 的结果
-Infinity // -1/0 的结果
NaN // 0/0 的结果

// 也有布尔值
true
false

// 可以通过单引号或双引号来构造字符串
'abc'
"Hello, world"

// 用!来取非
!true // = false
!false // = true

// 相等 ==
1 == 1 // = true
2 == 1 // = false

// 不等 !=
1 != 1 // = false
2 != 1 // = true

// 更多的比较操作符 
1 < 10 // = true
1 > 10 // = false
2 <= 2 // = true
2 >= 2 // = true

// 字符串用+连接
"Hello " + "world!" // = "Hello world!"

// 字符串也可以用 < 、> 来比较
"a" < "b" // = true

// 比较时会进行类型转换...
"5" == 5 // = true

// ...除非你是用 ===
"5" === 5 // = false

// 你可以用charAt来得到字符串中的字符
"This is a string".charAt(0)

// 还有两个特殊的值:null和undefined
null // 用来表示刻意设置成的空值
undefined // 用来表示还没有设置的值

// null, undefined, NaN, 0 和 "" 都是假的(false),其他的都视作逻辑真
// 注意 0 是逻辑假而  "0"是逻辑真, 尽管 0 == "0".

///////////////////////////////////
// 2. 变量、数组和对象

// 变量需要用 var 这个关键字声明. Javascript是动态类型语言
// 所以你在声明时无需指定类型。 赋值需要用 = 
var someVar = 5

// 如果你在声明时没有加var关键字,你也不会得到错误
someOtherVar = 10

// ...但是此时这个变量就会拥有全局的作用域,而非当前作用域

// 没有被赋值的变量都会返回undefined这个值
var someThirdVar // = undefined

// 对变量进行数学运算有一些简写法
someVar += 5 // 等价于 someVar = someVar + 5; someVar 现在是 10 
someVar *= 10 // 现在 someVar 是 100

// 自增和自减也有简写
someVar++ // someVar 是 101
someVar-- // 回到 100

// 数组是任意类型组成的有序列表
var myArray = ["Hello", 45, true]

// 数组的元素可以用方括号下标来访问
// 数组的索引从0开始
myArray[1] // = 45

// javascript中的对象相当于其他语言中的字典或映射:是键-值的集合
{key1: "Hello", key2: "World"}

// 键是字符串,但是引号也并非是必须的,如果键本身是合法的js标识符
// 而值则可以是任意类型的值
var myObj = {myKey: "myValue", "my other key": 4}

// 对象的访问可以通过下标
myObj["my other key"] // = 4

// ... 或者也可以用 . ,如果属性是合法的标识符
myObj.myKey // = "myValue"

// 对象是可变的,键和值也可以被更改或增加
myObj.myThirdKey = true

// 如果你想要访问一个还没有被定义的属性,那么会返回undefined
myObj.myFourthKey // = undefined

///////////////////////////////////
// 3. 逻辑与控制结构

// if语句和其他语言中一样
var count = 1
if (count == 3){
    // count 是 3 时执行
} else if (count == 4) {
    // count 是 4 时执行
} else {
    // 其他情况下执行 
}

// while循环
while (true) {
    // 无限循环
}

// Do-while 和 While 循环很像 ,但前者会至少执行一次
var input
do {
    input = getInput()
} while (!isValid(input))

// for循环和C、Java中的一样
// 初始化; 继续执行的条件; 遍历后执行.
for (var i = 0; i < 5; i++){
    // 遍历5次
}

// && 是逻辑与, || 是逻辑或
if (house.size == "big" && house.colour == "blue"){
    house.contains = "bear"
}
if (colour == "red" || colour == "blue"){
    // colour是red或者blue时执行
}

// && 和 || 是“短路”语句,在初始化值时会变得有用 
var name = otherName || "default"

///////////////////////////////////
// 4. 函数、作用域、闭包

// JavaScript 函数由function关键字定义
function myFunction(thing){
    return thing.toUpperCase()
}
myFunction("foo") // = "FOO"

// 函数也可以是匿名的:
function(thing){
    return thing.toLowerCase()
}
// (我们无法调用此函数,因为我们不知道这个函数的名字)

// javascript中的函数也是对象,所以函数也能够赋给一个变量,并且被传递
// 比如一个事件处理函数:
function myFunction(){
    // this code will be called in 5 seconds' time
}
setTimeout(myFunction, 5000)

// 你甚至可以直接把一个函数写到另一个函数的参数中

setTimeout(function myFunction(){
    // 5秒之后会执行这里的代码
}, 5000)

// JavaScript 仅有函数作用于,而其他的语句则没有作用域
if (true){
    var i = 5
}
i // = 5 - 并非我们在其他语言中所得到的undefined

// 这就导致了人们经常用一种叫做“即使执行匿名函数”的模式
// 这样可以避免一些临时变量扩散到外边去
function(){
    var temporary = 5
    // 我们可以访问一个全局对象来访问全局作用域
    // 在浏览器中是 'window' 这个对象。 
    // 在Node.js中这个对象的名字可能会不同。
    window.permanent = 10
    // 或者,我们也可以把var去掉就行了
    permanent2 = 15
}()
temporary // 抛出引用异常
permanent // = 10
permanent2 // = 15

// javascript最强大的功能之一就是闭包
// 如果一个函数在另一个函数中定义,那么这个函数就拥有外部函数的所有访问权
function sayHelloInFiveSeconds(name){
    var prompt = "Hello, " + name + "!"
    function inner(){
        alert(prompt)
    }
    setTimeout(inner, 5000)
    // setTimeout 是异步的,所以这个函数会马上终止不会等待。
    // 然而,在5秒结束后,inner函数仍然会弹出prompt信息。
}
sayHelloInFiveSeconds("Adam") // 会在5秒后弹出 "Hello, Adam!" 

///////////////////////////////////
// 5. 对象、构造函数与原型

//  对象包含方法
var myObj = {
    myFunc: function(){
        return "Hello world!"
    }
}
myObj.myFunc() // = "Hello world!"

// 当对象中的函数被调用时,这个函数就可以通过this关键字访问这个对象
myObj = {
    myString: "Hello world!",
    myFunc: function(){
        return this.myString
    }
}
myObj.myFunc() // = "Hello world!"

// 但这个函数访问的其实是其运行时环境,而非定义时环境
// 所以如果函数所在的环境不在当前对象的环境中运行时,就运行不成功了
var myFunc = myObj.myFunc
myFunc() // = undefined

// 相应的,一个函数也可以被指定为一个对象的方法,并且用过this可以访问
// 这个对象的成员,即使在定义时并没有绑定任何值
var myOtherFunc = function(){
    return this.myString.toUpperCase()
}
myObj.myOtherFunc = myOtherFunc
myObj.myOtherFunc() // = "HELLO WORLD!"

// 当你通过new关键字调用一个函数时,就会生成一个对象
// 而对象的成员需要通过this来定义。
// 这样的函数就叫做构造函数

var MyConstructor = function(){
    this.myNumber = 5
}
myNewObj = new MyConstructor() // = {myNumber: 5}
myNewObj.myNumber // = 5

// 每一个js对象都有一个原型,当你要访问一个没有定义过的成员时,
// 解释器就回去找这个对象的原型

// 有一些JS实现会让你通过一个对象的__proto__方法访问这个原型。
// 这虽然对理解这个对象很有用,但是这并不是标准的一部分
// 我们之后会通过标准方式来访问原型。
var myObj = {
    myString: "Hello world!",
}
var myPrototype = {
    meaningOfLife: 42,
    myFunc: function(){
        return this.myString.toLowerCase()
    }
}
myObj.__proto__ = myPrototype
myObj.meaningOfLife // = 42

// This works for functions, too.
myObj.myFunc() // = "hello world!"

// 当然,如果你要访问的成员在原型当中也没有定义的话,解释器就会去找原型的原型。
myPrototype.__proto__ = {
    myBoolean: true
}
myObj.myBoolean // = true

// 这其中并没有对象的拷贝。每个对象的原型实际上是持有原型对象的引用
// 这说明当我们改变对象的原型时,会影响到其他以这个原型为原型的对象
myPrototype.meaningOfLife = 43
myObj.meaningOfLife // = 43

// 我们知道 __proto__ 并非标准规定,实际上也没有办法更改已经指定好的原型。
// 但是,我们有两种方式可以为新的对象指定原型。

// 第一种方式是 Object.create,这个方法是在最近才被添加到Js中的
// 也因此并不是所有的JS实现都有这个放啊
var myObj = Object.create(myPrototype)
myObj.meaningOfLife // = 43

// 第二种方式可以在任意版本中使用,不过需要通过构造函数。
// 构造函数有一个属性prototype。但是这 *不是* 构造函数本身的函数
// 而是通过构造函数和new关键字生成新对象时自动生成的。
myConstructor.prototype = {
    getMyNumber: function(){
        return this.myNumber
    }
}
var myNewObj2 = new myConstructor()
myNewObj2.getMyNumber() // = 5

// 字符串和数字等内置类型也有通过构造函数来创建的包装类型
var myNumber = 12
var myNumberObj = new Number(12)
myNumber == myNumberObj // = true

// 但是它们并非严格等价
typeof myNumber // = 'number'
typeof myNumberObj // = 'object'
myNumber === myNumberObj // = false
if (0){
    // 这段代码不会执行,因为0代表假
}
if (Number(0)){
    // 这段代码会执行,因为Number(0)代表真
}

// 但是,包装类型和内置类型共享一个原型
// 这样你就可以给内置类型也增加一些功能
String.prototype.firstCharacter = function(){
    return this.charAt(0)
}
"abc".firstCharacter() // = "a"

// 这个技巧可以用来用老版本的javascript子集来是实现新版本js的功能
// 这样就可以在老的浏览器中使用新功能了。

// 比如,我们知道Object.create并没有在所有的版本中都实现
// 但是我们仍然可以通过这个技巧来使用
if (Object.create === undefined){ // 如果存在则不覆盖
    Object.create = function(proto){
        // 用正确的原型来创建一个临时构造函数
        var Constructor = function(){}
        Constructor.prototype = proto
        // 之后用它来创建一个新的对象
        return new Constructor()
    }
}

jack.zh 标签:learnxinyminutes javascript 继续阅读

833 ℉

14.10.28

Simple-Haskell

-- 单行注释以两个破折号开头
{- 多行注释像这样
   被一个闭合的块包围
-}

----------------------------------------------------
-- 1. 简单的数据类型和操作符
----------------------------------------------------

-- 你有数字
3 -- 3
-- 数学计算就像你所期待的那样
1 + 1 -- 2
8 - 1 -- 7
10 * 2 -- 20
35 / 5 -- 7.0

-- 默认除法不是整除
35 / 4 -- 8.75

-- 整除
35 `div` 4 -- 8

-- 布尔值也简单
True
False

-- 布尔操作
not True -- False
not False -- True
1 == 1 -- True
1 /= 1 -- False
1 < 10 -- True

-- 在上述的例子中,`not` 是一个接受一个值的函数。
-- Haskell 不需要括号来调用函数。。。所有的参数
-- 都只是在函数名之后列出来。因此,通常的函数调用模式是:
-- func arg1 arg2 arg3...
-- 查看关于函数的章节以获得如何写你自己的函数的相关信息。

-- 字符串和字符
"This is a string."
'a' -- 字符
'对于字符串你不能使用单引号。' -- 错误!

-- 连结字符串
"Hello " ++ "world!" -- "Hello world!"

-- 一个字符串是一系列字符
"This is a string" !! 0 -- 'T'


----------------------------------------------------
-- 列表和元组
----------------------------------------------------

-- 一个列表中的每一个元素都必须是相同的类型
-- 下面两个列表一样
[1, 2, 3, 4, 5]
[1..5]

-- 在 Haskell 你可以拥有含有无限元素的列表
[1..] -- 一个含有所有自然数的列表

-- 因为 Haskell 有“懒惰计算”,所以无限元素的列表可以正常运作。这意味着
-- Haskell 可以只在它需要的时候计算。所以你可以请求
-- 列表中的第1000个元素,Haskell 会返回给你

[1..] !! 999 -- 1000

-- Haskell 计算了列表中 1 - 1000 个元素。。。但是
-- 这个无限元素的列表中剩下的元素还不存在! Haskell 不会
-- 真正地计算它们知道它需要。

<FS>- 连接两个列表
[1..5] ++ [6..10]

-- 往列表头增加元素
0:[1..5] -- [0, 1, 2, 3, 4, 5]

-- 列表中的下标
[0..] !! 5 -- 5

-- 更多列表操作
head [1..5] -- 1
tail [1..5] -- [2, 3, 4, 5]
init [1..5] -- [1, 2, 3, 4]
last [1..5] -- 5

-- 列表推导
[x*2 | x <- [1..5]] -- [2, 4, 6, 8, 10]

-- 附带条件
[x*2 | x <-[1..5], x*2 > 4] -- [6, 8, 10]

-- 元组中的每一个元素可以是不同类型的,但是一个元组
-- 的长度是固定的
-- 一个元组
("haskell", 1)

-- 获取元组中的元素
fst ("haskell", 1) -- "haskell"
snd ("haskell", 1) -- 1

----------------------------------------------------
-- 3. 函数
----------------------------------------------------
-- 一个接受两个变量的简单函数
add a b = a + b

-- 注意,如果你使用 ghci (Hakell 解释器)
-- 你将需要使用 `let`,也就是
-- let add a b = a + b

-- 使用函数
add 1 2 -- 3

-- 你也可以把函数放置在两个参数之间
-- 附带倒引号:
1 `add` 2 -- 3

-- 你也可以定义不带字符的函数!这使得
-- 你定义自己的操作符!这里有一个操作符
-- 来做整除
(//) a b = a `div` b
35 // 4 -- 8

-- 守卫:一个简单的方法在函数里做分支
fib x
  | x < 2 = x
  | otherwise = fib (x - 1) + fib (x - 2)

-- 模式匹配是类型的。这里有三种不同的 fib 
-- 定义。Haskell 将自动调用第一个
-- 匹配值的模式的函数。
fib 1 = 1
fib 2 = 2
fib x = fib (x - 1) + fib (x - 2)

-- 元组的模式匹配:
foo (x, y) = (x + 1, y + 2)

-- 列表的模式匹配。这里 `x` 是列表中第一个元素,
-- 并且 `xs` 是列表剩余的部分。我们可以写
-- 自己的 map 函数:
myMap func [] = []
myMap func (x:xs) = func x:(myMap func xs)

-- 编写出来的匿名函数带有一个反斜杠,后面跟着
-- 所有的参数。
myMap (\x -> x + 2) [1..5] -- [3, 4, 5, 6, 7]

-- 使用 fold (在一些语言称为`inject`)随着一个匿名的
-- 函数。foldl1 意味着左折叠(fold left), 并且使用列表中第一个值
-- 作为累加器的初始化值。
foldl1 (\acc x -> acc + x) [1..5] -- 15

----------------------------------------------------
-- 4. 更多的函数
----------------------------------------------------

-- 柯里化(currying):如果你不传递函数中所有的参数,
-- 它就变成“柯里化的”。这意味着,它返回一个接受剩余参数的函数。

add a b = a + b
foo = add 10 -- foo 现在是一个接受一个数并对其加 10 的函数
foo 5 -- 15

-- 另外一种方式去做同样的事
foo = (+10)
foo 5 -- 15

-- 函数组合
-- (.) 函数把其它函数链接到一起
-- 举个列子,这里 foo 是一个接受一个值的函数。它对接受的值加 10,
-- 并对结果乘以 5,之后返回最后的值。
foo = (*5) . (+10)

-- (5 + 10) * 5 = 75
foo 5 -- 75

-- 修复优先级
-- Haskell 有另外一个函数称为 `$`。它改变优先级
-- 使得其左侧的每一个操作先计算然后应用到
-- 右侧的每一个操作。你可以使用 `.` 和 `$` 来除去很多
-- 括号:

-- before
(even (fib 7)) -- true

-- after
even . fib $ 7 -- true

----------------------------------------------------
-- 5. 类型签名
----------------------------------------------------

-- Haskell 有一个非常强壮的类型系统,一切都有一个类型签名。

-- 一些基本的类型:
5 :: Integer
"hello" :: String
True :: Bool

-- 函数也有类型。
-- `not` 接受一个布尔型返回一个布尔型:
-- not :: Bool -> Bool

-- 这是接受两个参数的函数:
-- add :: Integer -> Integer -> Integer

-- 当你定义一个值,在其上写明它的类型是一个好实践:
double :: Integer -> Integer
double x = x * 2

----------------------------------------------------
-- 6. 控制流和 If 语句
----------------------------------------------------

-- if 语句
haskell = if 1 == 1 then "awesome" else "awful" -- haskell = "awesome"

-- if 语句也可以有多行,缩进是很重要的
haskell = if 1 == 1
            then "awesome"
            else "awful"

-- case 语句:这里是你可以怎样去解析命令行参数
case args of
  "help" -> printHelp
  "start" -> startProgram
  _ -> putStrLn "bad args"

-- Haskell 没有循环因为它使用递归取代之。
-- map 应用一个函数到一个数组中的每一个元素

map (*2) [1..5] -- [2, 4, 6, 8, 10]

-- 你可以使用 map 来编写 for 函数
for array func = map func array

-- 然后使用它
for [0..5] $ \i -> show i

-- 我们也可以像这样写:
for [0..5] show

-- 你可以使用 foldl 或者 foldr 来分解列表
-- foldl <fn> <initial value> <list>
foldl (\x y -> 2*x + y) 4 [1,2,3] -- 43

-- 这和下面是一样的
(2 * (2 * (2 * 4 + 1) + 2) + 3)

-- foldl 是左手边的,foldr 是右手边的-
foldr (\x y -> 2*x + y) 4 [1,2,3] -- 16

-- 这和下面是一样的
(2 * 3 + (2 * 2 + (2 * 1 + 4)))

----------------------------------------------------
-- 7. 数据类型
----------------------------------------------------

-- 这里展示在 Haskell 中你怎样编写自己的数据类型

data Color = Red | Blue | Green

-- 现在你可以在函数中使用它:


say :: Color -> String
say Red = "You are Red!"
say Blue = "You are Blue!"
say Green =  "You are Green!"

-- 你的数据类型也可以有参数:

data Maybe a = Nothing | Just a

-- 类型 Maybe 的所有
Just "hello"    -- of type `Maybe String`
Just 1          -- of type `Maybe Int`
Nothing         -- of type `Maybe a` for any `a`

----------------------------------------------------
-- 8. Haskell IO
----------------------------------------------------

-- 虽然在没有解释 monads 的情况下 IO不能被完全地解释,
-- 着手解释到位并不难。

-- 当一个 Haskell 程序被执行,函数 `main` 就被调用。
-- 它必须返回一个类型 `IO ()` 的值。举个列子:

main :: IO ()
main = putStrLn $ "Hello, sky! " ++ (say Blue) 
-- putStrLn has type String -> IO ()

-- 如果你能实现你的程序依照函数从 String 到 String,那样编写 IO 是最简单的。
-- 函数
--    interact :: (String -> String) -> IO ()
-- 输入一些文本,在其上运行一个函数,并打印出输出

countLines :: String -> String
countLines = show . length . lines

main' = interact countLines

-- 你可以考虑一个 `IO()` 类型的值,当做一系列计算机所完成的动作的代表,
-- 就像一个以命令式语言编写的计算机程序。我们可以使用 `do` 符号来把动作链接到一起。
-- 举个列子:

sayHello :: IO ()
sayHello = do 
   putStrLn "What is your name?"
   name <- getLine -- this gets a line and gives it the name "input"
   putStrLn $ "Hello, " ++ name

-- 练习:编写只读取一行输入的 `interact`

-- 然而,`sayHello` 中的代码将不会被执行。唯一被执行的动作是 `main` 的值。
-- 为了运行 `sayHello`,注释上面 `main` 的定义,并代替它:
--   main = sayHello

-- 让我们来更好地理解刚才所使用的函数 `getLine` 是怎样工作的。它的类型是:
--    getLine :: IO String
-- 你可以考虑一个 `IO a` 类型的值,代表一个当被执行的时候
-- 将产生一个 `a` 类型的值的计算机程序(除了它所做的任何事之外)。我们可以保存和重用这个值通过 `<-`。
-- 我们也可以写自己的 `IO String` 类型的动作:

action :: IO String
action = do
   putStrLn "This is a line. Duh"
   input1 <- getLine 
   input2 <- getLine
   -- The type of the `do` statement is that of its last line.
   -- `return` is not a keyword, but merely a function 
   return (input1 ++ "\n" ++ input2) -- return :: String -> IO String

-- 我们可以使用这个动作就像我们使用 `getLine`:

main'' = do
    putStrLn "I will echo two lines!"
    result <- action 
    putStrLn result
    putStrLn "This was all, folks!"

-- `IO` 类型是一个 "monad" 的例子。Haskell 使用一个 monad 来做 IO的方式允许它是一门纯函数式语言。
-- 任何与外界交互的函数(也就是 IO) 都在它的类型签名处做一个 `IO` 标志
-- 着让我们推出 什么样的函数是“纯洁的”(不与外界交互,不修改状态) 和 什么样的函数不是 “纯洁的”

-- 这是一个强有力的特征,因为并发地运行纯函数是简单的;因此,Haskell 中并发是非常简单的。


----------------------------------------------------
-- 9. The Haskell REPL
----------------------------------------------------

-- 键入 `ghci` 开始 repl。
-- 现在你可以键入 Haskell 代码。
-- 任何新值都需要通过 `let` 来创建:

let foo = 5

-- 你可以查看任何值的类型,通过命令 `:t`:

>:t foo
foo :: Integer

-- 你也可以运行任何 `IO ()`类型的动作

> sayHello
What is your name?
Friend!
Hello, Friend!


qsort [] = []
qsort (p:xs) = qsort lesser ++ [p] ++ qsort greater
    where lesser  = filter (< p) xs
          greater = filter (>= p) xs

jack.zh 标签:Haskell learnxinyminutes 继续阅读

779 ℉

14.10.28

Simple-go

// 单行注释
/* 多行
    注释 */

// 导入包的子句在每个源文件的开头。
// Main比较特殊,它用来声明可执行文件,而不是一个库。
package main

// Import语句声明了当前文件引用的包。
import (
    "fmt"       // Go语言标准库中的包
    "net/http"  // 一个web服务器包
    "strconv"   // 字符串转换
)

// 函数声明:Main是程序执行的入口。
// 不管你喜欢还是不喜欢,反正Go就用了花括号来包住函数体。
func main() {
    // 往标准输出打印一行。
    // 用包名fmt限制打印函数。
    fmt.Println("Hello world!")

    // 调用当前包的另一个函数。
    beyondHello()
}

// 函数可以在括号里加参数。
// 如果没有参数的话,也需要一个空括号。
func beyondHello() {
    var x int   // 变量声明,变量必须在使用之前声明。
    x = 3       // 变量赋值。
    // 可以用:=来偷懒,它自动把变量类型、声明和赋值都搞定了。
    y := 4
    sum, prod := learnMultiple(x, y)        // 返回多个变量的函数
    fmt.Println("sum:", sum, "prod:", prod) // 简单输出
    learnTypes()                            // 少于y分钟,学的更多!
}

// 多变量和多返回值的函数
func learnMultiple(x, y int) (sum, prod int) {
    return x + y, x * y // 返回两个值
}

// 内置变量类型和关键词
func learnTypes() {
    // 短声明给你所想。
    s := "Learn Go!" // String类型

    s2 := `A "raw" string literal
can include line breaks.` // 同样是String类型

    // 非ascii字符。Go使用UTF-8编码。
    g := 'Σ' // rune类型,int32的别名,使用UTF-8编码

    f := 3.14195 // float64类型,IEEE-754 64位浮点数
    c := 3 + 4i  // complex128类型,内部使用两个float64表示

    // Var变量可以直接初始化。
    var u uint = 7  // unsigned 无符号变量,但是实现依赖int型变量的长度
    var pi float32 = 22. / 7

    // 字符转换
    n := byte('\n') // byte是uint8的别名

    // 数组类型编译的时候大小固定。
    var a4 [4] int              // 有4个int变量的数组,初始为0
    a3 := [...]int{3, 1, 5}     // 有3个int变量的数组,同时进行了初始化

    // Slice 可以动态的增删。Array和Slice各有千秋,但是使用slice的地方更多些。
    s3 := []int{4, 5, 9}        // 和a3相比,这里没有省略号
    s4 := make([]int, 4)        // 分配一个有4个int型变量的slice,全部被初始化为0

    var d2 [][]float64          // 声明而已,什么都没有分配
    bs := []byte("a slice")     // 类型转换的语法

    p, q := learnMemory()       // 声明p,q为int型变量的指针
    fmt.Println(*p, *q)         // * 取值

    // Map是动态可增长关联数组,和其他语言中的hash或者字典相似。
    m := map[string]int{"three": 3, "four": 4}
    m["one"] = 1

    // 在Go语言中未使用的变量在编译的时候会报错,而不是warning。
    // 下划线 _ 可以使你“使用”一个变量,但是丢弃它的值。
    _,_,_,_,_,_,_,_,_ = s2, g, f, u, pi, n, a3, s4, bs
    // 输出变量
    fmt.Println(s, c, a4, s3, d2, m)

    learnFlowControl() // 回到流程控制 
}

// Go全面支持垃圾回收。Go有指针,但是不支持指针运算。
// 你会因为空指针而犯错,但是不会因为增加指针而犯错。
func learnMemory() (p, q *int) {
    // 返回int型变量指针p和q
    p = new(int)    // 内置函数new分配内存
    // 自动将分配的int赋值0,p不再是空的了。
    s := make([]int, 20)    // 给20个int变量分配一块内存
    s[3] = 7                // 赋值
    r := -2                 // 声明另一个局部变量
    return &s[3], &r        // & 取地址
}

func expensiveComputation() int {
    return 1e6
}

func learnFlowControl() {
    // If需要花括号,括号就免了
    if true {
        fmt.Println("told ya")
    }
    // 用go fmt 命令可以帮你格式化代码,所以不用怕被人吐槽代码风格了,
    // 也不用容忍被人的代码风格。
    if false {
        // pout
    } else {
        // gloat
    }
    // 如果太多嵌套的if语句,推荐使用switch
    x := 1
    switch x {
    case 0:
    case 1:
        // 隐式调用break语句,匹配上一个即停止
    case 2:
        // 不会运行
    }
    // 和if一样,for也不用括号
    for x := 0; x < 3; x++ { // ++ 自增
        fmt.Println("iteration", x)
    }
    // x在这里还是1。为什么?

    // for 是go里唯一的循环关键字,不过它有很多变种
    for { // 死循环
        break    // 骗你的 
        continue // 不会运行的
    }
    // 和for一样,if中的:=先给y赋值,然后再和x作比较。
    if y := expensiveComputation(); y > x {
        x = y
    }
    // 闭包函数
    xBig := func() bool {
        return x > 100 // x是上面声明的变量引用
    }
    fmt.Println("xBig:", xBig()) // true (上面把y赋给x了) 
    x /= 1e5                     // x变成10
    fmt.Println("xBig:", xBig()) // 现在是false

    // 当你需要goto的时候,你会爱死它的!
    goto love
love:

    learnInterfaces() // 好东西来了!
}

// 定义Stringer为一个接口类型,有一个方法String
type Stringer interface {
    String() string
}

// 定义pair为一个结构体,有x和y两个int型变量。
type pair struct {
    x, y int
}

// 定义pair类型的方法,实现Stringer接口。
func (p pair) String() string { // p被叫做“接收器”
    // Sprintf是fmt包中的另一个公有函数。
    // 用 . 调用p中的元素。
    return fmt.Sprintf("(%d, %d)", p.x, p.y)
}

func learnInterfaces() {
    // 花括号用来定义结构体变量,:=在这里将一个结构体变量赋值给p。
    p := pair{3, 4}
    fmt.Println(p.String()) // 调用pair类型p的String方法 
    var i Stringer          // 声明i为Stringer接口类型 
    i = p                   // 有效!因为p实现了Stringer接口(类似java中的塑型) 
    // 调用i的String方法,输出和上面一样
    fmt.Println(i.String())

    // fmt包中的Println函数向对象要它们的string输出,实现了String方法就可以这样使用了。
    // (类似java中的序列化)
    fmt.Println(p) // 输出和上面一样,自动调用String函数。
    fmt.Println(i) // 输出和上面一样。

    learnErrorHandling()
}

func learnErrorHandling() {
    // ", ok"用来判断有没有正常工作 
    m := map[int]string{3: "three", 4: "four"}
    if x, ok := m[1]; !ok { // ok 为false,因为m中没有1
        fmt.Println("no one there")
    } else {
        fmt.Print(x) // 如果x在map中的话,x就是那个值喽。
    }
    // 错误可不只是ok,它还可以给出关于问题的更多细节。
    if _, err := strconv.Atoi("non-int"); err != nil { // _ discards value
        // 输出"strconv.ParseInt: parsing "non-int": invalid syntax"
        fmt.Println(err)
    }
    // 待会再说接口吧。同时,
    learnConcurrency()
}

// c是channel类型,一个并发安全的通信对象。
func inc(i int, c chan int) {
    c <- i + 1 // <-把右边的发送到左边的channel。
}

// 我们将用inc函数来并发地增加一些数字。
func learnConcurrency() {
    // 用make来声明一个slice,make会分配和初始化slice,map和channel。
    c := make(chan int)
    // 用go关键字开始三个并发的goroutine,如果机器支持的话,还可能是并行执行。
    // 三个都被发送到同一个channel。
    go inc(0, c) // go is a statement that starts a new goroutine.
    go inc(10, c)
    go inc(-805, c)
    // 从channel中独处结果并打印。
    // 打印出什么东西是不可预知的。
    fmt.Println(<-c, <-c, <-c) // channel在右边的时候,<-是读操作。

    cs := make(chan string)       // 操作string的channel
    cc := make(chan chan string)  // 操作channel的channel
    go func() { c <- 84 }()       // 开始一个goroutine来发送一个新的数字 
    go func() { cs <- "wordy" }() // 发送给cs
    // Select类似于switch,但是每个case包括一个channel操作。
    // 它随机选择一个准备好通讯的case。
    select {
    case i := <-c: // 从channel接收的值可以赋给其他变量
        fmt.Println("it's a", i)
    case <-cs: // 或者直接丢弃
        fmt.Println("it's a string")
    case <-cc: // 空的,还没作好通讯的准备 
        fmt.Println("didn't happen.")
    }
    // 上面c或者cs的值被取到,其中一个goroutine结束,另外一个一直阻塞。

    learnWebProgramming() // Go很适合web编程,我知道你也想学!
}

// http包中的一个简单的函数就可以开启web服务器。
func learnWebProgramming() {
    // ListenAndServe第一个参数指定了监听端口,第二个参数是一个接口,特定是http.Handler。
    err := http.ListenAndServe(":8080", pair{})
    fmt.Println(err) // 不要无视错误。
}

// 使pair实现http.Handler接口的ServeHTTP方法。
func (p pair) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // 使用http.ResponseWriter返回数据
    w.Write([]byte("You learned Go in Y minutes!"))
}

jack.zh 标签:learnxinyminutes golang 继续阅读

756 ℉

14.10.28

Simple-clojure macros

;; 使用defmacro定义宏。宏应该输出一个可以作为clojure代码演算的列表。
;;
;; 以下宏的效果和直接写(reverse "Hello World")一致。

(defmacro my-first-macro []
  (list reverse "Hello World"))

;; 使用macroexpand或macroexpand-1查看宏的结果。
;;
;; 注意,调用需要引用。
(macroexpand '(my-first-macro))
;; -> (#<core$reverse clojure.core$reverse@xxxxxxxx> "Hello World")

;; 你可以直接eval macroexpand的结果
(eval (macroexpand '(my-first-macro)))
; -> (\d \l \o \r \W \space \o \l \l \e \H)

;; 不过一般使用以下形式,更简短,更像函数:
(my-first-macro)  ; -> (\d \l \o \r \W \space \o \l \l \e \H)

;; 创建宏的时候可以使用更简短的引用形式来创建列表
(defmacro my-first-quoted-macro []
  '(reverse "Hello World"))

(macroexpand '(my-first-quoted-macro))
;; -> (reverse "Hello World")
;; 注意reverse不再是一个函数对象,而是一个符号。

;; 宏可以传入参数。
(defmacro inc2 [arg]
  (list + 2 arg))

(inc2 2) ; -> 4

;; 不过,如果你尝试配合使用引用列表,会导致错误,
;; 因为参数也会被引用。
;; 为了避免这个问题,clojure提供了引用宏的另一种方式:`
;; 在`之内,你可以使用~获得外圈作用域的变量。
(defmacro inc2-quoted [arg]
  `(+ 2 ~arg))

(inc2-quoted 2)

;; 你可以使用通常的析构参数。用~@展开列表中的变量。
(defmacro unless [arg & body]
  `(if (not ~arg)
     (do ~@body))) ; 别忘了 do!

(macroexpand '(unless true (reverse "Hello World")))

;; ->
;; (if (clojure.core/not true) (do (reverse "Hello World")))

;; 当第一个参数为假时,(unless)会演算、返回主体。 
;; 否则返回nil。

(unless true "Hello") ; -> nil
(unless false "Hello") ; -> "Hello"

;; 需要小心,宏会搞乱你的变量
(defmacro define-x []
  '(do
     (def x 2)
     (list x)))

(def x 4)
(define-x) ; -> (2)
(list x) ; -> (2)

;; 使用gensym来获得独有的标识符
(gensym 'x) ; -> x1281 (or some such thing)

(defmacro define-x-safely []
  (let [sym (gensym 'x)]
    `(do
       (def ~sym 2)
       (list ~sym))))

(def x 4)
(define-x-safely) ; -> (2)
(list x) ; -> (4)

;; 你可以在 ` 中使用 # 为每个符号自动生成gensym
(defmacro define-x-hygenically []
  `(do
     (def x# 2)
     (list x#)))

(def x 4)
(define-x-hygenically) ; -> (2)
(list x) ; -> (4)

;; 通常会配合宏使用帮助函数。
;; 让我们创建一些帮助函数来支持(无聊的)算术语法:

(declare inline-2-helper)
(defn clean-arg [arg]
  (if (seq? arg)
    (inline-2-helper arg)
    arg))

(defn apply-arg
  "Given args [x (+ y)], return (+ x y)"
  [val [op arg]]
  (list op val (clean-arg arg)))

(defn inline-2-helper
  [[arg1 & ops-and-args]]
  (let [ops (partition 2 ops-and-args)]
    (reduce apply-arg (clean-arg arg1) ops)))

;; 在创建宏前,我们可以先测试
(inline-2-helper '(a + (b - 2) - (c * 5))) ; -> (- (+ a (- b 2)) (* c 5))

; 然而,如果我们希望它在编译期执行,就需要创建宏
(defmacro inline-2 [form]
  (inline-2-helper form)))

(macroexpand '(inline-2 (1 + (3 / 2) - (1 / 2) + 1)))
; -> (+ (- (+ 1 (/ 3 2)) (/ 1 2)) 1)

(inline-2 (1 + (3 / 2) - (1 / 2) + 1))
; -> 3 (事实上,结果是3N, 因为数字被转化为带/的有理分数)

jack.zh 标签:clojure macros learnxinyminutes 继续阅读

830 ℉

14.10.28

Simple-common lisp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 0. 语法
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; 一般形式

;; Lisp有两个基本的语法单元:原子(atom),以及S-表达式。
;; 一般的,一组S-表达式被称为“组合式”。

10  ; 一个原子; 它对自身进行求值

:THING ;同样是一个原子;它被求值为一个符号 :thing

t  ;还是一个原子,代表逻辑真值。

(+ 1 2 3 4) ; 一个S-表达式。

'(4 :foo  t)  ;同样是一个S-表达式。


;;; 注释

;; 一个分号开头的注释表示仅用于此行(单行);两个分号开头的则表示一个所谓标准注释;
;; 三个分号开头的意味着段落注释;
;; 而四个分号开头的注释用于文件头注释(译者注:即对该文件的说明)。

#| 块注释
   可以涵盖多行,而且...
    #|
       他们可以被嵌套!
    |#
|#

;;; 运行环境

;; 有很多不同的Common Lisp的实现;并且大部分的实现是一致(可移植)的。
;; 对于入门学习来说,CLISP是个不错的选择。

;; 可以通过QuickLisp.org的Quicklisp系统管理你的库。

;; 通常,使用文本编辑器和“REPL”来开发Common Lisp;
;; (译者注:“REPL”指读取-求值-打印循环)。
;; “REPL”允许对程序进行交互式的运行、调试,就好像在系统“现场”操作。


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 1. 基本数据类型以及运算符
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; 符号

'foo ; => FOO  注意到这个符号被自动转换成大写了。

;; `intern`由一个给定的字符串而创建相应的符号

(intern "AAAA") ; => AAAA

(intern "aaa") ; => |aaa|

;;; 数字
9999999999999999999999 ; 整型数
#b111                  ; 二进制 => 7
#o111                  ; 八进制 => 73
#x111                  ; 十六进制 => 273
3.14159s0              ; 单精度
3.14159d0              ; 双精度
1/2                    ; 分数
#C(1 2)                ; 复数


;; 使用函数时,应当写成这样的形式:(f x y z ...);
;; 其中,f是一个函数(名),x, y, z为参数;
;; 如果你想创建一个“字面”意义上(即不求值)的列表, 只需使用单引号 ' ,
;; 从而避免接下来的表达式被求值。即,只“引用”这个数据(而不求值)。
'(+ 1 2) ; => (+ 1 2)
;; 你同样也可以手动地调用一个函数(译者注:即使用函数对象来调用函数):
(funcall #'+ 1 2 3) ; => 6
;; 一些算术运算符
(+ 1 1)              ; => 2
(- 8 1)              ; => 7
(* 10 2)             ; => 20
(expt 2 3)           ; => 8
(mod 5 2)            ; => 1
(/ 35 5)             ; => 7
(/ 1 3)              ; => 1/3
(+ #C(1 2) #C(6 -4)) ; => #C(7 -2)

                     ;;; 布尔运算
t                    ; 逻辑真(任何不是nil的值都被视为真值)
nil                  ; 逻辑假,或者空列表
(not nil)            ; => t
(and 0 t)            ; => t
(or 0 nil)           ; => 0

                     ;;; 字符
#\A                  ; => #\A
#\λ                  ; => #\GREEK_SMALL_LETTER_LAMDA(希腊字母Lambda的小写)
#\u03BB              ; => #\GREEK_SMALL_LETTER_LAMDA(Unicode形式的小写希腊字母Lambda)

;;; 字符串被视为一个定长字符数组
"Hello, world!"
"Benjamin \"Bugsy\" Siegel"   ;反斜杠用作转义字符

;; 可以拼接字符串
(concatenate 'string "Hello " "world!") ; => "Hello world!"

;; 一个字符串也可被视作一个字符序列
(elt "Apple" 0) ; => #\A

;; `format`被用于格式化字符串
(format nil "~a can be ~a" "strings" "formatted")

;; 利用`format`打印到屏幕上是非常简单的
;;(译者注:注意到第二个参数是t,不同于刚刚的nil);~% 代表换行符
(format t "Common Lisp is groovy. Dude.~%")


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 2. 变量
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 你可以通过`defparameter`创建一个全局(动态)变量
;; 变量名可以是除了:()[]{}",'`;#|\ 这些字符之外的其他任何字符

;; 动态变量名应该由*号开头与结尾!
;; (译者注:这个只是一个习惯)

(defparameter *some-var* 5)
*some-var* ; => 5

;; 你也可以使用Unicode字符:
(defparameter *AΛB* nil)


;; 访问一个在之前从未被绑定的变量是一种不规范的行为(即使依然是可能发生的);
;; 不要尝试那样做。


;; 局部绑定:在(let ...)语句内,'me'被绑定到"dance with you"上。
;; `let`总是返回在其作用域内最后一个表达式的值

(let ((me "dance with you"))
  me)
;; => "dance with you"

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 3. 结构体和集合
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; 结构体
(defstruct dog name breed age)
(defparameter *rover*
    (make-dog :name "rover"
              :breed "collie"
              :age 5))
*rover* ; => #S(DOG :NAME "rover" :BREED "collie" :AGE 5)

(dog-p *rover*) ; => t  ;; ewww)
(dog-name *rover*) ; => "rover"

;; Dog-p,make-dog,以及 dog-name都是由defstruct创建的!

;;; 点对单元(Pairs)
;; `cons`可用于生成一个点对单元, 利用`car`以及`cdr`将分别得到第一个和第二个元素
(cons 'SUBJECT 'VERB) ; => '(SUBJECT . VERB)
(car (cons 'SUBJECT 'VERB)) ; => SUBJECT
(cdr (cons 'SUBJECT 'VERB)) ; => VERB

;;; 列表

;; 所有列表都是由点对单元构成的“链表”。它以'nil'(或者'())作为列表的最后一个元素。
(cons 1 (cons 2 (cons 3 nil))) ; => '(1 2 3)
;; `list`是一个生成列表的便利途径
(list 1 2 3) ; => '(1 2 3)
;; 并且,一个引用也可被用做字面意义上的列表值
'(1 2 3) ; => '(1 2 3)

;; 同样的,依然可以用`cons`来添加一项到列表的起始位置
(cons 4 '(1 2 3)) ; => '(4 1 2 3)

;; 而`append`也可用于连接两个列表
(append '(1 2) '(3 4)) ; => '(1 2 3 4)

;; 或者使用`concatenate`

(concatenate 'list '(1 2) '(3 4))

;; 列表是一种非常核心的数据类型,所以有非常多的处理列表的函数
;; 例如:
(mapcar #'1+ '(1 2 3))             ; => '(2 3 4)
(mapcar #'+ '(1 2 3) '(10 20 30))  ; => '(11 22 33)
(remove-if-not #'evenp '(1 2 3 4)) ; => '(2 4)
(every #'evenp '(1 2 3 4))         ; => nil
(some #'oddp '(1 2 3 4))           ; => T
(butlast '(subject verb object))   ; => (SUBJECT VERB)


;;; 向量

;; 向量的字面意义是一个定长数组
;;(译者注:此处所谓“字面意义”,即指#(......)的形式,下文还会出现)
#(1 2 3) ; => #(1 2 3)

;; 使用`concatenate`来将两个向量首尾连接在一起
(concatenate 'vector #(1 2 3) #(4 5 6)) ; => #(1 2 3 4 5 6)

;;; 数组

;; 向量和字符串只不过是数组的特例

;; 二维数组

(make-array (list 2 2))

;; (make-array '(2 2)) 也是可以的

; => #2A((0 0) (0 0))

(make-array (list 2 2 2))

; => #3A(((0 0) (0 0)) ((0 0) (0 0)))

;; 注意:数组的默认初始值是可以指定的
;; 下面是如何指定的示例:

(make-array '(2) :initial-element 'unset)

; => #(UNSET UNSET)

;; 若想获取数组[1][1][1]上的元素:
(aref (make-array (list 2 2 2)) 1 1 1)

; => 0

;;; 变长向量

;; 若将变长向量打印出来,那么它的字面意义上的值和定长向量的是一样的

(defparameter *adjvec* (make-array '(3) :initial-contents '(1 2 3)
      :adjustable t :fill-pointer t))

*adjvec* ; => #(1 2 3)

;; 添加新的元素:
(vector-push-extend 4 *adjvec*) ; => 3

*adjvec* ; => #(1 2 3 4)



;;; 不怎么严谨地说,集合也可被视为列表

(set-difference '(1 2 3 4) '(4 5 6 7)) ; => (3 2 1)
(intersection '(1 2 3 4) '(4 5 6 7)) ; => 4
(union '(1 2 3 4) '(4 5 6 7))        ; => (3 2 1 4 5 6 7)
(adjoin 4 '(1 2 3 4))     ; => (1 2 3 4)

;; 然而,你可能想使用一个更好的数据结构,而并非一个链表

;;; 在Common Lisp中,“字典”和哈希表的实现是一样的。

;; 创建一个哈希表
(defparameter *m* (make-hash-table))

;; 给定键,设置对应的值
(setf (gethash 'a *m*) 1)

;; (通过键)检索对应的值
(gethash 'a *m*) ; => 1, t

;; 注意此处有一细节:Common Lisp往往返回多个值。`gethash`返回的两个值是t,代表找到了这个元素;返回nil表示没有找到这个元素。
;;(译者注:返回的第一个值表示给定的键所对应的值或者nil;)
;;(第二个是一个布尔值,表示在哈希表中是否存在这个给定的键)
;; 例如,如果可以找到给定的键所对应的值,则返回一个t,否则返回nil

;; 由给定的键检索一个不存在的值,则返回nil
;;(译者注:这个nil是第一个nil,第二个nil其实是指该键在哈希表中也不存在)
 (gethash 'd *m*) ;=> nil, nil

;; 给定一个键,你可以指定其对应的默认值:
(gethash 'd *m* :not-found) ; => :NOT-FOUND

;; 在此,让我们看一看怎样处理`gethash`的多个返回值。

(multiple-value-bind
      (a b)
    (gethash 'd *m*)
  (list a b))
; => (NIL NIL)

(multiple-value-bind
      (a b)
    (gethash 'a *m*)
  (list a b))
; => (1 T)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 3. 函数
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; 使用`lambda`来创建一个匿名函数。
;; 一个函数总是返回其形式体内最后一个表达式的值。
;; 将一个函数对象打印出来后的形式是多种多样的...

(lambda () "Hello World") ; => #<FUNCTION (LAMBDA ()) {1004E7818B}>

;; 使用`funcall`来调用lambda函数
(funcall (lambda () "Hello World")) ; => "Hello World"

;; 或者使用`apply`
(apply (lambda () "Hello World") nil) ; => "Hello World"

;; 显式地定义一个函数(译者注:即非匿名的)
(defun hello-world ()
   "Hello World")
(hello-world) ; => "Hello World"

;; 刚刚上面函数名"hello-world"后的()其实是函数的参数列表
(defun hello (name)
   (format nil "Hello, ~a " name))

(hello "Steve") ; => "Hello, Steve"

;; 函数可以有可选形参并且其默认值都为nil

(defun hello (name &optional from)
    (if from
        (format t "Hello, ~a, from ~a" name from)
        (format t "Hello, ~a" name)))

 (hello "Jim" "Alpacas") ;; => Hello, Jim, from Alpacas

;; 你也可以指定那些可选形参的默认值
(defun hello (name &optional (from "The world"))
   (format t "Hello, ~a, from ~a" name from))

(hello "Steve")
; => Hello, Steve, from The world

(hello "Steve" "the alpacas")
; => Hello, Steve, from the alpacas


;; 当然,你也可以设置所谓关键字形参;
;; 关键字形参往往比可选形参更具灵活性。

(defun generalized-greeter (name &key (from "the world") (honorific "Mx"))
    (format t "Hello, ~a ~a, from ~a" honorific name from))

(generalized-greeter "Jim")   ; => Hello, Mx Jim, from the world

(generalized-greeter "Jim" :from "the alpacas you met last summer" :honorific "Mr")
; => Hello, Mr Jim, from the alpacas you met last summer

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 4. 等式
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Common Lisp具有一个十分复杂的用于判断等价的系统,下面只是其中一部分的例子

;; 若要比较数值是否等价,使用`=`
(= 3 3.0) ; => t
(= 2 1) ; => nil

;; 若要比较对象的类型,则使用`eql`
;;(译者注:抱歉,翻译水平实在有限,下面是我个人的补充说明)
;;(`eq` 返回真,如果对象的内存地址相等)
;;(`eql` 返回真,如果两个对象内存地址相等,或者对象的类型相同,并且值相等)
;;(例如同为整形数或浮点数,并且他们的值相等时,二者`eql`等价)
;;(想要弄清`eql`,其实有必要先了解`eq`)
;;([可以参考](http://stackoverflow.com/questions/547436/whats-the-difference-between-eq-eql-equal-and-equalp-in-common-lisp))
;;(可以去CLHS上分别查看两者的文档)
;;(另外,《实用Common Lisp编程》的4.8节也提到了两者的区别)
(eql 3 3) ; => t
(eql 3 3.0) ; => nil
(eql (list 3) (list 3)) ; => nil

;; 对于列表、字符串、以及位向量,使用`equal`
(equal (list 'a 'b) (list 'a 'b)) ; => t
(equal (list 'a 'b) (list 'b 'a)) ; => nil

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 5. 控制流
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; 条件判断语句

(if t                ; “test”,即判断语句
    "this is true"   ; “then”,即判断条件为真时求值的表达式
    "this is false") ; “else”,即判断条件为假时求值的表达式
; => "this is true"

;; 在“test”(判断)语句中,所有非nil或者非()的值都被视为真值
(member 'Groucho '(Harpo Groucho Zeppo)) ; => '(GROUCHO ZEPPO)
(if (member 'Groucho '(Harpo Groucho Zeppo))
    'yep
    'nope)
; => 'YEP

;; `cond`将一系列测试语句串联起来,并对相应的表达式求值
(cond ((> 2 2) (error "wrong!"))
      ((< 2 2) (error "wrong again!"))
      (t 'ok)) ; => 'OK

;; 对于给定值的数据类型,`typecase`会做出相应地判断
(typecase 1
  (string :string)
  (integer :int))

; => :int

;;; 迭代

;; 当然,递归是肯定被支持的:

(defun walker (n)
  (if (zerop n)
      :walked
      (walker (1- n))))

(walker) ; => :walked

;; 而大部分场合下,我们使用`DOLIST`或者`LOOP`来进行迭代


(dolist (i '(1 2 3 4))
  (format t "~a" i))

; => 1234

(loop for i from 0 below 10
      collect i)

; => (0 1 2 3 4 5 6 7 8 9)


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 6. 可变性
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; 使用`setf`可以对一个已经存在的变量进行赋值;
;; 事实上,刚刚在哈希表的例子中我们已经示范过了。

(let ((variable 10))
    (setf variable 2))
 ; => 2


;; 所谓好的Lisp编码风格就是为了减少使用破坏性函数,防止发生副作用。

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 7. 类与对象
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; 我们就不写什么有关动物的类了,下面给出的人力车的类

(defclass human-powered-conveyance ()
  ((velocity
    :accessor velocity
    :initarg :velocity)
   (average-efficiency
    :accessor average-efficiency
   :initarg :average-efficiency))
  (:documentation "A human powered conveyance"))

;; `defclass`,后面接类名,以及超类列表
;; 再接着是槽的列表(槽有点像Java里的成员变量),最后是一些可选的特性
;; 例如文档说明“:documentation”

;; 如果超类列表为空,则默认该类继承于“standard-object”类(standard-object又是T的子类)
;; 这种默认行为是可以改变的,但你最好有一定的基础并且知道自己到底在干什么;
;; 参阅《The Art of the Metaobject Protocol》来了解更多信息。

(defclass bicycle (human-powered-conveyance)
  ((wheel-size
    :accessor wheel-size
    :initarg :wheel-size
    :documentation "Diameter of the wheel.")
   (height
    :accessor height
    :initarg :height)))

(defclass recumbent (bicycle)
  ((chain-type
    :accessor chain-type
    :initarg  :chain-type)))

(defclass unicycle (human-powered-conveyance) nil)

(defclass canoe (human-powered-conveyance)
  ((number-of-rowers
    :accessor number-of-rowers
    :initarg :number-of-rowers)))


;; 在REPL中对human-powered-conveyance类调用`DESCRIBE`后结果如下:

(describe 'human-powered-conveyance)

; COMMON-LISP-USER::HUMAN-POWERED-CONVEYANCE
;  [symbol]
;
; HUMAN-POWERED-CONVEYANCE names the standard-class #<STANDARD-CLASS
;                                                    HUMAN-POWERED-CONVEYANCE>:
;  Documentation:
;    A human powered conveyance
;  Direct superclasses: STANDARD-OBJECT
;  Direct subclasses: UNICYCLE, BICYCLE, CANOE
;  Not yet finalized.
;  Direct slots:
;    VELOCITY
;      Readers: VELOCITY
;      Writers: (SETF VELOCITY)
;    AVERAGE-EFFICIENCY
;      Readers: AVERAGE-EFFICIENCY
;      Writers: (SETF AVERAGE-EFFICIENCY)

;; 注意到这些有用的返回信息——Common Lisp一直是一个交互式的系统。

;; 若要定义一个方法;
;; 注意,我们计算自行车轮子周长时使用了这样一个公式:C = d * pi

(defmethod circumference ((object bicycle))
  (* pi (wheel-size object)))

;; pi在Common Lisp中已经是一个内置的常量。

;; 假设我们已经知道了效率值(“efficiency value”)和船桨数大概呈对数关系;
;; 那么效率值的定义应当在构造器/初始化过程中就被完成。

;; 下面是一个Common Lisp构造实例时初始化实例的例子:

(defmethod initialize-instance :after ((object canoe) &rest args)
  (setf (average-efficiency object)  (log (1+ (number-of-rowers object)))))

;; 接着初构造一个实例并检查平均效率...

(average-efficiency (make-instance 'canoe :number-of-rowers 15))
; => 2.7725887


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 8. 宏
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; 宏可以让你扩展语法

;; 例如,Common Lisp并没有自带WHILE循环——所以让我们自己来为他添加一个;
;; 如果按照汇编程序的直觉来看,我们会这样写:

(defmacro while (condition &body body)
    "While `condition` is true, `body` is executed.

`condition` is tested prior to each execution of `body`"
    (let ((block-name (gensym)))
        `(tagbody
           (unless ,condition
               (go ,block-name))
           (progn
           ,@body)
           ,block-name)))

;; 让我们来看看它的高级版本:

(defmacro while (condition &body body)
    "While `condition` is true, `body` is executed.

`condition` is tested prior to each execution of `body`"
  `(loop while ,condition
         do
         (progn
            ,@body)))

;; 然而,在一个比较现代化的编译环境下,这样的WHILE是没有必要的;
;; LOOP形式的循环和这个WHILE同样的好,并且更易于阅读。

;; 注意反引号'`',逗号','以及'@'这三个符号; 
;; 反引号'`'是一种所谓“quasiquote”的引用类型的运算符,有了它,之后的逗号“,”才有意义。
;; 逗号“,”意味着解除引用(unquote,即开始求值);
;; “@”符号则表示将当前的参数插入到当前整个列表中。
;;(译者注:要想真正用好、用对这三个符号,需要下一番功夫)
;;(甚至光看《实用 Common Lisp 编程》中关于宏的介绍都是不够的)
;;(建议再去读一读Paul Graham的两本著作《ANSI Common Lisp》和《On Lisp》)

;; 函数`gensym`创建一个唯一的符号——这个符号确保不会出现在其他任何地方。
;; 这样做是因为,宏是在编译期展开的
;; 而在宏中声明的变量名极有可能和常规代码中使用的变量名发生冲突。

;; 可以去《实用 Common Lisp 编程》中阅读更多有关宏的内容。

jack.zh 标签:common lisp learnxinyminutes 继续阅读

1 2 3 4 5 6 7 8 9 10
Fork me on GitHub