C言語 ポインタの練習問題

ポインタを使う問題を、初級から上級まで12問そろえました。多くの問題では main 関数ができあがっていて、指定された関数の中身を書きます。「採点する」を押すと、本物の gcc でいくつかの入力を試し、出力が正しいかを判定します。

この練習問題の使い方
  1. 問題文と実行例を読み、下の欄にコードを書く
  2. 「▶ 実行」で自分で入力を与えて動かしてみる
  3. 「✔ 採点する」で、用意したいくつかの入力に対して出力が正しいかを判定
  4. 行き詰まったら「ヒント」、解けたら「模範解答と解説」で確認
採点のルール
  • 出力は実行例と同じ形で表示する。「数を入力してください」のような入力を促すメッセージは表示しない
  • 行末の空白と、最後の空行の有無は区別しない
  • 書いたコードはこのブラウザに自動保存されます

※ 実行と採点では、コードを外部の無料コンパイルサービス Wandbox に送信して gcc で動かします。

全12問(初級 4・中級 5・上級 3)

ポインタの練習問題(12問)

P01 初級

問1. 2つの値を入れ替える swap

2つの int 変数の値を入れ替える関数 void swap(int *a, int *b) を書け。main 関数はできあがっている(2つの整数を読み込み、swap を呼んでから「a = 値, b = 値」の形で表示する)。
実行例(下線が入力)
3 5 a = 5, b = 3
コードを書く
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
一時的な変数 tmp を使います。*a は「a が指す先の変数(main の a)」そのものなので、tmp = *a; *a = *b; *b = tmp; で入れ替えられます。
#include <stdio.h>

void swap(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

int main(void) {
    int a, b;
    scanf("%d %d", &a, &b);
    swap(&a, &b);
    printf("a = %d, b = %d\n", a, b);
    return 0;
}
解説:void swap(int a, int b) のように値渡しで書くと、関数の中で入れ替わるのはコピーだけで、main の変数は変わりません。呼び出し側は &a でアドレスを渡し、関数側は *a で指す先を書き換えます。a = b; のように * を付け忘れると、ポインタ(ローカル変数)を書き換えるだけで何も起きません。
P02 初級

問2. 商と余りを同時に返す

整数 a(0 ≤ a ≤ 1000000)と b(1 ≤ b ≤ 1000)について、a ÷ b の商と余りをポインタ引数で返す関数 void divmod(int a, int b, int *q, int *r) を書け。main 関数はできあがっている。
実行例(下線が入力)
17 5 商: 3 余り: 2
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
return で返せる値は1つだけです。結果を入れてほしい変数のアドレス(&q, &r)を受け取り、*q = a / b; のように指す先に書き込みます。
#include <stdio.h>

void divmod(int a, int b, int *q, int *r) {
    *q = a / b;
    *r = a % b;
}

int main(void) {
    int a, b, q, r;
    scanf("%d %d", &a, &b);
    divmod(a, b, &q, &r);
    printf("商: %d\n", q);
    printf("余り: %d\n", r);
    return 0;
}
解説:「結果を2つ以上返したい」ときにポインタ引数を使うのは定番の形です(scanf に & を付けるのも同じ理由です)。q = a / b; と * を付け忘れると、int をポインタに代入することになるので、コンパイラが警告(gcc 14 以降はエラー)を出します。仮に動いたとしても main の q は書き換わらず、初期化されないままです。
P03 初級

問3. ポインタで配列の合計

要素数 n の int 配列の合計を返す関数 int sum(const int *a, int n) を書け。添字 [ ] を使わず、ポインタを1つずつ進めて合計すること。main 関数はできあがっている(N(1 ≤ N ≤ 100)と N 個の整数(各 −10000 以上 10000 以下)を読み込み、合計を表示する)。
実行例(下線が入力)
5 3 1 4 1 5 合計: 14
コードを書く
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
for (const int *p = a; p < a + n; p++) とすると、p が先頭から最後の要素まで順に指します。指している要素の値は *p です。
#include <stdio.h>

int sum(const int *a, int n) {
    int s = 0;
    for (const int *p = a; p < a + n; p++) {
        s += *p;
    }
    return s;
}

int main(void) {
    int n, a[100];
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
    }
    printf("合計: %d\n", sum(a, n));
    return 0;
}
解説:配列名 a を関数に渡すと先頭要素のアドレスが渡ります。a + i は i 個先の要素のアドレスで、a[i] と *(a + i) は同じ意味です。const int * は「この関数は指す先を書き換えない」という約束で、うっかり *p = 0; と書くとコンパイルエラーになります。a + n は最後の要素の1つ後ろを指すので、条件は < にします(<= だと範囲外を読みます)。
P04 初級

問4. 文字列の長さを数える

文字列の長さ('\0' の手前までの文字数)を返す関数 int my_strlen(const char *s) を、strlen を使わずにポインタで書け。main 関数はできあがっている(空白を含まない英数字の文字列(1〜99文字)を読み込み、長さを表示する)。
実行例(下線が入力)
pointer 長さ: 7
コードを書く
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
*s が '\0' でない間、s++ でポインタを1文字ずつ進めながら数えます。while (*s != '\0') は while (*s) と書くこともできます。
#include <stdio.h>

int my_strlen(const char *s) {
    int len = 0;
    while (*s != '\0') {
        len++;
        s++;
    }
    return len;
}

int main(void) {
    char str[100];
    scanf("%99s", str);
    printf("長さ: %d\n", my_strlen(str));
    return 0;
}
解説:C の文字列は最後に '\0'(ヌル文字)が付いた char の配列です。長さは配列の大きさ(100)ではなく、'\0' までの文字数です。s++ で進めているのは関数の中のポインタ(コピー)なので、main の str は動きません。s と *s を取り違えて while (s != '\0') と書くと、'\0' は値 0 の定数なので「s が NULL かどうか」の比較になってしまいます。s は NULL にならないのでループが止まらず、配列の範囲外を読み続けます(コンパイラも警告します)。
P05 中級

問5. 最大値と最小値を返す

要素数 n の int 配列の最大値と最小値を求め、ポインタ引数 max, min が指す変数に書き込む関数 void minmax(const int *a, int n, int *max, int *min) を書け。main 関数はできあがっている(N(1 ≤ N ≤ 100)と N 個の整数(各 −10000 以上 10000 以下)を読み込み、結果を表示する)。
実行例(下線が入力)
6 4 -2 9 0 9 3 最大値: 9 最小値: -2
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
まず *max = *min = a[0]; と先頭の要素で初期化し、2番目以降と比べて更新します。比べる相手は *max(指す先の値)です。
#include <stdio.h>

void minmax(const int *a, int n, int *max, int *min) {
    *max = a[0];
    *min = a[0];
    for (int i = 1; i < n; i++) {
        if (a[i] > *max) *max = a[i];
        if (a[i] < *min) *min = a[i];
    }
}

int main(void) {
    int n, a[100], max, min;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
    }
    minmax(a, n, &max, &min);
    printf("最大値: %d\n", max);
    printf("最小値: %d\n", min);
    return 0;
}
解説:初期値を 0 にすると、全部が負の数(テスト3)のとき最大値が 0 になってしまいます。配列の先頭要素で初期化するのが安全です。if (a[i] > max) のように * を付け忘れると、値とアドレスを比べることになりコンパイラが警告します。
P06 中級

問6. 配列を逆順に並べ替える

要素数 n の int 配列の並びをその場で逆順にする関数 void reverse(int *a, int n) を書け。先頭と末尾を指す2つのポインタを使い、近づけながら入れ替えること。main 関数はできあがっている(N(1 ≤ N ≤ 100)と N 個の整数を読み込み、reverse 後の配列を空白区切りで表示する)。
実行例(下線が入力)
5 1 2 3 4 5 5 4 3 2 1
コードを書く
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
int *left = a; と int *right = a + n - 1; を用意し、left < right の間 *left と *right を入れ替えて、left++, right-- で内側へ進めます。
#include <stdio.h>

void reverse(int *a, int n) {
    int *left = a;
    int *right = a + n - 1;
    while (left < right) {
        int tmp = *left;
        *left = *right;
        *right = tmp;
        left++;
        right--;
    }
}

int main(void) {
    int n, a[100];
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
    }
    reverse(a, n);
    for (int i = 0; i < n; i++) {
        printf("%d ", a[i]);
    }
    printf("\n");
    return 0;
}
解説:末尾は a + n ではなく a + n - 1 です(a + n は配列の外)。配列は先頭アドレスが渡るので、関数の中で書き換えると main の配列も変わります。最後まで(left が末尾に達するまで)入れ替え続けると、2回入れ替わって元に戻ってしまうので、真ん中で止めるのがポイントです。
P07 中級

問7. 小文字を大文字に変換

文字列の中の英小文字(a〜z)を、その場で大文字に書き換える関数 void to_upper(char *s) を書け。それ以外の文字はそのままにする。toupper などのライブラリ関数は使わず、ポインタで1文字ずつ処理すること。main 関数はできあがっている(空白を含まない英数字の文字列(1〜99文字)を読み込み、変換後を表示する)。
実行例(下線が入力)
Hello2026c HELLO2026C
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
while (*s != '\0') で回し、'a' <= *s && *s <= 'z' なら *s = *s - 'a' + 'A'; と書き換えます。最後に s++ を忘れずに。
#include <stdio.h>

void to_upper(char *s) {
    while (*s != '\0') {
        if ('a' <= *s && *s <= 'z') {
            *s = *s - 'a' + 'A';
        }
        s++;
    }
}

int main(void) {
    char str[100];
    scanf("%99s", str);
    to_upper(str);
    printf("%s\n", str);
    return 0;
}
解説:文字は文字コード(整数)なので、*s - 'a' + 'A' で小文字から大文字へずらせます。書き換える関数なので引数は const なしの char * です。s++ を if の中に入れてしまうと、小文字以外の文字で止まって無限ループになります。
P08 中級

問8. 値を探してポインタを返す

要素数 n の int 配列から値 key を先頭から探し、最初に見つかった要素へのポインタを返す関数 int *find(int *a, int n, int key) を書け。見つからなければ NULL を返す。main 関数はできあがっていて、返ってきたポインタと配列の先頭の差(ポインタの引き算)から添字を求めて表示する。入力は N(1 ≤ N ≤ 100)、N 個の整数、key の順。
実行例(下線が入力)
6 5 8 2 8 1 3 8 位置: 1
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
for (int *p = a; p < a + n; p++) で走査し、*p == key になったらその p を return します。ループを抜けたら見つからなかったので NULL を返します。
#include <stdio.h>

int *find(int *a, int n, int key) {
    for (int *p = a; p < a + n; p++) {
        if (*p == key) {
            return p;
        }
    }
    return NULL;
}

int main(void) {
    int n, a[100], key;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
    }
    scanf("%d", &key);

    int *p = find(a, n, key);
    if (p == NULL) {
        printf("見つかりません\n");
    } else {
        printf("位置: %d\n", (int)(p - a));
    }
    return 0;
}
解説:同じ配列を指す2つのポインタの引き算 p - a は、バイト数ではなく要素何個分離れているかになります(だから添字になります)。返したポインタが指すのは main の配列 a なので、関数が終わっても有効です。逆に、関数の中のローカル変数のアドレスを返すのは誤りです(関数が終わると消えるため)。
P09 中級

問9. 3つの値を小さい順に

3つの int 変数の値を、*a ≤ *b ≤ *c となるように並べ替える関数 void sort3(int *a, int *b, int *c) を書け。入れ替えには用意されている swap 関数を使ってよい。main 関数はできあがっている(3つの整数を読み込み、sort3 後に空白区切りで表示する)。
実行例(下線が入力)
9 2 5 2 5 9
コードを書く
「実行」で入力を与えて動かす/「採点する」で 6 個のテストを自動で試します
「*a > *b なら入れ替え」「*b > *c なら入れ替え」「もう一度 *a > *b なら入れ替え」の3回で並びます。a はすでにポインタなので、swap には & を付けずに swap(a, b) と渡します。
#include <stdio.h>

void swap(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void sort3(int *a, int *b, int *c) {
    if (*a > *b) swap(a, b);
    if (*b > *c) swap(b, c);
    if (*a > *b) swap(a, b);
}

int main(void) {
    int x, y, z;
    scanf("%d %d %d", &x, &y, &z);
    sort3(&x, &y, &z);
    printf("%d %d %d\n", x, y, z);
    return 0;
}
解説:2回目までで最大値が *c に来ますが、*a と *b の順はまだ決まっていないので3回目が必要です(入力 2 3 1 で確かめられます)。swap(&a, &b) と書くと int ** を渡すことになり型が合いません。受け取ったポインタはそのまま渡せばよいのがポイントです。
P10 上級

問10. mallocで動的配列

整数 N(1 ≤ N ≤ 100000)を読み込み、malloc で int を N 個入れられる領域を確保して N 個の整数(各 −1000 以上 1000 以下)を読み込め。平均値を小数第2位まで表示し、次の行に平均より大きい値を入力順に空白区切りで表示せよ(1つもなければ「平均より大きい値:」のあとは何も表示しない)。確保に失敗したら「メモリ確保に失敗しました」と表示して終了し、最後は必ず free すること。
実行例(下線が入力)
5 3 8 1 9 4 平均: 5.00 平均より大きい値: 8 9
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
int *a = malloc(sizeof(int) * n); で確保し、a == NULL なら失敗です。確保できたら a[i] と普通の配列のように使えます。「平均より大きい」は小数で比べず、a[i] * n > sum と整数で比べると誤差がありません。
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n;
    scanf("%d", &n);

    int *a = malloc(sizeof(int) * n);
    if (a == NULL) {
        printf("メモリ確保に失敗しました\n");
        return 1;
    }

    long long sum = 0;
    for (int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
        sum += a[i];
    }
    printf("平均: %.2f\n", (double)sum / n);

    printf("平均より大きい値:");
    for (int i = 0; i < n; i++) {
        if ((long long)a[i] * n > sum) {
            printf(" %d", a[i]);
        }
    }
    printf("\n");

    free(a);
    return 0;
}
解説:N が実行時に決まるときは malloc で必要な分だけ確保します。確保した領域は使い終わったら必ず free(忘れるとメモリリーク)。malloc は失敗すると NULL を返すので、使う前に確かめます。C では malloc の戻り値(void *)はキャストしなくても int * に代入できます。sizeof(int) を掛け忘れて malloc(n) とすると N バイトしか確保されず、範囲外に書き込んでしまいます。
P11 上級

問11. 2つの文字列をつないだ新しい文字列

2つの文字列 a, b をこの順につないだ文字列を malloc で確保した領域に作り、その先頭へのポインタを返す関数 char *concat(const char *a, const char *b) を書け。確保に失敗したら NULL を返す。main 関数はできあがっている(空白を含まない英数字の文字列(各1〜19文字)を2つ読み込み、結果とその長さを表示してから free する)。string.h の関数は使ってよい。
実行例(下線が入力)
abc xyz abcxyz (6文字)
コードを書く
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
必要な大きさは strlen(a) + strlen(b) + 1 バイトです(+1 は最後の '\0' の分)。確保したら strcpy(s, a); で a を写し、strcat(s, b); で b をつなげます。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *concat(const char *a, const char *b) {
    char *s = malloc(strlen(a) + strlen(b) + 1);
    if (s == NULL) {
        return NULL;
    }
    strcpy(s, a);
    strcat(s, b);
    return s;
}

int main(void) {
    char a[20], b[20];
    scanf("%19s %19s", a, b);

    char *s = concat(a, b);
    if (s == NULL) {
        printf("メモリ確保に失敗しました\n");
        return 1;
    }
    printf("%s (%d文字)\n", s, (int)strlen(s));
    free(s);
    return 0;
}
解説:関数の中で char s[40]; のようなローカル配列を作ってそのアドレスを返すのは誤りです。関数が終わると配列は消え、返したポインタは無効になります。malloc で確保した領域は free するまで残るので返してかまいません。その代わり、受け取った側(main)が free する責任を持ちます。+1 を忘れると '\0' が確保した範囲の外に書かれます。
P12 上級

問12. ポインタのポインタで名前を並べ替え

N 人(1 ≤ N ≤ 10)の名前(空白を含まない英字、各1〜19文字)を読み込み、辞書順(strcmp の順)に並べて表示せよ。名前の配列 names そのものは動かさず、各名前を指すポインタの配列 p の中身だけを並べ替える。次の2つの関数を書くこと。
・void swap_ptr(char **x, char **y):2つの char * 変数の中身(ポインタ)を入れ替える
・void sort_names(char *p[], int n):swap_ptr を使ってバブルソートする
main 関数はできあがっていて、並べ替えた順と元の順を1行ずつ表示する。
実行例(下線が入力)
4 sato ito kato abe 並べ替え後: abe ito kato sato 元の順: sato ito kato abe
コードを書く
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
char * 型の変数を関数の中で書き換えたいので、そのアドレス(char **)を受け取ります。swap_ptr の中身は int の swap と同じ形で、一時変数の型が char * になるだけです。並べ替えでは strcmp(p[j], p[j + 1]) > 0 のとき swap_ptr(&p[j], &p[j + 1]); を呼びます。
#include <stdio.h>
#include <string.h>

void swap_ptr(char **x, char **y) {
    char *tmp = *x;
    *x = *y;
    *y = tmp;
}

void sort_names(char *p[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            if (strcmp(p[j], p[j + 1]) > 0) {
                swap_ptr(&p[j], &p[j + 1]);
            }
        }
    }
}

int main(void) {
    int n;
    char names[10][20];
    char *p[10];

    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%19s", names[i]);
        p[i] = names[i];
    }

    sort_names(p, n);

    printf("並べ替え後:");
    for (int i = 0; i < n; i++) printf(" %s", p[i]);
    printf("\n元の順:");
    for (int i = 0; i < n; i++) printf(" %s", names[i]);
    printf("\n");
    return 0;
}
解説:「int を書き換えたいなら int * を渡す」のと同じく、「char * を書き換えたいなら char ** を渡す」のがポインタのポインタの基本です。swap_ptr(char *x, char *y) にすると、入れ替わるのは関数の中のコピーだけです。文字列の中身を strcpy で入れ替えるのと違い、ポインタを入れ替えるだけなので文字のコピーが起きません。strcmp は文字コード順なので、大文字(K)は小文字(a)より前に来ます。p[j] > p[j + 1] と書くとアドレスの比較になってしまいます。

復習する講座

← 練習問題の一覧へ