プログラミング言語C 第2版 K&R
2011/07/10(日) 24:34 C言語このエントリーをはてなブックマークに追加


いわゆるK&R本、C言語のバイブルといわれる書籍。
入門向けではなくて、和訳が微妙らしいけど。
気になった部分を目次に当てはめて書いていく方式で。
【目次】

第0章 はじめに

"文字列を比較するのにわざわざ関数を呼ばないといけない"というのは欠陥に見えるかもしれないが、これは言語仕様を適当な大きさに抑えるためでもある。Cは比較的小さい言語である。

第1章 やさしい入門

1.1 手始めに

一つのCプログラムは大きさはどうあれ、関数と変数からなる。

1.2 変数と算術式

int 16bit or 32bit ,float 32bit
char 文字1byte
short 短い整数
long 長い整数
double 倍精度浮動小数点
これらに加えて、基本型の配列、構造対、共用体とそれらへのポインタとそれらを返す関数がある。

printfはC言語の一部ではない。C自体では入力も出力も定義されていないからである。これらの挙動はANSI標準規格で定義されているため、その標準規格に従うものは同じ挙動になる。

5/9のように整数同士なら、整数演算が行われる。
どちらかが浮動小数点であるなら、演算結果は浮動小数点に変換される。

1.3 For 文

1.4 記号定数

プログラム中に300とかそういうマジックナンバーを埋め込むのは悪い習慣
代わりに#define構文を使って、記号定数としてマジックナンバーを定義できる。
#define 名前 マジックナンバー

1.5 文字入出力

getchar と putchar
getcharとよく組み合わせて使うEOFは<stdio.h>で定義されている整数である
getchar() != EOF

1.6 配列

1.7 関数

上手く設計された関数は*いかに*処理されるのかということを無視して、*何が*できるのかを知っていれば十分になる。
関数のプロトタイプと関数の定義の型や名前は一致しないとエラーだが、パラメーターの名前は一致する必要はない。だが、名前も上手くつけた方が文章的にはわかりやすい。

1.8 引数――値による呼出し(call by value)

1.9 文字配列

1.10 外部変数と通用範囲

extern宣言をヘッダファイル集めて先に宣言しておく風習。

第2章 データ型・演算子・式

2.1 変数名

31文字より少ない方がいいことも

2.2 データ型とサイズ

char 文字1byte
int 整数、自然な整数サイズ
float 単数浮動小数点数
double 倍精度浮動小数点
これに加えて、それらに適応できる修飾子(qualifier)として
short 短い整数(大抵16bit)
long 長い整数(大抵32bit)
がある。
short int sh;
long int counter;
この場合にintは省いてもいいので、通常は省かれる事多い。
signed -128~127
unsigned 0~255
の修飾子はcharや任意の整数に使える。
この辺の情報は<limits.h><float.h>に含まれている。

2.3 定数

文字定数は'\xhh'で表せる。
文字定数はコンパイル時に連結する
"hello" " world"
// と以下は同じ意味になる
"hellow world"
列挙定数
enumを使った列挙がある
enum  boolean{
    NO, // 0
    YES,// 1
};

2.4 宣言

2.5 算術演算子

%はモジュロ演算子というのか。

2.6 関係演算子と論理演算子

次は同様の意味となる
if(!valid)
// ==
if(valid == 0)

2.7 型変換

float + int
のような場合は情報を失わないように"より狭い"被演算子から"より広い"被演算子に変換すうためfloatの情報が帰ってくる。
unsignedの場合を除けば、自動的に高い方の型に格上げされて演算される
long double > double > float > (char , short) -> int
そして片方がlongならもう片方もlongにする。
明示的な型変換はキャストを使う。
sqrt((double) n)
// これはsqrtにnを渡す前にdoubleにキャスト変換している。
// sqrtに渡す値が変換するだけでn自体には副作用はない。
また、関数プロトタイプによって明示的なキャストしなくても、自動的な型変換が行われる。
double sqrt(double);//プロトタイプ
// 処理
root2 = sqrt(2);// 2.0(double)に変換されてsqrtされる。

2.8 インクレメントとデクレメントの演算子

2.9 ビットごとの論理演算子

2.10 代入演算子と式

+=のように圧縮した形を代入演算子とという。

2.11 条件式

条件式も式であるため、先ほどの自動変換にならって,
fがfloatなら、nがintであろうとfloat型に変換されたものが変える。
(演算結果が変換されるわけではなく、演算するために変換されているので)
(n > 0) ? f : n; // 結果はfloatに変換されたものになる

2.12 優先度と評価順序

a[i] = i++;
このa[i]がどのiなのかはコンパイラまかせて、標準規格ではわざと規定されていない。実行順序は計算機のアーキテクチャに依存するため、最適化が難しい。
こういう、環境依存のコードを悪とすべき。

第3章 制御の流れ

3.1 文とブロック

{と}は複文、ブロックを表していて、変数は任意のブロック内部で宣言できる。

3.2 If-Else

3.3 Else-If

3.4 Switch

3.5 ループ(While と for)

for(i=0,len=10; ..)
のようにコンマ演算子を使って、左から右へ順番に計算される。
関数引数、宣言中の変数などを分離するコンマはコンマ演算子ではないため、左から右への評価は保証されない。

3.6 ループ(do-While)

3.7 Break 文と Continue 文

3.8 Goto と名札

gotoさんはこの頃から悪名高い…

第4章 関数とプログラム構造

4.1 関数についての基本事項

ANSIでは関数定義するときに引数の型も宣言する事ができる。

4.2 非整数を返す関数

プロトタイプ宣言をするときはできるだけ引数の型についても書くべきである
何も返さない関数の引数にはvoidをいれる。

4.3 外部変数

Cでは関数内で定義される引数と自動変数を内部的と表現し、外部的な外部変数は関数の外で定義し多数の関数から利用できるグローバル変数を示している。

4.4 通用範囲に関する規則

4.5 ヘッダ・ファイル

4.6 静的変数

static変数

4.7 レジスタ変数

register宣言は変数が頻繁に使われることをコンパイラに伝えるために使う。
register宣言された変数はマシンのレジスタに置くことで、小さく早くなる。(しかし、コンパイラはこれを無視してもいい)
register宣言は自動変数と関数の仮引数のみに適応可能
f(register unsigned m, register long n)
{
    register int i;
}
registerという名前の通り、レジスタ数を超過した宣言は許されないので、具体的な制限はマシンによって変わる。

4.8 ブロック構造

4.9 初期化

外的変数(グローバル)と静的変数(static)は自動的に0に初期化されることが保証されるが、自動変数とレジスタ変数は不定値となっている。
int days[] = {1, 2, 3}
char pattern[] = "ould";
// これは以下の簡略形となる
char pattern[] = { 'o', 'u', 'l', 'd', '\0'};}

4.10 再帰(Recursion)

4.11 C のプリプロセッサ

コンパイルの前に行われる処理
#include <>
#include ""
// マクロの置換
#define name 置換テキスト
#undef getchar // getcharは未定義であるとする
#if !define(HDR)
#define HDR
// HDRの内容~
#endif

第5章 ポインタと配列

goto文と並び理解しにくいプログラムを作ってしまうものとしてもあげられていた。なので、ANSI Cではポインタの扱いを明確にして、明確で単純なプログラムを作りやすくした。

5.1 ポインタとアドレス

メコンピューターのモリは連続したグループとして扱う事ができるので、連続的な番号付き(アドレスのこと)のメモリセルという形の配列を持っている。
p = &c;
としたとき、変数pにcのアドレスが代入される。これをpはcを"指し示す"という。(&はオブジェクトのアドレスを与えてくれる)
単項演算子*は間接演算子つまり逆算参照演算子である。
そのため、ポインタの宣言とかいわれるものは次の意味だ
int *ip; // ip は int へのポインタである。
int x = 1, y = 2, z[10];
int *ip; // ip は int へのポインタである。

ip = &x; // ip は xのアドレスを示す
y = *ip; // y は 1になる
*ip = 0; // x は 0になる
ip = &z[0]; // ip はz[0]のアドレスを示す

5.2 ポインタと関数引数

演算子&は変数のアドレス(参照を渡せる)ので副作用のある関数をかける。

5.3 ポインタと配列

Cにおける配列はポインタと密接な関係
関数定義の仮引数としては
char s[]

char *s
は同じ意味なる。どう使うかは各自判断

5.4 アドレス計算

ポインタと整数は相互交換可能ではないが、例外として0が存在する
0はNULLの代わりに使える。まあポインタにはNULLを使うべきだが。

5.5 文字ポインタと関数

char amessage[] = "test"; // 配列
char *pmessage = "test;   // ポインタ
この二つは大きな違いがある。
配列はここの文字が変わっても同じメモリ一を示すが、pmessageの方は文字列定数を示すように初期化されたポインタである
pmessage: * -> "test"
amessage: "test"

5.6 ポインタ配列:ポインタへのポインタ

5.7 多次元配列

5.8 ポインタ配列の初期化

5.9 ポインタ対多次元配列

5.10 コマンド行の引数

慣習的ににargcとargv(argument vector)が使われる。
main(int argc, char * argv[])

5.11 関数へのポインタ

関数へのポインタ表現かなりややこしい。
引数に関数を渡すような事が可能になる。
#include <stdio.h>

int add(int, int);//プロトタイプ
int main(void)
{
		int (*fn)(int, int);// 関数(引数がint,int)へのポインタの宣言
	        int x = 10,y = 20;
		int res;
		
		fn = add;// ポインタの代入
		res = (*fn)(x, y);// addを呼び出してる
		printf("%d\n",res);
		return 0;
}
int add(int x, int y){
		return x+y;
}
見た目がヤバイ事になってる気がするので、typedefなどを使った方がよさそう。

5.12 複雑な宣言

int *f(); // f: intへのポインタを返す関数
int (*pf)(); // pf: intを返す関数へのポインタ

第6章 構造体

Pascalでは構造はレコードと呼ばれる。

6.1 構造体についての基本事項

struct point {
    int x;
    int y;
}
structの語の後に構造対タグと呼ばれる省略可能な名前(point)をつけられる。
構造体のそれぞれの中の変数をメンバーと呼ぶ。
このstrucは型を定義する機能も持っていて、メンバーの並びの最後に変数を並べると、それらの変数に型が適応される
struct {....} x,y,z;
// これと同じ感じ
int x,y,x;
構造体の初期化は代入文によっても可能なため、先ほどのように構造対タグを定義しておけば、struct pointa型の構造体を初期化できる
struct point maxpt = { 320, 200};
メンバーへのアクセスはドットでつなぐ

6.2 構造体と関数

構造体に対して許される演算は、それをコピーすることのみである
これは、一つの単位として代入、&でアドレスを求める、そのメンバーにアクセスすることを意味する。
構造対同士の比較はできない。
構造体の型は関数にも適応できるので、次のように構造体を返す関数も書ける。
// 二つの点を加えた構造体を返す
struct point addpoint(struct point p1, struct point p2
{
    p1.x += p2.x;
    p1.y += p2.y;
    return p1;
構造体とポインタ
struct {
    int len;
    char *str;
}
という宣言があった場合に、次のようにポインタでメンバーでアクセスできる。
->の優先度に注意
++p->len ;// ++(p->len)

6.3 構造体の配列

Cでににのオブジェクトのサイズを計算するのには、コンパイル時の単項演算子sizeofがある。
sizeof obj
sizeof(型名)

6.4 構造体へのポインタ

構造体のサイズはそのメンバーのサイズの合計だと思ってはいけない
たとえば、次の構造は5バイトではなく8バイトを必要とするため、sizeof演算は8と返す。
struct node{
    char c; // 1byte
    int i;// 4byte
} x;
printf("%d\n",sizeof(x));// 8

6.5 自己参照的構造体

2分木の話
struct tnode{
    char *word;
    int count;
    struct tnode *left;// 右の子供
    struct tnode *right;// 左の子供
}
このように、構造体を再帰的に宣言する事もありえる。
leftがtnodeへのポインタであって、tnode自身ではないのでおかしくない。

6.6 テーブル参照

ハッシュテーブルの作り方
struct nlist{
    struct nlist *next;
    char *name; 
    char *defn;// 置換テキスト
}
#defien HASHSIZE 101
static struct nlist *hashtab[HASHSIZE]; // ポインタのテーブル

6.7 Typedef

Cで、新しいデータ型の名前を作るのにtypedefというものが使える
typedef int Length;
というようにLengthをintと同義語な型にすることもできる。
typedef char *String;
とすれば、Stringはchar *と同じ、つまり文字ポインタと同義語になる。
先ほどの構造体と合わせれば、
typedef struct tnode{
    char *word;
    int count;
    struct tnode *left;// 右の子供
    struct tnode *right;// 左の子供
} Treenode;
というように、Treenodeというものがstruct tnodeと同じ意味になる。
ここで大事なのはtypedef宣言は新しい型を作るのではなくて、既存ものに新しい名前をつけるだけのものである。
ただし、マクロとは違い、プリプロセッサを超えた処理ができる。
typedef int (*PFI)(char *, char *);
PFI strcmp, numcmp;

6.8 共用体

共用体(union)はサイズや整合の要求をコンパイラに任せて異なる型とサイズのオブジェクトを保持するための変数。
unionは構造体に基づいた宣言ができる。
union u_tag{
    int ival;
    float fval;
    char *sval;
} u;
変数uには3つのタイプのうちの最大値を保持できるだけのスペースがとられる(実装依存だけど) gccだとtypeof uが4だった。

6.9 ビット・フィールド

struct {
    unsigned int is_keyword : 1;
    unsigned int is_static : 1;
} flags;
こんな感じのビットフィールドという。

1: シーラー 2012年04月01日(日) 深夜2時33分

6.6 テーブル参照のソース間違ってますよね?
これじゃ連結リストが連結していかない^^;


名前:  非公開コメント   

  • TB-URL  http://efcl.info/adiary/0123/tb/