C言語プログラミング入門
このプロジェクトは、C言語の基礎を体系的に学ぶための教材です。できるだけ簡潔に、けれど規格に則ってC言語の基本を理解できるように設計されています。
概要
本教材では、C言語の以下の要素について学習します:
- 基本概念: コンパイル、分割コンパイル、ソースファイルとヘッダファイル
- 文字と型: 整数定数、浮動小数点定数、文字定数、型システム
- 変数と配列: 記憶クラス、配列の宣言と操作
- ポインタ: アドレス演算、ポインタ演算、配列との関係
- 制御構造: 条件分岐、繰り返し、ジャンプ文
- 関数: 関数定義、呼び出し、再帰、main関数
- プリプロセッサ: マクロ定義、条件付きコンパイル
- 標準ライブラリ: ANSI標準ヘッダとその機能
章構成
- 第1章: C言語の基礎 - コンパイルプロセスと基本概念
- 第2章: 文字 - 定数表現とスコープ
- 第3章: 型 - 型システムと型変換
- 第4章: 変数 - 変数の宣言と記憶クラス
- 第5章: 配列 - 配列の宣言、初期化、多次元配列
- 第6章: ポインタ - ポインタの基本と応用
- 第7章: 制御の流れ - 制御構造と文
- 第8章: 関数 - 関数の定義と呼び出し
- 第9章: プリプロセッサ - 前処理命令
- 第10章: ライブラリ - 標準ライブラリの活用
学習の進め方
各章は独立して学習できるように構成されていますが、順番に学習することで、C言語の理解が深まるように設計されています。特に、ポインタと配列の関係(第5章と第6章)、関数とプリプロセッサの連携(第8章と第9章)など、章間の関連性にも注目してください。
対象読者
- プログラミング初学者
- C言語を体系的に学びたい方
注意事項
本教材のコード例は、理解しやすさを重視して作成されています。実際のプロダクションコードでは、エラーハンドリングやセキュリティ対策など、追加の考慮が必要です。
=== 1. C言語 ==========================
コンパイル
分割コンパイル
ソースファイルを複数に分けて、別々に作成、コンパイルすることができる。これを分割コンパイルという。
メリット:
- 他の人と作業を分担することができる
- ソースファイルの管理がしやすくなる
- 変更があったソースファイルのみをコンパイルすることで、実行ファイル作成のための時間を少なくすることができる
ソースファイルとヘッダファイル
Cコンパイラはソースファイルに書かれているものとヘッダファイルに書かれているものを区別することはない。ただ、慣習としてどちらに何を書くかは決まっている。「ヘッダ内では関数や変数の定義をしない」つまり、関数や変数の「宣言」はいいが、実際にメモリが確保される「定義」はヘッダ内に書かない。型の定義などマクロはヘッダに書いても構わない。これは、ヘッダは複数のソースファイルから#includeされるということを考えれば納得できる。
変数
変数は型と記憶クラスを持っている
型
C言語の型には、char, int, double, voidなどのプリミティブな型がある。
C言語には、変数をまとめる機構として、同じ型の変数を並べて番号をつけた配列(array)、異なる型の変数を1つにまとめた構造体(struct)1つの変数を複数の型で使用できるようにした共用体(union)がある。また、既存の型に別名をつける(typedef)機能もある。
変数のアドレスを入れる変数としてポインタを作成することもできる。
記憶クラス
記憶クラスとは、変数がメモリ上のどこに確保されるのかを表現するもの。extern, static, auto, registerなどがある。
関数
C言語のプログラムは関数の集まりからなっている。計算を実行する方法や手順は、全て関数の中に書かれた文によって表現される。プログラムを実行して最初に実行される関数はmainという名前です。
引数と戻り値
C言語の関数は、引数と呼ばれるinputと戻り値と呼ばれるoutputをもつ。引数は任意の個数で、戻り値は0個 or 1個。引数と戻り値はそれぞれ型を持ち、関数呼び出し側と関数定義側で型が一致しなければならない。関数を終了して、呼び出し元に戻る構文をreturn文という
宣言と定義
関数の名前、引数の型、戻り値の型を示したものを関数の宣言と呼び、実際の関数の動作内容まで示したものを関数の定義と呼びます。そのソースファイルの中だけで使われる関数にはstaticというキーワードをつけます。
文
実行の最小単位を文という。{}でくくったものを複文とよび、複数の文をあたかも1つの文のように順に実行させるために用いる。
文には、
- if-else文
- while文
- for文
- goto文
などがある。
式
計算を行う最小単位を式という。
型変換、加減剰余、ビット演算、関係演算子(&&, ||, !など)がある。
C言語では式にセミコロンをつけて文にすることができる。
=== 2. 文字 ==========================
整数定数
整数の定数は、16進数(0x2a8), 10進数(680), 8進数(01250)の表記が可能。
16進数の時は0xで始まり、8進数は0で始まり、10進数はそれ以外の数字で始まる。
浮動小数点定数
整数部 . 小数部 e指数 接尾子
1.23e4
文字定数
文字定数は、シングルクォートでくくられた文字の列。例えば、xという文字定数は'x'と記述する。改行文字などを表現するためにエスケープ列が定義されている。これ以外のエスケープ列を書いたときの動作は不定となる。
\n,\r,\f,\t,\v,\b,\a\?,\',\",\\\012,\x0c
文字列リテラル(文字列定数)
文字列リテラルは、staticなcharの配列という型で、文字列リテラルの最後には文字列の終わりを示す文字として'\0'が自動的に付加される。"abc\n"という文字列リテラルは、static char abc[5] = {'a', 'b', 'c', '\n', '\0'}という配列abcと同じ。
文字列リテラルの中では文字定数と同じエスケープ列が使える。
スコープ
スコープには次の4種類がある
- ファイルスコープ
- ブロックスコープ
- プロトタイプスコープ
- 関数スコープ
=== 3. 型 ===========================
型とは
型は、データ型またはタイプとも呼ばれ、値の意味を定めるもの。
C言語には初めから備わっている基本型(プリミティブ型)と派生型がある。
たとえば、2つの変数 a + b という式があったとすると、両方ともint型なら整数の加算が行われるし、どちらかが浮動小数点数型なら適切な型変換が行われた上で加算される。さらに、変数aがポインタでbが整数ならば、ポインタを先に進めるという演算が行われる。
型の分類
C言語では、それぞれの型が具体的に何byte、何bit幅になるかの規定はゆるやかにしか行われていない。それぞれの型の詳細についてはコンパイラに依存することになる(処理系依存)標準ヘッダ<limits.h>には、個々の型の最小値と最大値が定義されているので、そこのシンボルを使うと移植性の高いプログラムを書くことができる。
プリミティブ(基本)型
C言語で初めから定義されている型。主に使うのは以下の4つ。
整数型(Integer Types)
charint
浮動小数点型(Floating-point Types)
double
void型(値を持たない型。変数には使えないが、関数の戻り値やポインタで使用される)
void
派生型
プログラマが自由に定義することのできる型
- 配列
- 関数
- ポインタ
- 構造体
- 共用体
関数型は「ある型の引数を受け取り、ある型の値を返す」という意味で派生型。なので、関数ポインタという概念が許されてる。
整数型
整数
intは整数を表す型。intは、そのコンパイラが対象としている機械で最も取り扱いやすい長さの整数を表す。
int, long int, short intは、全て符号付きです。符号なしにするには、それぞれにunsignedキーワードを付与します。
文字型(char)
文字型とは、キーボードから入力した文字やディスプレイに表示する文字、プリンタに印字する文字を表現する時に用いる型です。charは、C言語では整数型の一種です。なので、整数に対する演算は文字型に対しても行うことができます。
文字
文字定数は、'A'のように文字をシングルクォートでくくる。改行は'\n'、 改ページは'\f'。
文字列は、文字を並べたもの。文字の配列で表されて、'\0'(ヌル文字)で終端する。
有名な落とし穴
getcharはintで受け取ることになっている。それは、値がasciiに対応した文字の時とファイルの終端でEOFの時があるから。EOFは通常-1として扱われていて、char型が意味するものはC言語の規格上、signed charなのかunsigned charなのかは実装依存となっている。そのため、-1が255などの別の値に変換されてしまい、判定できなくなる。これは、isalpha()とかisalnum()などでも同じ
void型
C言語の型は通常は「何かを表現するため」にある。しかし、void型は「どれでもないものを表現するため」にある一風変わった型です。void型は値を持たず、演算もできない。
void型は、次の時に使われる
- 関数の戻り値や引数として、値を返さないことや値を受け付けないことを示す
- void *は汎用ポインタ型として使われる
void型の変数を宣言することはできませんが、変数をvoid型にキャスト(型変換)することはできます。voidは値を持たないので、その変数の値は破棄されます。(未使用の変数があって怒られた時に使える)
ポインタ型
ポインタ型とは、ある型のオブジェクトを指す型のこと。
ポインタ型の演算には以下のようなものがある
- アドレス演算子(&a):変数aのアドレス
- 間接参照演算子(*a):ポインタaの指す値
- 代入(=)
- 整数の加減算(+, -)
- その他、比較など(==, <, >, …)
配列
配列は、ある型のオブジェクトを連続的に並べた型。C言語では任意の型の配列を定義することができる。配列の大きさはコンパイル時に定まっていなければいけない。配列の次元数は任意。
構造体型
構造体型とは、複数の異なる型を1つにまとめたもので、プログラマが定義する派生型の一種です。
型の大きさ(sizeof)
sizeof演算子を用いると、ある型の変数がメモリ上に占める領域の大きさを得ることができる。C言語ではある型が何バイトを占有するかは規格ではゆるやかにしか定められていないので、メモリ管理のプログラムなど、ある型のバイト数を特定しなくてはいけないプログラムを書くときはsizeof演算子が必須。
キャスト(型変換)
プログラムの時刻途中で、値の型が変化することがある。これを型変換と呼ぶ。
- キャスト演算子による強制型変換
- 通常の算術変換
- 代入時の型変換
- 関数呼び出しでの型変換
がある。
=== 4. 変数 ==========================
変数とは、名前がつけられているメモリ上の領域のことで、変数につけられた名前のことを変数名と呼ぶ。オブジェクトの参照する式を左辺値という。変数は、「型」と「記憶クラス」の2つの属性を持ってる。記憶クラスは、その変数が存在するメモリがどれだけの寿命を持っているかを示します。
記憶クラス
自動的(auto)な記憶クラスを持つ変数とは、ブロックの中で定義され、そのブロックが終了すると値を失う変数のこと。
静的staticな記憶クラスを持つ変数とは、プログラムの起動に先立って一回だけ初期化され、プログラムの動作中に値を失うことのない変数。
記憶クラスを指定するキーワードは、次の5種類
typedefexternstaticregisterauto
typedefは、新しい型名を定義するための指定子
externは、外部変数・外部関数を指定する記憶クラス指定子
staticは、外部変数や外部関数ではないことを指定する記憶クラス指定子
registerは、頻繁にアクセスされる変数であることを指定する記憶クラス指定子
autoは、変数が自動的な記憶クラスを持つことを指定する記憶クラス指定子で、ブロックの変数定義でのみ使用される。autoは省略できるので、通常はC言語のプログラムにautoというキーワードが登場することはない
=== 5. 配列 ==========================
配列とは、同じ型のオブジェクトを並べ、0から始まる番号をつけたもの。配列のサイズは0より大きく、コンパイル時に定まる定数でなくてはなりません。
配列の要素の参照
配列aの最初の要素を参照するには、a[0]とかく。配列の要素は0から始まる。(0オリジン)
配列全体への参照は不可能
配列全体へまとめて代入することはできません。配列全体を関数へ引数として渡すことはできません。配列全体を関数の戻り値とすることはできません。
ただし、配列全体をまとめて初期化することはできます。
配列の宣言では配列のサイズを指定しないこともできます。このような配列は不完全な型と呼ばれています。
多次元配列
多次元配列は、配列の要素がさらに配列であるような配列です。例えば、
int a[3][4]は、3行4列の整数の2次元配列を表します。
単体の配列は、メモリ上では必ず連続した領域に確保されます。多次元配列は、1次元配列の要素は必ず連続でそれ以外は規定されていませんが、ほとんどのコンパイラでは連続した領域に確保されます。
配列の初期化
配列は定義時に初期化することができます。要素数が決定できる時には、一番左にある要素数に限り配列の大きさを省略することができます。
int a [] = {1, 2, 3}
int b [][3] = {
{1, 2, 3},
{4, 5, 6},
}
配列とポインタ
配列とポインタの違い
char *pa[] = {"abcde", "fg", "hij", ""};
char aa[][6] = {"abcde", "fg", "hij", ""};
配列paと配列aaは、普通に参照する上ではほぼ同じだが、メモリ上では全く異なる。
個々の配列が連続したメモリ領域であることは規格で決められている。処理系依存だが全てが連続することも多い。
paに比べて、aaの方が文字を格納する分として広いメモリ領域を使っていることがわかる
配列と構造体の違い
配列は同じ型の変数を集めて番号をつけたものであり、構造体は異なる型の変数を集めて名前をつけたもの。
=== 6. ポインタ ======================
ある型Tがあった時、型T*を「型Tを指すポインタ型」という。ポインタ型は他の型から作るので派生型。
ポインタの計算
アドレス演算子:変数のアドレスを&変数名で得る
間接参照演算子:ポインタが指している先の変数の値を*変数名で調べる
ポインタに整数を加えると、指定個数だけ先を指すポインタを得ることができる。
同じ型のポインタは、比較演算子で比較できる。
NULLポインタはどこも指さないポインタを表す定数です。NULLポインタの先を参照してはいけない。参照したらプログラム実行時にエラーになることがある。一般にNULLポインタの先の参照は、コンパイラがコンパイル時に見つけることはできません。
無効なポインタ
無効なポインタとは、有効なオブジェクトや関数を指していないポインタのこと。メモリを解放した後の変数や初期化されていないautoのポインタ変数の値などは参照してはいけない。
配列とポインタ
配列の名前は、その配列の先頭の要素へのポインタ値と解釈される。
int a[10];
という配列が定義されるとき、aは&a[0]と同じ型、同じ値になる。配列の名前が配列の先頭要素へのポインタと見做される規則には一つだけ例外がある。sizeof演算子の振る舞いで、sizeof(a)の値は配列aの全体の大きさとなる。なので、sizeof(a)とsizeof(&a[0])の値は異なる。式a[i]は、*((a) + (i))と定義されている。また、関数の引数に配列が登場した場合、それは実際にはポインタとなる。例えば、
int count_it(char a[]);
という関数宣言は、
int count_it(char *a);
という意味になる
=== 7. 制御の流れ ====================
文
複数の文が並んでいる時、文は書かれている順番通りに実行される
複数の文を集めて{}で括ると、1つの文として扱う。これを複文またはブロックという
文には、
分岐
if文if-else文switch文
反復
for文while文do-while文
その他
return文break文continue文goto文
がある
式と文
式にセミコロンをつけると文になる
x = 10;
printf("hello");
このような、式にセミコロンをつけて作った文を式文という。C言語の文はほとんどが式文。
条件分岐(if)
条件を調べて、その結果に応じて処理の流れを変える構文
if(expr)
statement
exprは、条件を判断する式。statementは条件が真である時に実行される文
繰り返し
繰り返して文を実行させる構文。for文なら、初期化(初期化式)、条件(条件式)、次の一歩(継続処理式)を書くことができる。
while(expr)
statement
処理の中断(break)
break文は反復処理のブロックの中やswitch文の中で使われ、その処理を中断するのに用いる。break文と同じことをgoto文でも原理的にはできますが、breakの方が明確に意図を伝えられる。
繰り返しの継続(continue)
反復処理の中で使われ、強制的に次の繰り返しに進む時に用いる。
return文
return文は関数を終了させて、その関数を呼び出した側に制御を戻す構文。
C言語で関数を終了させる方法は次に示すもののみ。
- 関数で
return文を使う - 関数の最後の文の実行が終了する
longjmp,exitなどの特殊な関数を用いる
=== 8. 関数 ==========================
プログラマは複雑で困難な情報処理を、単純で容易な情報処理に分割してプログラムを構築していく。分割した処理は関数という単位に分けられる。関数とは、あるまとまった処理を行う文の集まりで、引数というinputと戻り値というoutputを持つ。関数という形にまとまった処理には関数名という名前がつけられ、他の関数から呼び出されて利用する。
関数の呼び出し
C言語の関数は0個以上の引数をもち、0個あるいは1個の戻り値を持ちます。呼び出された関数は、その関数の定義の終端にたどり着くか、return文を実行した時点で終了し、呼び出し元の関数へ帰っていく。
関数を定義するときの引数を仮引数といい、関数が呼び出すときの引数を実引数という。
値渡し
C言語の関数は、すべて値渡しを行う。すなわち、引数は全て呼び出し側で評価され、その値のみが関数へ渡されます。関数の側で引数の値をいくら変更しても呼び出し元に影響を与えることはありません。
stateful function(自己記憶関数, 状態保持関数)
関数内で定義されている変数のうち、自動変数は、その関数が終了すると解放される。しかし、静的な変数(staticな変数)は関数が終了しても値を保持し続けます。static付きの変数を関数内で定義することで、「記憶を持った関数(remaining function)」を定義することができます。
int get_counter(void)
{
static int counter = 0;
return(counter++);
}
この関数get_counterは呼び出されるごとに、0, 1, 2, ...を順に返します。この関数は自分が今までに何回呼び出されたかを覚えているみたいになる。
再帰関数
C言語では、関数を再帰的に呼び出すことができる。すなわち、関数は自分自身を呼び出せる。関数定義の中で自分自身を呼び出しても構わないし、関数が相互に呼び合って(相互再帰)も構わない。
main関数
C言語ではプログラムが起動すると、main関数が一番最初に呼び出される。従って、プログラムの中には、たった1つだけmain関数が定義される必要がある。その関数がプログラムの入り口となる。main関数からreturnすると、プログラム全体が終了することになる。
main関数の宣言
主なmain関数の宣言は次の2つ。
int main(void);
int main(int argc, char* argv[]);
関数mainは、引数を全く取らないか、2つの引数argc, argvをとる。戻り値はいずれもint。プログラムが起動するときにOSから引数を受け取る。これをコマンドライン引数という。
$> ./a.out abc 123
※コンパイラによっては、main関数が3番目の引数をもっているものもある。それは、char *envp[]で環境変数を表す。
=== 9. プリプロセッサ =================
プリプロセッサは、Cコンパイラがソースプログラムを解析する前に処理を行うプログラム。プリプロセッサが行う処理のことを前処理ともいう。C言語のソースファイルの中で、プリプロセッサに対する指示は#で始まる。
#define#undef#include#if#ifdef#ifndef#else#elif#endif#(ヌル命令)#line#error#pragma
単純マクロ定義(#define)
C言語の任意のトークン列に好きな名前をつけることができる。例えば、データの個数を表す数にMAX_DATAと名付ければ(#define MAX_DATA 100)、コードの中でMAX_DATAという名前を統一的に使える。
#define name tokens
=== 10. ライブラリ ====================
ANSIの標準ヘッダの一覧は以下の通り。それらを用いればより移植性の高いプログラムをかける。
assert.hctype.herrno.hfloat.hlimits.hlocale.hmath.hsetjmp.hsignal.hstdarg.hstddef.hstring.hstdio.hstdlib.htime.h
ヘッダは、どのような順序で#includeしても構いません。また、同じ標準ヘッダを何度#includeしても構いません。
42の課題でよく使うヘッダ
stdio.hstdlib.hstddef.hunistd.hstring.h
おすすめの関数(RTFM)
ctype.h
islower()isupper()isdigit()isxdigit()- 16進数
isalpha()isalnum()isspace()isprint()isgraph()- space以外のprintableな文字
ispunct()- space, アルファベット以外
iscntrl()- 制御文字でtrue
tolower()toupper()
limits.h
CHAR_BITINT_MIN,INT_MAXUINT_MAX
stddef.h
NULL定数NULLはどのオブジェクトも指さないポインタ定数
offsetofsize_tuintptr_t
string.h
strlen()strcpy(),strncpy(),strlcpy()strlcpy()は、BSD拡張。Linuxで使うなら#include<bsd/string.h>とcc -lbsd *.cが必要になる
strcat(),strncat(),strlcat()strcmp(),strncmp()strchr(),strrchr()strspn(),strcspn(),strpbrk(),strtok()strstr()strtol()