【C言語】初心者が中級者になるまでに身につけるべきトピック

「学校で一通りC言語プログラミングを学んだ、次に学ぶべき言語はどれ？」

大学ではこういった質問を受けることがよくあります。もちろん、「広く浅く」をモットーに様々な言語に手を出していくのも一つのやり方。しかし、せっかくC言語を学んでいるのだから、講義レベルで終わりにするのも、もったいない。

そこで、「とりあえず(授業レベルの)C言語は一通り学んだよ!」という方向けに、次に学ぶとよいトピック(C言語以外のものを含めて)をいくつか挙げたいと思います。

<strong>※ レベル別になっている訳ではないので、興味のあるものから取り組むとよいでしょう。</strong>

# すでに学んでいること

まず、**C言語の授業ですでに学んだところを復習しましょう**。例えば以下のようなトピックは十分理解できているでしょうか?(授業で扱っていない場合は初等的な本もしくは[Waseda Course Channel](http://course-channel.waseda.jp/subject/contents/99ZZ000002/ZZ/26)で学んでください。)

- 変数・標準出力
- 繰り返し
- 配列・ポインタ
- 関数呼び出し
- 文字列
- 条件分岐
- switch文
- 構造体
- 数学関数

[C言語の初歩的なトピックの理解を確認する10の問題](https://qiita.com/yasuo-ozu/items/a7f09898067fbb973c43)で確認してください。

## 自分でゼロからプログラムをかけますか?

授業で与えられたプログラムを読むことは重要ですが、読むだけでなく自分でプログラムを書かなければ上達しません。そのプログラム、ちゃんと理解できているのであれば、自分でゼロから書き直せるはずですよね。

# 教科書

各言語には原典とも呼ばれるような有名な教科書があります。C言語の場合、それは間違いなく「[プログラミング言語C (B.W.カーニハン/D.M.リッチー)](https://www.kyoritsu-pub.co.jp/bookdetail/9784320026926)」です。よく**K&amp;R**と呼ばれています。
少しとっつきにくいという方もいますが、手に入れて損はない長く使える本です。(第2版であれば、内容が古い/記法が古い、ということもありません)

この本について紹介したいことは山ほどありますが、本題からそれるので省略します。詳しくは[Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E8%A8%80%E8%AA%9EC)参照。

# BNF記法

プログラミング言語の文法は何らかの手段で厳密に定義されていることが多く、そのための方法として最も有名なのが[BNF記法](https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%83%E3%82%AB%E3%82%B9%E3%83%BB%E3%83%8A%E3%82%A6%E3%82%A2%E8%A8%98%E6%B3%95)です。
実際のところ、素のBNF記法がそのまま使われている例はあまり見ないのですが、そのエッセンスを汲んだ独自の方法で文法を記述することが多いため、基本を理解しておくと何かと役に立ちます。

例えば、先述した「プログラミング言語C」の「付録A. 参照マニュアル」では、C言語の文法が規定されています。その一部を示します。(純粋なBNF記法とは少し異なります)

> *asignment-expression*:
> 　　*conditional-expression*
> 　　*unary-expression* *assignment-operator* *assignment-expression*
> *assignment-operator*: one of
> 　　= \*= /= %= += -= <<= >>= &amp;= ^= |=

これが意味するところは、

- *assignment-expression*は、「*conditional-expression*」または「*unary-expression*, *assignment-operator*, *assignment-expression*を並べたもの」である
- *assignment-operator*は、= \*= /= %= += -= <<= >>= &amp;= ^= |= のうちの1つである

となります。なお、*conditional-expression*, *unary-expression*は本文中の他の部分で定義されています。
例えば、変数`a`, `b`, `c`が定義されていて、`a`が*unary-expression*, `b || c`が*conditional-expression*であるとすれば、

```c
a /= b %= c = b || c
```

は*assignment-expression*であることが確認できます。(確認してみてください)
ヒント: **assignment-expression**の定義の中に再び**assignment-expression**が入っていますね?(このような定義を**右再帰的な定義**といいます。)

# 演算子の優先順序

足し算、引き算より掛け算、割り算のほうが先に実行される、という初歩的な計算法則はC言語でも成り立ちます。例えば、

```c
#include <stdio.h>
main() { printf("%d\n", 3 + 4 * 5); }
```

は`23`を出力します。これを実現するのが演算子の優先順序です。表にまとめると以下になります。

| 優先順位 | 演算子 | 結合 |
|:-:|:-:|:-:|
| 1 | 関数呼び出し() 添字[] . -> 後置++ 後置-- | 左結合 |
| 2 | 前置++ 前置-- sizeof 単項&amp; 単項* 単項+ 単項- ~ ! | 右 |
| 3 | キャスト演算子 | 右 |
| 4 | * / % | 左 |
| 5 | + - | 左 |
| 6 | << >> | 左 |
| 7 | < <= > >= | 左 |
| 8 | == != | 左 |
| 9 | &amp; | 左 |
| 10 | ^ | 左 |
| 11 | ｜ | 左 |
| 12 | &amp;&amp; | 左 |
| 13 | ｜｜ | 左 |
| 14 | :? | 右 |
| 15 | = += -= *= /= %= <<= >>= &amp;= ^= ｜= | 右 |
| 16 | , | 左 |

いくつか用語の説明をします。

- **単項 / 前置 / 後置について** : 式を1つとる演算子を**単項演算子**といいます。特に、式の前に演算子を書く場合、それを**前置演算子**といい、後ろにかく場合は**後置演算子**といいます。例えば、`i++`の`++`は後置演算子です。式を2つ取る演算子を**二項演算子**といいます。例えば`1+2`の`+`は二項演算子です。
- **結合**について : 同じ優先度の演算子が並列に並んでいる場合の計算順序を規定します。例えば二項演算子の`-`は左結合なので、`10-5-3`は`(10-5)-3`と解釈されます。
- **キャスト演算子** は `(double)`のようなものです。

# ビット演算

C言語で書かれたプログラムを実行するコンピュータは2進数を解釈する論理回路であるため、通常の計算(足し算など)の他に**ビット演算**というものを効率よく実行することができます。

例えば、十進数で`8`という値を持つオブジェクトは、論理回路上では`1000`という2進数表現で扱われます。(このような2進数による表現のことを**値の表現**という)
この表現を`1000`を`1010`に変えたいと思ったとすると、`10`は十進数では`2`になるため、`8 + 2`を計算すればよいです。しかしながら足し算を使わなくても、**ビットOR演算**を用いて `8 | 2`を計算することでも同様の効果が得られます。

詳しくは述べませんが、例えば以下のような演算子について学ぶとよいでしょう。

- **ビットAND演算子** *(bitwise-AND operator)* 
- **ビットOR演算子** *(bitwise-OR operator)* 
- **ビット反転演算子**
- **ビットXOR(EOR)演算子**

また、ビット演算の有用性を確認するために以下のプログラムを確認してみましょう。

```c
#include <stdio.h>
const int APPLE = 1;
const int ORANGE = 2;
const int GRAPE= 4;
int cost(int flag) {
	int ret = 0;
	if (flag & APPLE) ret += 300;
	if (flag & ORANGE) ret += 100;
	if (flag & GRAPE) ret += 200;
	return ret;
}

int main(void) {
	printf("%d\n", cost(APPLE | GRAPE));
}
```

# 翻訳単位の分割(分割コンパイル)およびヘッダファイル

大きなプログラムを作る場合、コンパイル時間を短くしたい、等の理由からプログラムを複数のファイルに分けて書くことがあります。例えば、2つのファイル`a.c`, `b.c`について

```a.c
#include <stdio.h>
void f(void) {
    printf("hello\n");
}
```

```b.c
void f(void);
int main(void) {
    f();
}
````

と記述し、以下のようにコンパイルします。

```terminal
$ gcc -o hello a.c b.c
```

また、さらに大きいプロジェクトでは各ファイルの先頭に`void f(void);`のように関数のプロトタイプ宣言を記述するのは現実的でなくなるため、**ヘッダファイル**というものを用意してそこにプロトタイプ宣言をまとめて記述するのが普通です。上の例の場合、`b.h`のプロトタイプ宣言をヘッダファイルに移すと以下のようになります。(コンパイルの仕方は変わりません)

```b.h
void f(void);
````

```b.c
#include "b.h"
int main(void) {
    f();
}
````

# 暗黙の型変換

C言語では、数値型のオブジェクトは様々な局面で自由に使えるようになっています。例えば以下のコードを見てください。

```c
unsigned long long a = 100ull;
int b = a;
```

`a`は`unsigned long long`型であり、`b`は`int`型なので、`a`と`b`の型が異なっていますが、C言語の場合は**暗黙の型変換**によって型変換が行われ、`a`を`b`に代入することができます。

また、通常の算術演算でも同様のことが起こります。

```c
int a = -10;
unsigned int b = 10;
printf("%d\n", a / b);
```

このようにすると、`a`, `b`はどちらも`unsigned int`に変換されて計算されます。(-1にならない)

参考
[少し詳しい型変換の説明](http://www9.plala.or.jp/sgwr-t/detail/TypeConversion.html)

# 副作用完了点

式には値を求める作用(**正の作用**)と、環境に対して何らかの影響を与える**副作用**が存在します。この副作用がどのタイミングで実行されるかを規定するのが副作用完了点です。

参考
[副作用完了点について](https://qiita.com/yz2cm/items/1b717ad13ea37c579793)

# 型の読み方

C言語の特徴の1つとして、型の記述方法が複雑という点が挙げられます。例えば、以下に示す識別子`a`は何を表すでしょうか。

```c
char (*a(int, int))[10](int);
```

C言語の型の読み方を解説したサイトはいくつかあるため、ここでは割愛します。

例
[やり直しC言語：複雑な宣言の読み方](http://blog.tojiru.net/article/426537111.html)
[C言語の型の読み方](https://qnighy.hatenablog.com/entry/2017/03/06/233150)