ポインタと配列
理工学部 情報科学科
上級プログラミング
ポインタの復習
ポインタはアドレスを代入するための変数
int a=10,b=20;
int *p, *q;
p=&a;
q=&b;
int sum=*p+*q;
a b
p q
アドレス 1200 1204 1208 1212 1216
・・・
・・・
・・・
1320 1324 1328
sum
ポインタを使用したコード例
メモリ内の様子
20 30
1204 1208
10
ポインタはアド レスを代入して
初めて使える
ポインタの復習
ポインタは「指し示すもの」なので以下のように表す。
コードを記述しているときは、アドレスの値は気にしないので・・・
int a=10,b=20;
int *p, *q;
p=&a;
q=&b;
int sum=*p+*q;
a b
アドレス 1200 1204 1208 1212 1216
・・・
・・・
・・・
1320 1324 1328
sum
ポインタを使用したコード例
メモリ内の様子
20 30
10
p
q
配列とアドレス
配列データは、同じ型のデータをメモリ内に連続して並べられる。
int a[10];
・・・・・・
・・・
メモリ内の様子
アドレス 2500 2504 2508 2512 2516
2528 2532 2536
a[0]
a[1]
a[2]
a[3]
a[4]
a[7]
a[8]
a[9]
a
配列名は、その領域の 先頭アドレスを指す アドレス定数である
ポインタと似ているが、配列名 自体は変数ではないので、ポ インタのように他のアドレスを 代入し直すことはできない。
a
上記配列aの先頭アドレスは、
a
&a[0]
のどちらでも表せる。
配列とポインタ(1)
ポインタを用いて配列データの各要素にアクセスする方法
int a[10];
int *p;
・・・・・・
・・・
メモリ内の様子
a[0]
a[1]
a[2]
a[3]
a[4]
a[7]
a[8]
a[9]
a
for(int i=0; i<10; i++) cin >> a[i];
for(p=a; p<a+10; p++) cin >> *p;
同じ処理を意味する
a+10
p
++で
p
1つ分 進む--で1つ分 戻る
ポインタにとって++や--は指す位置を、
データ型1つ分ずらすことを意味する。
アドレスの値を1増減する意味ではない。
配列とポインタ(2)
ポインタを用いて配列データの各要素にアクセスする方法
int a[10];
int *p=a;
// pを配列aの先頭アドレスで // 初期化
・・・
・・・
・・・
メモリ内の様子
a[0]
a[1]
a[2]
a[3]
a[4]
a[7]
a[8]
a[9]
a p
*p または p[0]
*(p+1)またはp[1]
*(p+2)またはp[2]
*(p+3)またはp[3]
*(p+4)またはp[4]
*(p+7)またはp[7]
*(p+8)またはp[8]
*(p+9)またはp[9]
a[2]=38;
*(p+2)=38;
p[2]=38;
どれも同じ 代入
配列とポインタ(3)
ポインタを用いて配列データの各要素にアクセスする方法
int a[10];
int *p=&a[3];
// pをa[3]のアドレスで // 初期化
・・・
・・・
・・・
メモリ内の様子
a[0]
a[1]
a[2]
a[3]
a[4]
a[7]
a[8]
a[9]
a
p
*(p-3)
*(p-2)
*(p-1)
*pまたはp[0]
*(p+1)またはp[1]
*(p+4)またはp[4]
*(p+5)またはp[5]
*(p+6)またはp[6]
a[2]=38;
*(p-1)=38;
どちらも 同じ代入
ポインタ間の演算
◎同じ型の2つのポインタの差は定義されている int a[8];
int *p1=a, *p2=&a[5];
このあと、例えば p2-p1 という計算は可能
◎ポインタ間の差は整数となる
•
ポインタの指すオブジェクト単位で差を表す
•
何要素分ずれているかを示す
•
結果が負になることもある
•
同一配列内の要素へのポインタの差のみ計算は有効 上記の例で、
p2-p1の値は
5となる
◎2つのポインタの和は計算できない
cout << (p1 + p2); ←コンパイルエラー
二次元配列のアドレス
int a[5][7];
a[0][0] a[0][1] a[0][2] a[0][3] a[0][4] a[0][5] a[0][6]
a[1][0] a[1][1] a[1][2] a[1][3] a[1][4] a[1][5] a[1][6]
a[2][0] a[2][1] a[2][2] a[2][3] a[2][4] a[2][5] a[2][6]
a[3][0] a[3][1] a[3][2] a[3][3] a[3][4] a[3][5] a[3][6]
a[4][0] a[4][1] a[4][2] a[4][3] a[4][4] a[4][5] a[4][6]
a[0]
a[1]
a[2]
a[3]
a[4]
二次元配列の
1つ目の次元は、各行の先頭アドレスとなっている
a 二次元配列の名前は、その領域の先頭アドレスである
a と a[0] と &a[0][0] はどれも同じアドレスを持つ。
ただし、型が異なる。
a は int [][]またはint**、 a[0]と &a[0][0]は int[]またはint*
コマンドラインの取得
コマンドラインへの入力例
comsv% example inputfile.txt 12 abc
この部分を受け取るのが
main関数の引数
コマンド プロンプト
と呼ぶ
int main(int argc, char *argv[]){
どのようにコマンドラインを受け取っているのか?
コマンドラインの取得
コマンドラインへの入力例
comsv% example inputfile.txt 12 abc argc=4 「4つの文字列がある」
e x amp l e ¥0
i n p u t f i ¥0
1 2 ¥0 a b c ¥0
l e . t x t
argv[0]
argv[1]
argv[2]
argv[3]
要素数argcのポインタ配列
各ポインタが、各文字列の先頭アドレスを指している。
文字列リテラルは必ず
¥0(NULL文字、アスキーコー ドが0の文字)で終わる。
関数への配列渡し(例1)
int main(){
int ar[5]={4,6,2,3,1};
int sum;
sum=makeSum(ar,5);
・・・
int makeSum(int *ap, int n){
int s=0;
for(int i=0; i<n; i++) s+=ap[i];
return s;
}
4 6 2 3 1
ar
sum
main関数 makeSum関数
ap n s
05 4 10 12 15
16 16
i
012345関数への配列渡し(例2)
int main(){
int ar[5]={4,6,2,3,1};
int sum;
makeSum(ar,5,&sum);
・・・
void makeSum(int *ap, int n, int *sp){
*sp=0;
for(int i=0; i<n; i++) *sp+=ap[i];
}
4 6 2 3 1
ar
sum
main関数 makeSum関数
ap n sp
5 0
i
0123454 10 12 15 16
関数への配列渡し(例3)
int main(){
int ar[5]={4,6,2,3,1};
int sum;
makeSum(ar,5,&sum);
・・・
void makeSum(int *ap, int n, int *sp){
*sp=0;
for(int i=0; i<n; i++) *sp+=*ap++;
}
4 6 2 3 1
ar
sum
main関数 makeSum関数
ap n sp
5 0
i
0123454 10 12 15 16
この処理は
*sp+=*ap;
ap++;
と同じである。