701 ℉

14.10.28

Simple-Clojure

; 注释以分号开始。

; Clojure代码由一个个form组成, 即写在小括号里的由空格分开的一组语句。
; Clojure解释器会把第一个元素当做一个函数或者宏来调用,其余的被认为是参数。

; Clojure代码的第一条语句一般是用ns来指定当前的命名空间。
(ns learnclojure)

; 更基本的例子:

; str会使用所有参数来创建一个字符串
(str "Hello" " " "World") ; => "Hello World"

; 数学计算比较直观
(+ 1 1) ; => 2
(- 2 1) ; => 1
(* 1 2) ; => 2
(/ 2 1) ; => 2

; 等号是 =
(= 1 1) ; => true
(= 2 1) ; => false

; 逻辑非
(not true) ; => false

; 嵌套的form工作起来应该和你预想的一样
(+ 1 (- 3 2)) ; = 1 + (3 - 2) => 2

; 类型
;;;;;;;;;;;;;

; Clojure使用Java的Object来描述布尔值、字符串和数字
; 用函数 `class` 来查看具体的类型
(class 1) ; 整形默认是java.lang.Long类型
(class 1.); 浮点默认是java.lang.Double类型的
(class ""); String是java.lang.String类型的,要用双引号引起来
(class false) ; 布尔值是java.lang.Boolean类型的
(class nil); "null"被称作nil

; 如果你想创建一组数据字面量,用单引号(')来阻止form被解析和求值
'(+ 1 2) ; => (+ 1 2)
; (单引号是quote的简写形式,故上式等价于(quote (+ 1 2)))

; 可以对一个引用列表求值
(eval '(+ 1 2)) ; => 3

; 集合(Collection)和序列
;;;;;;;;;;;;;;;;;;;

; List的底层实现是链表,Vector的底层实现是数组
; 二者也都是java类
(class [1 2 3]); => clojure.lang.PersistentVector
(class '(1 2 3)); => clojure.lang.PersistentList

; list本可以写成(1 2 3), 但必须用引用来避免被解释器当做函数来求值。
; (list 1 2 3)等价于'(1 2 3)

; 集合其实就是一组数据
; List和Vector都是集合:
(coll? '(1 2 3)) ; => true
(coll? [1 2 3]) ; => true

; 序列 (seqs) 是数据列表的抽象描述
; 只有列表才可称作序列。
(seq? '(1 2 3)) ; => true
(seq? [1 2 3]) ; => false

; 序列被访问时只需要提供一个值,所以序列可以被懒加载——也就意味着可以定义一个无限序列:
(range 4) ; => (0 1 2 3)
(range) ; => (0 1 2 3 4 ...) (无限序列)
(take 4 (range)) ;  (0 1 2 3)

; cons用以向列表或向量的起始位置添加元素
(cons 4 [1 2 3]) ; => (4 1 2 3)
(cons 4 '(1 2 3)) ; => (4 1 2 3)

; conj将以最高效的方式向集合中添加元素。
; 对于列表,数据会在起始位置插入,而对于向量,则在末尾位置插入。
(conj [1 2 3] 4) ; => [1 2 3 4]
(conj '(1 2 3) 4) ; => (4 1 2 3)

; 用concat来合并列表或向量
(concat [1 2] '(3 4)) ; => (1 2 3 4)

; 用filter来过滤集合中的元素,用map来根据指定的函数来映射得到一个新的集合
(map inc [1 2 3]) ; => (2 3 4)
(filter even? [1 2 3]) ; => (2)

; recuce使用函数来规约集合
(reduce + [1 2 3 4])
; = (+ (+ (+ 1 2) 3) 4)
; => 10

; reduce还能指定一个初始参数
(reduce conj [] '(3 2 1))
; = (conj (conj (conj [] 3) 2) 1)
; => [3 2 1]

; 函数
;;;;;;;;;;;;;;;;;;;;;

; 用fn来创建函数。函数的返回值是最后一个表达式的值
(fn [] "Hello World") ; => fn

; (你需要再嵌套一组小括号来调用它)
((fn [] "Hello World")) ; => "Hello World"

; 你可以用def来创建一个变量(var)
(def x 1)
x ; => 1

; 将函数定义为一个变量(var)
(def hello-world (fn [] "Hello World"))
(hello-world) ; => "Hello World"

; 你可用defn来简化函数的定义
(defn hello-world [] "Hello World")

; 中括号内的内容是函数的参数。
(defn hello [name]
  (str "Hello " name))
(hello "Steve") ; => "Hello Steve"

; 你还可以用这种简写的方式来创建函数:
(def hello2 #(str "Hello " %1))
(hello2 "Fanny") ; => "Hello Fanny"

; 函数也可以有多个参数列表。
(defn hello3
  ([] "Hello World")
  ([name] (str "Hello " name)))
(hello3 "Jake") ; => "Hello Jake"
(hello3) ; => "Hello World"

; 可以定义变参函数,即把&后面的参数全部放入一个序列
(defn count-args [& args]
  (str "You passed " (count args) " args: " args))
(count-args 1 2 3) ; => "You passed 3 args: (1 2 3)"

; 可以混用定参和变参(用&来界定)
(defn hello-count [name & args]
  (str "Hello " name ", you passed " (count args) " extra args"))
(hello-count "Finn" 1 2 3)
; => "Hello Finn, you passed 3 extra args"


; 哈希表
;;;;;;;;;;

; 基于hash的map和基于数组的map(即arraymap)实现了相同的接口,hashmap查询起来比较快,
; 但不保证元素的顺序。
(class {:a 1 :b 2 :c 3}) ; => clojure.lang.PersistentArrayMap
(class (hash-map :a 1 :b 2 :c 3)) ; => clojure.lang.PersistentHashMap

; arraymap在足够大的时候,大多数操作会将其自动转换成hashmap,
; 所以不用担心(对大的arraymap的查询性能)。

; map支持很多类型的key,但推荐使用keyword类型
; keyword类型和字符串类似,但做了一些优化。
(class :a) ; => clojure.lang.Keyword

(def stringmap {"a" 1, "b" 2, "c" 3})
stringmap  ; => {"a" 1, "b" 2, "c" 3}

(def keymap {:a 1, :b 2, :c 3})
keymap ; => {:a 1, :c 3, :b 2}

; 顺便说一下,map里的逗号是可有可无的,作用只是提高map的可读性。

; 从map中查找元素就像把map名作为函数调用一样。
(stringmap "a") ; => 1
(keymap :a) ; => 1

; 可以把keyword写在前面来从map中查找元素。
(:b keymap) ; => 2

; 但不要试图用字符串类型的key来这么做。
;("a" stringmap)
; => Exception: java.lang.String cannot be cast to clojure.lang.IFn

; 查找不存在的key会返回nil。
(stringmap "d") ; => nil

; 用assoc函数来向hashmap里添加元素
(def newkeymap (assoc keymap :d 4))
newkeymap ; => {:a 1, :b 2, :c 3, :d 4}

; 但是要记住的是clojure的数据类型是不可变的!
keymap ; => {:a 1, :b 2, :c 3}

; 用dissoc来移除元素
(dissoc keymap :a :b) ; => {:c 3}

; 集合(Set)
;;;;;;

(class #{1 2 3}) ; => clojure.lang.PersistentHashSet
(set [1 2 3 1 2 3 3 2 1 3 2 1]) ; => #{1 2 3}

; 用conj新增元素
(conj #{1 2 3} 4) ; => #{1 2 3 4}

; 用disj移除元素
(disj #{1 2 3} 1) ; => #{2 3}

; 把集合当做函数调用来检查元素是否存在:
(#{1 2 3} 1) ; => 1
(#{1 2 3} 4) ; => nil

; 在clojure.sets模块下有很多相关函数。

; 常用的form
;;;;;;;;;;;;;;;;;

; clojure里的逻辑控制结构都是用宏(macro)实现的,这在语法上看起来没什么不同。
(if false "a" "b") ; => "b"
(if false "a") ; => nil

; 用let来创建临时的绑定变量。
(let [a 1 b 2]
  (> a b)) ; => false

; 用do将多个语句组合在一起依次执行
(do
  (print "Hello")
  "World") ; => "World" (prints "Hello")

; 函数定义里有一个隐式的do
(defn print-and-say-hello [name]
  (print "Saying hello to " name)
  (str "Hello " name))
(print-and-say-hello "Jeff") ;=> "Hello Jeff" (prints "Saying hello to Jeff")

; let也是如此
(let [name "Urkel"]
  (print "Saying hello to " name)
  (str "Hello " name)) ; => "Hello Urkel" (prints "Saying hello to Urkel")

; 模块
;;;;;;;;;;;;;;;

; 用use来导入模块里的所有函数
(use 'clojure.set)

; 然后就可以使用set相关的函数了
(intersection #{1 2 3} #{2 3 4}) ; => #{2 3}
(difference #{1 2 3} #{2 3 4}) ; => #{1}

; 你也可以从一个模块里导入一部分函数。
(use '[clojure.set :only [intersection]])

; 用require来导入一个模块
(require 'clojure.string)

; 用/来调用模块里的函数
; 下面是从模块`clojure.string`里调用`blank?`函数。
(clojure.string/blank? "") ; => true

; 在`import`里你可以给模块名指定一个较短的别名。
(require '[clojure.string :as str])
(str/replace "This is a test." #"[a-o]" str/upper-case) ; => "THIs Is A tEst."
; (#""用来表示一个正则表达式)

; 你可以在一个namespace定义里用:require的方式来require(或use,但最好不要用)模块。
; 这样的话你无需引用模块列表。
(ns test
  (:require
    [clojure.string :as str]
    [clojure.set :as set]))

; Java
;;;;;;;;;;;;;;;;;

; Java有大量的优秀的库,你肯定想学会如何用clojure来使用这些Java库。

; 用import来导入java类
(import java.util.Date)

; 也可以在ns定义里导入
(ns test
  (:import java.util.Date
           java.util.Calendar))

; 用类名末尾加`.`的方式来new一个Java对象
(Date.) ; <a date object>

; 用`.`操作符来调用方法,或者用`.method`的简化方式。
(. (Date.) getTime) ; <a timestamp>
(.getTime (Date.)) ; 和上例一样。

; 用`/`调用静态方法
(System/currentTimeMillis) ; <a timestamp> (system is always present)

; 用`doto`来更方便的使用(可变)类。
(import java.util.Calendar)
(doto (Calendar/getInstance)
  (.set 2000 1 1 0 0 0)
  .getTime) ; => A Date. set to 2000-01-01 00:00:00

; STM
;;;;;;;;;;;;;;;;;

; 软件内存事务(Software Transactional Memory)被clojure用来处理持久化的状态。
; clojure里内置了一些结构来使用STM。
; atom是最简单的。给它传一个初始值
(def my-atom (atom {}))

; 用`swap!`更新atom。
; `swap!`会以atom的当前值为第一个参数来调用一个指定的函数,
; `swap`其余的参数作为该函数的第二个参数。
(swap! my-atom assoc :a 1) ; Sets my-atom to the result of (assoc {} :a 1)
(swap! my-atom assoc :b 2) ; Sets my-atom to the result of (assoc {:a 1} :b 2)

; 用`@`读取atom的值
my-atom  ;=> Atom<#...> (返回Atom对象)
@my-atom ; => {:a 1 :b 2}

; 下例是一个使用atom实现的简单计数器
(def counter (atom 0))
(defn inc-counter []
  (swap! counter inc))

(inc-counter)
(inc-counter)
(inc-counter)
(inc-counter)
(inc-counter)

@counter ; => 5

; 其他STM相关的结构是ref和agent.
; Refs: http://clojure.org/refs
; Agents: http://clojure.org/agents

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

809 ℉

14.10.28

Simple-Lua

-- 单行注释以两个连字符开头 

--[[ 
     多行注释
--]]

---------------------------------------------------- 
-- 1. 变量和流程控制
---------------------------------------------------- 

num = 42  -- 所有的数字都是双精度浮点型。
-- 别害怕,64位的双精度浮点型数字中有52位用于 
-- 保存精确的整型值; 对于52位以内的整型值, 
-- 不用担心精度问题。

s = 'walternate'  -- 和Python一样,字符串不可变。 
t = "也可以用双引号" 
u = [[ 多行的字符串
       以两个方括号
       开始和结尾。]] 
t = nil  -- 撤销t的定义; Lua 支持垃圾回收。 

-- 块使用do/end之类的关键字标识: 
while num < 50 do 
  num = num + 1  -- 不支持 ++ 或 += 运算符。 
end 

-- If语句: 
if num > 40 then 
  print('over 40') 
elseif s ~= 'walternate' then  -- ~= 表示不等于。 
  -- 像Python一样,用 == 检查是否相等 ;字符串同样适用。 
  io.write('not over 40\n')  -- 默认标准输出。
else 
  -- 默认全局变量。 
  thisIsGlobal = 5  -- 通常使用驼峰。

  -- 如何定义局部变量: 
  local line = io.read()  -- 读取标准输入的下一行。 

  -- ..操作符用于连接字符串: 
  print('Winter is coming, ' .. line) 
end 

-- 未定义的变量返回nil。 
-- 这不是错误: 
foo = anUnknownVariable  -- 现在 foo = nil. 

aBoolValue = false 

--只有nil和false为假; 0和 ''都均为真! 
if not aBoolValue then print('twas false') end 

-- 'or'和 'and'短路 
-- 类似于C/js里的 a?b:c 操作符: 
ans = aBoolValue and 'yes' or 'no'  --> 'no' 

karlSum = 0 
for i = 1, 100 do  -- 范围包含两端 
  karlSum = karlSum + i 
end 

-- 使用 "100, 1, -1" 表示递减的范围: 
fredSum = 0 
for j = 100, 1, -1 do fredSum = fredSum + j end 

-- 通常,范围表达式为begin, end[, step]. 

-- 循环的另一种结构: 
repeat 
  print('the way of the future') 
  num = num - 1 
until num == 0 

---------------------------------------------------- 
-- 2. 函数。 
---------------------------------------------------- 

function fib(n) 
  if n < 2 then return 1 end 
  return fib(n - 2) + fib(n - 1) 
end 

-- 支持闭包及匿名函数: 
function adder(x) 
  -- 调用adder时,会创建返回的函数,
  -- 并且会记住x的值: 
  return function (y) return x + y end 
end 
a1 = adder(9) 
a2 = adder(36) 
print(a1(16))  --> 25 
print(a2(64))  --> 100 

-- 返回值、函数调用和赋值都可以
-- 使用长度不匹配的list。 
-- 不匹配的接收方会被赋值nil; 
-- 不匹配的发送方会被丢弃。 

x, y, z = 1, 2, 3, 4 
-- x = 1、y = 2、z = 3, 而 4 会被丢弃。 

function bar(a, b, c) 
  print(a, b, c) 
  return 4, 8, 15, 16, 23, 42 
end 

x, y = bar('zaphod')  --> 打印 "zaphod  nil nil" 
-- 现在 x = 4, y = 8, 而值15..42被丢弃。 

-- 函数是一等公民,可以是局部的,也可以是全局的。 
-- 以下表达式等价: 
function f(x) return x * x end 
f = function (x) return x * x end 

-- 这些也是等价的: 
local function g(x) return math.sin(x) end 
local g; g  = function (x) return math.sin(x) end 
-- 'local g'使得g可以自引用。 

-- 顺便提下,三角函数以弧度为单位。 

-- 用一个字符串参数调用函数,可以省略括号: 
print 'hello'  --可以工作。 

-- 调用函数时,如果只有一个table参数,
-- 同样可以省略括号(table详情见下):
print {} -- 一样可以工作。

---------------------------------------------------- 
-- 3. Table。 
---------------------------------------------------- 

-- Table = Lua唯一的组合数据结构; 
--         它们是关联数组。 
-- 类似于PHP的数组或者js的对象, 
-- 它们是哈希表或者字典,也可以当初列表使用。 

-- 按字典/map的方式使用Table: 

-- Dict字面量默认使用字符串类型的key: 
t = {key1 = 'value1', key2 = false} 

-- 字符串key可以使用类似js的点标记: 
print(t.key1)  -- 打印 'value1'. 
t.newKey = {}  -- 添加新的键值对。 
t.key2 = nil   -- 从table删除 key2。 

-- 使用任何非nil的值作为key: 
u = {['@!#'] = 'qbert', [{}] = 1729, [6.28] = 'tau'} 
print(u[6.28])  -- 打印 "tau" 

-- 数字和字符串的key按值匹配的
-- table按id匹配。 
a = u['@!#']  -- 现在 a = 'qbert'. 
b = u[{}]     -- 我们或许期待的是 1729,  但是得到的是nil: 
-- b = nil ,因为没有找到。 
-- 之所以没找到,是因为我们用的key与保存数据时用的不是同
-- 一个对象。 
-- 所以字符串和数字是移植性更好的key。 

-- 只需要一个table参数的函数调用不需要括号: 
function h(x) print(x.key1) end 
h{key1 = 'Sonmi~451'}  -- 打印'Sonmi~451'. 

for key, val in pairs(u) do  -- 遍历Table
  print(key, val) 
end 

-- _G 是一个特殊的table,用于保存所有的全局变量 
print(_G['_G'] == _G)  -- 打印'true'. 

-- 按列表/数组的方式使用: 

-- 列表字面量隐式添加整数键: 
v = {'value1', 'value2', 1.21, 'gigawatts'} 
for i = 1, #v do  -- #v 是列表的大小
  print(v[i])  -- 索引从 1 开始!! 太疯狂了! 
end
-- 'list'并非真正的类型,v 其实是一个table, 
-- 只不过它用连续的整数作为key,可以像list那样去使用。 

---------------------------------------------------- 
-- 3.1 元表(metatable) 和元方法(metamethod)。 
---------------------------------------------------- 

-- table的元表提供了一种机制,支持类似操作符重载的行为。
-- 稍后我们会看到元表如何支持类似js prototype的行为。 

f1 = {a = 1, b = 2}  -- 表示一个分数 a/b. 
f2 = {a = 2, b = 3} 

-- 这会失败:
-- s = f1 + f2 

metafraction = {} 
function metafraction.__add(f1, f2) 
  sum = {} 
  sum.b = f1.b * f2.b 
  sum.a = f1.a * f2.b + f2.a * f1.b 
  return sum
end

setmetatable(f1, metafraction) 
setmetatable(f2, metafraction) 

s = f1 + f2  -- 调用在f1的元表上的__add(f1, f2) 方法 

-- f1, f2 没有关于元表的key,这点和js的prototype不一样。 
-- 因此你必须用getmetatable(f1)获取元表。
-- 元表是一个普通的table, 
-- 元表的key是普通的Lua中的key,例如__add。 

-- 但是下面一行代码会失败,因为s没有元表: 
-- t = s + s 
-- 下面提供的与类相似的模式可以解决这个问题: 

-- 元表的__index 可以重载用于查找的点操作符: 
defaultFavs = {animal = 'gru', food = 'donuts'} 
myFavs = {food = 'pizza'} 
setmetatable(myFavs, {__index = defaultFavs}) 
eatenBy = myFavs.animal  -- 可以工作!感谢元表 

-- 如果在table中直接查找key失败,会使用
-- 元表的__index 递归地重试。

-- __index的值也可以是function(tbl, key)
-- 这样可以支持自定义查找。 

-- __index、__add等的值,被称为元方法。 
-- 这里是一个table元方法的清单: 

-- __add(a, b)                     for a + b 
-- __sub(a, b)                     for a - b 
-- __mul(a, b)                     for a * b 
-- __div(a, b)                     for a / b 
-- __mod(a, b)                     for a % b 
-- __pow(a, b)                     for a ^ b 
-- __unm(a)                        for -a 
-- __concat(a, b)                  for a .. b 
-- __len(a)                        for #a 
-- __eq(a, b)                      for a == b 
-- __lt(a, b)                      for a < b 
-- __le(a, b)                      for a <= b 
-- __index(a, b)  <fn or a table>  for a.b 
-- __newindex(a, b, c)             for a.b = c 
-- __call(a, ...)                  for a(...) 

---------------------------------------------------- 
-- 3.2 与类相似的table和继承。 
---------------------------------------------------- 

-- Lua没有内建的类;可以通过不同的方法,利用表和元表
-- 来实现类。 

-- 下面是一个例子,解释在后面: 

Dog = {}                                   -- 1. 

function Dog:new()                         -- 2. 
  newObj = {sound = 'woof'}                -- 3. 
  self.__index = self                      -- 4. 
  return setmetatable(newObj, self)        -- 5. 
end 

function Dog:makeSound()                   -- 6. 
  print('I say ' .. self.sound) 
end 

mrDog = Dog:new()                          -- 7. 
mrDog:makeSound()  -- 'I say woof'         -- 8. 

-- 1. Dog看上去像一个类;其实它是一个table。 
-- 2. 函数tablename:fn(...) 等价于
--    函数tablename.fn(self, ...)
--    冒号(:)只是添加了self作为第一个参数。 
--    阅读7 & 8条 了解self变量是如何得到其值的。 
-- 3. newObj是类Dog的一个实例。 
-- 4. self = 被继承的类。通常self = Dog,不过继承可以改变它。 
--    如果把newObj的元表和__index都设置为self, 
--    newObj就可以得到self的函数。 
-- 5. 备忘:setmetatable返回其第一个参数。 
-- 6. 冒号(:)的作用和第2条一样,不过这里 
--    self是一个实例,而不是类 
-- 7. 等价于Dog.new(Dog),所以在new()中,self = Dog。 
-- 8. 等价于mrDog.makeSound(mrDog); self = mrDog。 

---------------------------------------------------- 

-- 继承的例子: 

LoudDog = Dog:new()                           -- 1. 

function LoudDog:makeSound() 
  s = self.sound .. ' '                       -- 2. 
  print(s .. s .. s) 
end 

seymour = LoudDog:new()                       -- 3. 
seymour:makeSound()  -- 'woof woof woof'      -- 4. 

-- 1. LoudDog获得Dog的方法和变量列表。 
-- 2. 因为new()的缘故,self拥有了一个'sound' key,参见第3条。 
-- 3. 等价于LoudDog.new(LoudDog),转换一下就是 
--    Dog.new(LoudDog),这是因为LoudDog没有'new' key, 
--    但是它的元表中有 __index = Dog。 
--    结果: seymour的元表是LoudDog,并且 
--    LoudDog.__index = Dog。所以有seymour.key 
--    = seymour.key, LoudDog.key, Dog.key 
--    从其中第一个有指定key的table获取。 
-- 4. 在LoudDog可以找到'makeSound'的key; 
--    等价于LoudDog.makeSound(seymour)。 

-- 如果有必要,子类也可以有new(),与基类相似: 
function LoudDog:new() 
  newObj = {} 
  -- 初始化newObj 
  self.__index = self 
  return setmetatable(newObj, self) 
end 

---------------------------------------------------- 
-- 4. 模块 
---------------------------------------------------- 


--[[ 我把这部分给注释了,这样脚本剩下的部分可以运行 

-- 假设文件mod.lua的内容类似这样: 
local M = {} 

local function sayMyName() 
  print('Hrunkner') 
end 

function M.sayHello() 
  print('Why hello there') 
  sayMyName() 
end 

return M 

-- 另一个文件可以使用mod.lua的功能: 
local mod = require('mod')  -- 运行文件mod.lua. 

-- require是包含模块的标准做法。 
-- require等价于:     (针对没有被缓存的情况;参见后面的内容) 
local mod = (function () 
  <contents of mod.lua> 
end)() 
-- mod.lua被包在一个函数体中,因此mod.lua的局部变量
-- 对外不可见。 

-- 下面的代码可以工作,因为在这里mod = mod.lua 中的 M: 
mod.sayHello()  -- Says hello to Hrunkner. 

-- 这是错误的;sayMyName只在mod.lua中存在: 
mod.sayMyName()  -- 错误 

-- require返回的值会被缓存,所以一个文件只会被运行一次, 
-- 即使它被require了多次。 

-- 假设mod2.lua包含代码"print('Hi!')"。 
local a = require('mod2')  -- 打印Hi! 
local b = require('mod2')  -- 不再打印; a=b. 

-- dofile与require类似,但是不缓存: 
dofile('mod2')  --> Hi! 
dofile('mod2')  --> Hi! (再次运行,与require不同) 

-- loadfile加载一个lua文件,但是并不运行它。 
f = loadfile('mod2')  -- Calling f() runs mod2.lua. 

-- loadstring是loadfile的字符串版本。 
g = loadstring('print(343)')  --返回一个函数。 
g()  -- 打印343; 在此之前什么也不打印。 

--]] 

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

938 ℉

14.10.28

Simple-C

// 单行注释以//开始。(仅适用于C99或更新的版本。)

/*
多行注释是这个样子的。(C89也适用。)
*/

// 常数: #define 关键词
#define DAYS_IN_YEAR 365

// 以枚举的方式定义常数
enum days {SUN = 1, MON, TUE, WED, THU, FRI, SAT};
// MON自动被定义为2,TUE被定义为3,以此类推。

// 用#include来导入头文件
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

// <尖括号>间的文件名是C标准库的头文件。
// 标准库以外的头文件,使用双引号代替尖括号。
#include "my_header.h"

// 函数的签名可以事先在.h文件中定义,
// 也可以直接在.c文件的头部定义。
void function_1(char c);
void function_2(void);

// 如果函数出现在main()之后,那么必须在main()之前
// 先声明一个函数原型
int add_two_ints(int x1, int x2); // 函数原型

// 你的程序的入口是一个返回值为整型的main函数
int main() {

// 用printf打印到标准输出,可以设定格式,
// %d 代表整数, \n 代表换行
printf("%d\n", 0); // => 打印 0
// 所有的语句都要以分号结束

///////////////////////////////////////
// 类型
///////////////////////////////////////

// 在使用变量之前我们必须先声明它们。
// 变量在声明时需要指明其类型,而类型能够告诉系统这个变量所占用的空间

// int型(整型)变量一般占用4个字节
int x_int = 0;

// short型(短整型)变量一般占用2个字节
short x_short = 0;

// char型(字符型)变量会占用1个字节
char x_char = 0;
char y_char = 'y'; // 字符变量的字面值需要用单引号包住

// long型(长整型)一般需要4个字节到8个字节; 而long long型则至少需要8个字节(64位)

long x_long = 0;
long long x_long_long = 0; 

// float一般是用32位表示的浮点数字
float x_float = 0.0;

// double一般是用64位表示的浮点数字
double x_double = 0.0;

// 整数类型也可以有无符号的类型表示。这样这些变量就无法表示负数
// 但是无符号整数所能表示的范围就可以比原来的整数大一些

unsigned short ux_short;
unsigned int ux_int;
unsigned long long ux_long_long;

// 单引号内的字符是机器的字符集中的整数。
'0' // => 在ASCII字符集中是48
'A' // => 在ASCII字符集中是65

// char类型一定会占用1个字节,但是其他的类型却会因具体机器的不同而各异
// sizeof(T) 可以返回T类型在运行的机器上占用多少个字节 
// 这样你的代码就可以在各处正确运行了
// sizeof(obj)返回表达式(变量、字面量等)的尺寸
printf("%zu\n", sizeof(int)); // => 4 (大多数的机器字长为4)

// 如果`sizeof`的参数是一个表达式,那么这个参数不会被演算(VLA例外,见下)
// 它产生的值是编译期的常数
int a = 1;
// size_t是一个无符号整型,表示对象的尺寸,至少2个字节
size_t size = sizeof(a++); // a++ 不会被演算
printf("sizeof(a++) = %zu where a = %d\n", size, a);
// 打印 "sizeof(a++) = 4 where a = 1" (在32位架构上)

// 数组必须要被初始化为具体的长度
char my_char_array[20]; // 这个数组占据 1 * 20 = 20 个字节
int my_int_array[20]; // 这个数组占据 4 * 20 = 80 个字节
                      // (这里我们假设字长为4)


// 可以用下面的方法把数组初始化为0:
char my_array[20] = {0};

// 索引数组和其他语言类似 -- 好吧,其实是其他的语言像C
my_array[0]; // => 0

// 数组是可变的,其实就是内存的映射!
my_array[1] = 2;
printf("%d\n", my_array[1]); // => 2

// 在C99 (C11中是可选特性),变长数组(VLA)也可以声明长度。
// 其长度不用是编译期常量。
printf("Enter the array size: "); // 询问用户数组长度
char buf[0x100];
fgets(buf, sizeof buf, stdin);

// stroul 将字符串解析为无符号整数
size_t size = strtoul(buf, NULL, 10);
int var_length_array[size]; // 声明VLA
printf("sizeof array = %zu\n", sizeof var_length_array);

// 上述程序可能的输出为:
// > Enter the array size: 10
// > sizeof array = 40

// 字符串就是以 NUL (0x00) 这个字符结尾的字符数组,
// NUL可以用'\0'来表示.
// (在字符串字面量中我们不必输入这个字符,编译器会自动添加的)
char a_string[20] = "This is a string";
printf("%s\n", a_string); // %s 可以对字符串进行格式化
/*
也许你会注意到 a_string 实际上只有16个字节长.
第17个字节是一个空字符(NUL) 
而第18, 19 和 20 个字符的值是未定义。
*/

printf("%d\n", a_string[16]); // => 0
//  byte #17值为0(18,19,20同样为0)

// 单引号间的字符是字符字面量
// 它的类型是`int`,而 *不是* `char`
// (由于历史原因)
int cha = 'a'; // 合法
char chb = 'a'; // 同样合法 (隐式类型转换

// 多维数组
int multi_array[2][5] = {
        {1, 2, 3, 4, 5},
        {6, 7, 8, 9, 0}
    }
// 获取元素
int array_int = multi_array[0][2]; // => 3

///////////////////////////////////////
// 操作符
///////////////////////////////////////

// 多个变量声明的简写
int i1 = 1, i2 = 2;
float f1 = 1.0, f2 = 2.0;

int a, b, c;
a = b = c = 0;

// 算数运算直截了当
i1 + i2; // => 3
i2 - i1; // => 1
i2 * i1; // => 2
i1 / i2; // => 0 (0.5,但会被化整为 0)

f1 / f2; // => 0.5, 也许会有很小的误差
// 浮点数和浮点数运算都是近似值

// 取余运算
11 % 3; // => 2

// 你多半会觉得比较操作符很熟悉, 不过C中没有布尔类型
// 而是用整形替代
// (C99中有_Bool或bool。)
// 0为假, 其他均为真. (比较操作符的返回值总是返回0或1)
3 == 2; // => 0 (false)
3 != 2; // => 1 (true)
3 > 2; // => 1
3 < 2; // => 0
2 <= 2; // => 1
2 >= 2; // => 1

// C不是Python —— 连续比较不合法
int a = 1;
// 错误
int between_0_and_2 = 0 < a < 2;
// 正确
int between_0_and_2 = 0 < a && a < 2;

// 逻辑运算符适用于整数
!3; // => 0 (非)
!0; // => 1
1 && 1; // => 1 (且)
0 && 1; // => 0
0 || 1; // => 1 (或)
0 || 0; // => 0

// 条件表达式 ( ? : )
int a = 5;
int b = 10;
int z;
z = (a > b) ? a : b; //  10 “若a > b返回a,否则返回b。”

// 增、减
char *s = "iLoveC"
int j = 0;
s[j++]; // "i" 返回s的第j项,然后增加j的值。
j = 0;
s[++j]; // => "L"  增加j的值,然后返回s的第j项。
// j-- 和 --j 同理

// 位运算
~0x0F; // => 0xF0 (取反)
0x0F & 0xF0; // => 0x00 (和)
0x0F | 0xF0; // => 0xFF (或)
0x04 ^ 0x0F; // => 0x0B (异或)
0x01 << 1; // => 0x02 (左移1位)
0x02 >> 1; // => 0x01 (右移1位)

// 对有符号整数进行移位操作要小心 —— 以下未定义:
// 有符号整数位移至符号位 int a = 1 << 32
// 左移位一个负数 int a = -1 << 2
// 移位超过或等于该类型数值的长度
// int a = 1 << 32; // 假定int32位


///////////////////////////////////////
// 控制结构
///////////////////////////////////////

if (0) {
  printf("I am never run\n");
} else if (0) {
  printf("I am also never run\n");
} else {
  printf("I print\n");
}

// While循环
int ii = 0;
while (ii < 10) { // 任何非0的值均为真
    printf("%d, ", ii++); // ii++ 在取值过后自增
} // =>  打印 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "

printf("\n");

int kk = 0;
do {
    printf("%d, ", kk);
} while (++kk < 10); // ++kk 先自增,再被取值
// => 打印 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "

printf("\n");

// For 循环
int jj;
for (jj=0; jj < 10; jj++) {
    printf("%d, ", jj);
} // => 打印 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "

printf("\n");

// *****注意*****:
// 循环和函数必须有主体部分,如果不需要主体部分:
int i;
    for (i = 0; i <= 5; i++) {
    ; // 使用分号表达主体(null语句)
}

// 多重分支:switch()
switch (some_integral_expression) {
case 0: // 标签必须是整数常量表达式
    do_stuff();
    break; // 如果不使用break,控制结构会继续执行下面的标签
case 1:
    do_something_else();
    break;
default:
    // 假设 `some_integral_expression` 不匹配任何标签
    fputs("error!\n", stderr);
    exit(-1);
    break;
    }

///////////////////////////////////////
// 类型转换
///////////////////////////////////////

// 在C中每个变量都有类型,你可以将变量的类型进行转换
// (有一定限制)

int x_hex = 0x01; // 可以用16进制字面量赋值

// 在类型转换时,数字本身的值会被保留下来
printf("%d\n", x_hex); // => 打印 1
printf("%d\n", (short) x_hex); // => 打印 1
printf("%d\n", (char) x_hex); // => 打印 1

// 类型转换时可能会造成溢出,而且不会抛出警告
printf("%d\n", (char) 257); // => 1 (char的最大值为255,假定char为8位长)

// 使用<limits.h>提供的CHAR_MAX、SCHAR_MAX和UCHAR_MAX宏可以确定`char`、`signed_char`和`unisigned char`的最大值。


// 整数型和浮点型可以互相转换
printf("%f\n", (float)100); // %f 格式化单精度浮点
printf("%lf\n", (double)100); // %lf 格式化双精度浮点
printf("%d\n", (char)100.0);

///////////////////////////////////////
// 指针
///////////////////////////////////////

// 指针变量是用来储存内存地址的变量
// 指针变量的声明也会告诉它所指向的数据的类型
// 你可以使用得到你的变量的地址,并把它们搞乱,;-)

int x = 0;
printf("%p\n", &x); // 用 & 来获取变量的地址
// (%p 格式化一个类型为 void *的指针)
// => 打印某个内存地址

// 指针类型在声明中以*开头
int* px, not_a_pointer; // px是一个指向int型的指针
px = &x; // 把x的地址保存到px中
printf("%p\n", (void *)px); // => 输出内存中的某个地址
printf("%zu, %zu\n", sizeof(px), sizeof(not_a_pointer));
// => 在64位系统上打印“8, 4”。

// 要得到某个指针指向的内容的值,可以在指针前加一个*来取得(取消引用)
// 注意: 是的,这可能让人困惑,'*'在用来声明一个指针的同时取消引用它。
printf("%d\n", *px); // => 输出 0, 即x的值

// 你也可以改变指针所指向的值
// 此时你需要取消引用上添加括号,因为++比*的优先级更高
(*px)++; // 把px所指向的值增加1
printf("%d\n", *px); // => 输出 1
printf("%d\n", x); // => 输出 1

// 数组是分配一系列连续空间的常用方式
int x_array[20];
int xx;
for (xx=0; xx<20; xx++) {
    x_array[xx] = 20 - xx;
} // 初始化 x_array 为 20, 19, 18,... 2, 1

// 声明一个整型的指针,并初始化为指向x_array
int* x_ptr = x_array;
// x_ptr现在指向了数组的第一个元素(即整数20). 
// 这是因为数组通常衰减为指向它们的第一个元素的指针。
// 例如,当一个数组被传递给一个函数或者绑定到一个指针时,
//它衰减为(隐式转化为)一个指针。
// 例外: 当数组是`&`操作符的参数:
int arr[10];
int (*ptr_to_arr)[10] = &arr; // &arr的类型不是`int *`!
                              // 它的类型是指向数组的指针(数组由10个int组成)
// 或者当数组是字符串字面量(初始化字符数组)
char arr[] = "foobarbazquirk";
// 或者当它是`sizeof`或`alignof`操作符的参数时:
int arr[10];
int *ptr = arr; // 等价于 int *ptr = &arr[0];
printf("%zu, %zu\n", sizeof arr, sizeof ptr); // 应该会输出"40, 4"或"40, 8"

// 指针的增减多少是依据它本身的类型而定的
// (这被称为指针算术)
printf("%d\n", *(x_ptr + 1)); // => 打印 19
printf("%d\n", x_array[1]); // => 打印 19

// 你也可以通过标准库函数malloc来实现动态分配
// 这个函数接受一个代表容量的参数,参数类型为`size_t`
// 系统一般会从堆区分配指定容量字节大小的空间
// (在一些系统,例如嵌入式系统中这点不一定成立
// C标准对此未置一词。)
int *my_ptr = malloc(sizeof(*my_ptr) * 20);
for (xx=0; xx<20; xx++) {
    *(my_ptr + xx) = 20 - xx; // my_ptr[xx] = 20-xx
} // 初始化内存为 20, 19, 18, 17... 2, 1 (类型为int)

// 对未分配的内存进行取消引用会产生未定义的结果
printf("%d\n", *(my_ptr + 21)); // => 谁知道会输出什么

// malloc分配的区域需要手动释放
// 否则没人能够再次使用这块内存,直到程序结束为止
free(my_ptr);

// 字符串通常是字符数组,但是经常用字符指针表示
// (它是指向数组的第一个元素的指针)
// 一个优良的实践是使用`const char *`来引用一个字符串字面量,
// 因为字符串字面量不应当被修改(即"foo"[0] = 'a'犯了大忌)
const char* my_str = "This is my very own string";
printf("%c\n", *my_str); // => 'T'

// 如果字符串是数组,(多半是用字符串字面量初始化的)
// 情况就不一样了,字符串位于可写的内存中
char foo[] = "foo";
foo[0] = 'a'; // 这是合法的,foo现在包含"aoo"

function_1();
} // main函数结束

///////////////////////////////////////
// 函数
///////////////////////////////////////

// 函数声明语法:
// <返回值类型> <函数名称>(<参数>)

int add_two_ints(int x1, int x2){
    return x1 + x2; // 用return来返回一个值
}

/*
函数是按值传递的。当调用一个函数的时候,传递给函数的参数
是原有值的拷贝(数组除外)。你在函数内对参数所进行的操作
不会改变该参数原有的值。

但是你可以通过指针来传递引用,这样函数就可以更改值

例子:字符串本身翻转
*/

// 类型为void的函数没有返回值
void str_reverse(char *str_in){
    char tmp;
    int ii = 0;
    size_t len = strlen(str_in); // `strlen()`` 是C标准库函数
    for(ii = 0; ii < len / 2; ii++){
        tmp = str_in[ii];
        str_in[ii] = str_in[len - ii - 1]; // 从倒数第ii个开始
        str_in[len - ii - 1] = tmp;
    }
}

/*
char c[] = "This is a test.";
str_reverse(c);
printf("%s\n", c); // => ".tset a si sihT"
*/

// 如果引用函数之外的变量,必须使用extern关键字
int i = 0;
void testFunc() {
    extern int i; // 使用外部变量 i
}

// 使用static确保external变量为源文件私有
static int i = 0; // 其他使用 testFunc()的文件无法访问变量i
void testFunc() {
    extern int i;
}
//**你同样可以声明函数为static**


///////////////////////////////////////
// 用户自定义类型和结构
///////////////////////////////////////

// Typedefs可以创建类型别名
typedef int my_type;
my_type my_type_var = 0;

// struct是数据的集合,成员依序分配,按照
// 编写的顺序
struct rectangle {
    int width;
    int height;
};

// 一般而言,以下断言不成立:
// sizeof(struct rectangle) == sizeof(int) + sizeof(int)
//这是因为structure成员之间可能存在潜在的间隙(为了对齐)[1]

void function_1(){

    struct rectangle my_rec;

    // 通过 . 来访问结构中的数据
    my_rec.width = 10;
    my_rec.height = 20;

    // 你也可以声明指向结构体的指针
    struct rectangle *my_rec_ptr = &my_rec;

    // 通过取消引用来改变结构体的成员...
    (*my_rec_ptr).width = 30;

    // ... 或者用 -> 操作符作为简写提高可读性
    my_rec_ptr->height = 10; // Same as (*my_rec_ptr).height = 10;
}

// 你也可以用typedef来给一个结构体起一个别名
typedef struct rectangle rect;

int area(rect r){
    return r.width * r.height;
}

// 如果struct较大,你可以通过指针传递,避免
// 复制整个struct。
int area(const rect *r)
{
    return r->width * r->height;
}

///////////////////////////////////////
// 函数指针
///////////////////////////////////////
/*
在运行时,函数本身也被存放到某块内存区域当中
函数指针就像其他指针一样(不过是存储一个内存地址) 但却可以被用来直接调用函数,
并且可以四处传递回调函数
但是,定义的语法初看令人有些迷惑

例子:通过指针调用str_reverse
*/
void str_reverse_through_pointer(char *str_in) {
    // 定义一个函数指针 f. 
    void (*f)(char *); // 签名一定要与目标函数相同
    f = &str_reverse; // 将函数的地址在运行时赋给指针
    (*f)(str_in); // 通过指针调用函数
    // f(str_in); // 等价于这种调用方式
}

/*
只要函数签名是正确的,任何时候都能将任何函数赋给某个函数指针
为了可读性和简洁性,函数指针经常和typedef搭配使用:
*/

typedef void (*my_fnp_type)(char *);

// 实际声明函数指针会这么用:
// ...
// my_fnp_type f; 

// 特殊字符
'\a' // bell
'\n' // 换行
'\t' // tab
'\v' // vertical tab
'\f' // formfeed
'\r' // 回车
'\b' // 退格
'\0' // null,通常置于字符串的最后。
     //   hello\n\0. 按照惯例,\0用于标记字符串的末尾。
'\\' // 反斜杠
'\?' // 问号
'\'' // 单引号
'\"' // 双引号
'\xhh' // 十六进制数字. 例子: '\xb' = vertical tab
'\ooo' // 八进制数字. 例子: '\013' = vertical tab

// 打印格式:
"%d"    // 整数
"%3d"   // 3位以上整数 (右对齐文本)
"%s"    // 字符串
"%f"    // float
"%ld"   // long
"%3.2f" // 左3位以上、右2位以上十进制浮
"%7.4s" // (字符串同样适用)
"%c"    // 字母
"%p"    // 指针
"%x"    // 十六进制
"%o"    // 八进制
"%%"    // 打印 %

///////////////////////////////////////
// 演算优先级
///////////////////////////////////////
//---------------------------------------------------//
//        操作符                     | 组合          //
//---------------------------------------------------//
// () [] -> .                        | 从左到右      //
// ! ~ ++ -- + = *(type)sizeof       | 从右到左      //
// * / %                             | 从左到右      //
// + -                               | 从左到右      //
// << >>                             | 从左到右      //
// < <= > >=                         | 从左到右      //
// == !=                             | 从左到右      //
// &                                 | 从左到右      //
// ^                                 | 从左到右      //
// |                                 | 从左到右      //
// &&                                | 从左到右      //
// ||                                | 从左到右      //
// ?:                                | 从右到左      //
// = += -= *= /= %= &= ^= |= <<= >>= | 从右到左      //
// ,                                 | 从左到右      //
//---------------------------------------------------//

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

874 ℉

14.10.27

Jeff Atwood 推荐:程序员必读之书

Jeff Atwood 推荐:程序员必读之书

  • 代码大全2
  • 人月神话
  • Don‘t Make Me Think
  • Rapid Develpoment 快速软件开发
  • 人件
  • 设计心理学
  • About Face3:交互设计精髓
  • 交互设计之路-让高科技产品回归人性
  • GUI设计禁忌
  • 编程珠玑
  • 程序员修炼之道-从小工到专家
  • Web可用性设计
  • 精通正则表达式

jack.zh 标签:高效程序员的修炼 继续阅读

849 ℉

14.10.27

高效程序员的修炼-轻重缓急 了然于心

轻重缓急 了然于心

马斯洛需求层析理论
  • 生理上的需要
    • 呼吸
    • 食物
    • 睡眠
    • 生理平衡
    • 分泌
  • 安全上的需要
    • 人身安全
    • 健康保障
    • 资源所有性
    • 财产所有性
    • 道德保障
    • 工作职位保障  
    • 家庭安全
  • 情感和归属的需要
    • 友情
    • 爱情
    • 性亲密
  • 尊重的需要
    • 自我尊重
    • 信心
    • 成就 
    • 对他人尊重
    • 被他人尊重
  • 自我实现的需要
    • 道德
    • 创造力
    • 自觉性
    • 问题解决能力
    • 公正度
    • 接受现实能力
程序员 你幸福吗?
  • 经历胜过物质
  • 助人为乐
  • 让幸福细水长流
  • 少卖保险
  • 为将来买单
  • 三思而后行
  • 小心比较购物的陷阱
  • 随大流
来也匆匆 去也匆匆 到头来两头空
  • 时代

jack.zh 标签:高效程序员的修炼 继续阅读

744 ℉

14.10.27

高效程序员的修炼-揭露营销伎俩,以及如何规避

揭露营销伎俩,以及如何规避

谨防九种营销诡计
  • 用不恰当的比较来误导
    • 价格提升来假装提升产品
    • 人们会为了省7元钱去远处买菜 不会为了省10元钱去远处买一件1000元的衣服
    • 高价商品存在的意义一般就是为了来比较的
    • 省钱要与相节省的时间比较
  • 利用消费惯性
    • 根据实际需要决定你的消费水平
    • 尽量客观的去评价你要买的东西的本身价值
  • 免费的诱惑
    • 人们一般过高的估计了免费物品的价值
    • 免费一般是有陷阱的
    • 免费造成时间等损失有时候会得不偿失
  • 假借社会行为准则
    • 你是否被利用了?
  • 故意允许拖延
    • 小心避开一些低价试用期,而随后会转变为高价的诱惑
    • 对那些允许你拖延单手后面会有持续消费的东西 警惕
  • 利用禀赋效应
    • 人们常常沉迷于我们已经拥有的东西:过多的关注我们可能失去的东西
    • 你在某个产品 服务上投入的精力也许没你想的那么多 别舍不得放弃
    • 一般买下某个东西,别太主观的判断他的价值
  • 利用人的“损失厌恶”
    • 如果你的选择机会被认为减少了,别被动就范
    • 除非你非常确定你需要某些特性,否邪恶不要为此牺牲大的代价
  • 制造不合理的期望
    • 小马过河 别道听途说
    • 别看重那些标签 高级 专业 获奖 等等
  • 利用对价格的偏见
    • 价格与价值没有必然的联系
    • 不要成为“拜金主义”的牺牲品
网络广告该休矣
  • 休矣
从《偷天情缘》看A/B测试的问题
  • 亲 去看电影吧
如果流于俗套 请即刻改变
  • 去死吧 那些所谓的专业精神 所谓的顺理成章
软件定价,我们深谙其道吗
  • 不了解

jack.zh 标签:高效程序员的修炼 继续阅读

818 ℉

14.10.27

高效程序员的修炼-创建并管理社区,并从中受益

创建并管理社区,并从中受益

倾听社区的声音,但别被他们牵着鼻子走
  • 90%的社区反馈都是垃圾 剩余的10%是非常非常有意义的
  • 别抵挡不住诱惑而误入歧途
  • 坦诚的说出你不想做的事情
  • 倾听社区的声音,但别被他牵着鼻子走
  • 参与并支持你的社区
重申:别盲目听从你的客户
  • 有时候用户不知道自己需要一个什么东西
  • 有时候用户说的你理解起来并不是他想要表达的
  • 所以:
    • 观察人们的实际做法
    • 不要相信人们说他们做了什么
    • 绝对不要相信人们预想他们将来可能做什么
  • 重申:关注他们做了什么,提取信息比他们说了什么更重要
游戏化
  • 学习应该是有趣的
  • 要提出改进
  • 游戏是学习的助手
  • 游戏促使人们同心协力
暂停 禁止或者打入地狱
  • 干掉破坏者
    • 打入地狱
    • 降低访问速度
    • 制造页面错误

jack.zh 标签:高效程序员的修炼 继续阅读

775 ℉

14.10.27

高效程序员的修炼-加强代码测试,别让他太差劲

加强代码测试,别让他太差劲

与客户共患难
  • 把开发者带到客户的战壕里面去
  • 让开发者知道客户的艰辛
  • 开发者与客户共患难
结交“混世魔猴”
  • 把事情弄到最坏,然后再最坏的基础上看上去做到完美
  • 后备服务器
  • 做容错
  • 去掉冗余
  • 立即去把它变得更好
代码评审
  • 同级评审 意义最大
  • 评审不是在浪费时间
    • 加速开发
    • 增强自身
    • 改进自身
    • 健壮性
    • 减少后期投入
  • 加大测试力度
我同情那些不写单元测试的傻瓜
  • 单元测试可以证明你的代码是能真正解决问题的
  • 你可以获得一个底层模块的回归测试工具
  • 你可以在不破坏现有功能的基础上持续改进设计
  • 一边写单元测试 一边写代码 这种方式更有乐趣
  • 他们可以被用来真是的展示开发进度
  • 单元测试可以用来做示例代码
  • 他逼你在写代码之前做好计划
  • 它可以降低Bug修复成本
  • 单元测试甚至比代码审查效果还要好
  • 它实际上为程序员消除了工作上的障碍
  • 单元测试可以催生更好的设计
  • 他比不写单元测试而直接上代码效率更高
  • 测试驱动开发
单元测试与Beta测试的对比
  • 有些Bug是无关紧要的
  • 真正的测试人员讨厌你的代码
  • 用户是疯狂的 Beta吧 亲
低保真的可用性测试
  • 你不找来真正的用户来测试,你是不知道你的程序是否可以运行的
  • 我该在什么时候测试? 持续测试
  • 我需要找多少用户? 3,4个
  • 要找什么样的用户?随机的 不要放过你的朋友
  • 测试持续时间?尽量保持简单 一个小时以内
  • 在哪里测试? 任何地方 不受干扰 观棋不语地点
  • 测试人员的特质?耐心者
  • 什么设备?屏幕录像软件 等
  • 准备?文档
  • 钱?还是要给的
  • 对待结果?随你的变
  • 《Don’t make me think》
比程序崩溃更糟糕的是什么
  • 1 程序能正常工作,从不崩溃
  • 2 极少崩溃,基本碰不到
  • 3 程序正常使用的情况下时有崩溃
  • 4 程序正常实用下会出现死锁并停止响应
  • 5 程序动不动就崩溃,根本没法用
  • 6 程序导致用户数据丢失,并且(或者)引起系统崩溃

  • 快速失败是好的策略

  • 在失败中缓过来

  • 负起责任来 安全的修复问题

  • 如果修复不了,保护用户数据

jack.zh 标签:高效程序员的修炼 继续阅读

725 ℉

14.10.27

高效程序员的修炼-保护用户隐私

保护用户隐私

1 所有的网络通信都应该加密
  • HTTPS的使用,更成熟 更快 最重要的是更安全
2 防范字典式攻击
  • 限制次数
  • 防范字典
3 快速哈希加密
  • 安全哈希应该能防止篡改
  • 安全的哈希运行缓慢
  • 哈希和密码
4 网络密码的可怕真相
  • 千万不能保存明文密码
  • 加密方式的选择
  • 建站收集癖

jack.zh 标签:高效程序员的修炼 继续阅读

810 ℉

14.10.24

高效程序员的修炼-设计时要把用户放在心上

设计时要把用户放在心上

  • 你永远不会有足够的奶酪
    • 你的应用受不了一点电击
    • 用户体验是“可选的”
    • CNN不会担心易用性,用户体验(核心功能的重要性)
  • 细节决定成败
    • 注重细节,持续改进
    • 把最重要的事情做正确
    • 善于倾听反馈,改进细节
  • 用户界面代表了软件
    • 易用 TeX和Ruby
    • 有好的界面,才有时间在用户接受的时间里面更改背后的Bug?
  • 用户界面代表了你的软件 . + 用户界面需要优先设计
    • 你草拟用户界面的时候,必须置身与技术开发环境之外
    • 在纸上草拟原型是个不错的选择
    • 纸上草拟的好处(技术的保鲜期与香蕉无异)(纸上技术保存期很强)
    • 用户界面有限设计的软件开发模式的最求比选择任何工具都重要
  • 分页显示该休矣
    • 在理想情况下,每次搜索都应该只返回一个结果
    • 无穷显示模式 滚滚滚
  • 对待弱视用户
    • 用户必回阅读你放在界面上的任何信息
    • 用户只会读取少量的,让他们完成任务的文字上
  • 浏览器底栏
    • 以前,把信息塞到底栏上面去,因为用户不会滚动界面
    • 不要把全部塞到底栏以上,只要要你的界面看上去是可以滚动的就好
    • 小心那些与底栏齐平的生硬的水平线
    • 避免页面内的滚动条
    • 把最重要的东西写在上面最先看到的敌方
  • 菲兹定律与无限宽度
  • 单元测试的终极失败
    • 用户是否想要用你的软件,如果这个终极测试失败,其他的测试都是失败的
    • 用户说这个是一个Bug,这个就是Bug,不论这个是写在什么上的
  • 第一版我们做的不好,但我们照样发布
    • 做砸了还是还可以,用户说的算
    • 你的团队最用户反馈的响应速度会为你的软件定下基调
    • 不要迷信完美软件
    • 即使第一版做的不好,也要坚持把它发布出去

jack.zh 标签:高效程序员的修炼 继续阅读

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