はじめに
C言語は、コンピュータのメモリ操作ができるのが最大の特徴です。このメモリの操作はC言語において、最も難しい箇所の一つです。そのため、ここをマスターすることがC言語を習得するための必須条件となります。
そこで、今回は、変数のアドレスとポインタについて解説します。
変数のアドレス
変数のアドレスとは、ある変数が存在する位置を表すものです。例えば、aという変数があるとき、&aとすることで、その位置を表す数値であるアドレスを取得することが可能です。これにより、変数の値がメモリのどこにあるのか?ということが取得可能となります。
実際にプログラムを動かし確認してみましょう。
コード
#include <stdio.h>
int main(int argc, char** argv) {
int a = 100; // int型の変数
double b = 123.4; // double型の変数
float c = 123.4f; // float型の変数
char d = 'a'; // char型の変数
printf("aの値は%d、大きさは%dbyte、アドレスは0x%x\n"
, a, sizeof(int), &a);
printf("bの値は%f、大きさは%dbyte、アドレスは0x%x\n"
, b, sizeof(double), &b);
printf("cの値は%f、大きさは%dbyte、アドレスは0x%x\n"
, c, sizeof(float), &c);
printf("dの値は%c、大きさは%dbyte、アドレスは0x%x\n"
, d, sizeof(char), &d);
return 0;
出力結果
aの値は100、大きさは4byte、アドレスは0xcffc00
bの値は123.400000、大きさは8byte、アドレスは0xcffbf0
cの値は123.400002、大きさは4byte、アドレスは0xcffbe4
dの値はa、大きさは1byte、アドレスは0xcffbdb
解説
sizeof演算子
sizeof演算子は、変数や型のメモリのサイズを取得する演算子です。
ポインタ
変数には、値の他に値を格納するアドレスが存在します。変数は値を入れることを前提としています。
一方でポインタとは、アドレスを入れることを前提とした変数を指します。例えば以下です。
int *p;
もしくは
int* p;
このように、変数名の先頭か変数の型のあとに「*」をつけると、sの変数がポインタ変数であることを定義できます。
形態 | 通常の変数 | ポインタ変数 | 解説 |
---|---|---|---|
宣言 | int a; | int* p; | ポインタ変数は、変数名の先頭か変数の型のあとに「*」をつける |
値 | a | *p | ポインタ変数で値を取得するには、先頭に「*」をつける |
アドレス | &a | p | ポインタ変数は、アドレスを入れる |
ポインタ変数の活用方法
コード
#include <stdio.h>
void show(int, int, int);
int main(int argc, char** argv) {
int a = 100; // 整数型変数a
int b = 200; // 整数型変数b
int* p = NULL; // 整数型のポインタ変数p
p = &a; // pにaのアドレスを代入
show(a, b, *p);
*p = 300; // *pに値を代入
show(a, b, *p);
p = &b; // pにbのアドレスを代入
show(a, b, *p);
*p = 400; // *pに値を代入
show(a, b, *p);
return 0;
}
void show(int n1, int n2, int n3) {
printf("a = %d b = %d *p = %d\n", n1, n2, n3);
}
実行結果
a = 100 b = 200 *p = 100
a = 300 b = 200 *p = 300
a = 300 b = 200 *p = 200
a = 300 b = 400 *p = 400
解説
- void show(int, int, int);:プロトタイプ宣言
- int* p = NULL:pの変数にNULLを代入→初期化
- p = &a; :pにaのアドレスを代入→pの変数は、100になる
- *p = 300:*pに値を代入→aの変数の値が300になる
- p = &b; : pにbのアドレスを代入→pの変数は、200になる
- *p = 400; : *pに値を代入→bの変数の値が400になる
ポインタと配列
次に、配列とポインタの関係を解説します。
配列とは?
配列のイメージは、教室ね。一番前の右に座ってるのは太郎くん。後ろりかちゃん。みたいに、箱の中に整列された複数の値を入れることが出来るってイメージね。下の図でいうと黒の数字が住所、白が格納されている数字。それらを好きなように取り出したり、入れ替えたりできる。
複数の要素(値)の集合を格納・管理するのに用いられるデータ構造が配列である。数学のベクトルおよび行列に近い概念であり、実際にベクトルおよび行列をプログラム上で表現する場合に配列が使われることが多い。同様に複数要素の集合を管理するデータ構造(コレクションあるいはコンテナ)には連結リストやハッシュテーブルなどがあるが、通常はメモリアドレス上での連続性の違いなどから配列とは区別される。1次元の配列は特に線形配列 (linear array) とも呼ばれる。
コード例1
このコード例では、配列とポインタの関係性を確認します。
#include <stdio.h>
#define SIZE 5
int main(int argc, char** argv) {
// サイズSIZEの配列を用意する
int ar1[SIZE];
char ar2[SIZE];
int i;
int* p1 = NULL;
char* p2 = NULL;
// 値を代入
for (i = 0; i < SIZE; i++) {
ar1[i] = i;
ar2[i] = 'A' + i;
}
// ポインタにアドレスを代入
p1 = &ar1[0];
p2 = &ar2[0];
// 値を出力
for (i = 0; i < SIZE; i++) {
printf("ar1[%d]=%d *(p1+%d)=%d ", i, ar1[i], i, *(p1 + i));
printf("ar2[%d]=%c *(p2+%d)=%c\n", i, ar2[i], i, *(p2 + i));
}
return 0;
}
コード例1:出力結果
ar1[0]=0 *(p1+0)=0 ar2[0]=A *(p2+0)=A
ar1[1]=1 *(p1+1)=1 ar2[1]=B *(p2+1)=B
ar1[2]=2 *(p1+2)=2 ar2[2]=C *(p2+2)=C
ar1[3]=3 *(p1+3)=3 ar2[3]=D *(p2+3)=D
ar1[4]=4 *(p1+4)=4 ar2[4]=E *(p2+4)=E
コード例1:解説
- #define SIZE 5:SIZEに5を代入
- int ar1[SIZE];:intで整数を指定
- char ar2[SIZE];:charでASCIIコードを指定
for分で次々にアクセスして、値を取り出すプログラムです。
コード例2
次のコードでは、配列変数について解説します。大切なのは、配列変数は、ポインタ変数の特殊な形と言えることです。
#include <stdio.h>
int main(int argc, char** argv) {
// サイズSIZEの配列を用意する
double d[3] = { 0.2 , 0.4 , 0.6 };
double* p1 = NULL, * p2 = NULL;
int i;
p1 = d; // p1にdのアドレスを入力
p2 = d; // p2にdのアドレスを入力
for (i = 0; i < 3; i++) {
printf("%f %f %f\n", *(d + i), p1[i], *p2);
p2++; // p2のアドレスをインクリメント
}
return 0;
}
コード2:出力結果
0.200000 0.200000 0.200000
0.400000 0.400000 0.400000
0.600000 0.600000 0.600000
コード2:解説
- p1 = d;
あれ??なんでこれでアドレスが代入できるの?
ここが大切です。配列変数は、これでアドレスを代入できます。
まとめ
変数のアドレスとポインタについて解説しました。非常にややこしい話でした。アドレスとポインタという概念があり、アドレスとは住所、ポインタとは、アドレスを入れることを前提とした変数です。
もっと詳しくC言語を学習したい方は以下の記事を参考にしてみてください。オンライン学習サイトUdemyのおすすめ講座についてまとめたものです。
筆者がC言語の学習は以下の本がおすすめです。参考にしてみてください。
コメント