ポインタを使う問題を、初級から上級まで12問そろえました。多くの問題では main 関数ができあがっていて、指定された関数の中身を書きます。「採点する」を押すと、本物の gcc でいくつかの入力を試し、出力が正しいかを判定します。
※ 実行と採点では、コードを外部の無料コンパイルサービス Wandbox に送信して gcc で動かします。
void swap(int *a, int *b) を書け。main 関数はできあがっている(2つの整数を読み込み、swap を呼んでから「a = 値, b = 値」の形で表示する)。「実行」で入力を与えて動かす/「採点する」で 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; のように * を付け忘れると、ポインタ(ローカル変数)を書き換えるだけで何も起きません。void divmod(int a, int b, int *q, int *r) を書け。main 関数はできあがっている。「実行」で入力を与えて動かす/「採点する」で 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; }
scanf に & を付けるのも同じ理由です)。q = a / b; と * を付け忘れると、int をポインタに代入することになるので、コンパイラが警告(gcc 14 以降はエラー)を出します。仮に動いたとしても main の q は書き換わらず、初期化されないままです。int sum(const int *a, int n) を書け。添字 [ ] を使わず、ポインタを1つずつ進めて合計すること。main 関数はできあがっている(N(1 ≤ N ≤ 100)と N 個の整数(各 −10000 以上 10000 以下)を読み込み、合計を表示する)。「実行」で入力を与えて動かす/「採点する」で 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つ後ろを指すので、条件は < にします(<= だと範囲外を読みます)。'\0' の手前までの文字数)を返す関数 int my_strlen(const char *s) を、strlen を使わずにポインタで書け。main 関数はできあがっている(空白を含まない英数字の文字列(1〜99文字)を読み込み、長さを表示する)。「実行」で入力を与えて動かす/「採点する」で 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; }
'\0'(ヌル文字)が付いた char の配列です。長さは配列の大きさ(100)ではなく、'\0' までの文字数です。s++ で進めているのは関数の中のポインタ(コピー)なので、main の str は動きません。s と *s を取り違えて while (s != '\0') と書くと、'\0' は値 0 の定数なので「s が NULL かどうか」の比較になってしまいます。s は NULL にならないのでループが止まらず、配列の範囲外を読み続けます(コンパイラも警告します)。max, min が指す変数に書き込む関数 void minmax(const int *a, int n, int *max, int *min) を書け。main 関数はできあがっている(N(1 ≤ N ≤ 100)と N 個の整数(各 −10000 以上 10000 以下)を読み込み、結果を表示する)。「実行」で入力を与えて動かす/「採点する」で 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; }
if (a[i] > max) のように * を付け忘れると、値とアドレスを比べることになりコンパイラが警告します。void reverse(int *a, int n) を書け。先頭と末尾を指す2つのポインタを使い、近づけながら入れ替えること。main 関数はできあがっている(N(1 ≤ N ≤ 100)と N 個の整数を読み込み、reverse 後の配列を空白区切りで表示する)。「実行」で入力を与えて動かす/「採点する」で 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回入れ替わって元に戻ってしまうので、真ん中で止めるのがポイントです。void to_upper(char *s) を書け。それ以外の文字はそのままにする。toupper などのライブラリ関数は使わず、ポインタで1文字ずつ処理すること。main 関数はできあがっている(空白を含まない英数字の文字列(1〜99文字)を読み込み、変換後を表示する)。「実行」で入力を与えて動かす/「採点する」で 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 の中に入れてしまうと、小文字以外の文字で止まって無限ループになります。int *find(int *a, int n, int key) を書け。見つからなければ NULL を返す。main 関数はできあがっていて、返ってきたポインタと配列の先頭の差(ポインタの引き算)から添字を求めて表示する。入力は N(1 ≤ N ≤ 100)、N 個の整数、key の順。「実行」で入力を与えて動かす/「採点する」で 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; }
p - a は、バイト数ではなく要素何個分離れているかになります(だから添字になります)。返したポインタが指すのは main の配列 a なので、関数が終わっても有効です。逆に、関数の中のローカル変数のアドレスを返すのは誤りです(関数が終わると消えるため)。*a ≤ *b ≤ *c となるように並べ替える関数 void sort3(int *a, int *b, int *c) を書け。入れ替えには用意されている swap 関数を使ってよい。main 関数はできあがっている(3つの整数を読み込み、sort3 後に空白区切りで表示する)。「実行」で入力を与えて動かす/「採点する」で 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; }
*c に来ますが、*a と *b の順はまだ決まっていないので3回目が必要です(入力 2 3 1 で確かめられます)。swap(&a, &b) と書くと int ** を渡すことになり型が合いません。受け取ったポインタはそのまま渡せばよいのがポイントです。malloc で int を N 個入れられる領域を確保して N 個の整数(各 −1000 以上 1000 以下)を読み込め。平均値を小数第2位まで表示し、次の行に平均より大きい値を入力順に空白区切りで表示せよ(1つもなければ「平均より大きい値:」のあとは何も表示しない)。確保に失敗したら「メモリ確保に失敗しました」と表示して終了し、最後は必ず free すること。「実行」で入力を与えて動かす/「採点する」で 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; }
malloc で必要な分だけ確保します。確保した領域は使い終わったら必ず free(忘れるとメモリリーク)。malloc は失敗すると NULL を返すので、使う前に確かめます。C では malloc の戻り値(void *)はキャストしなくても int * に代入できます。sizeof(int) を掛け忘れて malloc(n) とすると N バイトしか確保されず、範囲外に書き込んでしまいます。malloc で確保した領域に作り、その先頭へのポインタを返す関数 char *concat(const char *a, const char *b) を書け。確保に失敗したら NULL を返す。main 関数はできあがっている(空白を含まない英数字の文字列(各1〜19文字)を2つ読み込み、結果とその長さを表示してから free する)。string.h の関数は使ってよい。「実行」で入力を与えて動かす/「採点する」で 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' が確保した範囲の外に書かれます。strcmp の順)に並べて表示せよ。名前の配列 names そのものは動かさず、各名前を指すポインタの配列 p の中身だけを並べ替える。次の2つの関数を書くこと。void swap_ptr(char **x, char **y):2つの char * 変数の中身(ポインタ)を入れ替えるvoid sort_names(char *p[], int n):swap_ptr を使ってバブルソートする「実行」で入力を与えて動かす/「採点する」で 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; }
swap_ptr(char *x, char *y) にすると、入れ替わるのは関数の中のコピーだけです。文字列の中身を strcpy で入れ替えるのと違い、ポインタを入れ替えるだけなので文字のコピーが起きません。strcmp は文字コード順なので、大文字(K)は小文字(a)より前に来ます。p[j] > p[j + 1] と書くとアドレスの比較になってしまいます。