C言語の基本資料
目次
- C言語とは
- コンパイラとは
- プログラムの基本要素
- アセンブラとは
- インタープリタとは
- 主なプログラム言語
- C言語の基本
- 例:階乗計算プログラム
- インクルードガード
- 関数プロトタイプ
- ストレージクラス
- 型変換
- ファイルI/Oの詳細
- デバッグとテスト
- モジュール化
- C言語のセキュリティ
- シューティングゲームの実装
1. C言語とは
C言語はベル研究所で開発されたプログラミング言語。低レベルの開発もできる。C++やC#は、C言語をベースにしたオブジェクト指向言語。
2. コンパイラとは
コンパイラは、C言語などで書かれたソースコードを機械語に翻訳するプログラム。ソースコード全体を一度に翻訳し、実行可能ファイルとして出力する。代表的なコンパイラには、gccやclangがある。
3. プログラムの基本要素
プログラムは基本的に「順次」「分岐」「反復」の三つで成り立っている。これらを組み合わせて色々な処理を作る。
- 順次処理: 順番に処理を行う。
- 分岐処理: 条件によって処理を分ける。
- 反復処理: 条件を満たすまで処理を繰り返す。
4. アセンブラとは
アセンブラ(アセンブリ言語)は機械語とほぼ1対1対応する低水準言語。CPUの命令を直接操作するために使う。
5. インタープリタとは
インタープリタは、ソースコードを一行ずつ実行するプログラム。逐次実行するため、対話的な開発やデバッグが容易。例:Python、Ruby、JavaScript。
6. 主なプログラム言語
主なプログラム言語の特徴
言語 | 特徴 | 用途 |
---|---|---|
C++ | C言語をオブジェクト指向に対応させた言語。高速で、低レベルの開発やゲームエンジンにも利用。 | システムソフトウェア、ゲーム開発 |
Java | 仮想マシン上で動作する高級言語。ガベージコレクタを備え、プラットフォームに依存しない。 | エンタープライズシステム、Webアプリケーション |
JavaScript | 主にWEBブラウザで動作するスクリプト言語。Node.jsでサーバーサイドでも利用可能。 | Web開発 |
Ruby | シンプルかつ直感的な文法。主にWebアプリケーション開発で利用。 | Webアプリケーション |
Swift | Apple社が開発。iOSやmacOSのアプリ開発に主に用いられる。 | iOS、macOSアプリ開発 |
Kotlin | 主にAndroidのアプリ開発に用いられる。Javaと完全互換性あり。 | Androidアプリ開発 |
Python | 人気No.1言語。簡単で読みやすい。AI、データ分析、Web開発など多岐にわたる。 | データ分析、AI開発、Web開発 |
Rust | 低レベル言語。メモリ安全を重視し、高速。Mozilla社が開発。 | システムソフトウェア、Web開発 |
Go | Googleが開発。シンプルで効率的。並行処理のサポートが強力。 | Webサーバー、クラウドサービス |
PHP | Web開発に広く利用される。動的なWebページ生成に使用。 | Webアプリケーション |
TypeScript | JavaScriptに型付けを加えた言語。大規模なプロジェクトでのエラーを減らす。 | 大規模なJavaScriptプロジェクト |
SQL | データベース操作の標準言語。データの照会、更新、削除に使用。 | データベース操作 |
C# | Microsoftによって開発された言語。Javaに似た構文で、.NETフレームワークと密接に統合。 | Windowsアプリケーション、Web開発 |
Perl | 高いテキスト処理能力を持つ言語。スクリプトやWebサーバサイドで使用。 | テキスト処理、Webサーバサイド |
R | 統計解析とデータビジュアライゼーションに特化した言語。 | 統計解析、データサイエンス |
MATLAB | 数値解析と技術計算に使用される。高レベルのプログラミング言語。 | 数値解析、シミュレーション、データ可視化 |
7. C言語の基本
7.1 Cプログラムの基本形
#include <stdio.h>
// 関数プロトタイプ宣言
void sayHello();
int main() {
// 処理を記述
sayHello(); // 関数の呼び出し
return 0;
}
// 関数の定義
void sayHello() {
printf("Hello, World!\n");
}
7.2 main関数
main関数は、C言語のプログラムの中で処理を行う関数。これ以外の関数はmain関数内で呼び出して処理を行う。
int main() {
//ここに処理を記述
return 0;
}
7.3 ヘッダーファイル
ヘッダーファイルは、関数やマクロをまとめたファイル群。#include
で読み込む。例:`#include <stdio
.h>`
7.4 型
C言語では変数や関数には型が決まっている。
型 | 説明 |
---|---|
void | 型なし。無いことを表す。 |
int | 整数型。4バイト。 |
long int | 整数型。8バイト。 |
float | 浮動小数点型。4バイト。 |
double | 浮動小数点型。8バイト。 |
char | 文字型。1バイト。 |
struct | 構造体を表す。 |
union | 共用体を表す。 |
enum | 列挙型を表す。 |
7.5 演算子
演算子は、変数や値を操作するために使う記号。
演算子 | 説明 |
---|---|
= |
代入を示す。 |
+ |
加算を示す。 |
- |
減算を示す。 |
* |
乗算を示す。 |
/ |
除算を示す。 |
< |
小なりを示す。 |
> |
大なりを示す。 |
== |
等しいことを示す。 |
!= |
等しくないことを示す。 |
<= |
以下を示す。 |
>= |
以上を示す。 |
% |
余剰(あまり)を示す。 |
&& |
論理積(AND)を示す。 |
|| |
論理和(OR)を示す。 |
! |
論理否定(NOT)を示す。 |
7.6 printf関数
printf
関数は、標準出力にフォーマットされた文字列を出力する。
printf("書式文字列", 変数1, 変数2, ...);
7.6.1 書式指定子
書式指定子を使って、変数の値を表示する形式を指定。
指定子 | 説明 | 使用例 |
---|---|---|
%d | 整数(10進数) | printf("%d", 10); |
%i | 整数(10進数) | printf("%i", 10); |
%o | 整数(8進数) | printf("%o", 10); |
%x | 整数(16進数、小文字) | printf("%x", 10); |
%X | 整数(16進数、大文字) | printf("%X", 10); |
%u | 符号なし整数 | printf("%u", 10); |
%f | 浮動小数点数(小数点以下6桁まで) | printf("%f", 10.0); |
%e | 浮動小数点数(指数表記、小文字e) | printf("%e", 10.0); |
%E | 浮動小数点数(指数表記、大文字E) | printf("%E", 10.0); |
%g | 浮動小数点数(%fまたは%eの短い方) | printf("%g", 10.0); |
%G | 浮動小数点数(%fまたは%Eの短い方) | printf("%G", 10.0); |
%c | 文字 | printf("%c", 'A'); |
%s | 文字列 | printf("%s", "Hello"); |
%p | ポインタアドレス | printf("%p", ptr); |
%% | %文字そのもの | printf("%%"); |
7.6.2 フィールド幅と精度指定
フィールド幅と精度を指定して、出力の形式を制御。
- フィールド幅: 出力フィールドの最小幅を指定。
- 精度: 小数点以下の桁数や、文字列の最大長を指定。
printf("%10d", 123); // 幅10の右寄せで整数を出力
printf("%.2f", 3.14159); // 小数点以下2桁までの浮動小数点数を出力
7.6.3 フラグ
フラグを使って、出力形式をさらに制御。
フラグ | 説明 | 使用例 |
---|---|---|
- | 左寄せ | printf("%-10d", 123); |
+ | 正の符号を表示 | printf("%+d", 123); |
0 | 0で埋める | printf("%010d", 123); |
空白 | 正数の符号部分を空白で埋める | printf("% d", 123); |
# | 進数の基数プレフィックスを追加する | printf("%#x", 123); |
7.7 条件文
7.7.1 if文(分岐)
条件がTrueかFalseかで実行する内容を分岐。
if (条件) {
// 条件が真のときに実行する処理
}
7.7.2 if-else文
if文に間違っていた場合の処理を加えたもの。
if (条件) {
// 条件が真のときに実行する処理
} else {
// 条件が偽のときに実行する処理
}
7.7.3 if-else if-else文
if-else文にさらに条件を加えたもの。
if (条件1) {
// 条件1が真のときに実行する処理
} else if (条件2) {
// 条件2が真のときに実行する処理
} else {
// すべての条件が偽のときに実行する処理
}
7.7.4 switch文
ある条件の時の処理をいくつか用意。
switch (式) {
case 定数1:
// 式が定数1と等しい場合の処理
break;
case 定数2:
// 式が定数2と等しい場合の処理
break;
default:
// その他の場合の処理
break;
}
7.8 ループ文
7.8.1 for文
for (初期化; 条件; 変化式) {
// 条件が真の間繰り返し実行する処理
}
- 初期化: ループの初めに1度だけ実行される式。
- 条件: この条件が真である間、ループが実行。
- 変化式: 各ループの終了時に実行される式。
7.8.2 while文
while (条件) {
// 条件が真の間繰り返し実行する処理
}
- 条件: この条件が真である間、ループが実行。
7.8.3 do-while文
do {
// 最低1回は実行される処理
} while (条件);
- 条件: この条件が真である間、ループが実行。
7.9 配列
配列は、同じ型のデータをまとめて管理するためのデータ構造。
7.9.1 一次元配列
型 配列名[長さ];
例
int array[10]; // 長さ10の整数型の配列を宣言
配列の要素にアクセスするには、インデックスを使用。インデックスは0から始まる。(長さ10なら、インデックスは0~9である。)
array[0] = 1; // 配列の最初の要素に値を代入
printf("%d", array[0]); // 配列の最初の要素を出力
7.9.2 多次元配列
int matrix[3][4]; // 3行4列の整数
型の2次元配列を宣言
多次元配列の要素にアクセスするには、各次元のインデックスを指定。
matrix[0][1] = 5; // 1行2列目の要素に値を代入
printf("%d", matrix[0][1]); // 1行2列目の要素を出力
7.10 ポインタ
ポインタは、変数のメモリアドレスを格納するための変数。ポインタを使うと、メモリの直接操作や間接参照が可能。
7.10.1 ポインタの宣言と初期化
int *ptr; // 整数型のポインタを宣言
int value = 10;
ptr = &value; // ポインタに変数valueのアドレスを格納
7.10.2 ポインタの間接参照
printf("%d", *ptr); // ポインタを通じて変数valueの値を出力
7.10.3 ポインタの用途
ポインタは以下の用途に使用:
-
動的メモリ割り当て:
malloc
、calloc
、realloc
関数を使ってメモリを動的に確保。 - 配列の操作:配列の先頭要素のアドレスを指し示すことで効率的に操作。
- 関数への引数渡し:大きなデータ構造を関数に渡す際にメモリ消費を抑え高速にアクセス。
- 文字列操作:文字列は文字の配列であり、ポインタを使って操作。
7.10.4 ポインタを使った配列の操作
int array[5] = {1, 2, 3, 4, 5};
int *ptr = array; // 配列の先頭要素のアドレスをポインタに格納
for (int i = 0; i < 5; i++) {
printf("%d ", *(ptr + i)); // ポインタを使って配列の要素にアクセス
}
7.11 構造体
構造体は、複数の異なるデータ型の変数をまとめて一つのデータ型として扱うためのもの。
7.11.1 構造体の定義
struct
キーワードを使って定義。
struct 構造体名 {
データ型 メンバ名;
データ型 メンバ名;
データ型 メンバ名;
};
7.11.2 構造体の宣言と初期化
構造体変数を宣言し、必要に応じてメンバに値を代入。
struct 構造体名 変数名;
変数名.メンバ名 = 値;
7.11.3 構造体のメンバアクセス
ドット演算子(.
)を使って構造体のメンバにアクセス。
変数名.メンバ名;
7.11.4 typedefによる構造体の別名定義
typedef
を使って構造体に別名を付ける。
typedef struct {
データ型 メンバ名;
データ型 メンバ名;
データ型 メンバ名;
} 別名;
7.12 共用体
共用体は、同じメモリ領域を複数のデータ型で共有するデータ構造。
7.12.1 共用体の定義
union
キーワードを使って定義。
union 共用体名 {
データ型 メンバ名;
データ型 メンバ名;
データ型 メンバ名;
};
7.12.2 共用体の宣言と初期化
共用体変数を宣言し、必要に応じてメンバに値を代入。
union 共用体名 変数名;
変数名.メンバ名 = 値;
7.12.3 共用体のメンバアクセス
ドット演算子(.
)を使って共用体のメンバにアクセス。
変数名.メンバ名;
7.13 列挙型
列挙型は、関連する定数の集合を定義するために使用。
7.13.1 列挙型の定義
enum
キーワードを使って定義。
enum 列挙型名 {
定数名1,
定数名2,
定数名3
};
7.13.2 列挙型の宣言と使用
列挙型変数を宣言し、定数を割り当て。
enum 列挙型名 変数名;
変数名 = 定数名1;
7.14 関数
関数は、特定の処理をまとめて定義し、必要に応じて何度でも呼び出せる。
7.14.1 関数の定義
戻り値の型 関数名(引数リスト) {
// 関数の処理内容
return 戻り値; // 戻り値がある場合
}
例:
int add(int a, int b) {
return a + b;
}
7.14.2 関数の呼び出し
int result = add(3, 5); // 関数addを呼び出して結果をresultに代入
printf("%d", result); // 結果を出力
7.14.3 void型関数
戻り値がない関数の場合、戻り値の型としてvoid
を指定。
void printHello() {
printf("Hello, World!\n");
}
呼び出し:
printHello(); // 関数printHelloを呼び出し
7.14.4 関数プロトタイプ
関数の本体を定義する前に、関数の宣言を行うことを「関数プロトタイプ」と言う。
戻り値の型 関数名(引数リスト); // 関数プロトタイプ宣言
例:
int add(int, int); // 関数プロトタイプ宣言
7.14.5 関数ポインタ
関数ポインタは、関数のアドレスを格納するためのポインタ。関数ポインタを使うと、関数を引数として渡したり、関数の動的な選択が可能になる。
int (*funcPtr)(int, int); // 関数ポインタの宣言
funcPtr = &add; // 関数ポインタに関数addのアドレスを格納
int result = funcPtr(3, 5); // 関数ポインタを使用して関数を呼び出し
7.15 標準入出力の詳細
7.15.1 ファイル操作
ファイルを開いたり閉じたり、データの読み書きを行うために標準入出力ライブラリを使用。
#include <stdio.h>
int main() {
FILE *file;
file = fopen("example.txt", "r"); // ファイルを開く
if (file != NULL) {
char line[100];
while (fgets(line, sizeof(line), file)) {
printf("%s", line); // ファイルから読み取った行を出力
}
fclose(file); // ファイルを閉じる
} else {
printf("ファイルを開けませんでした。\n");
}
return 0;
}
7.15.2 フォーマットされた入出力
scanf
、fprintf
、fscanf
を使ってフォーマットされた入出力を行う。
#include <stdio.h>
int main() {
int num;
printf("整数を入力してください: ");
scanf("%d", &num); // 標準入力から整数を読み取る
printf("入力された整数: %d\n", num);
return 0;
}
7.16 プログラムにおけるメモリの扱い
プログラムが実行される際、データはメ
モリ上に配置される。メモリは主に以下の4つのセグメントに分けられる。
- コードセグメント: 実行する命令が格納される領域。
- データセグメント: 静的に割り当てられる変数(グローバル変数や静的変数)が格納される領域。
- ヒープセグメント: 動的に割り当てられるメモリ領域。必要に応じてメモリを確保したり解放したりする。
- スタックセグメント: 関数呼び出し時のローカル変数や戻りアドレスなどが格納される領域。
7.17 メモリ管理
C言語ではメモリの管理が重要。適切にメモリを管理しないと、メモリリークやバッファオーバーフローなどの問題が発生。
7.17.1 動的メモリ割り当て
動的メモリ割り当ては、プログラムの実行中に必要なメモリを確保する方法。標準ライブラリの関数を使ってメモリを動的に確保し、使用後に解放する必要がある。
-
malloc
: 指定されたバイト数のメモリを確保。成功すると、確保されたメモリの先頭アドレスを返す。失敗するとNULLを返す。
int *ptr = (int *)malloc(sizeof(int) * 10); // 整数型の配列を動的に確保
-
calloc
: 要素数と要素サイズを指定してメモリを確保し、すべてのビットをゼロに初期化。
int *ptr = (int *)calloc(10, sizeof(int)); // 整数型の配列を動的に確保し初期化
-
realloc
: 既存のメモリブロックのサイズを変更。
ptr = (int *)realloc(ptr, sizeof(int) * 20); // メモリブロックのサイズを再割り当て
-
free
: 確保したメモリを解放。
free(ptr); // 確保したメモリを解放
7.18 メモリリークの防止
メモリリークは、動的に確保されたメモリが解放されずに残ること。メモリリークを防ぐためには、動的に確保したメモリを使用後に適切に解放することが重要。
void allocateMemory() {
int *ptr = (int *)malloc(sizeof(int) * 10);
if (ptr == NULL) {
printf("メモリ割り当てに失敗しました。\n");
return;
}
// メモリを使用する処理
free(ptr); // メモリを解放
}
7.19 バッファオーバーフローの危険性と対策
バッファオーバーフローは、配列やバッファの境界を超えてデータを書き込むこと。これにより、プログラムの動作が予期しないものになる可能性がある。バッファオーバーフローを防ぐためには、適切な範囲チェックを行うことが重要。
void copyString(char *dest, const char *src, size_t destSize) {
if (strlen(src) >= destSize) {
printf("バッファが不足しています。\n");
return;
}
strcpy(dest, src);
}
7.20 ビット操作
ビット操作は、ビット単位でデータを操作する方法。C言語では、ビット演算子を使ってビット操作を行う。
7.20.1 ビット演算子
演算子 | 説明 |
---|---|
& |
AND演算 |
` | ` |
^ |
XOR演算 |
~ |
NOT演算 |
<< |
左シフト |
>> |
右シフト |
int a = 5; // 0101
int b = 3; // 0011
int andResult = a & b; // 0001
int orResult = a | b; // 0111
int xorResult = a ^ b; // 0110
int notResult = ~a; // 1010 (2の補数表現での反転)
int leftShiftResult = a << 1; // 1010
int rightShiftResult = a >> 1; // 0010
7.21 エラー処理
エラーハンドリングは、プログラムが予期しない状況に対処するために重要。C言語では、errno
を使ってエラーハンドリングを行う。
7.21.1 errnoの使用
errno
は、標準ライブラリのエラーナンバーを格納する変数。エラーが発生した場合、関数は通常、特定の値を返し、errno
にエラーコードを設定する。
#include <stdio.h>
#include <errno.h>
#include <string.h>
void openFile(const char *filename) {
FILE *file = fopen(filename, "r");
if (file == NULL) {
printf("ファイルを開けません: %s\n", strerror(errno));
return;
}
// ファイルを使用する処理
fclose(file);
}
7.22 標準ライブラリ
C言語の標準ライブラリには、多くの便利な関数が含まれている。以下に主な標準ライブラリとその関数の一部を示す。
7.22.1 stdio.h
関数 | 説明 |
---|---|
printf |
標準出力にフォーマットされた文字列を出力 |
scanf |
標準入力からフォーマットされた入力を取得 |
fopen |
ファイルを開く |
fclose |
ファイルを閉じる |
fread |
ファイルからデータを読み取る |
fwrite |
ファイルにデータを書き込む |
fprintf |
ファイルにフォーマットされた文字列を出力 |
fscanf |
ファイルからフォーマットされた入力を取得 |
7.22.2 string.h
関数 | 説明 |
---|---|
strcpy |
文字列をコピー |
strncpy |
指定した長さまで文字列をコピー |
strcmp |
文字列を比較 |
strlen |
文字列の長さを取得 |
strcat |
文字列を連結 |
strncat |
指定した長さまで文字列を連結 |
7.22.3 stdlib.h
関数 | 説明 |
---|---|
malloc |
メモリを動的に確保 |
calloc |
メモリを動的に確保し初期化 |
realloc |
メモリブロックのサイズを変更 |
free |
メモリを解放 |
atoi |
文字列を整数に変換 |
atof |
文字列を浮動小数点数に変換 |
rand |
乱数を生成 |
srand |
乱数生成器の初期化 |
7.22.4 math.h
関数 | 説明 |
---|---|
sin |
正弦を計算 |
cos |
余弦を計算 |
tan |
正接を計算 |
sqrt |
平方根を計算 |
pow |
累乗を計算 |
7.22.5 time.h
関数 | 説明 |
---|---|
time |
現在の時間を取得 |
difftime |
二つの時間の差を計算 |
mktime |
構造体から時間を生成 |
strftime |
時間をフォーマット |
7.23
多重定義防止
多重定義防止は、同じヘッダファイルが複数回インクルードされるのを防ぐために使用。通常、#ifndef
, #define
, #endif
を使う。
#ifndef MYHEADER_H
#define MYHEADER_H
// ヘッダファイルの内容
#endif // MYHEADER_H
または、#pragma once
を使うこともできる。
#pragma once
// ヘッダファイルの内容
7.24 可変長引数
可変長引数関数は、引数の数が変動する関数を定義するために使用。stdarg.h
ヘッダファイルを使って実装。
#include <stdarg.h>
#include <stdio.h>
void printNumbers(int count, ...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; i++) {
int num = va_arg(args, int);
printf("%d ", num);
}
va_end(args);
printf("\n");
}
7.25 プリプロセッサディレクティブ
プリプロセッサディレクティブは、#
から始まる命令で、コンパイル前にソースコードを処理するために使う。
7.25.1 #include
#include
ディレクティブは、指定したファイルをソースファイルに挿入するために使用。
#include <stdio.h> // 標準ライブラリのヘッダファイルをインクルード
#include "myheader.h" // ユーザー定義のヘッダファイルをインクルード
7.25.2 #define
#define
ディレクティブは、定数やマクロを定義するために使用。
#define PI 3.14159 // 定数の定義
#define SQUARE(x) ((x) * (x)) // マクロの定義
7.25.3 #undef
#undef
ディレクティブは、定義された定数やマクロを無効にするために使用。
#undef PI // 定義されたPIを無効にする
7.25.4 条件付きコンパイル
条件付きコンパイルは、特定の条件に基づいてコードの一部をコンパイルするかどうかを制御。
-
#ifdef
,#ifndef
: 指定されたマクロが定義されているかどうかをチェック。 -
#if
,#elif
,#else
,#endif
: 条件に基づいてコードをコンパイルするかどうかを制御。
#ifdef DEBUG
printf("デバッグモード\n");
#endif
#ifndef VERSION
#define VERSION 1.0
#endif
#if VERSION >= 2.0
printf("バージョン2.0以上\n");
#else
printf("バージョン2.0未満\n");
#endif
7.25.5 #pragma
#pragma
ディレクティブは、コンパイラに特定の指示を与えるために使用。コンパイラ依存の動作を制御するために使用。
#pragma once // インクルードガードの代替
8. 例:階乗計算プログラム
#include <stdio.h> // 入出力用のヘッダーファイルstdio.hをインクルードする
#include <stdlib.h> // 動的メモリ管理用のヘッダーファイルstdlib.hをインクルードする
int factorial(int n); // 階乗を計算する関数のプロトタイプ宣言
int main(void) { // main関数を定義。{}内の処理を行う
int n = 10; // カウンタ変数nを定義。初期値10
int ans = factorial(n); // 階乗計算関数を呼び出し
printf("%d\n", ans); // 出力関数printfを用いて、出力用変数ansを出力。%dは変数を出力するための指定子。\nは改行を意味する
return 0; // 正常終了を示す
}
int factorial(int n) { // 階乗を計算する関数を定義
int result = 1;
while (n > 0) { // nが0以上の間{}内の処理を繰り返し
result = result * n; // 変数resultにresult × nの計算結果を代入
n = n - 1; // n--でも可。カウンタ変数nにn - 1を代入
}
return result; // 計算結果を返す
}
このプログラムは、関数factorial
を使ってnが10から0になるまでansにnを掛けるという数学でいう階乗の計算をしている。
9. インクルードガード
インクルードガードは、ヘッダファイルが複数回インクルードされるのを防ぐために使用。#ifndef
, #define
, #endif
を使う。
#ifndef MYHEADER_H
#define MYHEADER_H
// ヘッダファイルの内容
#endif // MYHEADER_H
または、#pragma once
を使うこともできる。
#pragma once
// ヘッダファイルの内容
10. 関数プロトタイプ
関数の本体を定義する前に、関数の宣言を行うことを「関数プロトタイプ」と言う。これにより、コンパイラが関数の使用を認識し、エラーチェックを行うことができる。
戻り値の型 関数名(引数リスト); // 関数プロトタイプ宣言
例:
int add(int, int); // 関数プロトタイプ宣言
11. ストレージクラス
C言語のストレージクラスは、変数のライフタイムと可視性を指定する。主なストレージクラスには、auto
、register
、static
、extern
がある。
-
auto
: デフォルトのストレージクラスで、ローカル変数に適用。 -
register
: 変数をレジスタに格納することを提案。 -
static
: 変数のライフタイムをプログラムの実行中に保つ。ローカル変数では関数の外から見えない。グローバル変数では他のファイルから見えない。 -
extern
: 変数が他のファイルで定義されていることを示す。
12. 型変換
C言語では、暗黙的および明示的な型変換が行われる。
12.1 暗黙的型変換
暗黙的型変換は、異なるデータ型間で自動的に行われる型変換。
int i = 10;
double d = i; // intからdoubleへの暗黙的型変換
12.2 明示的型変換
明示的型変換は、キャスト演算子を使って行われる型変換。
double d = 10.5;
int i = (int)d; // doubleからintへの明示的型変換
13. ファイルI/Oの詳細
C言語では、ファイルの読み書きを行うために標準入出力ライブラリを使う。
13.1 ファイルのオープンとクローズ
fopen
関数を使ってファイルを開き、fclose
関数を使ってファイルを閉じる。
FILE *file = fopen("example.txt", "r"); // ファイルを読み取りモードで開く
fclose(file); // ファイルを閉じる
13.2 ファイルからの読み取り
fgets
関数を使ってファイルからデータを読み取る。
char buffer[100];
fgets(buffer, sizeof(buffer), file); // ファイルから一行読み取る
13.3 ファイルへの書き込み
fprintf
関数を使ってファイルにデータを書き込む。
FILE *file = fopen("example.txt", "w"); // ファイルを開く
fprintf(file, "Hello, World!\n"); //
ファイルに書き込む
fclose(file); // ファイルを閉じる
14. デバッグとテスト
プログラムのデバッグとテストは、ソフトウェア開発プロセスにおいて非常に重要。
14.1 デバッグ
デバッグは、プログラムの実行中に発生する問題を見つけて修正するプロセス。C言語のデバッグには、gdb
(GNU Debugger)などのデバッガを使う。
gcc -g -o program program.c # デバッグ情報を含めてコンパイル
gdb ./program # デバッガを起動
14.2 テスト
単体テストは、個々の関数やモジュールが正しく動作することを確認するために行う。C言語の単体テストには、Google TestやCUnitなどのテストフレームワークを使う。
15. モジュール化
大規模なプログラムを構成するために、コードを複数のファイルに分割することが重要。モジュール化により、コードの再利用性と保守性が向上。
15.1 複数ファイルのプログラム構成
以下は、複数ファイルから成るプログラムの構成例。
// main.c
#include "module.h"
int main() {
function();
return 0;
}
// module.c
#include <stdio.h>
#include "module.h"
void function() {
printf("Hello, World!\n");
}
// module.h
#ifndef MODULE_H
#define MODULE_H
void function();
#endif // MODULE_H
15.2 Makefileの使用
make
ツールを使って、複数ファイルのコンパイルを自動化。
# Makefile
CC = gcc
CFLAGS = -Wall
all: program
program: main.o module.o
$(CC) -o program main.o module.o
main.o: main.c
$(CC) $(CFLAGS) -c main.c
module.o: module.c
$(CC) $(CFLAGS) -c module.c
clean:
rm -f *.o program
16. C言語のセキュリティ
C言語でプログラムを作成する際には、セキュリティ上の考慮も重要。
16.1 安全なコーディングのベストプラクティス
- 入力の検証: ユーザーからの入力を常に検証し、不正なデータを防ぐ。
- バッファオーバーフローの防止: 配列やバッファの境界を超えたアクセスを防ぐ。
- フォーマットストリング攻撃の防止: フォーマット文字列を使用する際には、信頼できるデータを使用する。
16.2 バッファオーバーフローの防止
バッファオーバーフローは、配列やバッファの境界を超えてデータを書き込むことで発生。これにより、プログラムの動作が予期しないものになる可能性がある。バッファオーバーフローを防ぐためには、適切な範囲チェックを行うことが重要。
void copyString(char *dest, const char *src, size_t destSize) {
if (strlen(src) >= destSize) {
printf("バッファが不足しています。\n");
return;
}
strcpy(dest, src);
}
16.3 フォーマットストリング攻撃の防止
フォーマットストリング攻撃は、printf
などのフォーマット文字列を使う関数において、不正な入力を利用して実行される。この攻撃を防ぐためには、フォーマット文字列に外部からの入力を直接使用しないようにすることが重要。
// 悪い例
printf(userInput);
// 良い例
printf("%s", userInput);
17.シューティングゲームのプログラム
ここでは、C言語を使ってコンソール上で動作する簡単なシューティングゲームを作成します。このプログラムでは、プレイヤーが敵を撃つことができる基本的なゲームロジックを実装しています。
プログラムコード
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <windows.h>
// ゲームフィールドのサイズ
#define WIDTH 20
#define HEIGHT 10
// プレイヤーと敵のシンボル
#define PLAYER 'P'
#define ENEMY 'E'
#define BULLET '^'
#define EMPTY ' '
// プレイヤーの位置
int playerX = WIDTH / 2;
int playerY = HEIGHT - 1;
// 敵の位置
int enemyX = WIDTH / 2;
int enemyY = 0;
// 弾の位置
int bulletX = -1;
int bulletY = -1;
// ゲームフィールド
char field[HEIGHT][WIDTH];
// フィールドを初期化
void initField() {
for (int y = 0; y < HEIGHT; y++) {
for (int x = 0; x < WIDTH; x++) {
field[y][x] = EMPTY;
}
}
field[playerY][playerX] = PLAYER;
field[enemyY][enemyX] = ENEMY;
}
// フィールドを表示
void displayField() {
system("cls");
for (int y = 0; y < HEIGHT; y++) {
for (int x = 0; x < WIDTH; x++) {
printf("%c", field[y][x]);
}
printf("\n");
}
}
// プレイヤーの移動
void movePlayer(char direction) {
field[playerY][playerX] = EMPTY;
if (direction == 'a' && playerX > 0) {
playerX--;
} else if (direction == 'd' && playerX < WIDTH - 1) {
playerX++;
}
field[playerY][playerX] = PLAYER;
}
// 弾の発射
void shoot() {
if (bulletY == -1) {
bulletX = playerX;
bulletY = playerY - 1;
}
}
// 弾の移動
void moveBullet() {
if (bulletY != -1) {
field[bulletY][bulletX] = EMPTY;
bulletY--;
if (bulletY >= 0) {
field[bulletY][bulletX] = BULLET;
} else {
bulletY = -1;
}
}
}
// 敵の移動
void moveEnemy() {
field[enemyY][enemyX] = EMPTY;
enemyY++;
if (enemyY < HEIGHT) {
field[enemyY][enemyX] = ENEMY;
} else {
enemyY = 0;
enemyX = rand() % WIDTH;
field[enemyY][enemyX] = ENEMY;
}
}
// 当たり判定
void checkCollision() {
if (bulletY == enemyY && bulletX == enemyX) {
field[enemyY][enemyX] = EMPTY;
bulletY = -1;
enemyY = 0;
enemyX = rand() % WIDTH;
field[enemyY][enemyX] = ENEMY;
}
}
int main() {
srand((unsigned int)time(NULL));
initField();
while (1) {
displayField();
if (_kbhit()) {
char input = _getch();
if (input == 'a' || input == 'd') {
movePlayer(input);
} else if (input == ' ') {
shoot();
}
}
moveBullet();
moveEnemy();
checkCollision();
Sleep(100);
}
return 0;
}
プログラムの説明
-
フィールドの初期化:
initField
関数でゲームフィールドを初期化し、プレイヤーと敵の初期位置を設定しています。 -
フィールドの表示:
displayField
関数で現在のフィールドを表示します。system("cls")
でコンソールをクリアしてから表示します。 -
プレイヤーの移動:
movePlayer
関数でプレイヤーの移動を処理します。a
キーで左に、d
キーで右に移動します。 -
弾の発射:
shoot
関数で弾を発射します。スペースキーを押すと、弾が発射されます。 -
弾の移動:
moveBullet
関数で弾を上方向に移動させます。弾が画面外に出たら弾の位置をリセットします。 -
敵の移動:
moveEnemy
関数で敵を下方向に移動させます。敵が画面の下端に達したら、再び上端に移動します。 -
当たり判定:
checkCollision
関数で弾と敵の当たり判定を行います。弾が敵に当たると、敵の位置をリセットし、新しい位置に敵を再配置します。
実行方法
-
コンパイル: このプログラムをコンパイルするには、
gcc
などのCコンパイラを使用します。gcc -o shooting_game shooting_game.c
-
実行: コンパイルが成功したら、生成された実行ファイルを実行します。
./shooting_game
このプログラムは、コンソール上で動作するシンプルなシューティングゲームの例です。プレイヤーは左 (a
)、右 (d
) に移動でき、スペースキー (
) で弾を発射します。