第8章:配列と文字列

この章で学ぶこと

  • 配列の基本
  • 配列の宣言と初期化
  • 配列とループ
  • 多次元配列
  • 文字列の基礎
  • 文字列操作関数
  • ヌル終端の重要性

---

8.1 配列とは

同じ型のデータをまとめる

配列(Array)は、同じ型のデータを連続して格納するデータ構造です。

例えば、10人の学生のテスト点数を管理したい場合:

配列を使わない場合:

int score1 = 85;
int score2 = 90;
int score3 = 78;
// ... 10個の変数が必要

配列を使う場合:

int scores[10];  // 10個の整数を格納

配列のイメージ

配列は、番号付きの引き出しの列です:

scores: [85][90][78][92][88][75][95][82][87][91]
index:    0   1   2   3   4   5   6   7   8   9

  • 各「引き出し」を要素(Element)と呼びます
  • 番号をインデックス(Index)と呼びます
  • インデックスは0から始まります(重要!)

---

8.2 配列の宣言と初期化

宣言

型 配列名[要素数];

int numbers[5];      // 5個のint
double prices[10];   // 10個のdouble
char letters[26];    // 26個のchar

初期化

宣言と同時に値を設定できます:

int numbers[5] = {10, 20, 30, 40, 50};

要素数を省略すると、初期値の数から自動で決まります:

int numbers[] = {10, 20, 30, 40, 50};  // 要素数5

一部だけ初期化すると、残りは0になります:

int numbers[5] = {10, 20};  // {10, 20, 0, 0, 0}

すべて0で初期化:

int numbers[5] = {0};  // {0, 0, 0, 0, 0}

要素へのアクセス

配列名[インデックス]

int scores[5] = {85, 90, 78, 92, 88};

printf("%d\n", scores[0]);  // 85(最初の要素)
printf("%d\n", scores[2]);  // 78(3番目の要素)
printf("%d\n", scores[4]);  // 88(最後の要素)

scores[1] = 95;  // 2番目の要素を変更

配列の範囲外アクセス

インデックスが範囲外だと、未定義動作になります。

int arr[5] = {1, 2, 3, 4, 5};

printf("%d\n", arr[5]);   // 危険!範囲外
printf("%d\n", arr[-1]);  // 危険!範囲外
arr[10] = 100;            // 危険!メモリ破壊の可能性

コンパイルエラーにならないことが多いので、自分で注意する必要があります

---

8.3 配列とループ

配列の全要素を処理

配列とループは相性抜群です。

#include <stdio.h>

int main(void)
{
    int scores[5] = {85, 90, 78, 92, 88};

    // 全要素を表示
    for (int i = 0; i < 5; i++) {
        printf("scores[%d] = %d\n", i, scores[i]);
    }

    return 0;
}

出力:

scores[0] = 85
scores[1] = 90
scores[2] = 78
scores[3] = 92
scores[4] = 88

合計と平均

#include <stdio.h>

int main(void)
{
    int scores[5] = {85, 90, 78, 92, 88};
    int sum = 0;

    for (int i = 0; i < 5; i++) {
        sum += scores[i];
    }

    double average = (double)sum / 5;

    printf("Sum: %d\n", sum);
    printf("Average: %.1f\n", average);

    return 0;
}

出力:

Sum: 433
Average: 86.6

最大値・最小値を見つける

#include <stdio.h>

int main(void)
{
    int scores[5] = {85, 90, 78, 92, 88};
    int max = scores[0];
    int min = scores[0];

    for (int i = 1; i < 5; i++) {
        if (scores[i] > max) {
            max = scores[i];
        }
        if (scores[i] < min) {
            min = scores[i];
        }
    }

    printf("Max: %d\n", max);
    printf("Min: %d\n", min);

    return 0;
}

出力:

Max: 92
Min: 78

配列サイズの計算

配列のサイズを計算で求めることができます:

int arr[] = {1, 2, 3, 4, 5, 6, 7};
int size = sizeof(arr) / sizeof(arr[0]);
printf("Size: %d\n", size);  // Size: 7

  • sizeof(arr): 配列全体のバイト数
  • sizeof(arr[0]): 1要素のバイト数
  • 割り算すると要素数がわかる

---

8.4 配列を関数に渡す

配列を引数に

void print_array(int arr[], int size)
{
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main(void)
{
    int numbers[] = {1, 2, 3, 4, 5};
    print_array(numbers, 5);
    return 0;
}

配列は参照渡し

通常の変数は値渡しですが、配列は参照渡しのように動作します。関数内で配列を変更すると、元の配列も変わります。

void double_values(int arr[], int size)
{
    for (int i = 0; i < size; i++) {
        arr[i] *= 2;  // 元の配列が変更される
    }
}

int main(void)
{
    int numbers[] = {1, 2, 3, 4, 5};

    printf("Before: ");
    for (int i = 0; i < 5; i++) printf("%d ", numbers[i]);
    printf("\n");

    double_values(numbers, 5);

    printf("After: ");
    for (int i = 0; i < 5; i++) printf("%d ", numbers[i]);
    printf("\n");

    return 0;
}

出力:

Before: 1 2 3 4 5
After: 2 4 6 8 10

---

8.5 多次元配列

2次元配列

表形式のデータには2次元配列を使います。

int matrix[3][4];  // 3行4列の配列

イメージ:

        列0  列1  列2  列3
行0  [  0    1    2    3  ]
行1  [  4    5    6    7  ]
行2  [  8    9   10   11  ]

初期化

int matrix[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

アクセス

matrix[0][0]  // 1(1行目1列目)
matrix[1][2]  // 7(2行目3列目)
matrix[2][3]  // 12(3行目4列目)

2次元配列のループ

#include <stdio.h>

int main(void)
{
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%3d", matrix[i][j]);
        }
        printf("\n");
    }

    return 0;
}

出力:

  1  2  3  4
  5  6  7  8
  9 10 11 12

---

8.6 文字列の基礎

文字列とは

C言語には「文字列型」がありません。代わりに、char型の配列で文字列を表現します。

char str[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

文字列の終わりは \0(ヌル文字) で示します。これをヌル終端と呼びます。

文字列リテラル

ダブルクォートで囲むと、自動的にヌル文字が追加されます:

char str[] = "Hello";  // {'H', 'e', 'l', 'l', 'o', '\0'}

"Hello" は5文字ですが、配列のサイズは6(ヌル文字を含む)です。

文字列の表示

#include <stdio.h>

int main(void)
{
    char greeting[] = "Hello, World!";
    printf("%s\n", greeting);
    return 0;
}

%s は文字列用のフォーマット指定子です。

文字列のサイズに注意

char str[5] = "Hello";  // エラー!ヌル文字の分が足りない
char str[6] = "Hello";  // OK
char str[] = "Hello";   // OK(自動で6になる)

---

8.7 ヌル終端の重要性

なぜヌル終端が必要か

C言語の文字列関数は、\0 を見つけるまで処理を続けます。

char str[] = "Hello";
printf("%s\n", str);

printf は、メモリを順番に読んで \0 が見つかるまで表示します。

ヌル終端がないと...

char str[5] = {'H', 'e', 'l', 'l', 'o'};  // ヌル終端なし
printf("%s\n", str);  // 未定義動作!ゴミが表示される可能性

ヌル文字がないと、printf はメモリの他の部分まで読み続けてしまいます。

文字列の長さ

文字列の長さは、ヌル文字を含まない文字数です。

#include <stdio.h>
#include <string.h>

int main(void)
{
    char str[] = "Hello";
    printf("Length: %zu\n", strlen(str));  // 5
    printf("Size: %zu\n", sizeof(str));    // 6(ヌル文字含む)
    return 0;
}

---

8.8 文字列操作

strlen - 文字列の長さ

#include <string.h>

char str[] = "Hello";
size_t len = strlen(str);  // 5

strcpy - 文字列のコピー

#include <string.h>

char src[] = "Hello";
char dest[10];

strcpy(dest, src);  // srcをdestにコピー
printf("%s\n", dest);  // "Hello"

注意: dest に十分なサイズが必要です。サイズチェックは行われません。

strncpy - 安全なコピー

char src[] = "Hello, World!";
char dest[6];

strncpy(dest, src, 5);
dest[5] = '\0';  // 手動でヌル終端
printf("%s\n", dest);  // "Hello"

strcmp - 文字列の比較

#include <string.h>

char str1[] = "Apple";
char str2[] = "Banana";

int result = strcmp(str1, str2);
if (result < 0)
    printf("str1 < str2\n");   // 辞書順で str1 が先
else if (result > 0)
    printf("str1 > str2\n");
else
    printf("str1 == str2\n");

注意: == では文字列を比較できません。

if (str1 == str2)  // これはアドレスの比較!

strcat - 文字列の連結

#include <string.h>

char str1[20] = "Hello, ";
char str2[] = "World!";

strcat(str1, str2);
printf("%s\n", str1);  // "Hello, World!"

注意: str1 に十分なサイズが必要です。

---

8.9 文字列の入力

scanf での入力

char name[50];
printf("Enter your name: ");
scanf("%s", name);  // スペースまで読む
printf("Hello, %s!\n", name);

注意: scanf("%s", ...) はスペースで区切られます。

Enter your name: John Smith
Hello, John!  // "Smith" は読み込まれない

fgets での入力

行全体を読むには fgets を使います。

#include <stdio.h>

int main(void)
{
    char line[100];

    printf("Enter a line: ");
    fgets(line, sizeof(line), stdin);

    printf("You entered: %s", line);
    return 0;
}

fgets は改行文字も含めて読み込みます。

---

8.10 実践:文字列関数を自作

ft_strlen - 文字列の長さ

int ft_strlen(char *str)
{
    int len = 0;
    while (str[len] != '\0') {
        len++;
    }
    return len;
}

ft_strcpy - 文字列のコピー

void ft_strcpy(char *dest, char *src)
{
    int i = 0;
    while (src[i] != '\0') {
        dest[i] = src[i];
        i++;
    }
    dest[i] = '\0';
}

ft_strcmp - 文字列の比較

int ft_strcmp(char *s1, char *s2)
{
    int i = 0;
    while (s1[i] != '\0' && s2[i] != '\0') {
        if (s1[i] != s2[i]) {
            return s1[i] - s2[i];
        }
        i++;
    }
    return s1[i] - s2[i];
}

これらの関数を自作することは、42のPiscineやlibftプロジェクトで求められます。

---

8.11 この章のまとめ

学んだこと

  • 配列の基本
- 同じ型のデータをまとめて格納 - インデックスは0から始まる - 範囲外アクセスは未定義動作

  • 配列の操作
- 宣言と初期化 - ループでの全要素処理 - 関数への渡し方(参照渡し的)

  • 多次元配列
- 2次元配列は表形式のデータに - ネストしたループで処理

  • 文字列
- char型の配列 - ヌル終端 \0 が重要 - "" で自動的にヌル終端される

  • 文字列関数
- strlen: 長さ - strcpy: コピー - strcmp: 比較 - strcat: 連結

重要な注意点

  • インデックスは0から: arr[n] の最後の要素は arr[n-1]
  • 範囲外アクセス禁止: コンパイルエラーにならないので自分で注意
  • 文字列はヌル終端必須: 忘れると未定義動作
  • 文字列比較は strcmp: == ではアドレス比較になる
  • バッファサイズに注意: strcpy などはサイズチェックしない

次のステップ

この章で学んだ配列と文字列は、Cプログラミングの基礎です。

42のPiscineでは:

  • これらの概念を深く理解していることが前提
  • 標準関数を使わず自作することが求められる
  • ポインタとの関係(次のレベル)も重要

この教材で基礎を固めて、Piscineに臨みましょう!

---

Column: 配列とメモリ

配列のメモリ配置

配列の要素は、メモリ上に連続して配置されます。

int arr[4] = {10, 20, 30, 40};

メモリ上のイメージ(intが4バイトの場合):

アドレス: 0x1000  0x1004  0x1008  0x100C
値:         10      20      30      40
インデックス: [0]     [1]     [2]     [3]

このため、arr[i] は実際には「配列の先頭アドレス + i × 要素サイズ」の位置にアクセスしています。

配列とポインタ

C言語では、配列名は先頭要素へのポインタとして扱われます。これは中級者向けの重要な概念ですが、今は「配列は連続したメモリ領域」と覚えておけば十分です。

なぜインデックスは0から?

多くの言語で配列のインデックスが0から始まるのは、C言語の影響です。

0から始めると、arr[i] の計算が単純になります:

  • 0始まり: 先頭アドレス + i × サイズ
  • 1始まり: 先頭アドレス + (i - 1) × サイズ

1を引く必要がないため、わずかに効率的です。1970年代のコンピュータでは、この違いも重要でした。

---

確認問題

問題1

以下の配列宣言で、要素数はいくつですか?

int arr[] = {5, 10, 15, 20, 25};

解答

5

初期値の数から自動で決まる。

問題2

配列 int arr[5] の最後の要素にアクセスするには?

解答

arr[4]

インデックスは0から始まるので、要素数5の配列の最後は arr[4]

問題3

以下の文字列の配列サイズは?

char str[] = "Hello";

解答

6

"Hello" は5文字だが、ヌル文字 \0 が追加されるので、サイズは6。

問題4

2つの文字列が等しいか比較するには、どの関数を使いますか?

解答

strcmp

== ではアドレスの比較になってしまう。

if (strcmp(str1, str2) == 0) {
    // 等しい
}

問題5

配列の全要素を0で初期化する最も簡単な方法は?

解答

int arr[10] = {0};

最初の要素を0で初期化すると、残りも自動的に0になる。

---

おわりに

おめでとうございます!この教材の全8章を学び終えました。

学んだこと:

  • プログラミングの基本概念
  • ターミナル操作とvim
  • C言語のHello World
  • 変数と型
  • 条件分岐
  • ループ
  • 関数
  • 配列と文字列

これらは、42のPiscineで必要となる基礎知識です。

Piscineは「C言語を学ぶ場所」ではなく「C言語で問題を解く場所」です。この教材で基礎を固めておけば、Piscineで課題に集中できます。

次のステップ:

  • コードをたくさん書く(練習あるのみ!)
  • エラーと仲良くなる
  • 分からないことを調べる習慣をつける
  • 仲間と協力する

42での学習を心から応援しています。頑張ってください!