今回の内容はポインタとアドレスです。C言語特有な感じの文法なのでややとっつきづらい部分もあるかもしれません。ただし一歩ずつ理解していけばそんなに恐れるほどでもないので、あまり身構えずに気楽に勉強していきましょう!
前回の復習
前回は自作関数を中心に勉強しました。関数とはある程度の処理の集合物のことでしたね。それを自分で定義するものが自作関数で、あらかじめ定義されているのが標準関数と呼ぶのでした。
自作関数の基本形は以下のようになるのでした。戻り値や引数などの各用語については前回の記事を参照してもらえればと思います。
戻り値の型 関数名(引数1の型 引数1, 引数2の型 引数2) {
処理1;
処理2;
return 戻り値;
}
また後半では変数の有効範囲(スコープ)について勉強しましたね。変数の寿命が存在するものをローカル変数と呼び、逆に変数の寿命が存在しないものをグローバル変数と呼ぶのでした。
前回の練習問題の解答例
A問題
入力
とは正の整数であり、1以上10000以下であることが保証されています。
出力
にはとのうち大きい方の値を出力してください。
入力例1
出力例1
6000 + 20 = 6020, 6000 × 20 = 120000 なので120000が答えになります。
入力例2
出力例2
解答例
#include <stdio.h>
int calc(int a, int b);
int main() {
int x, y;
scanf("%d %d", &x, &y);
printf("%d\n", calc(x, y));
return 0;
}
int calc(int a, int b) {
int t = (a + b > a * b)? a + b : a * b;
return t;
}
A問題が解けなかった人は前回の記事を見直して自作関数の作り方を思い出しつつ、すぐ下のB問題を自力で解いてみてくださいね。
B問題
入力
は正の整数であり、1以上1000000000以下であることが保証されています。
出力
にはの各位の和を出力してください。
入力例1
出力例1
7 + 3 + 1 = 11。
入力例2
出力例2
解答例
#include <stdio.h>
int digitSum(int a);
int main() {
int t;
scanf("%d", &t);
printf("%d\n", digitSum(t));
return 0;
}
int digitSum(int a) {
int temp = 0;
while(a) {
temp += a % 10;
a /= 10;
}
return temp;
}
自作関数の中でwhile文を使った実装を要求される分A問題より難しくなっています。この発想は初見だとなかなか出にくいかもしれませんね。
ポインタとアドレス
復習が済んだところでポインタとアドレスのお話に入っていきます。まずはそれぞれの用語について説明しておきます。
アドレスというのは英単語のaddressからもわかる通り場所を表すために割り振られた数値のことです。メモリ上の場所に直接アクセスして操作を行うためにはアドレスを指定してあげる必要があります。アドレスは16進数の形で表記されています。
ポインタというのはアドレスを格納するための変数のことです。ポインタのことをポインタ変数と呼ぶこともありますが同じものです。
基本的にはこれだけです。普通の変数は値をそのまま保持しているのに対してポインタは値が格納されている場所の情報(アドレス)を保持しているだけの違いになります。これらに加えて『&』と『*』の使い方を覚えるだけで今回の記事のほとんどが終わります!(簡単デスヨネ)
アドレスとポインタという用語について説明したところで使い方の勉強に入っていきましょうか。
ポインタはアドレスを格納する変数でしたよね。ということはポインタも変数なので使うためには宣言してあげる必要があります。ただし普通の変数と同じように以下のような宣言をすることはできません。(ポインタはアドレスを格納する特別な変数だからです)
int 変数名; //普通の変数の宣言
ポインタ変数を宣言する際には以下のように『*』を付けてあげる必要があります。
int *変数名; //ポインタ変数の宣言
ちなみにアドレスを格納するポインタ変数において『int *』のようにintとかcharとかを書かなければいけないのかはそれぞれの型によって使用するバイト数が異なることに起因しています。
ポインタ変数の作り方を実際にサンプルコードを試して確認してみましょう。
#include <stdio.h>
int main() {
int *p;
long *q;
return 0;
}
実行結果
このようにポインタ変数を宣言することができます。
変数を作ったら次は中身を入れて使っていきます。ポインタ変数はやはり特別なので普通の変数とはそのままやり取りすることができません。そこで登場するのが『&』です。
例えば普通のint型の変数aを作ったとします。この変数には当然int型の数値しか入りません。int型の数値をint*型のポインタ変数pに入れようとすると型の不一致が発生してしまいます。この不一致を解消するためにはint*型のポインタ変数pに変数aの値が格納されているアドレスを代入できれば良さそうです。
その変数aのアドレスを調べるために使われるのが『&』になるわけです。
実際に使ってみましょう!!
#include <stdio.h>
int main() {
int a = 10; //普通の変数
int *p; //ポインタ変数
p = &a; //変数aの値が格納されているアドレスをポインタ変数pに代入
printf("変数aの値が格納されているアドレスは%pです\n", &a);
printf("ポインタ変数pのアドレスは%pです\n", p);
return 0;
}
実行結果
変数aの値が格納されているアドレスは0x7ffee698a968です
ポインタ変数pのアドレスは0x7ffee698a968です
上のサンプルコードではポインタ変数と普通の変数の間のやり取りで変数側のアドレスを取得する必要があったため『&』が必要でしたが、ポインタ変数同士の場合には以下のように『&』が不要になります!
#include <stdio.h>
int main() {
int temp = 1;
int *p;
int *q = &temp;
p = q; //ポインタ変数pにポインタ変数qに格納されているアドレスを代入
printf("変数tempの値が格納されているアドレスは%pです\n", &temp);
printf("ポインタ変数pのアドレスは%pです\n", p);
return 0;
}
実行結果
変数tempの値が格納されているアドレスは0x7ffee7ef7968です
ポインタ変数pのアドレスは0x7ffee7ef7968です
配列のところでサラッと言っただけなので覚えている人がいないかもしれませんが、配列もポインタ変数同様アドレスを保持しています。そのため配列に保持されたアドレスをポインタ変数に代入することができます。これを利用して自作関数に配列のアドレスを渡したのが前回の記事のコラムに書かれているので興味があれば読んでみてくださいね。
ここまではポインタ変数の宣言と代入についてメインに話してきました。ここからはポインタ変数に格納されたアドレスに入っている値を参照する方法を説明していきます。
ポインタ変数が保持しているアドレスの先にある値を取得するにはポインタ変数に『*』を付けます。(この辺の記号がごちゃごちゃして紛らわしいのでポインタに苦手意識がある人もいそうですね)
ものは試しなので、早速サンプルコードで確認してみましょう!!
#include <stdio.h>
int main() {
long x = 123;
long *p = &x; //123という値が格納されたアドレスがポインタ変数pに代入された
printf("ポインタ変数pのアドレスの先が保持しているlong型の値は%ldです\n", *p);
return 0;
}
実行結果
ポインタ変数pのアドレスの先が保持しているlong型の値は123です
5行目でポインタ変数pの中に123が格納された場所を示すアドレスが渡されているのでこのような結果になります。下のサンプルコードでもう少し実験をしてみましょう。
#include <stdio.h>
int main() {
int x = 10;
int *p = &x; //123という値が格納されたアドレスがポインタ変数pに代入された
x = 20; //変数xの値を変更
printf("ポインタ変数pのアドレスの先が保持しているint型の値は%dです\n", *p);
printf("変数xの値は%dです\n", x);
return 0;
}
実行結果
ポインタ変数pのアドレスの先が保持しているint型の値は20です
変数xの値は20です
この結果からもわかるようにポインタ変数pは変数xの値が格納されているところのアドレスを保持しているため、xの値が変更されると*pで取得できる値もxと同じ値になるわけですね。
記号がごっちゃにならないように最後に簡単にまとめた表を掲載しておきます。ぜひ活用して頭を整理して下さい。
ポインタとはアドレスを格納するための変数である。
— | 宣言 | アドレスの参照 | 値の参照 |
---|---|---|---|
普通の変数 | int x; | &x | x |
ポインタ変数 | int *p; | p | *p |
配列 | int num[3]; | num | num[0] |
ポインタのポインタ
やや発展的な内容になるのでコラム扱いにしました。ポインタのポインタとかいう初学者泣かせの文法があります。ポインタのポインタというのは、ある場所のアドレスを格納しているアドレスを保持する変数のことです。
ポインタのポインタを実際に使ってみましょうか。
#include <stdio.h>
int main() {
int x = 100;
int *p = &x; //ポインタ
int **pp = &p; //ポインタのポインタ
printf("ポインタのポインタが保持しているアドレス: %p\n", pp);
printf("ポインタのポインタが保持しているアドレスに格納されているアドレス: %p\n", *pp);
printf("ポインタのポインタが保持しているアドレスに格納されているアドレスの先の値: %d\n", **pp);
return 0;
}
是非実行して確かめてみてください!
まとめ
今回の記事はポインタとアドレスについて説明してきました。使う記号を間違えないように気をつけて練習問題に取り組んでみてくださいね。
今回の内容をざっとまとめておきます。
- アドレスとはメモリ上の場所を指し示すための数値で16進数(0からfまで)で表記される。このアドレスを格納するための変数がポインタである。
- 普通の変数のアドレスを知るためには『&』をつけてあげれば良い。また、ポインタ変数に格納されたアドレスが指し示す先の値を取得するには『*』をつけてあげれば良い。
次回は構造体について勉強していきましょう!(多分めちゃくちゃボリュームが多くなります)
練習問題
A問題
#include <stdio.h>
int main() {
int x = 100;
int *p = *x;
printf("ポインタの保持しているアドレス: %p\n", *p);
return 0;
}
B問題
void swap(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
return;
}
最後まで記事を見ていただきありがとうございます。また別の記事でお会いできることを祈っております。