C言語によるプログラミングに関する世界観1(基本)です。
C++も参照のこと。
ツール関係はC/C++ツールに移動しました。
システムに依存する部分はLinux(システムコール・API)やWindowsプログラミングも参照のこと。
C言語は、プログラミング言語の中でもっとも有名な言語のひとつで、カーネルやコンパイラ、システムソフトウェアやライブラリモジュールなど、比較的低レベルで、パフォーマンス(速度・効率)が必要なレイヤーで使われる。
C言語は、ALGOL系の構造化プログラミング言語であり、if文、switch文、for文、while文など、ネスト可能(for文の中にfor文を入れるなど、文を入れ子にできる)なブロックや、サブルーチンとして使うことのできる関数を使うことで、構造化されたプログラミングができる。goto文も用意されているが、サブルーチンとしての関数に引数を与えて関数呼び出しを行い、return文によって関数から値を返すことで、原理上ラベルを用いたgotoを行う必要はない。そのため、BASICなどのような行番号は存在しない。
C言語は、静的型付け言語であり、変数を使う際には、データ型(英語ではタイプと呼ばれる)を宣言して使用するデータ領域を定義する必要がある。データ型には、小さなものから主にchar(文字)、int(整数)、float(単精度浮動小数点数)、double(倍精度浮動小数点数)などがあり、データ型の異なる変数を組み合わせて使った時など(たとえばintとcharを足す際)には小さなものから大きなものへとキャスト(型の変換操作)が行われる。文字列はchar型の配列として表現される。
このほか、C言語には、ポインタや配列、構造体、列挙型などがある。ポインタはメモリアドレスを指し示す変数であり、データのメモリ上の位置から変数を参照できる。基本的に、ポインタ(あるいは配列)は、静的あるいは動的に作成した、メモリ上の単一あるいは連続した要素に対する、基本的に最初の要素を指し示す。ポインタの場合、ポインタをインクリメントすることで、要素に次々にアクセスする「カーソル」として使うことができる。C言語の配列の場合、データは連続して確保され、そのサイズは固定長であり、配列のサイズを後で大きくしたり小さくしたり、あるいは任意の位置に挿入したり削除したりすることは困難である。このような場合、malloc()でヒープ上に動的なサイズの要素を確保し、これをポインタによって操作することで、プログラムの中でサイズの変わる要素を操作できる。C言語にはガーベッジコレクションのように、不要になった動的に確保された変数を自動で削除する仕組みがないため、動的にmalloc()で作ったメモリ上のデータは破棄すべき時にfree()できちんと削除しなければメモリリークが起きる。あるいは、C++の使える環境であれば、STLのvectorなどのコンテナを使うことで、任意の位置に挿入・削除したりサイズを変更したりできるベクター配列を使うこともできる。この場合、反復子(イテレータ)を用いてコンテナの要素に順次アクセスしたり、STLのアルゴリズムを使ってシーケンスに対する処理(並べ替えなど)を行うこともできる。また、C++ではスマートポインタを使うことで、不要になった要素をガーベッジコレクションと同様に自動削除してくれる。
また、構造体はデータ要素を複数まとめてひとつのデータ要素にすることのできるデータ型であり、とてもよく使われる。たとえば、X軸とY軸のある点(ポイント)を表現したり(四角形は二つのポイントによって表現できる)、次の要素へのポインタを持つ連結リストやツリーを表現したり、ストレージやネットワークにおけるデータ属性をマッピングしたりできる。C言語にはオブジェクト指向の機能はないが、関数に構造体のポインタを渡して実行すれば、構造体のポインタを操作する一連の関数(メソッド的なもの)を実現でき、疑似オブジェクト指向のようなことはできる。また、マジックナンバー(int型の整数値を不用意に真偽値のようなさまざまな用途に転用すること)を避けるためにenum型(列挙型)を使うことも多い。
C言語はコンパイラ言語であり、コンパイラがコンパイルする時点でアセンブルやリンクが行われ、OSによって直接実行可能な機械語のバイナリファイルが作成される。そのため、逐次的に一行一行読み込んで実行するインタープリタ言語に比べて、速度や効率などのパフォーマンスが高い。また、ハードウェアで直接実行できるため、ランタイムは基本的に必要なく、ライブラリやOSのシステムモジュールを除けば、別途ほかのランタイムを必要とせず、ネイティブでそのまま実行できる。また、インラインアセンブラやunsafeコード(システムのメモリを破壊する可能性のある危険なコード)を記述できるため、カーネルや言語処理系の開発にはほとんどと言っていいほどC言語が使われる。しかしながら、インタープリタ言語に比べて、低水準であり、機能も少なく、標準のクラスライブラリの豊富なJavaや.NET、あるいはモジュールが豊富なPythonなどに比べて、自らの手でコードを書かなければならない(自分で同じものを書かなければ使うことのできない)場面は多く、記述も冗長になる傾向にある。JavaのようにVMで実行されるわけではなく、ガーベッジコレクションもないため、バグやきちんと動かない場面は他よりも多いかもしれない。また、コンパイラ言語では実行する前に必ずコンパイラによるコンパイルが必要であり、C言語のコンパイラには、標準で「変更されたファイルだけをコンパイルする機能」がないため、makeを用いてMakefileを書く必要がある。
また、C言語は、プラットフォーム依存性が強い。VMを使うJavaなどでは、当初からプラットフォーム移植性が極力少ないことを目指しているため、Javaでは同じプログラムが別のプラットフォームでも、再コンパイルする必要が無くそのまま実行できる。これに比べてC/C++では、別のプラットフォームで動かすためには再コンパイルが必要であり、libcなどの一部の標準化されたライブラリだけを使っている場合でなければ、#ifdefなどによるプリプロセッサなどを用いて、たとえばPOSIX向けとWindows向けに別々のコードを書く必要がある。特にC/C++でGUI開発を行う場合、X11やGTK/Qtの環境とWindowsの環境では「まったく異なるコードを書く必要」があり、当然ながらX11向けのプログラムはWindowsでは動作しない(最近のWSLのような新技術は例外)。C言語にはテンプレートはないが、簡単なマクロ演算(二つの値を足すようなマクロなど)であればプリプロセッサによるマクロを使って実現できる。
C言語のコンパイラには、GNUによるGPLのGCCのほか、最近では非GNU系のオープンソースのLLVMなどがあるが、WindowsでC/C++で開発を行いたい場合は、MFCなどの付属するVisual C++を買うことが必要だが、このVisual C++には高額なコストが必要である。特にWindowsにこだわらない場合(それがレアケースであることは分かっているとしても)であれば、コストのかからないLinuxを使うこともできるが、LinuxでもしGUI開発を行うのであれば、GTKやQtを使う必要があり、これはマイナーな環境であるため日本語の情報量も少ない。本当にプログラミング初心者であれば、いきなりC/C++から入るのではなく、PythonやRubyのような「初心者にも優しい言語」を使うことをおすすめする。
C言語にはC++という兄弟言語があり、C言語には用意されていないクラスを用いたオブジェクト指向のプログラミングや、テンプレートを用いたジェネリックプログラミング、あるいはSTLといった便利なライブラリを利用出来る。多くの場合、C言語の使える環境ではC++も使うことができる。GCC、LLVM、Visual C++などほとんどのC言語のコンパイラはC++もサポートされている。また、MFCのようなプラットフォーム依存のAPIライブラリは、C言語と同時にC++も用いられることを前提として提供されている。C++の全ての機能を使う必要はない。C++は「必要としない機能によってパフォーマンスなどの面でコストがかからないように」設計されているため、一部のC++の機能だけを使う「ベターC」としてC++を使うこともできる。
また、これはC言語に限らない話だが、プログラミングをする際の注意点として、「完璧なひとつのプログラム」を作ろうとしないこと。さまざまなモジュールやプログラムを小分けにして、小さなプログラムの集合体として全体のプログラムを構成するようにしよう。たとえば、コンパイラを作るなら、フロントエンドとバックエンドをきちんと分けること。JavaやPHPを使うなら、なんでもできる処理をmain()にすべて書くようなプログラムではなく、最低限の処理を行うもっとも小さなクラスを作り、そのクラスの集合体としてプログラムを設計すること。UNIXの哲学として、KISSの法則と呼ばれる「シンプルにしろ、愚か者」という格言があるが、プログラムを作る時は、ひとつの完璧なプログラムではなく、もっともシンプルなプログラムの集合体としてシステムを設計するようにしよう。また、プログラムを開発する時は、最初から全部を作ろうとするのではなく、最小限の「プロトタイプ」を作った上で、そのプロトタイプに少しずつ機能を追加していき、必要に応じてリファクタリング(コードの整理整頓)をしていくことにしよう。
1.基本
C言語サンプルコードも参照のこと。
以下は参考文献。
変数。変数は基本的にint x;のように「変数の型」と「変数の名前」を指定する。代入は=で行う。
int x, y; char c; x = 10; y = 3 + x * 6; c = 'C'; printf("%c and %d\n", c, y);
出力結果は以下のようになる。
C and 63
数学と同じように、掛け算(*)は足し算(+)よりも演算の優先順位が上であることに注意しよう。C言語の演算子では、*は+よりも先に計算される。
2023.01.19編集
代入演算子には、単に値を変数に代入する「=」以外にも、加算・減算を行う+=や-=などがある。
x = x + 1;
は
x += 1;
あるいは
x++;
と書くことが出来る。
x = x - 1;
も同様に
x -= 1;
あるいは
x--;
と書ける。C言語ではこのような代入演算子やインクリメント演算子を多用する。
2023.01.19編集
数の値(リテラル)は、10進数については13のようにそのまま記述する。8進数は先頭に0をつけて015(10進数の13)などとする。16進数は先頭に0xをつけて0x0d(10進数の13)などとする。浮動小数点数(小数点以下のある数)は100.25や1.0025e+2(指数表記、1.0025×102)などのように記述する。文字は'c'、文字列は"Hoge"と囲んで表記する。
変数には型と呼ばれるものが存在する。型は変数の性質を表す。char(文字型、バイト数1)、int(整数型、バイト数2)、long(倍長整数型、バイト数4)、float(単精度浮動小数点型、バイト数4)、double(倍精度浮動小数点型、バイト数8)などとなる。
後日注記:演算子などで異なる型同士の演算をした際には、小さいほう(たとえばint)から大きい方(たとえばdouble)への自動的な型キャストが行われる。代入や関数の引数渡しなどにおいても自動的に変数の型が変換される。()を使うことで強制的な型変換ができる。たとえば、n = (int) v;など。
後日注記:プログラム(関数)は、単に成功した場合に値を返すだけではなく、失敗した場合のエラーチェックも返さなければならない。このため、たとえ文字を返す関数であっても、返り値の型をcharではなくintにする場合がある。これはchar型の値に加えてエラーチェックのための値を返す必要があるからである。
型も参照のこと。
2023.01.19編集
基本的な演算子:
演算子 | 演算 |
---|---|
= | 代入 |
+ | 足し算 |
- | 引き算 |
* | 掛け算 |
/ | 割り算 |
% | 余りの計算 |
また、++でインクリメント(その数値に+1を計算して代入)、--でデクリメント(その数値に-1を計算して代入)ができる。
インクリメント・デクリメント演算子には、後置演算(a++)と前置演算(++a)が存在する。単独で用いた時は結果は変わらないが、=とともに用いると、++aは先に処理をしてから代入され、a++は代入してから後に処理が行われる。
後日注記:数学と同じように、+や-よりも*や/が優先されることに注意。もし+や-を優先させたければ()を使う。Cの演算子の優先順位は仕様として決まっている。
このほか、math.h(libm)の中のpow()関数で累乗が、sqrt()関数で平方根が、fabs()関数で絶対値が計算できる。三角関数のためのsin()やcos()やtan()もある。
これらの関数を使う場合、
#include <math.h>
を必ず記述し、コンパイル時にlibmとリンクさせる。GCCの場合以下のようにコンパイルする。
gcc -o calc calc.c -lm
-lmがlibmとのリンクを表す。
GNUツールチェインも参照のこと。
2023.01.19
C言語の論理演算子には&& (AND), || (OR), ! (NOT)がある。
ビット演算子には& (AND), | (OR), ^ (XOR), ~ (NOT), <<= (左方向シフト), >>= (右方向シフト)がある。
構造化プログラミングも参照のこと。
2023.01.19
このような変数は、ポインタや配列として連続したデータにアクセスしたり、別名で同じ場所にある変数の記憶内容を書き換えたりできる。
C言語では、メモリ上のデータにメモリアドレスを使ってアクセスするためにポインタを使う。こうすることで、スコープが消えても削除されないようなヒープ領域の動的なメモリ領域(malloc()というAPIを使ってこうしたデータ領域を確保できる)などにメモリアドレスを用いてアクセスできる。
ポインタを使う場合は、int *pのように*をつけて宣言し、p = &nのように変数のアドレス(メモリ上の場所)を代入し、*p = 10のようにそのポインタの指す場所を書き換える。こうするとnという変数名を使った代入式でなくても、nの内容が10に書き換えられる。
配列の場合は、int x[10]のように連続したデータ領域を宣言し、x[2]=10とすればxの三番目の要素を書き換えられる。インデックス変数は0から始まるため、x[2]は二番目ではないことに注意しよう。
ポインタと配列は結びつきが強く、よく同じものであると解釈される。ポインタに配列のアドレスを代入し、p++とすることで、ポインタの指し示すアドレスをインクリメントし、次の要素を指すことができる。インクリメントした後で*p=++nとすれば、pの今指し示している要素にインクリメントしたnの値を代入できる。
詳しくはC言語(ポインタ)を参照のこと。
constは、「一度代入されたら変更されない変数」すなわち(名前のついた)定数を定義するのに使う。
たとえば一度代入したら再度変更されない文字列には、const char*型を使うことが多い。できるだけ定数として使われる変数にはすべてconstを付けることが推奨される。
また、関数の引数が文字列のポインタである場合、constをつけることで明示的に「変更されない」ということを示すことができる。
プログラムの記述した内容の意味が、あとあとになって本人以外の誰から見ても分かりやすくするために、コメントを書く習慣を心がけよう。
C言語では、/* ~ */で囲まれたブロックはコメントとして扱われ、プログラムに影響を与えずに説明文(あるいはほかのどんなテキストでも)を書くことができる。
コメントの中身はプログラムとしては無視される。そのため、一部のプログラムコードを一時的に無効にしたい場合などにもコメントは使われる。これを「コメントアウト」と呼ぶ。
コメントは、日本人ならば日本語で書きたいところだが、国際的なオープンソースプロジェクトなどでは、日本人以外の人種も参加しているため、事実上の国際標準である英語で書くことが推奨される場合もある。機械翻訳であっても日本語で書かれるよりはマシなので、そのような場合は英語で書くことを心がけよう。
また、コメントはネストすることができない。/* ~ */の中にさらに/* ~ */を書くことはできない。
2023.02.03
C++(1.オブジェクト指向), C++(2.ジェネリック), C++(3.STL・ライブラリ)から、「C++にオブジェクト指向の考え方をとり入れたスマートな言語」であるC++の情報が見れます。
C/C++ツールから、各プラットフォームのコンパイラ、開発ツール、ライブラリの情報が見れます。
アセンブリ言語では、C言語の深層と深く関わっているアセンブリ言語とマシン語(機械語)の情報が見れます。
Windows APIから、WindowsにおけるC/C++プログラミングのAPI(Windows APIと呼ばれる)の情報が見れます。
MFC/ATL/COM/応用技術から、WindowsにおけるC++をベースとした独自のラッパーライブラリやフレームワーク、応用技術の内容を見れます。
GNUツールチェインから、GNUによって提供されているフリーソフトウェアの開発ツールが見れます。
デバッグ(gdb)から、GNUによるコマンドラインデバッガのgdbの情報が見れます。
Linux システムコール・API, Linux システムコール・API(2.プロセス・メモリ)から、LinuxにおけるUNIXのシステムコール・ライブラリ変数のAPIが見れます。
Linux X11周辺, GTK+, Qtから、X11におけるGUIプログラミングの情報が見れます。
Cの入門。
C
書籍