【※ 当記事は2020年7月23日時点の情報です】
ペイヴメント(@pavement1234)です。
C言語の基本的な文法をざっくり網羅的に知りたい。
バージョン情報
Windows10 Home(64bit)1903
VMware Player 15上で動くUbuntu18.04
前編で出来ること
C言語の基本的な文法をざっくり紹介します。
極力さらっと読める感じにしていますが、
たまにマニアックな話題(中・上級者向けのネタ)に触れています。
前編ではカンタンなプログラム実行方法(HelloWorld)と
①プリプロセッサ指令、
②変数、
③定数(リテラル)、
④分岐処理、
⑤反復(ループ)処理、
⑥演算子を説明します。
C言語開発環境をつくる方法
①Linuxで開発する
≫【Ubuntu18.04】プログラミング環境を無料で構築する方法
②Windowsで開発する
≫【MinGW】無料のC/C++プログラミング環境をインストールしよう
今回のサンプルはLinux、Windows
どちらでも実行できるように記載しています。
以降、コンパイル&プログラム実行はLinuxを前提に記載していますが、
Windowsで実行する場合、gcc構文は同じでOK
プログラム実行時は「./test」を「test.exe」に読み替えてください。
超カンタンなプログラムの書き方(HelloWorld)
テキストエディタを開き「test.c」というファイルを作成。
以下の内容をコピペして保存。
サンプルプログラム:Hello World
1 2 3 4 5 6 7 8 9 |
/* test.c */ #include <stdio.h> int main( int argc, char** argv ) { printf("Hello World\n"); return 0; } |
コンパイル&プログラム実行。
HelloWorldと表示されます。
$ gcc -g -o test test.c
$ ./test
Hello World
解説します。
よく分からない用語が出てきたら是非自分で調べてみてください。
1~3行目:コメント
/*と*/で囲うことで複数行に渡るコメントを書けます。
//で行コメントにすることもできます。
1 2 3 |
/* test.c */ |
4行目:インクルード宣言。
標準入出力に関する関数を定義したstdio.hというヘッダーファイルを読み込みます。
stdio.hを読み込むことで7行目のprintf関数を呼ぶことができます。
stdioはSTandarDInputOutputの略語です。
1 |
#include <stdio.h> |
≫参考:#include <stdio.h>はおまじないじゃないぞ
5行目:メイン関数。
C言語のプログラムはmain関数から実行開始するルールになっています。
argc(argument counter)に引数の個数、
argv(argument vector)に引数の配列が格納されます。
※main関数を実行する前にアセンブラのプログラムが実行され
スタックメモリの初期化などC言語のプログラムを実行するための準備をしますが
今回は説明しません。
1 |
int main( int argc, char** argv ) |
6行目:ブロック開始。
1 |
{ |
7行目:printf関数
”HelloWorld\n”という文字列を標準出力(stdout)に表示します。
\nは改行コードです。
1 |
printf("Hello World\n"); |
8行目:リターン文。
プログラムの終了コード(今回は0)を返却します。
1 |
return 0; |
≫参考:C言語入門:関数(2):return文(Geekなページ)
9行目:ブロック終了。
1 |
} |
ちょっと面倒に思われるかもしれませんが
慣れないうちは上記のように
「インクルード宣言とは?」
「main関数とは?」
「printfとは?」
「returnとは?」と
1行ずつ意味を調べて
コツコツとC言語の語彙を増やしていくことをオススメします。
ちょっと調べたいときはインターネットで検索できますし、
体系的に学びたいときはC言語の本を読めばOKです。
①プリプロセッサ指令、
②変数、
③定数(リテラル)、
④分岐処理、
⑤反復(ループ)処理、
⑥演算子
①プリプロセッサ指令(ディレクティブ)
プリプロセッサ指令(ディレクティブ)は
コンパイルの前処理を指示します。
構文 | 意味 |
#include <標準ヘッダファイル.h> #include “自作ヘッダファイル.h” |
ファイルの読み込み。 |
#define 定数名 定数 #define マクロ名( 引数, … ) 処理 定義済マクロ |
定数、マクロ定義。 |
#error ”エラーメッセージ” |
コンパイル時にエラーを発生させます。 |
#warning “警告メッセージ”
|
コンパイル時にワーニングを発生させます。 |
#if 条件1 |
条件コンパイル |
#ifdef ( #ifndef ) 定数またはマクロ |
条件コンパイル。 |
#pragma プロセッサやOS依存の命令 |
プロセッサやOS依存の命令。
|
# TO_STRING( abcde ) |
マクロ実引数を文字列化する。 |
## MERGE_STRING( “param”, “1” ) |
前後の字句列を結合する。 |
【ノウハウ】2重インクルード防止
たとえば、my_func.hという自作ヘッダファイルを作ったときに、
複数のCソースからmy_func.hをインクルードされても
2回以上読み込ませないようにするための仕組みです。
my_func.hはmy_func.c、AAA.c、BBB.c
3つのCソースからインクルードされていますが、
1回目で__MY_FUNC__というマクロが定義され
2回目以降は#ifndef __MY_FUNC_H__の条件に合致しないため
ヘッダファイルの内容は読み込まれません。
サンプルプログラム:2重インクルード防止
my_func.hがmy_func.c、AAA.c、BBB.cでインクルードされるが
2重インクルード防止しているので1回だけ読み込まれます。
2重インクルード防止しないと同じファイルが複数回読み込まれてしまいます
例えば、2重インクルード防止していないヘッダファイルに変数を定義すると、
インクルードされた回数分、
変数が生成され多重定義エラーになります。
my_func.h
1 2 3 4 5 6 7 |
#ifndef __MY_FUNC_H__ #define __MY_FUNC_H__ //ヘッダファイルの内容 extern void my_func( void ); #endif //__MY_FUNC_H__ |
my_func.c
1 2 3 4 5 6 |
#include "my_func.h" void my_func( void ) { printf( "my function!!!\n" ); } |
AAA.c
1 2 3 4 5 6 |
#include "my_func.h" void AAA( void ) { my_func(); } |
BBB.c
1 2 3 4 5 6 |
#include "my_func.h" void BBB( void ) { my_func(); } |
【ノウハウ】アライメント調整
構造体(中編に出てきます)のアライメントを調整するときに
#pragmaを使うことがあります。
サンプルプログラム:#pragmaによるアライメント調整
1 2 3 4 5 6 7 8 9 |
#pragma pack(push) /* 現在のアライメントをスタックにプッシュ */ #pragma pack(1) /* 1バイト境界のアライメントにする */ typedef struct __AAA{ char a; int b; }; #pragma pack(pop) /* スタックから元のアライメントを復元 */ |
②変数
変数とは値を保存するメモリ領域です
保存する値によって型が変わります。
以下の例ではintという整数型のaという変数を定義し、
aに12345という値を代入します。
サンプルプログラム:変数宣言と代入
1 2 |
int a; //宣言 a = 12345; //代入 |
32bitプロセッサの例です。
型名 | 意味 | 値の範囲 |
char |
符号あり文字型(8bit) | -128~+127 |
unsigned char |
符号なし文字型(8bit) | 0~+255 |
short |
符号あり短整数型(16bit) | -32768~+32767 |
unsigned short |
符号あり短整数型(16bit) | 0~+65535 |
int |
符号あり整数型(32bit) |
処理系依存 32bitプロセッサの場合longと同じと考えてよい |
unsigned int |
符号あり整数型(32bit) |
処理系依存 32bitプロセッサの場合unsigned longと同じと考えてよい |
long |
符号あり長整数型(32bit) | -2147483648~+2147483647 |
unsigned long |
符号あり長整数型(32bit) | 0~+4294967295 |
float | 短精度浮動小数点型(32bit) |
処理系依存 |
double | 倍精度浮動小数点型(64bit) |
処理系依存 |
【ノウハウ】符号あり、なしとは何か?
符号ありとはプラスとマイナスの値を表現できることを意味します。
最上位ビット(黄色セル)が0なら正の数、1なら負の数を表します。
一方、符号なしとは8ビット全てを使って値を表現します。
ビットパターン | 符号あり(2の補数) | 符号なし |
00000000 | 0 | 0 |
00000001 | 1 | 1 |
… | … | … |
01111110 | 126 | 126 |
01111111 | 127 | 127 |
10000000 | -128 | 128 |
10000001 | -127 | 129 |
10000010 | -126 | 130 |
… | … | … |
11111110 | -2 | 254 |
11111111 | -1 | 255 |
【ノウハウ】
変数にはスコープと記憶クラスという概念があり、
参照できる範囲と保存する領域が定められています。
サンプルプログラム:変数スコープと記憶クラス
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <stdio.h> #include <stdlib.h> int a = 1; static int b = 2; int main( int argc, char** argv ) { int c = 3; static int d = 4; char *e = malloc( 128 ); free( e ); if( argc > 1 ){ int f = 4; } return 0; } |
変数 | スコープ | 記憶クラス |
a | グローバル(プログラム全体) | スタティック領域(永続的) |
b | グローバル(ファイル全体) | スタティック領域(永続的) |
c | ローカル(main関数内) | スタック領域(局所的) |
d | ローカル(main関数内) | スタティック領域(永続的) |
e | ローカル(main関数内) | ヒープ領域(mallocで獲得しfreeで返却する) |
f | ローカル(if文内) | スタック領域(局所的) |
【ノウハウ】型修飾子について
変数の型の前に型修飾子を付けることで型を修飾できます。
型修飾子 | 意味 |
signed, unsigned |
データの符号の有・無を指定できます。 |
short, long |
intを修飾します |
auto |
オート変数。 C言語は関数内で変数を定義するとローカル変数になるので指定不要 |
register |
レジスタ変数を定義します。 |
extern |
externを付けることでグローバル変数を他ファイルから参照できます。 |
const |
定数を作るときに指定します。 |
static |
staticを付けることで局所化できます。 関数宣言の前につけるとファイル内でのみ参照できる関数になります。 関数外(グローバル領域)に定義されたstatic変数はファイル内でのみ使えるグローバル変数になります。 関数内で定義されたstatic変数は関数内でのみ参照できるグローバル変数になります。これ意外に知らない人多いです。 |
③定数(リテラル)
定数(リテラル)とは固定値のことです。
以下の例のように
「#defineマクロで値を定義する方法」と
「constを付けた変数を定義して固定値を設定する方法」があります。
定数(定数)はコンパイル時に決定され書き換えできません。
#defineマクロはプリプロセス時に固定値(リテラル)が確定しますし、
constを付けた変数を書き換えようとするとコンパイルエラーが出ます。
1 2 |
#define CONSTANT_VALUE (100) const int constant_value = 200; |
④分岐処理
if~else if~else
if~else if~elseを使うと
条件によって処理を変えることができます。
サンプルプログラム:if~else if~else
1 2 3 4 5 6 7 8 9 10 11 12 |
if( param == 1 ){ // パラメタが1のときはエラー(-1)でリターンする return -1; } else if( param == 2 ){ // パラメタが2のときは正常(0)でリターンする return 0; } else{ // パラメタが1、2でないときはprintfでパラメタを表示する printf( "param=%d\n", param ); } |
switch文
変数の値によって処理を分岐するときに
switch文を使います。
case1: case2:のように
breakを省略してcase文を連続で書くのをフォールスルーと言って、
バグの元です。
意図的にフォールスルーするのは良いのですが
単にbreak忘れでもコンパイルエラーにならないので注意しましょう。
サンプルプログラム:switch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
int ret = 0; switch( parame ){ case 0: // 0のときは正常(0)を返却 ret = 0; break; case 1: case 2: case 3: // 1、2、3のときはパラメタ値を返却 ret = param; break; default: // その他のときはエラー(-1) ret = -1; break; } return ret; |
三項演算子
条件文 ? 条件文が真のときの処理 : 条件文が偽のときの処理;
という構文になります。
以下の例はparamが0以上ならそのままaに代入。
paramが0より小さいならマイナスをつけてaに代入しています
(絶対値が得られます)。
三項演算子を使うことで行数を減らすことが出来ますが、
直観的にわかりにくいし、
デバッガでステップ実行したときに値の変化を追いにくいのであまりオススメしません。
サンプルプログラム:三項演算子
1 2 |
int a; a = ( param >= 0 ) ? param : -param; |
⑤反復(ループ)処理
for文
for( 初期化処理; 条件文; ループ毎の終了処理 ){ 処理 }という構文です。
最初にiという変数を0に初期化。
iが10未満ならiの値と半角スペースをprintfしてiをインクリメント(+1)。
iが10になったらループを抜けます。
実行すると0 1 2 3 4 5 6 7 8 9 と表示されます。
サンプルプログラム:for文
1 2 3 4 |
int i; for( i = 0; i < 10; i++ ){ printf("%d ", i ); } |
while文
while( 条件文 ){ 処理 }という構文です。
ループに入る前にiという変数を定義して0に初期化。
iが10未満ならiの値と半角スペースをprintfしてiをインクリメント(+1)。
iが10になったらループを抜けます。
実行すると0 1 2 3 4 5 6 7 8 9 と表示されます。
サンプルプログラム:while文
1 2 3 4 5 |
int i = 0; while( i < 10 ){ printf("%d ", i ); i++; } |
do while文
do { 処理 } while( 条件文 );という構文です。
サンプルプログラム:do while文
1 2 3 4 5 6 |
int i = 0; do{ printf("%d ", i ); i++; } while( i < 10 ); |
ループに入る前にiという変数を定義して0に初期化。
iの値と半角スペースをprintfしてiをインクリメント(+1)。
iが10になったらループを抜けます。
実行すると0 1 2 3 4 5 6 7 8 9 と表示されます。
do whileは直観的にわかりにくいため、
あまりオススメしません。
continue
ループの中の処理を実行したくないときはcontinueします。
以下の例はiが10未満ならprintfをするループですが、
iが5のときはprintfしたくない場合continueします。
実行すると0 1 2 3 4 6 7 8 9 と表示されます(5はprintfされません)。
サンプルプログラム:continue
1 2 3 4 5 6 7 8 |
int i; for( i = 0; i < 10; i++ ){ if( i == 5 ){ // iが5のときはprintfしない continue; } printf("%d ", i ); } |
break
ループを途中で抜けるときはbreakします。
以下の例はiが10未満ならprintfをするループですが、
グローバル変数errが0でない(エラーが発生した)場合はループを抜けます。
ループ処理中に緊急事態が発生したときによくやる手法です。
例えばiが8のときにerrがセットされた場合、
0 1 2 3 4 5 6 7 と表示されたところでbreakが呼ばれループを抜けます。
サンプルプログラム:break
1 2 3 4 5 6 7 |
int i; for( i = 0; i < 10; i++ ){ if( err ){ break; } printf("%d \n", i ); } |
⑥演算子
演算子には優先順位と結合規則があります。
同じ優先順位の場合、結合規則の方向(←なら右から左)に計算がされます。
優先順位 | 結合規則 | 種別 | 構文 | 意味 |
1 | 左→右 | その他 | a() | 関数呼び出し |
1 | 左→右 | その他 | a[x] | 配列添え字 |
1 | 左→右 | その他 | a.x | 構造体実体のメンバ参照 |
1 | 左→右 | その他 | a->x | 構造体ポインタのメンバ参照 |
1 | 左→右 | 算術演算子 | a++ | +1 |
1 | 左→右 | 算術演算子 | a— | -1 |
2 | 左←右 | 算術演算子 | ++a | +1 |
2 | 左←右 | 算術演算子 | —a | -1 |
2 | 左←右 | 算術演算子 | +a | 正 |
2 | 左←右 | 算術演算子 | –a | 負 |
2 | 左←右 | 論理演算子 | !a | 否定 |
2 | 左←右 | ビット演算子 | ~a | ビット反転 |
2 | 左←右 | その他 | *a | ポインタ |
2 | 左←右 | その他 | &a | アドレス |
2 | 左←右 | その他 | sizeof( a ) | メモリサイズ参照 |
3 | 左←右 | 型変換演算子 | (a)x | キャスト |
4 | 左→右 | 算術演算子 | a * x | 乗 |
4 | 左→右 | 算術演算子 | a / x | 除 |
4 | 左→右 | 算術演算子 | a % x | 余 |
5 | 左→右 | 算術演算子 | a + x | 加 |
5 | 左→右 | 算術演算子 | a – x | 減 |
6 | 左→右 | ビット演算子 | a << x | 左ビットシフト |
6 | 左→右 | ビット演算子 | a >> x | 右ビットシフト |
7 | 左→右 | 比較演算子 | a < x | aはxより小さい |
7 | 左→右 | 比較演算子 | a > x | aはxより大きい |
7 | 左→右 | 比較演算子 | a <= x | aはx以下 |
7 | 左→右 | 比較演算子 | a >= x | aはx以上 |
8 | 左→右 | 比較演算子 | a == x | aとxは一致 |
8 | 左→右 | 比較演算子 | a != x | aとxは不一致 |
9 | 左→右 | ビット演算子 | a & x | ビットAND |
10 | 左→右 | ビット演算子 | a ^ x | ビットXOR |
11 | 左→右 | ビット演算子 | a | x | ビットOR |
12 | 左→右 | 論理演算子 | a && x | aかつx |
13 | 左→右 | 論理演算子 | a || x | aまたはx |
14 | 左←右 | その他 | a ? x : y | aが真ならx偽ならy |
15 | 左←右 | その他 | a = x | aにxを代入 |
15 | 左←右 | 算術演算子 | a += x | aにa+xを代入 |
15 | 左←右 | 算術演算子 | a -= x | aにa-xを代入 |
15 | 左←右 | 算術演算子 | a *= x | aにa*xを代入 |
15 | 左←右 | 算術演算子 | a /= x | aにa/xを代入 |
15 | 左←右 | 算術演算子 | a %= x | aにa%xを代入 |
15 | 左←右 | ビット演算子 | a <<= x | aにa<<xを代入 |
15 | 左←右 | ビット演算子 | a >>= x | aにa>>xを代入 |
15 | 左←右 | ビット演算子 | a &= x | aにa&=xを代入 |
15 | 左←右 | ビット演算子 | a |= x | aにa|=xを代入 |
15 | 左←右 | ビット演算子 | a ^= x | aにa^=xを代入 |
16 | 左→右 | その他 | a, b, c | 列挙 |
まとめ
中編に続きます。