新4年生用

【C言語1】歴史と現代における位置づけ・分岐・ループ

はじめに

本テキストは大学4年生を対象とした、C言語プログラミング学習教材です。構文の解説に加え、コンピュータサイエンスの根本原理とその実装方法について深い理解を目指します。

概念は理論的背景からしっかり説明し、実践的なコーディング例を通じて概念の定着を図ります。

➊ C言語とは – 歴史的背景と現代における位置づけ

C言語は1972年にベル研のデニス・リッチーによって開発されました。UNIXオペレーティングシステムの実装言語として誕生し、その後世界中で広く採用されるようになりました。

高水準な言語でありながらハードウェアへの直接的なアクセスを可能にすることが、他の言語との違いです。

現在でもC言語はオペレーティングシステム、組込みシステム、高性能コンピューティングなど、パフォーマンスとハードウェア制御が重要な分野で広く使用されています。

多くの最新言語(Python、Ruby、JavaScript実行環境など)の実装にもC言語が使われており、コンピュータサイエンスの基礎を築く言語として今日も重要な位置を占めています。

また、大手企業の最新のプログラムでは最新のプログラミング言語が使われていると考えられがちですが、過去のプログラムとの連携や開発のしやすさからC言語が使われる場面は多いため、ある程度触れておくと就職後に役立つかと思います。

C言語を学ぶ意義としては以下のようにまとめられると思います。

C言語を学ぶ意義

  1. 抽象化の理解: 高級な抽象化を提供する言語の裏側で何が起こっているかを理解できる
  2. 計算機アーキテクチャとの関連性: メモリ管理やポインタを通じてコンピュータの動作原理を深く理解できる
  3. 効率的なコードの記述: リソース制約のある環境で最適化されたコードを書く能力を養える
  4. 移植性の高いコード: 多様なプラットフォームで動作するコードの書き方を学べる
  5. システムプログラミングの基礎: OSやデバイスドライバなどのシステムレベルのプログラミングスキルを獲得できる

第2章:C言語の基本 – プログラミングパラダイムとしてのC

C言語は命令型プログラミングパラダイムに基づいており、プログラムは一連の操作(命令)として記述されます。C言語は構造化プログラミングをサポートし、プログラムは関数という単位に分割されます。

Hello, World!

最も基本的なプログラムを書きながら、C言語の構造を見ていきましょう。

#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}
  1. #include <stdio.h> – プリプロセッサディレクティブ。<標準入出力ライブラリ>
  2. int main() – メイン関数の宣言。プログラムのエントリーポイントとなる
  3. printf("Hello, World!\n"); – 標準出力への文字列表示
  4. return 0; – 正常終了を示す値を返す

コンパイルプロセス

C言語はコンパイル言語です。プログラムを実行するためには、ソースコードをコンパイラによってマシン語に変換する必要があります。

コンパイルのプロセスは以下の通りです。

  1. プリプロセッシング: #include#defineなどのプリプロセッサディレクティブを処理
  2. コンパイル: ソースコードを中間表現(アセンブリコード)に変換
  3. アセンブル: アセンブリコードをオブジェクトコード(機械語)に変換
  4. リンク: 複数のオブジェクトファイルやライブラリを結合して実行可能ファイルを生成

変数と基本データ型

C言語での変数は、データを格納するためのメモリ領域に名前を付けたものです。C言語には以下のような基本データ型があります。

整数型

  • char: 1バイト (通常は8ビット) – 文字を表現
  • short: 通常2バイト
  • int: 少なくとも2バイト (通常は4バイト)
  • long: 少なくとも4バイト
  • long long: 少なくとも8バイト (C99から)

浮動小数点型

  • float: 単精度浮動小数点 (通常は4バイト)
  • double: 倍精度浮動小数点 (通常は8バイト)
  • long double: 拡張精度浮動小数点

変数宣言の例

int count = 10;            // 整数型変数の宣言と初期化
char letter = 'A';         // 文字型変数
float pi = 3.14159;        // 単精度浮動小数点数
double precise_pi = 3.14159265358979323846;  // 倍精度浮動小数点数

型修飾子

基本型は以下の修飾子で変更できます:

  • signed: 符号付き (デフォルト)
  • unsigned: 符号なし
  • short: より小さい範囲
  • long: より大きい範囲

具体的には以下の通りです。

unsigned int positive_only = 100;  // 0から4294967295までの値
long int big_number = 2147483647;  // より大きな範囲の整数

演算子

C言語には以下に示すように多様な演算子があります。

算術演算子

  • + (加算)
  • - (減算)
  • * (乗算)
  • / (除算)
  • % (剰余)

関係演算子

  • == (等しい)
  • != (等しくない)
  • > (より大きい)
  • < (より小さい)
  • >= (以上)
  • <= (以下)

論理演算子

  • && (AND)
  • || (OR)
  • ! (NOT)

ビット演算子

  • & (ビットごとのAND)
  • | (ビットごとのOR)
  • ^ (ビットごとのXOR)
  • ~ (ビットごとのNOT)
  • << (左シフト)
  • >> (右シフト)

代入演算子

  • = (代入)
  • +=, -=, *=, /=, %= (複合代入)
  • &=, |=, ^=, <<=, >>= (ビット演算複合代入)

インクリメント・デクリメント演算子

  • ++ (インクリメント)
  • -- (デクリメント)

入力は以下の通りに行います。

int a = 5, b = 3;
int sum = a + b;         // 8
int diff = a - b;        // 2
int product = a * b;     // 15
int quotient = a / b;    // 1 (整数除算)
int remainder = a % b;   // 2

制御構造

条件分岐

if (condition) {
    // 条件が真の場合に実行
} else if (another_condition) {
    // 最初の条件が偽で、別の条件が真の場合に実行
} else {
    // すべての条件が偽の場合に実行
}
三項演算子を使った条件式
int max = (a > b) ? a : b;  // aとbの大きい方をmaxに代入
switch文
switch (expression) {
    case value1:
        // expressionがvalue1と等しい場合に実行
        break;
    case value2:
        // expressionがvalue2と等しい場合に実行
        break;
    default:
        // どのcaseにも該当しない場合に実行
}

ループ構造

for文
for (initialization; condition; increment) {
    // 条件が真である間、繰り返し実行
}

実際には以下のように書きます。

// 0から9までの数字を表示
for (int i = 0; i < 10; i++) {
    printf("%d ", i);
}
while文
while (condition) {
    // 条件が真である間、繰り返し実行
}
do-while文
do {
    // 最低一回は実行され、その後条件が真である間、繰り返し実行
} while (condition);

実践的なコード例:二分探索アルゴリズム

ここで実践的なコードのサンプルとして、有名な二分探索のアルゴリズムを示します。
#include <stdio.h>

// ソート済み配列内の要素を二分探索で検索する関数
int binary_search(int arr[], int left, int right, int x) {
    while (left <= right) {
        // オーバーフローを防ぐために中央値を計算
        int mid = left + (right - left) / 2;
        
        // 中央の要素が探している値と一致する場合
        if (arr[mid] == x)
            return mid;
        
        // 探している値が中央値より大きい場合、右半分を探索
        if (arr[mid] < x)
            left = mid + 1;
        // そうでなければ左半分を探索
        else
            right = mid - 1;
    }
    
    // 要素が見つからなかった場合
    return -1;
}

int main() {
    int arr[] = {2, 3, 4, 10, 40, 50, 70, 90};
    int n = sizeof(arr) / sizeof(arr[0]);
    int x = 10;  // 探す値
    
    int result = binary_search(arr, 0, n-1, x);
    
    if (result == -1)
        printf("要素は配列内に存在しません\n");
    else
        printf("要素は配列内のインデックス %d に存在します\n", result);
    
    return 0;
}

一行一行追っていくと、このアルゴリズムの効率性とC言語の基本的な制御構造が分かると思います。このアルゴリズムは時間複雑度O(log n)を持ち、非常に効率的な検索方法です。

まとめと次回の予告

今回は、C言語の歴史的な背景、現代における重要性、基本構文と基本的なプログラミング概念について学びました。次回は、関数、配列、ポインタというC言語の重要な概念について詳しく探求していきます。

演習問題

最後に演習課題をおいておきます。

  1. C言語プログラムのコンパイルプロセスの各段階について詳しく説明してください。
  2. 整数型と浮動小数点型の違いは何ですか?それぞれの使用シナリオを挙げてください。
  3. 二分探索アルゴリズムを拡張して、要素が見つからなかった場合に挿入すべき位置を返すようにしてください。
  4. ビット演算子を使用して、与えられた整数の特定のビットを設定・クリア・反転するプログラムを作成してください。
  5. C言語の型変換(暗黙的および明示的)について研究し、潜在的な問題点を指摘してください。