Linux

【初心者向け】C言語の基本的な文法(中編)

【※ 当記事は2020年7月25日時点の情報です】

ペイヴメント(@pavement1234)です。

エンジニア
エンジニア

C言語の基本的な文法をざっくり網羅的に知りたい。

バージョン情報
Windows10 Home(64bit)1903
VMware Player 15上で動くUbuntu18.04

中編で出来ること

C言語の基本的な文法をざっくり紹介します。
極力さらっと読める感じにしていますが、
たまにマニアックな話題(中・上級者向けのネタ)に触れています。

中編では
⑦関数、
⑧構造体・共用体・enum、
⑨配列、
⑩ポインタ、
⑪メモリの動的確保、
⑫キャスト
を説明します。

⑦関数、
⑧構造体・共用体・enum、
⑨配列、
⑩ポインタ、
⑪メモリの動的確保、
⑫キャスト

⑦関数

戻り値 関数名( 引数1, 引数2, 引数3, … ){ 処理 }という構文です。

関数とは引数と呼ばれるパラメタを受け取り、
何らかの処理を実行して、戻り値を返すものです。
※引数も戻り値もvoidを指定することで無しにできます。

前編でC言語プログラムはmain関数から始まるルールという話をしましたが、
main関数も関数の1つです。

プログラムはまとまった処理毎に関数分割することで見通しが良くなりますし、
再利用しやすくなります。

サンプルプログラム:関数分割


解説します。

3~6行目:AAA()という関数を定義しています。
”AAA\n”という文字列をprintf()します。

8~12行目
:BBB()という関数を定義しています。
“BBB a=%d b=%d\n”という文字列をprintf()します。
入力パラメタa、bによって表示が変わります。

16行目
:main関数からAAA()を呼び出しました。
17行目:main関数からBBB()を呼び出しました。
18行目:main関数からAAA()を呼び出しました(2回目)。

サンプルプログラム:関数分割しない

行数は減りますが、
たとえば関数AAA()で実装していた
printf(“AAA\n”);をprintf(“ABA\n”);に変更したい場合、
2か所変更しなければなりません。
メンテナンス性を上げるためにも関数分割は重要です。

⑧構造体・共用体・enum

構造体

struct 構造体名 { 型名 メンバ変数1; 型名 メンバ変数2; 型名 メンバ変数3; …. 型名 メンバ変数n; }という構文で宣言します。

構造体とは複数の変数を1つの型にまとめて扱いやすくするものです。
例えば3次元座標という構造体を作り
x, y, zという3つの変数をまとめて扱ったりすると便利です。

構造体を定義(実体確保)するときは 
truct 構造体名 構造体変数名;と書きます。

メンバ変数にアクセスするときは
構造体変数名.メンバ変数 = 値;という形でアクセスします。

サンプルプログラム:struct aaaという構造体を宣言し、main関数の中で定義して使う

【ノウハウ】宣言と定義の違い

宣言とは(変数とか関数の)名前の型をコンパイルに知らせること。
定義とは名前を持つ実体(メモリ)を確保すること。
という違いがあります。

分類 宣言 定義
変数 extern int aaa; int aaa;
関数 void AAA( void ); void AAA( void ){ }

【ノウハウ】構造体はネスト(入れ子)することができる

構造体は別の構造体をメンバに持つことが出来ます。

サンプルプログラム:bbbという構造体にaaaという構造体のメンバ変数aを持つ

メンバ変数aには15行目のように
b.a.a1 = 1;という形でアクセスします。

【ノウハウ】typedefで型名を新しくつけることが可能

typedef 既存の型名 新しい型名;
という構文で型名を新しくつけることが出来ます。

構造体を定義する(実体を作る)ときに
毎回structを付けるのは面倒なので
typedefで別名を定義しましょう。

サンプルプログラム:typedefで構造体の別名を定義

上記例ではstruct _aaaという構造体を
AAAという新しい型名で宣言しました。

type.hという標準ヘッダでは
以下のように型名を再定義しています。

共用体

union 共用体名 { 型名 メンバ変数1; 型名 メンバ変数2; 型名 メンバ変数3; …. メンバ変数n; }という構文です。

構文的には構造体とほとんど一緒ですが、
構造体のメンバ変数がそれぞれ独立のメモリ領域を持つのに対して、
共用体のメンバ変数はメモリ領域を共有します。

ちょっと分かりにくいと思うので
サンプルプログラムを使って具体的に説明します。

サンプルプログラム:union

上記の例で示したunion uuuという共用体は
a、b、c、dという4つのメンバ変数を持ち、
普通に計算すると11バイト(1+2+4+4)必要ですが、
sizeofでunion uuuのサイズを調べると4バイトです。
要はint型の変数1つ分のメモリしか確保されていません。
u.dに1を代入するとa、b、cも1になります。

実行結果は以下の通りです。

$ gcc -g -o test test.c

./test
sizeof( union uuu )=4
u.a=1
u.b=1
u.c=1
u.d=1

【ノウハウ】
unionは昔、推奨されていなかったが今は…

昔々、共用体はメモリを節約するためにある構文だがわかりにくいので非推奨という話を
先輩から聞いたことがあるのですが、
最近はLinuxカーネル内で拡張性を持たせるためにunionが使われているという話を
聞きました。

【プログラマーの心得】
過去の常識にとらわれず常に最新情報をウォッチしましょう。

≫参考:第9回 機能拡張でよく使われる共用体(union)※途中から有料記事なのでご注意ください(私は無料部分しか読んでません)

enum

enum キー名 { メンバ1, メンバ2, … , メンバn };という構文です。
共用体とは連続するユニークな定数を宣言する構文です。
列挙型とも言います。

例えばコマンドIDみたいな重複が許されない定数を#defineするには
重複しないように注意が必要ですが
enumを使えば必ずユニークになるのでストレスフリーです。

最初のメンバは0で初期化され、次のメンバは1。その次は2と+1されます。
代入することで初期値を変えることも可能。

サンプルプログラム:enum

上記例のように最後の要素を意味する「LAST」を定義しておくと、
LAST+1で全要素数を知ることが出来て便利です。

実行結果は以下の通りです。

$ gcc -g -o test test.c

./test
A=0
B=1
C=2
D=3
LAST=4

⑨配列

型名 変数名[ 要素数 ];という構文です。
配列とは、同じ型の変数を要素数分まとめて宣言するものです。
配列を定義(実体確保)すると連続されたメモリエリアが確保されます。

例えばint型のaという変数を定義した場合、
このように0xC0000000から0xC000000Fまでの
連続16バイトの領域が確保されます。


変数名[ 添え字 ]という構文で
配列の各要素にアクセスできます。

添え字とは0から始まる要素の通し番号です。
例えば要素数を4にした場合、0、1、2、3という添え字が使えます。
要素数-1 = 最後の添え字になることに注意。

サンプルプログラム:配列

これを実行すると、こうなります。

$ gcc -g -o test test.c

$./test
a[0]=0
a[0]=1
a[0]=2
a[0]=3

※n次元配列は後編で少し説明します。

⑩ポインタ

型名 *変数名; という構文です。
ポインタとはアドレスを保持する変数です。

初心者にとってポインタは難関だと思いますが、
ポインタを理解すると色々なプログラムが読めるようになります
頑張りましょう。

⑨配列の項で説明したプログラムを
ポインタで書き換えてみます。

サンプルプログラム:配列をポインタに書き換える

6行目:配列の先頭アドレスは&配列名[ 0 ]で取得できますので、
配列の先頭アドレス&a[0]をポインタ変数paに代入しました。

10行目
:ポインタ変数のアドレスが示す実体(int型の変数)にアクセスするには
*(アスタリスク)を付けます。

11行目
:ポインタ変数を1つ進めます。
int型のアドレスを1つ++すると、4バイト進みます。

13行目
配列の変数名=配列の先頭アドレスなので、
ポインタ変数paに配列名aを代入します。
※おそらく「???」と思った方もいらっしゃると思います。
私は最初に聞いたとき理解できませんでした。
もし今わからなくても慣れてきますので大丈夫です。

これを実行すると、こうなります。
配列版と同じですね。

$ gcc -g -o test test.c

$./test
a[0]=0
a[0]=1
a[0]=2
a[0]=3

ダブルポインタ(**)は後編で説明します。

main関数の第2引数argv(argument vector)は
char型のダブルポインタです。

⑪メモリの動的確保

mallocという標準関数でヒープメモリを動的確保し、
freeという標準関数で解放します。

サンプルプログラム:malloc / free

2行目:<stdlib.h>をインクルードしました。
mallocとfreeを使えるようになります。

6行名:mallocでヒープメモリを確保できます。
char型のメモリを100個(100バイト)確保したいので100を指定しました。
※int型のメモリを100個確保すると400バイトになるのでご注意ください。

7行目
:mallocで獲得した先頭アドレスを後で解放するときに使いたいので
ptopにpをコピーしておきます。

18行目
:freeに先頭アドレス(ptop)を渡すとメモリが解放されます。
※このタイミングのpは先頭アドレスの100バイト先を示しているのでご注意ください。

19、20行目
:ポインタ変数にNULL(ヌル)を設定します。
使い終わったポインタはNULLで初期化する習慣を付けましょう。
アドレスが設定されていると解放(free)したかしていないか
判別つかなくなることがあります。

これを実行すると、こうなります。

$ gcc -g -o test test.c

$./test
*p=0
*p=1
*p=2
(省略)
*p=99

⑫キャスト

変数動詞の演算や、関数の引数に渡すときに
型が異なるとエラーになることがあります。

それを回避するためにキャスト(型変換)することがありますので、
覚えておきましょう。

サンプルプログラム:キャスト(型変換)

解説します。

14行目:float型の変数fを定義しました。
15行目:int型の変数aを定義しました。

17行目
:int型の変数を引数にするAAAにfloat型の変数fを渡すために
( int )でint型にキャストしました。※小数点以下が切り捨てられます。

18行目
:float型変数を引数にするFFFにint型の変数fを渡すために
( float )でfloat型にキャストしました。

19行目
:f-aをfloat型で統一して計算すると0.200000になります。
20行目:f-aをint型で統一して計算すると0になります。
 ※小数点以下が切り捨てられます。

これを実行すると、こうなります。

$ gcc -g -o test test.c

$./test
aaa = 1
fff = 1.000000
f-a=0.200000
f-a=0

まとめ

後編に続きます。

 

ABOUT ME
ペイヴメント
ペイヴメントのエンジニア塾(当ブログ)では20年以上の経験から得られたプログラミング系ノウハウについてベテランにも満足して頂けるような内容の濃いコンテンツを初心者にも分かりやすい形で日々発信しています。【経歴】ベンチャーのソフトハウスで4年勤務後、精密機器メーカーのソフト開発部門に勤務し今に至ります。
error: Content is protected !!