C言語入門的テキスト
第1章:プログラミングとC言語
この章では、私たちがこれから学ぶ「プログラミング」とは何か、そしてなぜ「C言語」を選ぶのかについて解説します。コンピュータに指示を出し、動かすための第一歩を踏み出しましょう。
1-1. プログラミングとは?
プログラミングとは、コンピュータに「やってほしい仕事」を、コンピュータが理解できる言葉で「指示書(命令のリスト)」として書くことです。
難しく考える必要はありません。料理のレシピを想像してみてください。
- 「玉ねぎをみじん切りにする」
- 「フライパンで炒める」
- 「塩を少々ふる」
これらの「指示」が順番に並んでいるからこそ、誰でも(ある程度は)同じカレーが作れます。
プログラミングもこれと同じです。ただし、相手は人間ではなくコンピュータです。コンピュータは非常に優秀ですが、「あいまいな指示」や「書かれていないこと」は一切理解できません。
例:人間への指示 vs コンピュータへの指示
- 人間へ: 「(察して)いい感じに玉ねぎを炒めておいて」
- コンピュータへ:
- 「玉ねA」という名前の玉ねぎを用意せよ。
- 「フライパンB」に油を10cc注げ。
- 「フライパンB」の温度が180度になるまで加熱せよ。
- 「玉A」を「フライパンB」に入れよ。
- 3分間、かき混ぜ続けよ。
このように、コンピュータにわかるように、厳密に、順序立てて指示を書く作業がプログラミングです。そして、この指示を書くための「言葉」がプログラミング言語です。
1-2. C言語の歴史と特徴
世界には何百種類ものプログラミング言語がありますが、私たちはその中でも「C言語(シーげんご)」を学びます。
C言語の歴史
C言語は1972年、アメリカのベル研究所でデニス・リッチーらによって開発されました。UNIX(ユニックス)というオペレーティングシステム(OS)を作るために生まれました。
豆知識:OSとは?
Windows, macOS, Linux, Android, iOSなど、コンピュータ全体を管理する最も基本的なソフトウェアのことです。
C言語の特徴と学ぶ理由
C言語は古い言語ですが、今でも現役で使われ続けています。それには明確な理由があります。
- 実行速度が速いC言語は、コンピュータが理解しやすい言葉に「翻訳」されやすいため、非常に高速に動作するプログラムを作れます。これは、OSやゲーム、家電(組み込みシステム)など、速度が求められる分野で重宝されます。
- 多くの言語の「ご先祖様」現代の多くの人気言語(C++, C#, Java, Python, PHPなど)は、C言語の影響を強く受けています。C言語を学ぶことは、他の言語を学ぶ上での強力な土台となります。
- コンピュータの仕組みがわかるC言語は、プログラムがコンピュータの「メモリ」という記憶領域をどのように使っているかを、プログラマが意識しながら書く必要がある言語です。これは少し難しい点でもありますが、「プログラムがなぜ動くのか」という根本的な仕組みを理解するのに最適です。
C言語を学ぶことは、単に一つの言語を習得するだけでなく、コンピュータ科学の基礎を学ぶことでもあるのです。
1-3. プログラムができるまで (コンパイルと実行)
私たちがC言語で書いた指示書(ソースコード)は、そのままではコンピュータは理解できません。なぜなら、コンピュータが直接理解できるのは「0」と「1」だけで構成された機械語(マシンご)だけだからです。
そこで、「翻訳機」が必要になります。この翻訳機をコンパイラと呼びます。
C言語のプログラムが実行されるまでの流れは、以下の通りです。
- コーディング (Coding)私たちが、VSCodeのようなテキストエディタを使って、C言語の文法で指示書を書きます。このファイルをソースコード(またはソースファイル)と呼びます。
- (例:
hello.c)
- (例:
- コンパイル (Compile)コンパイラ(私たちはGCCというソフトを使います)が、hello.c を読み込み、文法が間違っていないかチェックします。
- 文法が間違っていれば「エラー」を出します。
- 文法が正しければ、コンピュータが理解できる機械語に「翻訳」します。
- 実行可能ファイルの生成コンパイルが成功すると、機械語で書かれた実行可能ファイル(Windowsなら .exe ファイル)が生成されます。
- (例:
hello.exeまたはa.out)
- (例:
- 実行 (Execute)私たちがこの実行可能ファイルを実行するように指示すると、コンピュータ(OS)がそのファイルを読み込み、書かれた指示をCPU(コンピュータの脳みそ)が順番に実行します。その結果が画面に表示されたりします。
VSCodeは、この「コーディング」から「実行」までの一連の流れをスムーズに行うための道具です。
次の章では、早速VSCodeとコンパイラを準備して、この流れを体験してみましょう。
第2章:開発環境の準備 (VSCode)
料理にキッチンとコンロが必要なように、プログラミングにも「道具」が必要です。この章では、C言語プログラミングを行うための「開発環境(かいはつかんきょう)」を整えます。
私たちが使うのは、VSCode (Visual Studio Code) という、現在世界中のプログラッマーに愛用されている無料のツールです。
2-1. VSCode (Visual Studio Code) とは?
VSCode(ブイエスコード)は、Microsoftが開発・提供している高機能なテキストエディタです。
Windowsに標準で入っている「メモ帳」もテキストエディタ(文字を書くソフト)ですが、VSCodeは「プログラミング専用に徹底的に強化されたメモ帳」だと考えてください。
- 無料で使える: Microsoft製ですが、完全に無料で利用できます。
- 動作が軽い: 高機能でありながら、軽快に動作します。
- 拡張機能が豊富: 最大の特徴が「拡張機能」です。これ(プラグイン)を追加インストールすることで、C言語だけでなく、Python、JavaScript、Web制作など、あらゆるプログラミング言語に対応できる「万能な作業場」になります。
2-2. VSCodeのインストール
まずはVSCode本体をインストールしましょう。
- Webブラウザで「VSCode」と検索し、公式サイトにアクセスします。(https://code.visualstudio.com/)
- トップページにある「Download for Windows (または macOS)」ボタンを押し、インストーラーをダウンロードします。
- ダウンロードしたファイルを実行し、画面の指示に従ってインストールします。
- 途中で「PATHへの追加」というチェックボックスがありますが、これは必ずオンのままにしておいてください。
2-3. Cコンパイラ (GCC/MinGWなど) のインストール
第1章で、C言語のソースコード(指示書)をコンピュータがわかる機械語に「翻訳」するのがコンパイラの役目だと学びました。
VSCodeはあくまで「エディタ(書く場所)」であり、「コンパイラ(翻訳機)」は内蔵していません。そのため、コンパイラを別途インストールする必要があります。
使用するOSによって、導入するコンパイラが異なります。
🌎 OS別コンパイラの準備
- Windows の場合:MinGW-w64 (MinGW) をインストールするのが一般的です。これはWindows上でC言語の代表的なコンパイラである GCC (ジーシーシー) を使えるようにするツール集です。
- (インストール方法はいくつかありますが、
wingetコマンドやインストーラーを使って導入します。)
- (インストール方法はいくつかありますが、
- macOS の場合:macOSには、標準ではコンパイラが入っていません。しかし、Appleが開発者向けに提供している Command Line Tools (コマンドラインツール) をインストールすればOKです。
- 「ターミナル」アプリを開きます。
xcode-select --installというコマンドを入力して実行します。- 指示に従ってインストールします。 (Cコンパイラである
clangがインストールされます)
✅ 導入の確認 (最重要)
インストールが完了したら、PCを再起動し、VSCodeを開いてください。
VSCodeのメニューから [ターミナル] -> [新しいターミナル] を開きます。
画面下に黒い(または白い)コマンド入力画面(ターミナル)が表示されたら、以下のコマンドを打ち込んでEnterキーを押します。
gcc --version
(macOSの場合は clang --version でも構いません)
gcc (MinGW.org GCC-...) ... や Apple clang version ... のように、バージョン情報が表示されれば、コンパイラの準備は成功です。
うまくいかない場合 (初学者の関門)
gcc コマンドが見つからない、とエラーが出た場合、コンパイラのインストールに失敗しているか、「PATH (パス) が通っていない」状態です。
PATHを通すとは、「OSに、gcc という翻訳機がどこに置いてあるか(どのフォルダにあるか)を教えてあげる」作業です。インストール方法を再確認し、環境変数PATHの設定を見直してください。
2-5. 必須拡張機能のインストール
VSCodeに「C言語開発モード」の能力を与えましょう。
- VSCodeの左側にある、4つの四角形が組み合わさったようなアイコン(拡張機能ビュー)をクリックします。
- 上部にある検索バーに「
C/C++」と入力します。 - Microsoft が提供している C/C++ Extension Pack (または C/C++) が表示されます。
- [Install] ボタンをクリックしてインストールします。
これにより、VSCodeは以下の賢い機能を使えるようになります。
- コード補完:
priと打つだけでprintfの候補を出してくれます。 - シンタックスハイライト:
mainやintなどのキーワードを色付けして見やすくします。 - エラーチェック: 文法が間違っていると、コンパイル前に赤線で教えてくれます。
2-6. VSCodeの基本的な使い方
これで道具が揃いました。VSCodeの基本的な操作を覚えましょう。
1. 作業フォルダ(ワークスペース)を開く
まず、C言語のプログラムファイルをまとめておくための「専用フォルダ」をPCの好きな場所(例:ドキュメント フォルダ内)に作ります。
- 例:
C_Practiceという名前のフォルダを作成
次に、VSCodeを起動し、メニューの [ファイル] -> [フォルダーを開く...] (Open Folder) を選択し、今作った C_Practice フォルダを指定します。
VSCodeの左側(エクスプローラービュー)に、そのフォルダが表示されればOKです。これ以降、ファイルはこのフォルダ内に保存されていきます。
2. ファイルの作成と保存
- エクスプローラービューで、
C_Practiceフォルダ名の横にある「新しいファイル」アイコンをクリックします。 - ファイル名を入力します。C言語のプログラムファイルは、必ず名前の最後を
.c(ドットシー) にする必要があります。- 例:
hello.c
- 例:
3. ターミナルの使い方
先ほどコンパイラの確認でも使いましたが、ターミナルはVSCodeで最もよく使う機能の一つです。
- メニューの [ターミナル] -> [新しいターミナル] で開きます。
- ここから
gccコマンドを使ってコンパイルを実行したり、出来上がったプログラムを実行したりします。 - VSCodeに内蔵されているため、別のウィンドウを開く必要がなく、コードを書きながらすぐにコマンドを実行できるのが強みです。
これで、C言語のコードを書き、コンパイルし、実行するための環境がすべて整いました。
次の章では、いよいよあなたの手で、最初のプログラムを動かしてみましょう。
第3章:最初のプログラム
いよいよ、あなたの手でC言語のプログラムを作成し、動かしてみましょう。プログラミング学習で、最初に作る「画面に文字を表示するプログラム」は、慣習として "Hello, World!" と呼ばれています。
3-1. "Hello, World!" - 画面に文字を表示する
私たちが今回作るプログラムは、コンピュータの画面(ターミナル)に「Hello, World!」という文字列を表示するだけの、とてもシンプルなものです。
3-2. ソースコードの作成と保存
まずは、第2章で準備したVSCodeを開き、作業フォルダ(C_Practiceなど)が開かれていることを確認してください。
- VSCodeの左側にあるエクスプローラー(ファイルのアイコン)で、フォルダ名の横にある「新しいファイル」アイコンをクリックします。
- ファイル名を
hello.cと入力してEnterキーを押します。(.cを忘れないでください) - 中央にエディタ画面が開くので、以下のコードを一字一句間違えずにキーボードで入力してください。(コピー&ペーストは練習になりません!)
#include <stdio.h>
int main(void) {
printf("Hello, World!\n");
return 0;
}
- 入力したら、
Ctrl + Sキー(macOSはCmd + Sキー)を押して、ファイルを保存します。保存すると、VSCodeのタブにあるファイル名の横の「●」印が消えます。
3-3. コンパイルと実行 (ターミナルからの操作)
コード(ソースコード)が書けたら、これを「コンパイル(翻訳)」して「実行」します。VSCodeのターミナルを使います。
- VSCodeのメニューから [ターミナル] -> [新しいターミナル] を開きます。
- ターミナルに、以下のコンパイルコマンドを入力してEnterキーを押します。
gcc hello.c -o hellogcc: 「GCCコンパイラさん、お願いします」という命令です。hello.c: 「このソースコードを翻訳してください」という指定です。-o hello: 「翻訳して出来上がった実行可能ファイルの名前をhelloにしてください」というオプション指定です。- (Windowsの場合、自動的に
hello.exeという名前になります) - (この
-oオプションを省略すると、a.outやa.exeという名前のファイルができます)
- (Windowsの場合、自動的に
- ターミナルに何もエラーメッセージが表示されなければ、コンパイル成功です。VSCodeのエクスプローラーを見ると、
hello(またはhello.exe)というファイルが新しくできているはずです。 - 最後に、出来上がったプログラムを実行します。ターミナルに以下のコマンドを入力します。
./hello- (Windowsの場合、
.\helloまたはhelloだけで実行できることもあります) ./(または.\)は、「今いるこのフォルダ(カレントディレクトリ)にあるhelloというファイルを実行してね」という意味のおまじないです。
- (Windowsの場合、
- 実行すると、ターミナルに次のように表示されるはずです。
Hello, World!
おめでとうございます! これであなたはC言語プログラマーとしての第一歩を踏み出しました。
3-4. プログラムの基本構造
では、先ほど書いたコードが何を意味しているのか、分解して見てみましょう。
#include <stdio.h>
int main(void) {
// ここに処理を書く
return 0;
}
#include <stdio.h>- これは「おまじない」のようなもので、「standard input/output(標準入出力)」の機能(
printfなど)を使いますよ、という宣言です。 - C言語は、基本的な機能(文字の出力など)を「ヘッダファイル」という外部ファイルとして持っています。
#includeは、そのファイルを読み込む(インクルードする)ための命令です。
- これは「おまじない」のようなもので、「standard input/output(標準入出力)」の機能(
int main(void) { ... }main関数(メインかんすう)と呼ばれ、Cプログラムが実行されるときに必ず最初に呼び出される、最も重要な場所です。- プログラムの「玄関口」であり、処理は必ずここからスタートします。
{から}までの波括弧(ブロック)に囲まれた部分が、main関数の本体(実行したい処理)です。
return 0;- プログラムが正常に終了したことを、OS(コンピュータを管理する親玉)に知らせるためのお作法です。「0(ゼロ)を返す(return)」ことは、「問題なく終わりました」という合図になります。
3-5. printf関数 (出力の基本)
printf(プリントエフ)は、C言語で最もよく使う「関数」の一つです。これは「( )の中身を画面に出力(表示)してください」という命令です。
printf("Hello, World!\n");
"Hello, World!""(ダブルクォーテーション)で囲まれた部分が、画面に表示したい「文字列」です。
\n(バックスラッシュ + n)- これは「改行(New Line)」を意味する特殊な文字です。これがなければ、
Hello, World!と表示された直後に、次のターミナルの表示(プロンプト)がくっついて表示されてしまいます。
- これは「改行(New Line)」を意味する特殊な文字です。これがなければ、
;(セミコロン)- C言語では、命令文(文)の終わりには必ずセミコロンを付けるというルールがあります。日本語の「。」(句点)と同じ役割です。これを忘れるとコンパイルエラーになります。
3-6. コメント (プログラムの説明)
プログラムが長くなってくると、「この部分は、何のために書いたんだっけ?」と忘れてしまうことがあります。そのために「メモ」を残す機能がコメントです。
コメントは、コンパイラからは無視されます(機械語に翻訳されません)。あくまで人間が読むためのものです。
C言語のコメントには2種類あります。
一行コメント// から行の終わりまでがコメントになります。
// これは一行コメントです
printf("Hello!\n"); // printfの後ろにも書けます複数行コメント/* と */ で囲まれた範囲が、複数行にわたってコメントになります。
/*
ここから
ここまで全部
コメントです。
*/
int main(void) { ... }VSCodeでは、コメントは通常、緑色などの別の色で表示されるため、コードと区別しやすくなっています。
「なぜこの処理が必要なのか」を自分の言葉で書き残す癖をつけると、後でプログラムを見返したときに非常に役立ちます。
次の章からは、いよいよC言語の本格的な文法である「変数」や「計算」について学んでいきます。
第4章:データと計算
第3章では、決まった文字列を画面に表示しました。しかし、プログラミングの本当の力は、「データを記憶」し、それを「計算(加工)」できる点にあります。
この章では、データを扱うための「変数」と、計算を行うための「演算子」について学びます。
4-1. 変数 (データを入れる箱)
変数(へんすう)とは、数値や文字などのデータ(値)を一時的に入れておくための「名前付きの箱」のようなものです。
なぜ変数が必要なのでしょうか?
例えば、「ユーザーが入力した2つの数値を足し算する」プログラムを考えてみましょう。
- まず、1つ目の数値を受け取って、どこかに覚えておく(箱に入れる)必要があります。
- 次に、2つ目の数値を受け取って、別の箱に入れます。
- 最後に、2つの箱の中身を取り出して足し算します。
このように、データを一時的に保存するために変数が使われます。C言語では、変数を使う前に「こういう名前で、こういう種類のデータを入れる箱を使います」と宣言(せんげん)する必要があります。
int x; // 「int型(整数)」を入れる「x」という名前の箱を準備する
x = 10; // 「x」という箱に「10」という値を入れる(代入)
4-2. データ型 (数値、文字など)
C言語は、変数(箱)に入れるデータの種類を厳密に区別します。これをデータ型(データがた)と呼びます。
なぜなら、コンピュータの内部では、「整数」を記憶する方法と「小数」を記憶する方法は、全く異なるからです。
まずは、最も基本的なデータ型を覚えましょう。
int(イント型)- 整数 (integer) を入れるための型です。(例:
1,100,-50,0)
- 整数 (integer) を入れるための型です。(例:
float(フロート型) /double(ダブル型)- 小数 (浮動小数点数) を入れるための型です。(例:
3.14,-0.05) doubleの方がfloatよりも、より大きな(またはより精密な)小数を扱えます。迷ったらdoubleを使うのが一般的です。
- 小数 (浮動小数点数) を入れるための型です。(例:
char(チャー型 または キャラ型)- 1文字 (character) を入れるための型です。(例:
'A','!','5') - 注意:
'(シングルクォート) で囲みます。"A"(ダブルクォート) は文字列(第11章)となり、意味が異なります。
- 1文字 (character) を入れるための型です。(例:
4-3. 演算子
変数にデータを入れたら、それらを使って計算ができます。計算に使う記号を演算子(えんざんし)と呼びます。
算術演算子 (さんじゅつえんざんし)
数学の計算とほとんど同じです。
| 演算子 | 意味 | 例 |
+ | 足し算 | 5 + 3 (結果: 8) |
- | 引き算 | 5 - 3 (結果: 2) |
* | 掛け算 | 5 * 3 (結果: 15) |
/ | 割り算 | 5 / 3 (結果: 1) ※ |
% | 余り | 5 % 3 (結果: 2) ※ |
※ int (整数) 同士の割り算 / の注意点
C言語では、整数同士の割り算の結果は、必ず整数になります(小数点以下は切り捨て)。
5 / 3は1.666...ではなく1になります。7 / 2は3.5ではなく3になります。
小数点の計算が必要な場合は、5.0 / 3.0 のように double 型で計算する必要があります。
※ % (剰余演算子) の使い道
「余り」を求める計算です。
5 % 3は、5を3で割った余りなので2です。10 % 2は、10を2で割った余りなので0です。- この
%は、「偶数か奇数か(2で割った余りが0か)」を判定するときなどによく使われます。
代入演算子 (だいにゅうえんざんし)
| 演算子 | 意味 | 例 |
= | 右辺の値を左辺の変数に代入する | x = 10; (xに10を入れる) |
+= | 左辺の値と右辺の値を足し、結果を左辺に代入 | x += 5; ( x = x + 5; と同じ) |
-= | 左辺の値から右辺の値を引き、結果を左辺に代入 | x -= 3; ( x = x - 3; と同じ) |
*= | (同様) | x *= 2; ( x = x * 2; と同じ) |
/= | (同様) | x /= 4; ( x = x / 4; と同じ) |
%= | (同様) | x %= 3; ( x = x % 3; と同じ) |
重要: = は「等しい」ではない
プログラミングにおいて、= は数学の「等号(イコール)」とは意味が異なります。
= は、「右側の値を、左側の変数(箱)に入れる」という代入 (Assignment) の命令です。
4-4. printfによる書式付き出力 (変数の値を表示する)
第3章では printf("Hello!\n"); のように決まった文字しか表示しませんでした。
printf の本当の力は、変数の箱の中身を表示できることです。
それには「変換指定子(へんかんしていし)」(または書式指定子)を使います。
| データ型 | 変換指定子 |
int | %d (decimal: 10進数) |
double | %f (floating point) |
char | %c (character) |
使い方
printf("書式", 変数1, 変数2, ...);
printf の最初の "" の中に %d などの「値のプレースホルダー(場所取り)」を置き、"" の後に、その場所に入れたい変数を順番に書きます。
実践:計算プログラム
calc.c という名前で新しいファイルを作成し、以下のコードを書いてコンパイル・実行してみましょう。
#include <stdio.h>
int main(void) {
int a; // int型の変数 a を宣言
int b; // int型の変数 b を宣言
a = 10; // a に 10 を代入
b = 3; // b に 3 を代入
int sum; // 合計を入れる変数 sum を宣言
sum = a + b; // a と b を足した結果を sum に代入
// 変数の値を表示する
printf("a の値は %d です。\n", a);
printf("b の値は %d です。\n", b);
printf("a + b の合計は %d です。\n", sum);
// printfの中で直接計算もできる
printf("%d - %d = %d\n", a, b, a - b);
printf("%d * %d = %d\n", a, b, a * b);
printf("%d / %d = %d (余り %d)\n", a, b, a / b, a % b);
// --- 小数の計算 ---
double pi = 3.14159;
// %f は小数を表示する
printf("円周率は %f です。\n", pi);
return 0;
}
実行結果の例:
a の値は 10 です。
b の値は 3 です。
a + b の合計は 13 です。
10 - 3 = 7
10 * 3 = 30
10 / 3 = 3 (余り 2)
円周率は 3.141590 です。
ポイント
- printf("%d / %d = %d (余り %d)\n", a, b, a / b, a % b);このように、"" の中に %d が4つあれば、カンマの後ろにも変数を4つ、順番通りに並べる必要があります。
これで、データを記憶し、計算し、その結果を表示する基本を学びました。
次の章では、計算結果に応じて「もし〜なら、こうする」という、プログラムの流れを変える方法を学びます。
第5章:プログラムの流れを変える (分岐)
これまでのプログラムは、main関数の上から下へ、書かれた順番通りに一直線に実行されるだけでした。
しかし、実際のプログラムは「もしAだったら、Bをする」「もしCだったら、Dをする」というように、状況に応じて実行する処理を変える必要があります。
例えば、「もし入力された点数が60点以上なら『合格』と表示し、そうでなければ『不合格』と表示する」といった具合です。このような処理の流れを分岐(ぶんき)と呼びます。この章では、分岐処理の基本である if文と switch文を学びます。
5-1. if文 (もし〜なら)
if文は、C言語における最も基本的な分岐構文です。
基本形:
if (条件式) {
// 条件式が正しい(真:しん)場合に、ここの処理が実行される
}
( )の中に条件式を書きます。- 条件式が正しい(真:True)と評価された場合のみ、直後の
{ }(ブロック)の中の処理が実行されます。 - 条件式が正しくない(偽:False)と評価された場合、
{ }の中は無視(スキップ)されます。
5-2. 関係演算子 (かんけいえんざんし)
if文の条件式では、2つの値を比較するために関係演算子を使います。
| 演算子 | 意味 | 例 |
== | 等しい | a == 10 (aが10と等しいか?) |
!= | 等しくない | a != 10 (aが10と等しくないか?) |
> | より大きい | a > 10 (aが10より大きいか?) |
< | より小さい | a < 10 (aが10より小さいか?) |
>= | 以上 | a >= 10 (aが10以上か?) |
<= | 以下 | a <= 10 (aが10以下か?) |
最重要: = と == の違い
a = 10;(=)- 代入です。「aに10を入れなさい」という命令です。
a == 10(==)- 比較です。「aは10と等しいですか?」という質問です。結果は「はい(真)」か「いいえ(偽)」になります。
if (a = 10) と間違えて書いてしまうと、意図しない動作(とコンパイラの警告)の原因になります。if文の中は == と覚えましょう。
5-3. if-else文 (〜ならA、でなければB)
if文に else(エルス:そうでなければ)を組み合わせると、「もし条件が真ならAを実行し、偽ならBを実行する」という処理が書けます。
if (条件式) {
// 条件式が「真」の場合の処理
} else {
// 条件式が「偽」の場合の処理
}
実践:合否判定プログラム
judge.c というファイル名で保存して試してみましょう。
#include <stdio.h>
int main(void) {
int ten = 75; // 点数
printf("あなたの点数は %d 点です。\n", ten);
if (ten >= 60) {
// ten が 60 以上(真)の場合
printf("おめでとうございます!合格です。\n");
} else {
// ten が 60 未満(偽)の場合
printf("残念でした。不合格です。\n");
}
printf("プログラムを終了します。\n");
return 0;
}
tenの値を75から50に変えて実行すると、else側の処理が実行されることを確認してください。
5-4. else if文 (複数の条件分岐)
「60点以上は『合格』、60点未満40点以上は『追試』、それ以外は『不合格』」のように、3つ以上に分岐させたい場合は else if を使います。
if (条件式1) {
// 条件式1が「真」の場合
} else if (条件式2) {
// 条件式1が「偽」で、かつ 条件式2が「真」の場合
} else {
// すべての条件が「偽」だった場合
}
else if はいくつでも追加できます。上から順番に判定されます。
5-5. 論理演算子 (ろんりえんざんし)
「点数が80点以上 かつ 90点以下」や「点数が0点 または 100点」のように、複数の条件を組み合わせたい場合は論理演算子を使います。
| 演算子 | 意味 | 例 |
&& | AND (かつ) | a >= 80 && a <= 90 (aが80以上 かつ 90以下) |
| ` | ` | |
! | NOT (否定) | !(a == 10) (aが10と等しくない。a != 10 と同じ) |
実践:成績判定プログラム
grade.c というファイル名で保存して試してみましょう。
#include <stdio.h>
int main(void) {
int ten = 85;
printf("点数: %d\n", ten);
if (ten >= 90) {
printf("評価: 優\n");
} else if (ten >= 80 && ten < 90) { // 80以上 かつ 90未満
printf("評価: 良\n");
} else if (ten >= 60 && ten < 80) { // 60以上 かつ 80未満
printf("評価: 可\n");
} else { // 60未満
printf("評価: 不可\n");
}
return 0;
}
tenの値をいろいろ変えて、評価が正しく表示されるか確認しましょう。
5-6. switch文 (多くの分岐をスッキリ書く)
if と else if の連続でも分岐は書けますが、「変数の値が 1 のとき、2 のとき、3 のとき…」のように、特定の値と一致するかどうかで分岐させたい場合、switch文を使うとコードがスッキリします。
基本形:
switch (調べる変数) {
case 値1:
// 変数が 値1 と一致した場合の処理
break; // breakを忘れない!
case 値2:
// 変数が 値2 と一致した場合の処理
break;
default:
// どの case にも一致しなかった場合の処理
break;
}
break;が非常に重要です。breakは「switch文をここで終了する」という命令です。もしbreakを書き忘れると、一致したcaseの次のcaseの処理も実行してしまいます(フォールスルーと呼ばれます)。default:は、if文のelseに相当し、どのcaseにも当てはまらなかった場合に実行されます(省略可能です)。
実践:おみくじプログラム
omikuji.c というファイル名で保存して試してみましょう。
#include <stdio.h>
int main(void) {
int kuji = 2; // 1:大吉, 2:中吉, 3:凶
printf("おみくじの結果 (番号:%d)\n", kuji);
switch (kuji) {
case 1:
printf("大吉です!\n");
break; // これがないと中吉も表示される
case 2:
printf("中吉です。\n");
break;
case 3:
printf("凶です…\n");
break;
default:
printf("番号が間違っています。\n");
break;
}
return 0;
}
kujiの値を1や3、5(defaultの処理)に変えてみましょう。case 1:のbreak;をわざと消して(コメントアウトして)実行するとどうなるか、試してみてください。
これで、状況に応じて処理を変える「分岐」ができるようになりました。
次の章では、同じ処理を何度も「繰り返す」方法について学びます
第6章:繰り返し処理
第5章では「もし〜なら」という分岐を学びました。しかし、プログラミングの強力な点は「同じ(あるいは似た)処理を、指定した回数だけ自動で繰り返せる」ことにもあります。
例えば、「1から100までの数字をすべて足し合わせる」場合、1 + 2 + 3 + ... と100行もコードを書くのは現実的ではありません。
このような繰り返し処理(またはループ)を実現するのが、while文、for文、do-while文です。
6-1. while文 (〜の間繰り返す)
while文は、指定した「条件式」が「真(True)」である間、{ } の中の処理を繰り返し実行し続けます。
基本形:
while (条件式) {
// 条件式が「真」の間、ここの処理が実行され続ける
// (ここで条件式に関わる変数を変化させることが多い)
}
- ループに入る前に、まず条件式がチェックされます。
- 条件式が「真」なら
{ }の中を実行し、}に到達すると、再びwhileの条件式に戻ってチェックします。 - 条件式が「偽」になった時点で、ループを終了し、
{ }の次の処理に進みます。
実践:1から5まで数える
count_while.c というファイル名で保存して試してみましょう。
#include <stdio.h>
int main(void) {
int i = 1; // 1. カウンター変数を初期化
while (i <= 5) { // 2.「iが5以下」の間、繰り返す
printf("%d\n", i);
i = i + 1; // 3. i の値を 1 増やす (これを忘れると無限ループ!)
}
printf("ループが終了しました。\n");
return 0;
}
実行結果:
1
2
3
4
5
ループが終了しました。
無限ループに注意!
while文で最も怖いのが無限ループです。もし i = i + 1; の行を書き忘れると、i は永遠に 1 のままです。
i <= 5 という条件は常に「真」になり、プログラムは「1」と表示し続け、終了しなくなります。
VSCodeのターミナルで無限ループに陥ったら、Ctrl + C キーを押してプログラムを強制終了してください。
6-2. for文 (決まった回数繰り返す)
while文は「条件が満たされる間」という漠然とした繰り返しでしたが、for文は「決まった回数だけ繰り返す」処理を書くのに非常に便利です。
for文は、while文で書いた3つの要素(1.初期化, 2.条件式, 3.変化)を1行にまとめて書けます。
基本形:
for (1. 初期化処理; 2. 条件式; 3. 更新処理) {
// 繰り返したい処理
}
- 1. 初期化処理:
for文が始まる最初の一回だけ実行されます。(例:int i = 1) - 2. 条件式: ループの各回が始まる前にチェックされます。(例:
i <= 10) - 3. 更新処理:
{ }の中の処理が実行された後に実行されます。(例:i++)
i = i + 1 は i++ と省略して書くことができます(i-- は i = i - 1 です)。これをインクリメント(デクリメント)演算子と呼び、for文で非常によく使われます。
実践:1から10までの合計を計算する
sum_for.c というファイル名で保存して試してみましょう。
#include <stdio.h>
int main(void) {
int total = 0; // 合計を保存する変数
int i; // カウンター変数
// i が 1 から 10 まで、1ずつ増えながらループする
for (i = 1; i <= 10; i++) {
total = total + i; // total に i の値を足していく
printf("%d 回目: total = %d\n", i, total);
}
printf("1から10までの合計は %d です。\n", total);
return 0;
}
実行結果:
1 回目: total = 1
2 回目: total = 3
...
10 回目: total = 55
1から10までの合計は 55 です。
while文より、ループ専用の変数の管理がスッキリしているのがわかります。
6-3. do-while文 (最低1回は実行する)
while文と for文は、最初に条件式をチェックするため、一度も実行されない可能性があります。
do-while文は、先に { } の処理を最低1回実行し、その後に条件式をチェックします。
基本形:
do {
// 実行したい処理 (最低1回は実行される)
} while (条件式); // 最後にセミコロン(;)が必要!
実践:メニューの表示
menu_do.c というファイル名で保存して試してみましょう。
#include <stdio.h>
int main(void) {
int selected;
do {
printf("--- メニュー ---\n");
printf("1: はい\n");
printf("2: いいえ\n");
printf("0: 終了\n");
printf("番号を入力してください: ");
// (次の章で学ぶ scanf のダミーです)
// ここでは selected に 0 を代入してループを終わらせます
selected = 0;
printf("%d\n", selected); // 入力したフリ
} while (selected != 0); // 0 が入力されるまで繰り返す
printf("終了しました。\n");
return 0;
}
- この例では、まずメニューが1回表示されます。その後、
while (selected != 0)がチェックされ、selectedが0なのでループを抜けて終了します。 while文で書くと、メニュー表示の前にselectedの値をチェックする必要があり、少し不自然なコードになります。
6-4. breakとcontinue (ループの制御)
ループの途中で、強制的に流れを変えたい場合があります。
break(ブレーク)switch文でも登場しましたが、ループの中で使うと「今いるループをただちに中断し、ループから脱出する」という命令になります。
continue(コンティニュー)- 「今回のループ処理はここまでにして、次の回(次の
i)に進む」という命令です。{ }の残りの処理をスキップします。
- 「今回のループ処理はここまでにして、次の回(次の
実践:break と continue の例
break_continue.c というファイル名で保存して試してみましょう。
#include <stdio.h>
int main(void) {
int i;
printf("--- break の例 (5 を見つけたら終了) ---\n");
for (i = 1; i <= 10; i++) {
if (i == 5) {
printf("5 を見つけました!\n");
break; // forループをここで脱出
}
printf("%d\n", i);
}
printf("\n--- continue の例 (偶数をスキップ) ---\n");
for (i = 1; i <= 10; i++) {
if (i % 2 == 0) { // もし i が 2 で割り切れたら(偶数なら)
continue; // printf をスキップして次の i に進む
}
printf("%d\n", i);
}
return 0;
}
実行結果:
--- break の例 (5 を見つけたら終了) ---
1
2
3
4
5 を見つけました!
--- continue の例 (偶数をスキップ) ---
1
3
5
7
9
これで、分岐と繰り返しという、プログラムの基本的な「流れの制御」をマスターしました。
次の章では、printf(出力)の相方である scanf を使って、キーボードからデータを「入力」してもらう方法を学びます。
第7章:入力
これまでのプログラムは、int ten = 75; のように、あらかじめコードの中にデータを書き込んでいました。これでは、点数を変えるたびにプログラムを書き直してコンパイルし直さなければならず、不便です。
この章では、プログラムの実行中に、キーボードからデータを受け取る方法を学びます。そのための標準的な関数が scanf(スキャンエフ)です。
7-1. scanf関数 (キーボードから値を受け取る)
scanf は、printf(出力)の相方となる「入力」のための関数です。ユーザーがキーボードから入力した値を、指定した変数(の箱)に格納します。
基本形:
scanf("変換指定子", &変数名);
"変換指定子":printfの時と同じものを使います。%d(整数)、%f(小数)、%c(1文字)など、これから入力されるデータの型を指定します。&変数名: ここが最重要です。scanfでは、変数名の前に必ず&(アンド、アンパサンド)という記号を付けます。
実践:名前(年齢)を入力してもらう
input_age.c というファイル名で保存して試してみましょう。
#include <stdio.h>
int main(void) {
int age; // 年齢を入れる変数を宣言(中身は空っぽ)
printf("あなたの年齢を入力してください: ");
// キーボードからの「整数(%d)」入力を待ち、
// その値を age 変数の「場所(&age)」に入れる
scanf("%d", &age);
printf("あなたは %d 歳ですね。\n", age);
// 入力された age を使って計算もできる
printf("%d 歳のあなたは、10年後 %d 歳です。\n", age, age + 10);
return 0;
}
実行(コンパイルして実行)の手順:
gcc input_age.c -o input_ageでコンパイルします。./input_ageで実行します。- ターミナルに「
あなたの年齢を入力してください:」と表示され、プログラムが一時停止します(カーソルが点滅します)。 - キーボードで
30などの半角数字を入力し、Enterキーを押します。 scanfがその値を変数ageに格納し、プログラムが再開します。
実行結果の例:
あなたの年齢を入力してください: 30 <-- (ここでキーボード入力)
あなたは 30 歳ですね。
30 歳のあなたは、10年後 40 歳です。
7-2. scanfを使う際の注意点
scanf は非常に便利ですが、C言語初学者が最もつまずきやすい関数のひとつです。以下の点に細心の注意を払ってください。
1. & (アドレス演算子)を忘れない
scanf("%d", age); のように & を忘れると、プログラムは正しく動作しません(多くの場合、実行時にクラッシュします)。
なぜ & が必要なのか?
printf は、変数 age の中身(値)だけわかれば、それを画面に表示できました。
しかし scanf は、入力された値を age という変数(箱)に書き込みに行かなければなりません。
そのためには、age の「中身」ではなく、age という箱がコンピュータのメモリ(記憶場所)の「どこにあるのか(住所:アドレス)」という情報が必要です。
& は「アドレス演算子」と呼ばれ、&age で「変数 age のメモリアドレス(住所)」という意味になります。
scanf は、age の住所(&age)を教えてもらうことで、その住所を頼りに入力された値を書き込みに行けるのです。
(この「アドレス」の概念は、第10章「ポインタ」で詳しく学びます。今は「scanf には & が必要」と覚えてください。)
2. double 型の入力は %lf
printf で double 型を表示するときは %f を使いました。
しかし、scanf で double 型の値を受け取るときは、%lf(エルエフ)を使います。
double height;
printf("身長(m)を入力: ");
scanf("%lf", &height); // %f ではなく %lf
printf("あなたの身長は %f m です。\n", height); // 表示は %f
3. 複数の値を同時に入力する
scanf は、複数の値を一度に入力できます。
int a, b;
printf("2つの整数をスペースで区切って入力してください: ");
scanf("%d %d", &a, &b); // %d と %d の間にスペースを入れる
printf("%d + %d = %d\n", a, b, a + b);
実行例:
2つの整数をスペースで区切って入力してください: 10 20 <-- (10 [スペース] 20 と入力)
10 + 20 = 30
scanf は、入力された 10 20 をスペースで区切り、最初の 10 を a に、次の 20 を b に代入します。
4. scanf はエラーに弱い
scanf("%d", &age); で整数を待っているときに、もしユーザーが abc のような文字を入力すると、scanf は変換に失敗し、プログラムは意図しない動作をすることがあります。
scanf は、「必ず指定した通りのデータが入力される」という信頼がある場合にのみ使うべき、少し扱いの難しい関数であることも知っておいてください。
これで、C言語の基本的な「出力」「計算」「分岐」「繰り返し」「入力」を学びました。これらを組み合わせれば、簡単な電卓やゲームなど、多くのプログラムが作れます。
第3部では、プログラムをより整理し、より複雑なデータを扱うための「関数」「配列」「ポインタ」といった、C言語の中核となる概念を学んでいきます。
第3部:データ構造と関数
ここからは第3部です。これまでに学んだ基本文法(分岐、繰り返し、入出力)を組み合わせて、より複雑で「整理された」プログラムを作るための技術を学びます。
第8章:関数 (処理の部品化)
main関数の中に何百行もコードを書いていると、プログラムは非常に読みにくくなり、修正も難しくなります。
関数(かんすう)とは、特定の処理(機能)をひとまとまりにして名前を付けた「部品」のことです。
私たちはすでに printf(画面に出力する部品)や scanf(キーボードから入力する部品)といった、C言語が標準で用意してくれている関数を使ってきました。
この章では、自分オリジナルの関数を作る方法を学びます。
8-1. 関数とは?
関数を作ることを「関数を定義(ていぎ)する」と言います。
関数を使うことを「関数を呼び出す」と言います。
関数化のメリット:
- 整理整頓:
main関数がスッキリします。「メニューを表示する」「合計を計算する」「結果を表示する」といった処理をそれぞれ関数に分ければ、main関数はそれらの関数を呼び出すだけになり、全体の流れが把握しやすくなります。 - 再利用性: 同じ処理をプログラムのあちこちで書く必要がなくなります。その処理を関数として1回定義しておけば、あとは必要な場所でその関数名を呼び出すだけです。
- メンテナンス性: もし「合計計算」の方法を変更したくなった場合、関数化していれば、その関数の中身を1箇所修正するだけで済みます。
8-2. 関数の作り方 (定義) と使い方 (呼び出し)
関数は main関数と同じような形で定義します。
関数の定義(基本形):
戻り値の型 関数名(引数の型 引数名) {
// この関数が実行する処理
return 戻り値; // 戻り値がない場合は return; または省略
}
関数の呼び出し:
関数名(引数);
実践:挨拶する関数
まずは、引数(ひきすう)も戻り値(もどりち)もない、最もシンプルな関数を作ってみましょう。
function_greet.c というファイル名で保存します。
#include <stdio.h>
// 1. 関数の定義
// "greet" という名前の関数を定義する
// void: 戻り値なし
// (void): 引数なし
void greet(void) {
printf("こんにちは!\n");
printf("C言語の学習、順調ですか?\n");
}
int main(void) {
printf("プログラムを開始します。\n");
// 2. 関数の呼び出し
greet(); // greet関数に処理をジャンプさせる
printf("プログラムを終了します。\n");
return 0;
}
実行結果:
プログラムを開始します。
こんにちは!
C言語の学習、順調ですか?
プログラムを終了します。
処理の流れ:
mainが始まり、「プログラムを開始します。」と表示。greet();でgreet関数が呼び出される。- 処理が
greet関数の中にジャンプし、「こんにちは!」「〜順調ですか?」と表示。 greet関数が終了し、呼び出し元のmainに戻る。mainの続きである「プログラムを終了します。」と表示。
プロトタイプ宣言(予告)について
C言語は、上から下にコードを読みます。main関数より後に greet関数を定義した場合、main関数が greet(); を読んだ時点で「greetなんて知らないよ」とコンパイラに怒られます。
それを防ぐため、mainより前(#include の直後など)に、
void greet(void);
のように、関数の「予告」だけを先に書いておく方法があります。これをプロトタイプ宣言と呼びます。本体はmainの後ろに書いてもOKになります。
8-3. 引数 (関数に情報を渡す)
greet関数は便利ですが、いつも同じ挨拶しかできません。「こんにちは、〇〇さん!」のように、呼び出すたびに違う動作をさせたい場合、引数(ひきすう)を使います。
引数は、関数を呼び出す側から、関数側へ情報を渡すための仕組みです。
実践:年齢を表示する関数
function_age.c というファイル名で保存します。
#include <stdio.h>
// age という「int型の引数」を1つ受け取る関数
void print_age(int age) {
printf("あなたは %d 歳ですね。\n", age);
}
int main(void) {
int my_age = 20;
// 引数として 20 を渡して関数を呼び出す
print_age(my_age);
// 引数として 35 を渡して関数を呼び出す
print_age(35);
return 0;
}
実行結果:
あなたは 20 歳ですね。
あなたは 35 歳ですね。
main側の my_age(値は20)が print_age側の age にコピーされ、関数が実行されます。print_age(35) の時は、age に 35 が入ります。
8-4. 戻り値 (関数から結果を受け取る)
引数が「関数への入力」なら、戻り値(もどりち)は「関数からの出力(結果)」です。
関数で計算した結果を、呼び出し元(mainなど)で使いたい場合に使います。
実践:2つの数を足し算する関数
function_add.c というファイル名で保存します。
#include <stdio.h>
// int型の戻り値を返す関数 "add"
// int型の引数 a と b を2つ受け取る
int add(int a, int b) {
int sum = a + b;
return sum; // 計算結果 sum を呼び出し元に返す
}
int main(void) {
int x = 10;
int y = 20;
// add関数を呼び出し、その「戻り値」を
// result 変数に代入する
int result = add(x, y);
printf("%d + %d = %d\n", x, y, result);
// 戻り値は直接 printf などにも使える
printf("5 + 3 = %d\n", add(5, 3));
return 0;
}
実行結果:
10 + 20 = 30
5 + 3 = 8
add(x, y) が呼び出されると、add関数が実行され、return sum; によって 30 という値が返されます。呼び出し元の int result = add(x, y); は、int result = 30; と同じ意味になります。
8-5. 変数のスコープ (使える範囲)
関数化すると、変数が使える範囲(スコープ)を意識する必要があります。
- ローカル変数 (Local Variables)
main関数やadd関数など、関数の中で宣言された変数のことです。- ローカル変数は、その関数の中(
{ }の中)でしか使えません。 add関数のsumをmain関数から使おうとしたり、main関数のresultをadd関数から使おうとすると、エラーになります。- 関数が終わると、そのローカル変数は自動的に消滅します。
- 引数(
aやb)もローカル変数の一種です。
- グローバル変数 (Global Variables)
- すべての関数の外(通常は
#includeの直後)で宣言された変数のことです。 - グローバル変数は、プログラム中のどこの関数からでも読み書きできます。
- 注意: グローバル変数は便利に見えますが、多用は禁物です。「どの関数がいつ値を変更したか」が追いにくくなり、バグの原因になります。基本は「ローカル変数」と「引数・戻り値」でやり取りするのが良い設計です。
- すべての関数の外(通常は
処理を「部品化」する関数を学びました。
次の章では、同じ種類のデータをたくさん扱うための「配列」について学びます。
第9章:配列 (たくさんのデータを扱う)
第8章までは、int x; や int score; のように、1つのデータを1つの変数(箱)に入れてきました。
しかし、もし「クラスの生徒40人分のテストの点数」を扱いたい場合、score1, score2, score3 ... score40 と40個も変数を用意するのは非常に面倒です。
そこで登場するのが配列(はいれつ)です。
9-1. 配列とは?
配列とは、同じデータ型の複数の値を、連続したメモリ領域にまとめて格納するための仕組みです。
- アパートやロッカーに例えられます。
scoresという名前のアパートがあり、その「0号室」「1号室」「2号室」... に、それぞれ点数データが入っているイメージです。 - 40人分の点数(
int型)を、scoresという1つの名前でまとめて管理できます。
9-2. 配列の宣言と使い方
配列の宣言(準備):
データ型 配列名[要素数];
- 例:
int scores[5];int型のデータを 5個 格納できるscoresという名前の配列を準備する、という意味です。- これにより、
scores[0]からscores[4]までの5個の箱が用意されます。
配列の「添字 (index)」- 超重要ルール
配列の各要素(各部屋)にアクセスするには、[ ] の中に添字(そえじ、インデックス)と呼ばれる番号を指定します。
ここでC言語の非常に重要なルールがあります。
配列の添字(番号)は、必ず 0 から始まります。
int scores[5]; と宣言した場合:
- 1番目の要素は
scores[0] - 2番目の要素は
scores[1] - 3番目の要素は
scores[2] - 4番目の要素は
scores[3] - 5番目(最後)の要素は
scores[4]
scores[5] という要素は存在しません。もし scores[5] = 100; のように、宣言した範囲外(この場合は0〜4の範囲外)にアクセスしようとすると、プログラムは重大なエラー(バグ)を引き起こします(配列の範囲外アクセス)。
配列の値の代入と初期化
後から代入する
int scores[5]; // まず5個の箱を準備
scores[0] = 80; // 0番目(1人目)に 80 を代入
scores[1] = 92;
// ...
scores[4] = 88;宣言と同時に初期化する
{ } (波括弧) を使って、宣言と同時に値を設定できます。
int scores[5] = {80, 92, 75, 68, 88};9-3. 配列とfor文
配列は、第6章で学んだ for文と非常に相性が良いです。
for文のカウンター変数 i(0, 1, 2, ... と変化する)を、そのまま配列の添字 [i] に使えるからです。
実践:配列のすべての要素を表示する
array_print.c というファイル名で保存して試してみましょう。
#include <stdio.h>
int main(void) {
int scores[5] = {80, 92, 75, 68, 88};
int i;
// i が 0 から 4 まで変化するループ (i < 5 は i <= 4 と同じ)
for (i = 0; i < 5; i++) {
// i が 0 の時: scores[0] を表示
// i が 1 の時: scores[1] を表示
// ...
printf("scores[%d] の値は %d です。\n", i, scores[i]);
}
return 0;
}
実行結果:
scores[0] の値は 80 です。
scores[1] の値は 92 です。
scores[2] の値は 75 です。
scores[3] の値は 68 です。
scores[4] の値は 88 です。
実践:配列の合計と平均を計算する
array_average.c というファイル名で保存して試してみましょう。
#include <stdio.h>
int main(void) {
int scores[5] = {80, 92, 75, 68, 88};
int i;
int total = 0; // 合計を入れる変数
double average; // 平均は小数になる可能性があるので double
// 1. 合計を計算するループ
for (i = 0; i < 5; i++) {
total = total + scores[i]; // total += scores[i]; とも書ける
}
// 2. 平均を計算する
// total も 5 も int なので、(double) でキャストして小数計算にする
average = (double)total / 5.0;
printf("合計点: %d\n", total);
printf("平均点: %f\n", average);
return 0;
}
実行結果:
合計点: 403
平均点: 80.600000
9-4. 2次元配列 (表のようなデータ)
配列をさらに拡張して、[行][列] のように添字を2つ持つ2次元配列も作れます。これは、エクセルのような「表(テーブル)」や「座標」を扱うのに便利です。
- 宣言:
int grid[3][4];(3行4列の int 型配列) - アクセス:
grid[0][0] = 10;(0行0列目に10を代入)
(まずは1次元配列 scores[i] をしっかりマスターすることが重要です)
9-5. 関数と配列
配列を関数の引数として渡すこともできます。
「配列の全要素を合計する」といった処理を関数化するのに役立ちます。
注意点:
関数に配列を渡すとき、関数側は「その配列に何個の要素が入っているか」を知ることができません。
そのため、配列本体と一緒に、配列の「要素数」も別の引数として渡す必要があります。
実践:配列を表示する関数
function_array.c というファイル名で保存して試してみましょう。
#include <stdio.h>
// 配列 arr[] と、その要素数 size を受け取る関数
void print_array(int arr[], int size) {
int i;
printf("配列の内容: ");
for (i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void) {
int scores[5] = {80, 92, 75, 68, 88};
int ages[3] = {20, 30, 40};
// scores 配列 (要素数5) を関数に渡す
print_array(scores, 5);
// ages 配列 (要素数3) を関数に渡す
print_array(ages, 3);
return 0;
}
mainからprint_arrayを呼び出すとき、配列名scoresを渡します。scores[5]やscores[]とは書きません。print_array関数は、配列(int arr[])と要素数(int size)を受け取り、for文でsize回ループを回します。
実行結果:
配列の内容: 80 92 75 68 88
配列の内容: 20 30 40
同じ種類のデータをまとめて扱う「配列」を学びました。
次の章では、C言語の最大の山場であり、最も強力な機能である「ポインタ」について学びます。第7章の scanf で登場した & の謎が、ついに解けます。
第10章:ポインタ (C言語の核心)
この章は、C言語の学習における最大の山場であり、同時にC言語が持つ強力な機能の源泉である「ポインタ」について学びます。
第7章で scanf を使ったときに登場した & 記号の謎が、ここでついに解明されます。ここを乗り越えれば、C言語の理解が飛躍的に深まります。
10-1. ポインタとは? (メモリアドレス)
コンピュータのメモリ(記憶装置)は、データを格納する小さな箱が連なった、巨大な棚のようなものです。そして、その一つ一つの箱には、場所を特定するための「住所(番地)」が割り当てられています。
この住所のことをメモリアドレス(または単にアドレス)と呼びます。
int x = 10;と宣言すると、OSはメモリの棚から空いている箱(例えば「100番地」)を見つけ、「x」という名前を付け、そこに10という値を入れます。
これまでの変数は、箱に付いた「名前(x)」で中身(10)を扱ってきました。
ポインタとは、この「中身(10)」を格納する変数ではなく、「住所(100番地)」そのものを格納するための、特殊な変数です。
例え:
- 普通の変数 (
x): 「10」という値(人)そのもの。- ポインタ変数 (
p): 「xさん家(100番地)」という住所が書かれたメモ。
10-2. アドレス演算子 (&) と間接参照演算子 (*)
ポインタを扱うには、2つの新しい演算子が必要です。& はすでに出会っていますね。
&アドレス演算子 (Address-of operator)- 変数名の前に付けると、その変数の「中身」ではなく「メモリアドレス(住所)」を取得します。
&xは「変数xのアドレス(住所)」という意味になります。
*間接参照演算子 (Dereference operator)- ポインタ変数の前に付けると、そのポインタが指す「住所」ではなく、その「住所の先にある中身(値)」にアクセスします。
pがxの住所を持っているとき、*pは「pが指す先の値」、つまりxの中身である10を意味します。
10-3. ポインタの宣言と使い方
ポインタ変数は、どのデータ型のアドレスを格納するかを指定して宣言します。
ポインタの宣言:
データ型 *ポインタ変数名;
int *p;*p(pが指す先)がint型である、という意味です。- これで
pは「int型変数のアドレスを格納するためのポインタ変数」となります。
double *d_ptr;double型のアドレスを格納するポインタ変数。
実践:ポインタを使ってみる
pointer_basic.c というファイル名で保存して試してみましょう。
#include <stdio.h>
int main(void) {
int x = 10; // 1. 普通のint型変数 x を用意 (中身は 10)
int *p; // 2. int型へのポインタ変数 p を用意 (中身はまだ空)
// 3. p に x の「アドレス」を代入する
p = &x;
printf("--- 値の確認 ---\n");
printf("変数 x の中身: %d\n", x);
printf("変数 p の中身 (xのアドレス): %p\n", p); // アドレスは %p で表示
printf("p が指す先の値 (*p): %d\n", *p); // p の住所にある中身
printf("\n--- ポインタ経由で値を変更 ---\n");
// 4. p が指す先の中身(つまり x の中身)を 20 に変更
*p = 20;
printf("変更後の x の中身: %d\n", x); // x の値が変わっている!
return 0;
}
実行結果の例: (アドレスの値は実行ごとに変わります)
--- 値の確認 ---
変数 x の中身: 10
変数 p の中身 (xのアドレス): 0x7ffeeabc1234
p が指す先の値 (*p): 10
--- ポインタ経由で値を変更 ---
変更後の x の中身: 20
*p = 20; と書くだけで、x そのものに触れていないのに x の中身が 20 に変わりました。
これが「住所(アドレス)を知っている」ことの強みです。
10-4. ポインタと関数 (値渡しと参照渡し)
ここがポインタの最も重要な使い道です。
C言語の基本:「値渡し (Call by Value)」
第8章で学びましたが、C言語の関数は基本的に値渡しです。引数として渡されるのは、変数の中身のコピーです。
void change(int num) {
num = 100; // 受け取った「コピー」を 100 に変える
}
int main(void) {
int a = 10;
change(a);
printf("%d\n", a); // 結果は 10 のまま
return 0;
}
mainの a (10) が change関数の num にコピーされます。change関数が num = 100 と変更しても、それはコピー(num)を変更しただけで、mainにある原本(a)は一切変わりません。
ポインタを使った「参照渡し (Call by Reference)」
もし、関数が main の原本(a)を直接書き換えられるようにするには、どうすればよいでしょうか?
...そうです。a の「中身(コピー)」ではなく、「住所(アドレス)」を関数に渡せばよいのです。
scanf の謎がここで解けます。
scanf("%d", &age);
私たちは scanf に age の中身ではなく、age のアドレス(&age)を渡していました。
なぜなら、scanf は main で宣言された age という変数の原本に、キーボードから入力された値を書き込む必要があったからです。
実践:変数の値を入れ替える swap 関数
swap.c というファイル名で保存して試してみましょう。
#include <stdio.h>
// 2つの「int型変数のアドレス」を受け取る関数
void swap(int *p_a, int *p_b) {
int temp;
// *p_a は a の中身
// *p_b は b の中身
temp = *p_a; // temp に a の中身(10) を退避
*p_a = *p_b; // a の中身を b の中身(20) で上書き
*p_b = temp; // b の中身を temp(10) で上書き
// これで a と b の中身が入れ替わった
}
int main(void) {
int a = 10;
int b = 20;
printf("変更前: a = %d, b = %d\n", a, b);
// swap関数に a の「アドレス」と b の「アドレス」を渡す
swap(&a, &b);
printf("変更後: a = %d, b = %d\n", a, b);
return 0;
}
実行結果:
変更前: a = 10, b = 20
変更後: a = 20, b = 10
mainの a と b の値が、swap関数によって見事に入れ替わりました。
10-5. ポインタと配列 (密接な関係)
第9章で、関数に配列を渡すとき、print_array(scores, 5); のように & を付けずに配列名をそのまま渡しました。
実は、C言語において、配列名は、その配列の先頭要素のアドレスを指すポインタとして扱われます。
つまり、scores という名前は、&scores[0](配列の0番目の要素のアドレス)とほぼ同じ意味を持つのです。
int scores[5];と宣言したとき...scores==&scores[0]です。scores + 1==&scores[1]です。(ポインタの足し算)*(scores + 1)==scores[1]です。
第9章の void print_array(int arr[], int size) という関数の引数 int arr[] は、実は int *arr(int型へのポインタ)と全く同じ意味です。コンパイラは int arr[] を int *arr として解釈します。
だから、配列を関数に渡すことは、実は配列の先頭アドレス(ポインタ)を渡していたのです。これが、scanf 以外で私たちが無意識に使っていた「参照渡し」でした。
ポインタはC言語の強力な武器ですが、アドレスを直接操作するため、「存在しない住所」を指したり(NULLポインタ)、「他人の住所」を書き換えたり(バグ)する危険もはらんでいます。
しかし、この仕組みを理解したことで、あなたはC言語プログラマーとして大きく成長しました。
次の章では、文字の「配列」である「文字列」について、ポインタとの関係も踏まえながら学びます。
第11章:文字列 (文字の並び)
第9章では int 型の「配列」を学びました。では、「文字」の配列はどうなるでしょうか。
'H', 'e', 'l', 'l', 'o' のように、文字が並んだものを文字列(もじれつ)と呼びます。
C言語には、PythonやJavaのような String 型という専用のデータ型はありません。
C言語では、文字列をchar 型の配列として扱います。
11-1. C言語での文字列の扱い (文字の配列)
char name[10]; のように宣言すれば、10文字分の配列が準備できます。しかし、printf などの関数は、name 配列に Hello という5文字が入っているとして、配列のどこまでが有効な文字列で、どこからがゴミデータなのかを、どうやって判断するのでしょうか?
ヌル終端文字 (\0) - C言語の最重要ルール
C言語では、文字列の「終わり」を示すために、\0 という特別な文字(ヌル終端文字またはヌル文字と呼びます)を配列の末尾に格納するというルールがあります。
"Hello"という文字列は、私たちが5文字に見えても、C言語のメモリ上では'H','e','l','l','o','\0'という 6文字として格納されます。printf("%s", ...)などの文字列を扱う関数は、配列の先頭から1文字ずつ処理していき、\0を見つけたら「ここで文字列は終わり」と判断して処理を終了します。
注意:
'A'と"A"の違い
'A'(シングルクォート):char型の「1文字」を表す。"A"(ダブルクォート):'A'と'\0'の2文字が格納された「文字列」を表す。
文字列の宣言(初期化)
文字列(char配列)を宣言するには、主に2つの方法があります。
// 方法1: ダブルクォートで初期化 (最も簡単)
// "Hello" (5文字) + \0 (1文字) = 計6文字分の配列が自動で確保される
char str1[] = "Hello";
// 方法2: 1文字ずつ初期化 ( \0 を忘れないこと!)
// 方法1と全く同じ意味になるが、面倒
char str2[] = {'H', 'e', 'l', 'l', 'o', '\0'};
// 良くない例
// "Hello" (5文字) を格納するには \0 を含めて 6文字 が必要
// char bad_str[5] = "Hello"; // ← 領域が足りず、バグの原因になる!
文字列の出力 (%s)
文字列を printf で表示するには、変換指定子 %s (string) を使います。
char message[] = "C Language";
printf("%s は楽しいです。\n", message);
// 実行結果: C Language は楽しいです。
printf は message の先頭から \0 が見つかるまでを表示します。
11-2. 文字列の操作 (標準ライブラリ関数 string.h)
int 型の変数なら a = b; や if (a == b) のように計算できました。
しかし、文字列(配列)は、そのようには扱えません。
char str1[10] = "abc";
char str2[10];
// str2 = str1; // ← エラー! 配列は代入できない
// if (str1 == str2) { ... } // ← エラー! (正しくはアドレスの比較になってしまう)
配列(文字列)をコピーしたり、比較したりするには、専用の関数を使う必要があります。
これらの関数を使うには、#include <stdio.h> に加えて、新しく #include <string.h> をプログラムの先頭に追加する必要があります。
よく使う3つの関数を覚えましょう。
strlen(str)(String Length: 文字列の長さ)strの文字数を返します(\0は数えません)。"Hello"なら5が返されます。
strcpy(dest, src)(String Copy: 文字列のコピー)srcの文字列(\0を含む)をdestにコピーします。dest = srcの代わりです。destにはsrcが入るのに十分な配列サイズが必要です。
strcmp(str1, str2)(String Compare: 文字列の比較)str1とstr2の内容を比較します。str1 == str2の代わりです。- 戻り値が 0 (ゼロ) なら、2つの文字列は等しい という意味です。
実践:文字列操作
string_test.c というファイル名で保存して試してみましょう。
#include <stdio.h>
#include <string.h> // string.h をインクルードする
int main(void) {
char str1[] = "Hello";
char str2[20]; // コピー先は十分なサイズを確保
int len;
// 1. 長さ (strlen)
len = strlen(str1);
printf("str1 の長さ (\\0を含まない): %d\n", len);
// 2. コピー (strcpy)
strcpy(str2, str1); // str1 の内容を str2 にコピー
printf("コピーされた str2: %s\n", str2);
// 3. 比較 (strcmp)
if (strcmp(str1, str2) == 0) {
printf("str1 と str2 は等しいです。\n");
}
// 別の文字列と比較
if (strcmp(str1, "World") != 0) {
printf("str1 と \"World\" は等しくありません。\n");
}
return 0;
}
実行結果:
str1 の長さ (\0を含まない): 5
コピーされた str2: Hello
str1 と str2 は等しいです。
str1 と "World" は等しくありません。
11-3. 文字列とポインタ
第10章で学んだポインタは、文字列と非常に密接な関係があります。
char str[] = "Hello"; と宣言するときの str という配列名は、&str[0](str の先頭アドレス)を指すポインタのように振る舞うのでした。
そのため、文字列は char *(char へのポインタ)を使って表現することもできます。
char *p_str = "World";
これは char str[] = "World"; と似ていますが、決定的な違いがあります。
char str[] = "Hello";(配列)- 新しく書き換え可能な配列
strを用意し、そこに"Hello"をコピーします。 str[0] = 'J';(Jello に) → 変更できます。
- 新しく書き換え可能な配列
char *p_str = "World";(ポインタ)p_strはポインタ変数です。"World"という文字列は、プログラムの書き換え禁止(読み取り専用)領域に格納されます。p_strは、その読み取り専用領域の先頭アドレスを指します。p_str[0] = 'J';(Jorld に) → エラー(またはクラッシュ)します!
関数に文字列を渡すとき、引数が char str[] と書かれていても char *str と書かれていても、どちらも「文字列(の先頭アドレス)を受け取る」という意味で、ほぼ同じように扱われます。
これで、C言語の基本データ型(整数、小数、文字)、そしてそれらの集合(配列、文字列)、さらにメモリを直接扱うポインタを学びました。
次の章では、int や double、char などを組み合わせて、あなただけのオリジナルのデータ型を作る「構造体」について学びます。
第12章:構造体 (オリジナルのデータ型)
これまでの章で、int(整数)や char(文字)、そしてそれらの集まりである int scores[5](配列)や char name[](文字列)を学んできました。
しかし、これだけでは管理しにくいデータがあります。例えば、「一人の学生」の情報を考えてみてください。
- 学籍番号 (
int) - 名前 (
charの配列) - 平均成績 (
double)
これらはデータ型がバラバラですが、「一人の学生」という1つのカタマリとして扱いたいものです。int の配列や char の配列だけでは、これらをまとめて管理できません。
この問題を解決するのが構造体(こうぞうたい、struct)です。
12-1. 構造体とは?
構造体とは、int や char[]、double などの複数の異なるデータ型を、1つの新しい「オリジナルのデータ型」としてまとめる機能です。
- 例え: 構造体は「学生証のテンプレート(設計図)」のようなものです。
- 「学生証」というテンプレートには、「学籍番号欄」「氏名欄」「成績欄」があります。
- このテンプレート(
struct)を使って、「Aさんの学生証」「Bさんの学生証」という実体(変数)を作ることができます。
12-2. 構造体の定義と使い方
構造体を使うには、2つのステップが必要です。
- 定義(設計図を作る):
structを使って、どのようなデータ(メンバと呼びます)を持つ型なのかを定義します。 - 宣言(実体を作る): 定義した型を使って、変数(実体)を宣言します。
1. 構造体の定義
通常、main関数の外(#include の直後)に書きます。
// "Student" という名前の構造体(設計図)を定義
struct Student {
int id; // 学籍番号 (メンバ)
char name[50]; // 名前 (メンバ)
double gpa; // 平均成績 (メンバ)
}; // ← 最後にセミコロン(;)を忘れない!
- これで
struct Studentという「新しいデータ型」が誕生しました。 - この時点では、まだ変数は作られておらず、メモリも消費していません。
2. 構造体の宣言とメンバへのアクセス
main関数の中で、int 型の変数を宣言するのと同じように宣言します。
int main(void) {
// struct Student 型の変数 stu1 を宣言(実体を作る)
struct Student stu1;
// ...
}
構造体のメンバ(中の各要素)にアクセスするには、<b>.</b>(ドット演算子)を使います。
変数名.メンバ名 のように書きます。
実践:学生データの管理
struct_basic.c というファイル名で保存して試してみましょう。
#include <stdio.h>
#include <string.h> // strcpy を使うため
// 1. 構造体(テンプレート)の定義
struct Student {
int id;
char name[50];
double gpa;
};
int main(void) {
// 2. 構造体変数の宣言(実体 stu1 を作成)
struct Student stu1;
// 3. メンバに値を代入 (ドット演算子 .)
stu1.id = 101;
// 注意: nameは配列なので = で代入できず、strcpy を使う (第11章)
strcpy(stu1.name, "Taro Yamada");
stu1.gpa = 3.5;
// 4. メンバの値を表示
printf("--- 学生情報 ---\n");
printf("学籍番号: %d\n", stu1.id);
printf("氏名 : %s\n", stu1.name);
printf("GPA : %f\n", stu1.gpa);
return 0;
}
実行結果:
--- 学生情報 ---
学籍番号: 101
氏名 : Taro Yamada
GPA : 3.500000
12-3. 構造体とポインタ
構造体は、int などに比べてサイズが大きくなりがちです。
これを関数に引数として渡すとき、丸ごとコピーする「値渡し」(第10章)では非効率です。
そこで、構造体もポインタ(アドレス)を使って「参照渡し」するのが一般的です。
struct Student stu1;という実体があるとき、struct Student *p_stu;という「構造体へのポインタ」を宣言できます。p_stu = &stu1;のように、stu1のアドレスをポインタに代入します。
アロー演算子 ->
ポインタ p_stu を経由して、メンバ(id や name)にアクセスするにはどうすればよいでしょうか?
第10章の方法を使うと、(*p_stu).id と書けます(p_stu を先に * で参照してから . でメンバにアクセスする)。
しかし、この書き方は面倒なため、C言語には専用のアロー演算子(矢印)-> が用意されています。
ポインタ変数->メンバ名
(*p_stu).id と p_stu->id は、全く同じ意味です。
実践:関数で構造体を扱う
struct_pointer.c というファイル名で保存して試してみましょう。
#include <stdio.h>
#include <string.h>
struct Student {
int id;
char name[50];
};
// 構造体の「アドレス(ポインタ)」を受け取る関数
// (コピーではなく参照渡しなので効率が良い)
void print_student(struct Student *s) {
printf("--- 関数による表示 ---\n");
// ポインタ経由なのでアロー演算子「->」を使う
printf("ID: %d\n", s->id);
printf("Name: %s\n", s->name);
}
int main(void) {
struct Student stu1;
stu1.id = 102;
strcpy(stu1.name, "Hana Suzuki");
// 関数に stu1 の「アドレス(&stu1)」を渡す
print_student(&stu1);
return 0;
}
実行結果:
--- 関数による表示 ---
ID: 102
Name: Hana Suzuki
これで、C言語の基本的な文法とデータ構造(配列、ポインタ、文字列、構造体)のすべてを学び終えました。これらを組み合わせれば、非常に複雑なデータも自由に扱えるようになります。
第4部からは、VSCodeの機能をさらに活用して、バグを見つける「デバッグ」や、プログラムを分割する方法など、より実践的な開発テクニックを学んでいきます。
第4部:VSCodeでの実践的な開発
おめでとうございます! これでC言語の主要な文法はすべて学び終えました。
しかし、実際にプログラムを作り始めると、必ず「バグ(Bug=虫)」に遭遇します。バグとは、プログラムが思った通りに動かない原因となる「間違い」や「欠陥」のことです。
第4部では、VSCodeの強力な機能を使い、バグを効率的に発見・修正する「デバッグ」技術と、大規模なプログラムを管理する方法を学びます。
第13章:デバッグ (バグ退治)
プログラムがコンパイルは通るのに、実行するとおかしな結果になる、あるいは途中で強制終了してしまう。このような時、printf をコードのあちこちに挿入して変数の値を確認するのも一つの手ですが、非常に非効率です。
そこで使うのがデバッガです。
13-1. デバッグとは?
デバッグとは、バグを発見し、その原因を特定して修正する作業のことです。
デバッガは、その作業を支援するための専門ツールです。デバッガを使うと、printf を書かなくても、プログラムを途中で一時停止させ、その時点での各変数の「中身」を覗き見したり、1行ずつ慎重に実行させたりすることができます。
13-2. VSCodeデバッガのセットアップ
VSCodeには、このデバッガ機能が組み込まれています(実際には裏側で gdb や lldb といった専門のデバッガと連携しています)。
デバッガを使うには、まず「どのプログラムをデバッグするか」を設定する launch.json というファイルが必要です。
- デバッグビューを開く:VSCodeの左側にある、虫に再生ボタンが付いたようなアイコン(実行とデバッグ)をクリックします。
- launch.json を作成:「launch.json ファイルを作成します」というリンクをクリックします。(もし「実行とデバッグ」ボタンがあればそれを押します)
- 構成を選択:環境を選択するポップアップが表示されたら、C++ (GDB/LLDB) を選びます。(C言語ですがこれでOKです)次に、構成(gcc や clang など)を選ぶ画面が出たら、自分が使っているコンパイラ(例:gcc.exe build and debug active file)を選びます。
- launch.json の確認:.vscode というフォルダが自動的に作られ、その中に launch.json が生成されます。多くの場合、自動生成された設定のままで動作します。
- このファイルは「F5キーを押したら、
programに書かれたファイル(例:${fileDirname}/${fileBasenameNoExtension}=今開いているファイル名の.exe版)をデバッグ実行する」という設定をVSCodeに教えています。
- このファイルは「F5キーを押したら、
- デバッグ用のコンパイル:デバッガが変数の名前などを正しく認識できるよう、コンパイル時に「デバッグ情報」を埋め込むオプション -g を付けてコンパイルし直す必要があります。
gcc -g hello.c -o hello(VSCodeのtasks.jsonを設定して、ビルド時に自動で-gが付くようにすることもできます)
13-3. ブレークポイントの設定
デバッガの最も基本的な機能がブレークポイント(Break = 中断)です。
これは「プログラムを実行するが、この行まで来たら一時停止してほしい」という「しおり」のようなものです。
使い方:
- VSCodeのエディタで、ソースコードの行番号の左側をクリックします。
- 赤い丸(●)が表示されます。これがブレークポイントです。
- もう一度クリックすると解除できます。
13-4. ステップ実行 (1行ずつ実行)
ブレークポイントを設定したら、いよいよデバッグ実行を開始します。
- デバッグしたい
.cファイルを開いた状態で、F5キーを押します(またはデバッグビューの緑の再生ボタンを押します)。 - プログラムが実行され、設定したブレークポイントの行で実行が一時停止します。その行は黄色くハイライトされます(まだ実行されていません)。
- 画面上部に、デバッグ用の操作パネル(再生/一時停止、ステップオーバーなど)が表示されます。
ここでよく使うのが「ステップ実行」です。
- ステップオーバー (F10):ハイライトされている行を1行だけ実行し、次の行で再び一時停止します。もしその行が関数呼び出しでも、関数の中には入らずに関数を実行し終えて戻ってきます。(基本はこれを使います)
- ステップイン (F11):もしハイライトされている行が関数呼び出しの場合、その関数の中にジャンプして、関数の先頭で一時停止します。
- ステップアウト (Shift+F11):ステップインで関数の中に入った後、その関数の残りをすべて実行し、関数を呼び出した元の場所に戻って一時停止します。
13-5. 変数の監視 (値の変化を見る)
プログラムを一時停止させている間、VSCodeのデバッグビュー(左側のパネル)が真価を発揮します。
- [変数] ペイン:現在停止しているスコープ(main関数など)で有効なローカル変数が一覧表示され、その現在の値がすべて表示されます。
- [ウォッチ] ペイン:自分が特に監視したい変数(例えばグローバル変数や p->id など)を登録しておくと、その値がステップ実行ごとにどう変わっていくかを追いかけられます。
💻 デバッグの流れ(例)
第6章の「1から10までの合計を計算する」sum_for.c でバグが起きたとします。
gcc -g sum_for.c -o sum_forでコンパイル。launch.jsonを設定。forループの中のtotal = total + i;の行にブレークポイント(●)を置きます。- F5キーでデバッグ実行。
total = total + i;の行で停止します。- 左側の[変数]ペインで
totalが0、iが1になっていることを確認します。 - ステップオーバー (F10) を1回押します。
for文のi++に移り、もう一度F10を押すと、再びtotal = total + i;に戻ってきます。- [変数]ペインで
totalが1に、iが2に更新されていることを確認します。 - このようにF10を押し続け、
iやtotalの値が自分の「期待通り」に変化しているか、それとも「期待と違う」動きをしているか(バグ)を特定します。
printfデバッグには戻れないほど強力な機能です。バグが起きたら、まずデバッガを起動する癖をつけましょう。
次の章では、printf や scanf 以外の、ファイルにデータを書き込んだり、読み込んだりする方法を学びます。
第14章:ファイル操作
これまでのプログラムは、scanf で入力したデータも、計算結果も、プログラムを終了すればすべて消えてしまいました。
アプリケーション(例:メモ帳、Excel)がデータを永続的に保存できるように、C言語にもファイルにデータを書き込んだり、ファイルからデータを読み込んだりする機能が備わっています。
printf が画面(標準出力)に書き込む関数、scanf がキーボード(標準入力)から読み込む関数だったのに対し、この章で学ぶ関数は、PC上の特定のファイルに対して読み書きを行います。
14-1. ファイル入出力の基本 (ファイルポインタ)
C言語でファイルを扱うには、ファイルポインタという特殊なポインタ変数を使います。
これは、プログラムとファイルの間の「データの通り道(ストリーム)」を管理するための変数です。
FILE という(stdio.h で定義されている)大文字の型を使います。
FILE *fp; // ファイルポインタ変数を宣言
ファイル操作の基本的な流れは、以下の3ステップです。
fopen(ファイルを開く): これから操作したいファイルを「モード(読み書きなど)」を指定して開きます。成功すると、FILE *型のポインタが返されます。(失敗するとNULLが返されます)- 読み書き:
fprintfやfscanfなどの関数を使って、開いたファイルに対して読み書きを行います。 fclose(ファイルを閉じる): 操作が終わったら、必ずファイルを閉じます。
ファイルを開くモード
fopen を使うとき、ファイルを「何のために」開くかを指定するモードが重要です。
| モード | 意味 | ファイルが存在しない場合 |
"w" | 書き込み (Write) | 新規作成する |
| ※存在する場合、中身は消去(上書き)される | ||
"r" | 読み込み (Read) | エラー (NULL が返る) |
"a" | 追記 (Append) | 新規作成する |
| ※存在する場合、末尾に追加していく |
14-2. ファイルへの書き込み (fprintf)
fprintf は、printf のファイル版です。使い方は printf とほぼ同じですが、最初の引数に書き込み先のファイルポインタを指定します。
fprintf(ファイルポインタ, "書式", 変数...);
実践:ファイルにテキストを書き込む
write_file.c というファイル名で保存して試してみましょう。
#include <stdio.h>
int main(void) {
FILE *fp; // ファイルポインタ
// 1. "output.txt" を「書き込みモード("w")」で開く
fp = fopen("output.txt", "w");
// 2. ファイルが開けたかチェック (重要!)
if (fp == NULL) {
printf("ファイルを開けませんでした。\n");
return 1; // 異常終了
}
// 3. ファイルに書き込む (printf ではなく fprintf)
fprintf(fp, "これは fprintf によるテストです。\n");
fprintf(fp, "C言語でファイルに書き込みました。\n");
int score = 100;
fprintf(fp, "点数: %d\n", score);
// 4. ファイルを必ず閉じる
fclose(fp);
printf("ファイル 'output.txt' に書き込みました。\n");
return 0;
}
実行と確認:
gcc write_file.c -o write_fileでコンパイルします。./write_fileで実行します。- ターミナルに「ファイル 'output.txt' に書き込みました。」と表示されます。
- VSCodeのエクスプローラー(左側のファイル一覧)を見てください。
output.txtという新しいファイルが作成されているはずです。output.txtをクリックして開くと、プログラムでfprintfした内容が書き込まれていることが確認できます。
14-3. ファイルからの読み込み (fscanf)
fscanf は、scanf のファイル版です。scanf がキーボードからの入力を待つのに対し、fscanf は指定したファイルからデータを読み込もうとします。
fscanf(ファイルポインタ, "書式", &変数...);
fscanf は、ファイルの終端(End Of File)に達するか、読み込みに失敗すると EOF という特別な値を返します。これを利用して、ファイルの最後まで読み込むループを書くことができます。
実践:ファイルからテキストを読み込む
(※先に output.txt を作成する write_file プログラムを実行しておいてください)
read_file.c というファイル名で保存して試してみましょう。
#include <stdio.h>
int main(void) {
FILE *fp;
int score;
char str1[100], str2[100]; // 読み込む文字列用の配列
// 1. "output.txt" を「読み込みモード("r")」で開く
fp = fopen("output.txt", "r");
if (fp == NULL) {
printf("ファイル 'output.txt' を開けません。\n");
return 1;
}
printf("--- 'output.txt' の内容 ---\n");
// 2. ファイルから読み込む (fscanf)
// 3行分の読み込み (今回は書き込んだ内容がわかっているため)
// (注意: 本来は fscanf よりも fgets の方が安全ですが、
// ここでは fscanf の動作確認をします)
// 1行目の "点数:" と 2行目の "100" を読み込む
// (output.txt の3行目 "点数: 100" から読み込む)
if (fscanf(fp, "%s %d", str1, &score) != EOF) {
printf("読み込んだ文字列: %s\n", str1);
printf("読み込んだ点数 : %d\n", score);
} else {
printf("ファイルの読み込みに失敗しました。\n");
}
// (注: `output.txt` の1行目、2行目を読むのは
// `fgets` という関数の方が適しています)
// 3. ファイルを閉じる
fclose(fp);
return 0;
}
(注: fscanf はスペースや改行を区切り文字として読み飛ばすため、output.txt の1行目、2行目を正確に読み込むのは少し厄介です。ここでは3行目の「点数: 100」を読み込む例を示しました。)
実行結果の例:
(※output.txt が write_file.c の例通りに作られていれば)
--- 'output.txt' の内容 ---
(ファイルの内容を読み飛ばして...)
読み込んだ文字列: 点数:
読み込んだ点数 : 100
(fscanf の挙動は複雑なため、単純な数値データ以外の読み込みには、1行ずつ読み込む fgets と sscanf を組み合わせるのが一般的です)
ファイル操作を覚えたことで、プログラムの実行結果を保存し、次回の実行時にそれを読み込む、といった本格的なアプリケーションの基礎ができました。
次の章では、main.c が巨大になるのを防ぐため、プログラムの機能を複数のファイルに分割して管理する方法を学びます。
第15章:分割コンパイル
これまでは、すべてのコードを main.c のような1つのファイルに記述してきました。しかし、プログラムが数千行、数万行と大規模になってくると、1つのファイルでは管理しきれません。
- 読みたい関数を探すのが大変
- 複数人で同時に作業(分担)するのが難しい
- 少しの修正でも、毎回プログラム全体をコンパイルし直す必要があり、時間がかかる
この問題を解決するのが分割コンパイルです。
分割コンパイルとは、プログラムの機能(例えば「学生データの管理」や「計算処理」など)ごとに、ソースコードを複数の .c ファイルに分割して管理する手法です。
15-1. プログラムを複数のファイルに分ける理由
プログラムを分割すると、以下のようなメリットがあります。
- モジュール化(部品化):関連する関数群を1つのファイル(部品)にまとめることで、プログラムの見通しが良くなります。
- 分業:Aさんは「入力処理」、Bさんは「計算処理」のように、ファイル単位で作業を分担できます。
- コンパイル時間の短縮:変更があったファイルだけを再コンパイルし、最後にそれらを「リンク(合体)」させるだけで良くなるため、大規模なプロジェクトほど開発時間が短縮されます。
15-2. ヘッダファイル (.h) とソースファイル (.c)
分割コンパイルでは、ファイルを2種類に分けて管理するのが一般的です。
- ソースファイル (
.cファイル)- 関数の具体的な処理内容(本体)を記述するファイル。
- (例:
math_util.c)
- ヘッダファイル (
.hファイル)- その
.cファイルが外部(main.cなど)に提供する「関数のリスト(プロトタイプ宣言)」や「構造体の定義」を記述するファイル。 - (例:
math_util.h)
- その
main.c は、具体的な処理が書かれた math_util.c を直接見るのではなく、関数のリストである math_util.h を #include します。
main.c は math_util.h を見て「add という関数があるな」と知るだけでコンパイルでき、math_util.c もコンパイルされます。
最後に、リンカというプログラムが、main.c の「add を呼び出したい」という部分と、math_util.c の「add の本体」を合体させて、1つの実行可能ファイル(.exe)を完成させます。
実践:計算関数を別ファイルに分離する
プロジェクトの構成:
C_Practice/
├── main.c (メインの処理)
├── math_util.c (計算処理の本体)
└── math_util.h (計算処理のリスト)
1. math_util.h (ヘッダファイル) の作成
math_util.h という名前で新しいファイルを作成します。
#ifndef MATH_UTIL_H // 1. 多重インクルード防止のおまじない
#define MATH_UTIL_H // (ifndef = if not defined)
// 外部に公開する「add」関数のプロトタイプ宣言(予告)
int add(int a, int b);
#endif // MATH_UTIL_H
#ifndef ... #define ... #endifは、このヘッダファイルが誤って2回以上includeされるのを防ぐための決まり文句(インクルードガード)です。
2. math_util.c (ソースファイル) の作成
math_util.c という名前で新しいファイルを作成します。
#include "math_util.h" // 自分のヘッダファイルをインクルードする
// add 関数の「本体(定義)」
int add(int a, int b) {
return a + b;
}
// (もし外部に公開しない関数(このファイル内だけで使う関数)
// があれば、それもここに書く)
- 重要: 自分で作ったヘッダファイルをインクルードする時は、
#include <stdio.h>の< >(システム標準)ではなく、#include "math_util.h"のように"(ダブルクォート)を使います。
3. main.c (メインファイル) の編集
main.c を以下のように編集します。
#include <stdio.h>
#include "math_util.h" // 自分で作った「関数のリスト」を読み込む
int main(void) {
int x = 10;
int y = 5;
// add() は math_util.c に書かれているが、
// math_util.h をインクルードしたので、使える
int result = add(x, y);
printf("%d + %d = %d\n", x, y, result);
return 0;
}
main.cはadd関数の本体がどこにあるか知りませんが、math_util.hのおかげでコンパイルが通ります。
コンパイルとリンクの方法
最後に、これら2つの .c ファイルをコンパイルし、1つの実行可能ファイルにリンク(合体)させます。
VSCodeのターミナルで、以下のコマンドを実行します。
gcc main.c math_util.c -o my_app
gccは、main.cとmath_util.cをそれぞれコンパイルし、- それらを合体(リンク)させて、
-o my_appという名前の実行可能ファイルmy_app(またはmy_app.exe) を生成します。
./my_app を実行すれば、正しく「10 + 5 = 15」と表示されるはずです。
15-3. VSCodeでのビルド設定 (Tasks.json)
毎回 gcc main.c math_util.c ... とたくさんのファイル名を手打ちするのは面倒です。
VSCodeには Tasks (タスク) という機能があり、この「コンパイルとリンク」のコマンドを自動化できます。
- VSCodeで
Shift + Ctrl + B(またはCmd + Shift + B)を押します。 - 「タスクの構成...」といった選択肢が出るので選びます。
- 「
tasks.jsonをテンプレートから作成」 - 「Others (その他)」を選びます。
.vscode/tasks.jsonというファイルが生成されるので、commandの部分を、先ほどのコンパイルコマンドに書き換えます。
(この設定は launch.json(デバッグ設定)とも連携しており、F5キーを押したときに自動でこのタスク(ビルド)を実行させることもできます)
分割コンパイルをマスターしたことで、あなたは個人の学習者のレベルを超え、チーム開発や大規模プロジェクトに対応できる「エンジニア」としての第一歩を踏み出しました。
次の第5部では、ここまでの知識を総動員して、簡単なアプリケーション(プロジェクト)を作成してみましょう。
第5部:応用プロジェクト
C言語の文法とVSCodeでの開発技術をすべて学び終えました。最後の章として、これまでの知識をすべて使って、2つの簡単なアプリケーションを作成してみましょう。
第16章:簡単なアプリケーションを作ってみよう
ここでは、仕様(何を作るか)を決め、設計(どう作るか)を考え、コーディング(実際に書く)する、というプログラム開発の一連の流れを体験します。
16-1. プロジェクト例1:簡単な電卓
1. 仕様(作るもの)
- 2つの数値と、1つの演算子(
+,-,*,/)をキーボードから入力してもらいます。 - 入力された演算子に応じて、2つの数値の計算結果を表示します。
switch文、if文、そして「関数」の知識を使って作成します。
2. 設計(どう作るか)
main関数は、ユーザーからの入力(scanf)と結果の表示(printf)を担当します。- 実際の計算処理は、
mainから分離し、専用の関数に任せましょう。- 例:
calculate(double a, double b, char op)
- 例:
- 0で割る場合 (
/を選び、2つ目の数値に0を入力)は、エラーメッセージを表示するようにします(if文)。 - どの演算子かによって処理を分ける部分には
switch文が適しています。
3. コーディング(calculator.c)
calculator.c という名前でファイルを作成し、以下のコードを参考に、ご自身でも考えながら打ち込んでみてください。
#include <stdio.h>
// 計算を実行する関数
// double型で値を返し、引数として2つの数値と1文字の演算子を受け取る
double calculate(double num1, double num2, char op) {
double result;
switch (op) {
case '+':
result = num1 + num2;
break;
case '-':
result = num1 - num2;
break;
case '*':
result = num1 * num2;
break;
case '/':
// 0除算(ゼロで割る)のチェック
if (num2 == 0.0) {
printf("エラー: 0で割ることはできません。\n");
result = 0.0; // エラーとして0を返す (本来はもっと良い方法がある)
} else {
result = num1 / num2;
}
break;
default:
// +, -, *, / 以外が入力された場合
printf("エラー: 無効な演算子です。\n");
result = 0.0;
break;
}
return result; // 計算結果を main に返す
}
int main(void) {
double num1, num2;
char operation;
double answer;
printf("--- 簡単な電卓 ---\n");
printf("計算式を入力してください (例: 10 + 20) \n");
// 10, +, 20 のようにスペース区切りで入力してもらう
// %lf (double), %c (char), %lf (double)
scanf("%lf %c %lf", &num1, &operation, &num2);
// 設計通り、計算処理を関数に任せる
answer = calculate(num1, num2, operation);
// エラーの場合 (0除算など) は、calculate関数内でメッセージが出る
// 正常な計算結果を表示する
// (%f ではなく %.2f とすると小数点以下2桁まで表示できる)
printf("結果: %.2f %c %.2f = %.2f\n", num1, operation, num2, answer);
return 0;
}
4. コンパイルと実行
gcc calculator.c -o calculatorでコンパイルします。./calculatorで実行します。- ターミナルに「計算式を入力してください」と表示されたら、
3.5 * 2のように入力し、Enterキーを押します。
実行結果の例:
--- 簡単な電卓 ---
計算式を入力してください (例: 10 + 20)
3.5 * 2
結果: 3.50 * 2.00 = 7.00
実行結果の例 (0除算):
--- 簡単な電卓 ---
計算式を入力してください (例: 10 + 20)
10 / 0
エラー: 0で割ることはできません。
結果: 10.00 / 0.00 = 0.00
16-2. プロジェクト例2:簡単なアドレス帳
1. 仕様(作るもの)
- 複数の「連絡先(名前と電話番号)」を管理します。
- 連絡先データを「構造体」を使って定義します。
- 連絡先データを「ファイル」に保存し、プログラム起動時に読み込みます。
2. 設計(どう作るか)
- 構造体:
struct Contact { char name[50]; char phone[20]; };という構造体を定義します。 - 配列: 構造体の配列
struct Contact book[100];のような形で、複数の連絡先をメモリに保持します。 - ファイル操作:
save_data(): 現在の配列の内容をaddress.txtに書き込みます(fopen("w"),fprintf)。load_data(): プログラム起動時、address.txtからデータを読み込み、配列に復元します(fopen("r"),fscanf)。
- メインループ: ユーザーに「1:一覧表示」「2:新規追加」「0:保存して終了」を選んでもらいます(
do-whileループとswitch文)。
3. コーディングのヒント
これは、第12章(構造体)、第14章(ファイル操作)、第9章(配列)、第6章(ループ)の知識を総動員する、優れた総合演習になります。
#include <stdio.h>
#include <string.h>
// 連絡先の構造体
struct Contact {
char name[50];
char phone[20];
};
// (ここに save_data関数 や load_data関数 を定義する)
// (main関数の中でメニューを表示し、入力を受け付ける)
/*
main() {
struct Contact book[100]; // 連絡先を100人分保持する配列
int count = 0; // 現在の登録件数
// 1. load_data() でファイルからデータを読み込み、count を更新
do {
// 2. メニュー表示 (1:一覧, 2:追加, 0:保存して終了)
switch(選択) {
case 1: // 一覧表示 (book配列をforで回す)
case 2: // 新規追加 (book[count] にscanfで名前と電話番号を追加し、count++)
case 0: // save_data(book, count) でファイルに保存して終了
}
} while (選択 != 0);
}
*/
このプロジェクト例2は、ぜひご自身の力で挑戦してみてください。バグが出たら、第13章で学んだデバッガ(F5, ブレークポイント)が強力な助けになるはずです。
テキストの終わりに
このテキストを通じて、C言語の基本的な文法から、VSCodeを使った実践的な開発技術までを学びました。
C言語は、ポインタやメモリ管理など、学ぶのが難しい部分もありますが、それらはすべて「コンピュータがプログラムをどう動かしているか」という根本的な仕組みに直結しています。この知識は、C言語以外の言語(PythonやJavaなど)を学ぶ際にも、必ずあなたの助けとなります。
これで「C言語入門」は終了です。素晴らしいプログラミングの世界へようこそ!
これでテキストの目次案はすべて完了しました。
このテキスト全体を通して、特に説明を加えたい部分や、変更したい箇所はありますか?
セイ・コンサルティング・グループの新人エンジニア研修のメニューへのリンク
投稿者プロフィール
- 代表取締役
-
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。
最新の投稿
山崎講師2025年11月18日C言語入門的テキスト
山崎講師2025年11月18日WACCって何?エンジニアが知っておくべき「会社の体温計」を徹底解説
山崎講師2025年11月18日「p値」だけで満足していませんか?統計学の真実が見える「効果量・信頼区間・検定力」を完全攻略
山崎講師2025年11月16日FP2級テキスト