読者です 読者をやめる 読者になる 読者になる

C++ファイル分割

またまた久しぶりにC++を書こうとすると,ヘッダファイル,ファイル分割を忘れてしまっていたので,整理.

全般

  • 共通にするものをヘッダに書く(当たり前だけど).
  • 宣言と定義の違いを持つこと.宣言は名前だけ.定義は実態を生成する文.
  • 定義は(普通)は一つ.ということで関数の定義や,変数の定義は書かない.
  • 逆に変数の宣言(extern)はヘッダに書く.共通したいから.
  • グローバル変数は実態を(何処か1つの)ソースファイルに書いて,そのヘッダにexternで宣言する.他のソースをはそのヘッダをインクルードする.
  • static変数は(普通は)ヘッダに書かない.ヘッダに書くってことはそのヘッダをインクルードしたソース(Cのインクルードは基本的にコピー)全てに,ソース固有のstatic変数が生成される.
  • ヘッダの中で不要にヘッダファイルをインクルードしない.相互依存が発生してしまうし,そのヘッダをインクルードしているソース全部が再コンパイルされてしまう.ラベルが欲しいだけなら前方宣言を利用する.

前方宣言

  • クラス定義をしていると,他のクラスを変数として持ちたいことが(当然だけど)ある.その場合にそのクラスのヘッダファイルをインクルードしないといけなくなって,すぐにヘッダの依存関係が混雑してしまう.
  • そんな時に利用するのが,その変数をポインタとして持つ方法.ポインタであればメソッドやメンバ変数などの情報(というかコンパイル時のメモリ割当)が不要(ポインタ64ビットか32ビットあれば良い)だから,ラベルさえあれば良い.
// シンプルVer.
class Hoge; // <- 前方宣言

class Foo {
private
  Hoge *hoge; // <- ポインタで持つ
};

// 名前空間の下にいるVer.
namespace BAR {
  class Hoge;
}

class Foo {
private
  BAR::Hoge *hoge;
};

extern C {...}

  • Cの関数をC++でも使えるようにするための宣言.
  • 関数名はコンパイル時にリネームされる.しかし,その方法がCとC++で異なるため,普通にコンパイルするとリンクエラー(そんな名前の関数無いよ)になる.
  • それを避けるために,extern C {...}で囲っておく.特に,g++とかだと_cplusplusマクロが定義されているから,それでifdefする.
#ifdef _cpuluspulus
extern C {
#endif
... // Cの宣言文

#ifdef _cpuluspulus
}
#endif

実行中プロセスのREPLに接続

メモ

長いシミュレーションなどをサーバ上でプロセスを実行させたまま帰って,あとでミスに気づいて関数を変更したい,などという状況. swankというのがSlimeの機能なのかEmacsの機能なのかSBCL(CL)の機能なのかが良く分からないけど,イメージとしては,SBCL側でSWANKサーバを立ち上げておいて,SlimeからそのSwankサーバにソケット接続してS式で通信,というイメージなのかな.

やり方

  • sbcl側でやっておくこと.
(ql:quickload :swank)
(swank:create-server :port 4005) ; swankサーバ立上げ
  • Emacs側からの接続/切断
M-x slime-connect RET 127.0.01 RET 4005 RET ; 接続
M-x slime-disconnect RET                    ; 切断

Ltkのメモ

  • 書きかけ

    雑記

  • with-ltkはwishの起動やmainloopの呼び出しとか全部やってくれるマクロ.基本的にltkのプログラミングではこれで全体を囲む.マニュアルでやりたいなら,(start-wish), (mainloop)を実行する.
  • ウィジットのmasterオプションは親ウィジットの指定.nilはトップが親の場合.

イベントハンドリング

  • 単純なイベント(ボタンのプレスとか)はウィジット生成時に:commandで定義できるけど,一般的にはbindを使う.(bind widget event function)形式で指定する.functionは1変数関数で,その変数にはイベントが格納される.マニュアルの下記例が分かりやすい.
(defun scribble ()
  (with-ltk ()
   (let ((canvas (make-instance 'canvas))
     (down nil))
     (pack canvas)
     (bind canvas "<ButtonPress-1>"
           (lambda (evt)
             (setf down t)                                    
             (create-oval canvas
                      (- (event-x evt) 10) (- (event-y evt) 10)
                      (+ (event-x evt) 10) (+ (event-y evt) 10))))
     (bind canvas "<ButtonRelease-1>" (lambda (evt) 
                                        (declare (ignore evt))
                                        (setf down nil)))
     (bind canvas "<Motion>"
           (lambda (evt)
             (when down
               (create-oval canvas
                    (- (event-x evt) 10) (- (event-y evt) 10)
                    (+ (event-x evt) 10) (+ (event-y evt) 10))))))))

sbclでスクリプト

実行方法

  1. RubyとかPerlみたいにファイルの先頭に#!/usr/bin/sbcl --scriptを付けて,実行権限を与えてシェルから実行.#!xxxはシェバンっていうらしい.sbcl 1.0.22から使えるようになった..ただし,~/.sbclrcを読み込まないので注意.
  2. scriptオプションを与えて実行する.
$ sbcl --script hoge.lisp

コマンドライン引数

$ sbcl hoge foo bar
* *posix-argv* #=> ("sbcl" "hoge" "foo" "bar")

エントリポイント

  • ファイルの上から順に実行されるようだ.
  • mainという名前の関数が実行される,みたいなことは”無い”

tcl/tk

GUIは使わないのですぐ忘れる.tkを使う最低限のメモをまとめておく.

tclメモ

  • データは全て文字列として扱われる.数値の場合は読み込まれてから数値に自動で変換される.
  • 行単位のプログラミング言語で,先頭の文字列がコマンド(butonとか)
  • 変数はsetで値を代入,値を取り出すときは$を付ける.
  • 四則演算はexpr(expression式って意味かな?)の後に数式.Bashみたいだ.
  • コマンドで置換する場合は[]で囲む.
  • ウィジットの名前は"."で始める.これは"."メインウィジットを指すから.Tcl/tkのウィジット名はパス付きの名前みたいなイメージ.
set x [expr 2 + 3]        # 変数とコマンド置換の例
set y "hoge [expr 2 + 3]" # 文字列内でもコマンド置換される
button .b -text "hoge"    # オプションは'-'で指定するUnix形式

各ウィジットのメモ

全体

  • イベントとコマンドを結びつけるにはbindコマンド.
bind .widget-name event-name command

パッケージ

  • 基本的にpackerを使う.グリッドの場合のみgridderを使う.placerは使わない.
  • ウィンドウに対して余白を埋めるには-fillオプション.x,yで縦横を指定する.

ボタン

  • 重要なのはcommandオプション.おした時のコマンドを指定できる.
  • ボタンおした時,離した時でコマンド指定したい場合はbindで各イベントとコマンドを結ぶ.

ラベル(label)

  • 文字列の表示
  • textavailableオプションが重要.ここに変数を指定しておくと,変数の値に変わると自動でアップデートしてくれる.
# buton/labelの例
#! /usr/bin/wish

# label widget
set buffer "hello world!"
label .l -textvariable buffer
pack .l

# procedure for below button command
proc push_button {n} {
    global buffer
    set buffer "I push $n button."
}

# button widget
foreach i {0 1 2 3} {
    button .b$i -text "button $i" -command "push_button $i"
    pack .b$i
}
# bind example
bind .b0 <ButtonPress> {puts "hello tcl/tk"}

# exec command
button .b4 -text "pwd" -command "exec pwd &"
pack .b4

Canvas

  • 図形を描画できるウィジット.
  • create object-type coordinate [option]形式
    • line : 直線,2点(多点でも可)を指定する.
    • rectangle: 長方形,左上と右下の2点を指定.
    • oval : 円,外接する長方形の左上と右下の2点を指定.
    • text : 文字列
    • image : 図
  • 共有オプション
    • fillで塗りつぶし,outlineで枠の色,widthで枠の太さ
  • 図形へのコマンド  - move :図形を動かせる
    • bind :図形へのイベントとコマンドをバインド
    • coords :図形の座標を取得
  • バインドでは図形のIDだけでなくタグで指定できる.
# create canvas widget
canvas .c0 -width 200 -height 150
pack .c0

# create a rectangle, and name it as r0.
set r0 [.c0 create rectangle 10 10 20 20 -fill cyan]

# move r0 (above rectangle) by mouse
# B1-Motion: left click, and its coordinates can be access by %x and %y.
# 
.c0 bind $r0 <B1-Motion> {
    set x1 [expr %x-5]
    set x2 [expr %x+5]
    set y1 [expr %y-5]
    set y2 [expr %y+5]
    .c0 coords current $x1 $y1 $x2 $y2
}

.c0 bind hoge <B1-Motion> {
    set x1 [expr %x-5]
    set x2 [expr %x+5]
    set y1 [expr %y-5]
    set y2 [expr %y+5]
    # current means currently pointed object
    .c0 coords current $x1 $y1 $x2 $y2
}


# create rectangle with tag
.c0 create rectangle 20 10 30 20 -fill red -tags hoge
.c0 create rectangle 30 10 40 20 -fill blue -tags hoge
.c0 create rectangle 40 10 50 20 -fill green -tags hoge

C/C++のライブラリについて

概要

  • 静的ライブラリ,動的ライブラリ(そんなものは無い.それは一般的には共有ライブラリ),共有ライブラリ,静的リンク,動的リンクなど,分かっているようで理解が曖昧な点を整理しておく.
  • C++ではヘッダファイルだけで提供するライブラリがある.ヘッダオンリーライブラリなどと呼ばれているようだ.それも簡単なメモを書いておく.

静的ライブラリと共有ライブラリ

  • 拡張子はそれぞれ,静的ライブラリは.a,共有ライブラリは.so.
  • コンパイル時の-lで指定するのは静的ライブラリも共有ライブラリも同じ.例えば,-lmはmathライブラリがリンクされる.ただし,デフォルトでは,まず共有ライブラリ(libhoge.so)を探して,見つかれば共有ライブラリがリンクされる.共有ライブラリがなければ,静的ライブラリ(libhoge.a)を探してリンクする.明示的に静的ライブラリをリンクしたい場合は,-staticオプションを付ける.
  • 静的ライブラリは単純には複数のオブジェクトファイルをまとめたもの.arコマンドで生成する.
  • 共有ライブラリは各オブジェクト生成時に-fPIC(Position Independent Code)を指定して,ライブラリ生成時に-sharedを指定する.

静的リンクと動的リンク

  • 静的リンクはライブラリがオブジェクトファイルに埋め込まれる.よって,ライブラリが無い環境でも動くバイナリを生成できる.しかし,ファイルサイズは大きくなる.
  • 反対に,動的リンクはオブジェクトファイルにはライブラリ自体は埋め込まれず,そこに埋め込めという情報だけが埋め込まれる.これは,ファイルサイズは小さくなるものの,ライブラリが無い環境では動かない.マシン上の複数のプログラムが同じ共有ライブラリを使う場合にもメモリの削減になる.libhoge.dllはダイナミックリンクライブラリであり,名前の通り実行時にリンクされる.
  • 共有ライブラリのリンクの際にライブラリを探索するパスはLD_LIBRARY_PATHである.共有ライブラリを使う場合は,このパスに対象のディレクトリが含まれていることを確認する.
  • 動的リンクは実行時に配置されるので,配置に依存しないコンパイル(PIC)をしておく

具体例

  • 自分のユーティリティライブラリ(myutil)を作る場合
///////////////////////////////////////////////////////////
// myutil.h
void hello_world(void);

///////////////////////////////////////////////////////////
// myutil.c
#include <stdio.h>
#include "myutil.h"

void hello_world(void) {
  printf("Hello world!! I'm a static library\n");
}

///////////////////////////////////////////////////////////
// main.c
#include <stdio.h>
#include "myutil.h"

int main()
{
  hello_world();
  return 0;
}

静的ライブラリとして使う場合

  • オブジェクトファイルを普通に作って,arでまとめて,リンクする.
$ gcc myutil.c -c             # myutil.oを生成
$ ar r libmyutil myutil.o     # libmyutil.aを生成
$ gcc main.c -L ./ -l myutil  # libmyutil.aをリンクしてコンパイル (libmyutil.soがない前提)
$ gcc -static main.c -L ./ -l myutil  # libmyutil.aをリンクしてコンパイル (libmyutil.soがある場合は明示)

共有ライブラリとして使う場合

  • -fPICで位置非依存なコードを作って,sharedでリンク
$ gcc -fPIC myutil.c -c                # myutil.oを生成
$ gcc -shared -o libmyutil.so myutil.o # libmyutil.soを生成
$ gcc main.c -L ./ -l myutil           # libmyutil.soをリンクしてコンパイル 

ヘッダオンリーライブラリ

  • ライブラリ提供者はヘッダファイルに全てを書いておいて,ライブラリ使用者はそのヘッダファイルを読み込む.
  • 毎回ヘッダファイルを読み込むのでコンパイル時間の増加の問題があるが,プリコンパイル済みヘッダファイルの利用によって避けることができる.
  • グローバル変数やスタティッククラス変数を定義しようとすると,複数のファイルが同じヘッダファイルを読み込むと多重定義エラーが発生する.避けるには,スタティックメンバ関数の中にスタティック変数として定義して,それを返す.

common lispプログラミング

Common lispの勉強をしていて出会ったことを書き連ねていく.

コーディングスタイル

  • 詳細を知りたい場合はGoogle Common lisp Coding規約を読む.
  • ファイル名はhoge-foo.lispという感じでハイフンでつなぐ。
  • グローバル変数は"hoge"
  • 定数は+hoge+
  • 大文字と小文字の違いはない。プログラムは小文字で書くことがほとんど。
  • 複数行のコメントは#| … |#.

シンボルとキーワード

  • アトムはシンボルとシンボル以外に分類される。
    • シンボル以外は評価するとそれ自身を返す。例えば数値10とか文字列"hoge"とか。
    • シンボルは評価されると,それが指すものを返す。例えば,x=3のときにxを評価すると3が返る。リテラルはシンボルって考えれば良いのか?
  • +, sin, xなどはシンボルである。シンボルは評価するとシンボルが指すものに評価される。シンボル自身が欲しい場合はクォートする.
  • キーワードとは,評価すると自分自身になる特集なシンボル。
  • リーダは:で始まるシンボルを見ると,シンボルを登録する際にそのシンボルが指すものとして,そのシンボル自身を登録する。

format

  • ~がCで言う%(指示詞)
  • ~%は改行。~5%は5行改行。
  • ~Dは整数(大文字Dでも小文字dでも変わらない)
  • ~Aは文字列でも数値でも良きにやる.
  • 指示子には個数を設定できる.~10tは10カラム分のタブ.
  • ~{…~}は1つのリストを消費して,リスト内の各要素を~{…~}の中の指示子で消費していく.
  • print-prettyで改行とかを良きにやってくれる.
;; ~{...~}の例
(format t "~{~d ~d ~d~} ~a" '(1 2 3) :hoge)

print/princ/prin1/pprint

  • prin1 : readし易い形。
  • print : 一度改行を出力してからprint1。
  • princ : 人向け(エスケープ文字出さないとか)
  • pprint: printを読みやすくしたもの(連続スペースを1つに変換)

print1-to-string/write-to-string

  • xxx-to-stringはストリームじゃなくて,文字列として出力する。
  • writeはprin1よりも原始的で,print1はwriteの一部のオプションが設定されたもの。

破壊的代入

  • INCF, DECF, PUSH, POPなどは破壊的な代入操作をする.モディファイアマクロと呼ぶらしい.
  • 便利なマクロとして,x,yの値をswapするマクロ:ROTATEFがある.PUSH,POPも破壊的.
(defvar *x* 1)
(defvar *y* 2)
(incf *x*)        ; (setf *x* (+ 1 *x*))
(decf *x*)        ; (setf *x* (- 1 *x*))
(incf *x* 10)     ; (setf *x* (+ 10 *x*))
(rotatef *x* *y*) ; swap(x,y)

defunの変数は値渡し

  • defunで渡される引数は値渡し(ポインタじゃなくて,参照している値が渡される).
  • だから,当然だけど変数を引数で渡して,defunの中でその変数を更新するようなことは間違い.
  • ポインタ(consセル)を指している変数を渡すと,それはそのポインタのアドレスを渡していることになるから,その先の変数を変更できる.
;; 間違い
(defparameter *x* 0)
(defun hoge (x))
   (setf x 1))
(hoge *x*)        ; *x*は更新されない
(setf *x* (hoge)) ; 更新したければ返り値を使う

(defparameter *lst* '(1 2 3 4 5))
(defun foo (lst)
   (setf (car lst) 0))
(foo lst)         ; (0 2 3 4 5) 更新される

リスト処理

  • consやappendは元のリストは破壊しないので注意.
  • リストに要素を追加したい場合は,明示的にconsしたものをsetfするか,PUSHを使う(取り出すときはPOP) .
  • sortすると元の配列が壊される.しかもsortされる訳でもない?サイズが変わったりもしてる.なぜ?
  • リストをシフトしたいときはalexandriaのrotate.引数で右も左もシフトできる.
(setf lst (loop for x from 0 to 100 collect x))
(sort lst #'>)
lst ; ソートされたものの一部が入っている?

Array

  • 可変長配列(:adjustableを指定すれば)
  • make-arrayで作って,arefで参照して,setf+arefで代入する。
;; 配列生成
(defparameter *arr* (make-array 3)) ; => #(0 0 0)
(aref *arr* 0) ; => 0

;; 初期値指定
(defparameter *arr* (make-array 3 :initial-element 5)) ; => #(5 5 5)

VectorとArray

  • VECTORは固定長,ARRAYは可変長であり,多次元も可能.
  • 可変長なARRAYを作りたい場合は,:fill-pointerと:adjustableを設定する.:fill-pointerは現在の末尾の要素番号を指す整数.可変長のARRAYに要素を追加する場合はvector-push-extendを使う(vector-pushじゃない)
  • VECTORにもmapを適用できる.(map ‘vector #'func array)形式.
(defparameter *varr* (make-array 0 :fill-pointer 0 :adjustable t))
(vector-push-extend 0 *varr*)   ; *varr* = #(0)
(vector-push-extend 1 *varr*)   ; *varr* = #(1 0)
(vector-push-extend 2 *varr*)   ; *varr* = #(2 1 0)
(map 'vector #'print *varr*)

ハッシュ

  • 文字列をKeyにするときは,:test ‘equalを指定する.デフォルトはeqlになってシンボルを想定している.
  • loopでループする時は独特なので覚えてしまう.
(defparameter *hash* (make-hash-table :test 'equal))
(setf (gethash "hoge" *hash*) 1)
(setf (gethash "hogehoge" *hash*) 2)
(setf (gethash "hogehogehoge" *hash*) 3)
(gethash "hogehoge" *hash*)
(loop for k being the hash-key in *hash* collect k)
(loop for v being the hash-value in *hash* collect v)

setfとsetqの違い

  • setqよりもsetfの方が賢い.
  • setqは第一引数を必ずQuateする.
  • setfは第一引数が必要なら評価して,不要なら評価しない.

eltとarefとnthの違い

  • eltはシーケンス全般に使えるが,nthはリスト,arefはアレイ(ベクター)のみに使える.文字列もベクタなのでarefが使える.
  • eltは汎用なので型チェックが動的に行われるが,nth/arefはそれが無いのでnth/arefの方が速い.
  • 速度差があると言われているけれど,sbclだと違いわからなかった.測定ミス???

defvarとdefparameterの違い

  • defvarは既に定義済みの変数の場合には再定義しない。
  • defparameterは定義済みの有無に関わらず再定義する。
  • defparameterは初期値が必ず必要.初期値が無い場合は:unboundを与える.

nullとendpの違い

  • nullはnilの時にTで,それ以外は文字列でもアトムでもFNILを返す.
  • endpはリスト専用で,アトムや文字列を入れるとエラーを発生する.

リーダマクロ

  • (quote (1 2 3)) -> ‘(1 2 3)
  • (function hoge) -> #‘hoge これは関数名から関数の本体を得る方法。つまり関数のシンボルhogeが指すものを得る。
  • “#+”,“#-"は処理系依存コードなどを書く目的で,#+は式が真のときに読み込まえて,#-は偽の時に読み込まれる。どんな処理系でも処理系のシンボルは定義されている。例えばSBCLなら:sbclが定義されている.これを使って,処理系によって分岐するコードは下記になる. 例) #+:sbcl (expr for sbcl case) #+:ccl (expr for clozure cl case) #-(or :sbcl :ccl) (expr unknown case)
  • Vector/StructはそれとReaderがわかるようにprintでもサフィックスがつく。printで出力して,それをreadで読みこめば,そのままベクタ,構造体として扱える.
    • vector: #(1,2,3)
    • struct: #S(struct-name (:slot-name …)* )

functionが不要?

  • 下のコードが動くのはなんで?"#“は要らないの?
(defun square (x)
  (* x x))
(mapcar #'square '(1 2 3 4 5)) ; (1 4 9 16 25) 
(mapcar 'square '(1 2 3 4 5))  ; (1 4 9 16 25) なんで???

ループ

do系

  • doは複雑なように見えて実はCのforと大きくは変わらない.初期値;更新式;終了条件;ループ本体,って感じだ.
;; リストでのループ
(dolist (v '(1 2 3 4 5 6 7 8 9))
  (print v))
;; 指定回数のループ
(dotimes (i 10)
  (print i))
;; do
;; (do ((変数, 初期値, 更新式)*) 
;;     (終了判定式 返り値)
;;     (body*))

loopマクロ

;; loop in list
(loop for x in '(a b c d e)
     do (print x))
;; with-when
(loop for d in '(0 1 2 3 4 5 6 7 8 9)
   when (evenp d)
   do (print d))
;; with if
(loop for d in '(0 1 2 3 4 5 6 7 8 9)
   if (evenp d)
   do (print d))
;; with while
(loop for d in '(0 1 2 3 4 5 6 7 8 9)
   while (< d 5)
   do (print d))

;; Counting
(loop for x from 0 to 10 by 2
     do (print x))

;; repeat
(loop repeat 5
     do (print 'hoge))

;; assign and update
(loop for x1 = 0 then x2
     for x2 = 1 then (+ x1 x2)
     while (< x1 30)
     do (print x1))

;; iota
(loop for x from 0 to 10 collect x)
(setf lst '(1 2 3 4 5))
(loop
   for x in lst
   for y in (cdr lst)
     do (format t "x:~d y:~d~%" x y))

#‘(lambda …)の意味

  • lambda式は関数オブジェクトを返すから,#‘は不要なんじゃないの?と思うけど,下のような例が見られる.
(mapcar #'(lambda (x) (* 2 x)) '(1 2 3 4 5))
(mapcar (lambda (x) (* 2 x)) '(1 2 3 4 5))   ; これじゃダメ?

これを評価してみると上の式でも下の式でも同じようになる. Webを見ていると,lambda自身がマクロで,(function (lambda … ))に展開される.すでに#‘されている場合と違う場合で展開を分けているのかな?

構造体(defstruct)

  • Cで言うところのstructというよりもclassに近い。
  • コンストラクタとアクセッサが自動で生成される。
    • コンストラクタ:make-<struct_name>
    • アクセッサ  :<struct_name>-<slot_name>
    • 述語     :<struct_name>-p

ディレクトリ操作

  • カレントディレクトリの確認 (truname “./”) #=> 処理系非依存 (sb-posix:getwd)
  • カレントディレクトリ移動(loadでのデフォルトパスネームは変わらない) (ccl::cd #p"/hoge/hoge/“) # CCL (sb-posix:chdir #p"directory-path”)
  • デフォルトのパスネームを変える (setf default-pathname-defaults #p"hogehoge")

乱数

  • common lispでの乱数の生成方法.
  • 一様乱数はrandで生成できる.
  • Alexandriaにはリストをシャッフルしたり正規分布からの抽出などの高機能なものもある。
  • TODO: 正規分布などの確率分布からのサンプリング方法
(alexandria:shuffle '(1 2 3 4 5)) ; 破壊的操作なので注意

リストからのランダム選択

(nth (random (length lst)) lst)

一様乱数

  • 仕様で規定されているようだけど,実装は規定されていない.
  • (random N) -> N以下の数をランダムに出力。Nが整数なら整数,少数なら少数を出力する。
  • 乱数はrandom-stateに従って生成される。random-stateは常に更新され続けるので,乱数の種を渡すときはrandom-stateを設定すればいい。(make-random-state state)で設定。
  • 各処理系の実装は下記
    • SBCL, CMUCL, ECL:MT19937 周期は219937
    • Clisp:Linear COngruential Generator 周期は264
    • CCL:MRG31k3p(Combined multiple recursive generator) 周期は2185
  • CCLでメルセンヌ・ツイスタを使いたい場合などは,mt19937パッケージを使う。CMUCLの実装をポータブルにしたものらしい。 (ql:quickload :mt19937) (mt19937 N)で同じことができる。

多値

  • 多値を返す時はvalues,それをmultiple-value-bindで受け取る.
  • 多値はリストじゃないことに注意.
  • リストで受け取りたいときはmultiple-value-listで受け取る.
  • multiple-value-bindでバインドされる変数のスコープはmultiple-value-bindの中だけなことに注意.

文字列の数値への変換

  • 整数への変換ならparse-integer.
  • 有理数浮動小数点への変換も含むなら,read-from-string.
(parse-integer "3")
(read-from-string "3.0")

ファイル

  • openは使わずにwith-open-fileでやる.
  • printとreadの対称性を使う.printはS式をreadが読み込み得る形式で書き出す.
;; 書込テンプレート
(defun save-file (filename)
  (with-open-file (fout filename
            :direction :output
            :if-exists :supersede)
    (with-standard-io-syntax
      (print *data* fout))))

行ごとの処理

  • 行毎に処理したい場合はwrite-lineとread-lineの対称性を使う.
  • read-lineは改行までを読み込んで,改行文字を削除した文字列を返す.
  • read-lineは多値を返す関数で,2つ目に終端が改行文字で終わってない場合はtを,改行文字の場合はnilを返す.
  • 第2引数がファイル末尾に達した時にエラーを返すか否か.デフォルトがtになっているので,nilを入れてエラー発生しないようにする.
;; file
(with-open-file (fout "./tmp.dat" :direction :output)
  (write-line "hogehoge" fout)
  (write-line "foo" fout)
  (write-line "bar" fout))

(with-open-file (fin "./tmp.dat" :direction :input)
  (loop for line = (read-line fin nil)
       while line
       do (format t "~s~%" line)))