cp-11. ポインタ
C プログラミング入門)
URL: https://www.kkaneko.jp/pro/adp/index.html
1
金子邦彦
内容
例題1.変数のメモリアドレス表示
例題2.配列のメモリアドレス
例題3.2次元配列のメモリアドレス
メモリとメモリアドレス
例題4.棒グラフを表示する関数
関数への配列の受け渡し
例題5.2次元配列の受け渡し
関数への配列の受け渡し
例題6.局所変数と仮引数のメモリアドレス
例題7.関数へのポインタ渡し
関数へのポインタ渡しとポインタ変数
2
目標
データは,アドレス付けされて,メモリに入っ
ていることを理解する
ポインタ変数を使い,関数との情報の受け渡し
ができるようになる
3
家と住所
名前 住所
福岡市東区
箱崎1丁目1番
Aさんの家
Bさんの家 福岡市東区
箱崎2丁目2番
4
メモリアドレスとは
メモリ
変数の中身 メモリアドレス
18
107.75
rate
age
変数名
5
メモリアドレスとは
すべてのデータには「メモリアドレス」が付けら
れている
変数の中身:
「18」 「107.75」 など
変数名: プログラム内で使うための名前
age, rateなど
メモリアドレス: 変数のそれぞれに付けられた「住
所」の
ようなもの
6
例題1.変数のメモリアドレス表示
次の3つの変数を使って,「底辺と高さを読み込
んで,面積を計算するプログラム」を作る.
底辺 teihen 浮動小数データ
高さ takasa 浮動小数データ
面積 menseki 浮動小数データ
これら変数のメモリアドレスの表示も行う
7
#include <stdio.h>
#pragma warning(disable:4996)
int main()
{double teihen,takasa,menseki;
printf("teihen=");
scanf("%lf", &teihen);
printf("takasa=");
scanf("%lf", &takasa);
menseki = teihen * takasa * 0.5;
printf("menseki = %f\n",menseki);
printf("address(teihen) = %p\n", &teihen );
printf("address(takasa) = %p\n", &takasa );
printf("address(menseki) = %p\n", &menseki );
return 0;
}
&」はメモリアドレス
の取得
%p」はメモリアドレス
の表示 8
変数のメモリアドレス表示
実行結果の例
teihen=3
takasa=4
menseki = 6.000000
address(teihen) = 0065FDF0
address(takasa) = 0065FDE8
address(menseki) = 0065FDE0
表示された
メモリアドレス
9
メモリアドレス
メモリ
teihen
takasa
menseki 6.000000
4.000000
3.000000
変数名 メモリアドレス
0065FDF0
0065FDE8
0065FDE0
10
メモリアドレスの取得と表示
変数からメモリアドレスの取得
&: メモリアドレスを取得するための演算子
変数名(など)の前に付ける
メモリアドレスの表示のための書式
%p: メモリアドレスを表示せよという指示
printf 文などで使用
printf("address(teihen) = %p\n", &teihen );
メモリアドレス
の取得
メモリアドレス
の表示
11
例題2.配列のメモリアドレス
次の2つの配列を使って,ベクトル(1.9, 2.8, 3.7
と,ベクトル(4.6, 5.5, 6.4)の内積を求めるプログ
ラムを作る.
ベクトル(1.9, 2.8, 3.7u 要素数3の浮動小数の
配列
ベクトル(4.6, 5.5, 6.4v 要素数3の浮動小数の
配列
これら配列の要素について,メモリアドレスの表
も行う
12
#include <stdio.h>
#pragma warning(disable:4996)
int main()
{double u[]={1.9, 2.8, 3.7};
double v[]={4.6, 5.5, 6.4};
int i;
double ip;
ip = 0;
for (i=0; i<3; i++) {
ip = ip + u[i]*v[i];
}
printf("内積=%f\n", ip);
printf("address(u[0]) = %p\n", &u[0]);
printf("address(u[1]) = %p\n", &u[1]);
printf("address(u[2]) = %p\n", &u[2]);
printf("address(v[0]) = %p\n", &v[0]);
printf("address(v[1]) = %p\n", &v[1]);
printf("address(v[2]) = %p\n", &v[2]);
return 0;
}
&」はメモリアドレス
の取得
%p」はメモリアドレス
の表示 13
配列のメモリアドレス
実行結果の例
内積=47.820000
address(u[0]) = 0065FDE0
address(u[1]) = 0065FDE8
address(u[2]) = 0065FDF0
address(v[0]) = 0065FDC8
address(v[1]) = 0065FDD0
address(v[2]) = 0065FDD8
表示された
メモリアドレス
14
メモリアドレス
v[0]
v[1]
v[2]
u[0]
u[1]
u[2]
メモリ
メモリアドレス
0065FDE0
4.6
5.5
6.4
1.9
2.8
3.7
0065FDE8
0065FDF0
0065FDC8
0065FDD0
0065FDD8
15
配列とメモリアドレス
添字
配列 u
(サイズは3)
添字
配列 v
(サイズは3)
2つの配列
メモリ内の配置
(配列の並びはそのままで
メモリに入る)
v[0]
v[1]
v[2]
u[0]
u[1]
u[2]
メモリアドレス
0065FDE0
4.6
5.5
6.4
1.9
2.8
3.7
0065FDE8
0065FDF0
0065FDC8
0065FDD0
0065FDD8
16
例題3.2次元配列のメモリアドレス
次の2つの配列を使って,2行3列の行列の和を
求めるようなプログラムを作る.
a 2行3列の行列 整数
b 2行3列の行列 整数
配列 a の要素について,メモリアドレスの表示
行う
17
#include <stdio.h>
#pragma warning(disable:4996)
int main()
{int a[2][3]={{1,2,3},{4,5,6}};
int b[2][3]={{9,8,7},{6,5,4}};
int i;
int j;
for (i=0; i<2; i++) {
for (j=0; j<3; j++) {
printf("%d, ", a[i][j]+b[i][j]);
}
printf("\n");
}
printf("address(a[0][0]) = %p\n", &a[0][0]);
printf("address(a[0][1]) = %p\n", &a[0][1]);
printf("address(a[0][2]) = %p\n", &a[0][2]);
printf("address(a[1][0]) = %p\n", &a[1][0]);
printf("address(a[1][1]) = %p\n", &a[1][1]);
printf("address(a[1][2]) = %p\n", &a[1][2]);
return 0;
}
&」はメモリアドレス
の取得
%p」はメモリアドレス
の表示 18
2次元配列のメモリアドレス
実行結果の例
10, 10, 10,
10, 10, 10,
address(a[0][0]) = 0065FDE0
address(a[0][1]) = 0065FDE4
address(a[0][2]) = 0065FDE8
address(a[1][0]) = 0065FDEC
address(a[1][1]) = 0065FDF0
address(a[1][2]) = 0065FDF4
表示された
メモリアドレス
19
メモリアドレス
a[0][0]
メモリ
メモリアドレス
0065FDEC
1
2
3
4
5
6
0065FDF0
0065FDF4
0065FDE0
0065FDE4
0065FDE8
a[0][1]
a[0][2]
a[1][0]
a[1][1]
a[1][2]
20
2次元配列とメモリアドレス
2次元配列 a
2次元配列
メモリ内の配置
a[0][0] → a[0][1] a[0][2]
a[1][0] → a[1][1] → a[1][2]
の順で入る)
メモリアドレス
0065FDEC
1
2
3
4
5
6
0065FDF0
0065FDF4
0065FDE0
0065FDE4
0065FDE8
a[0][0] a[0][1] a[0][2]
a[1][0] a[1][1] a[1][2]
a[0][0]
a[0][1]
a[0][2]
a[1][0]
a[1][1]
a[1][2]
21
例題4.棒グラフを表示する関数
整数の配列から,その棒グラフを表示する
bar_graph 関数を作る.同時に, bar_graph
関数を呼び出すmain関数も作る
整数の配列及び配列のサイズをbar_graph関数に渡
すこと
bar_graph関数の返り値はない(void とする)
22
#include <stdio.h>
#pragma warning(disable:4996)
void bar( int len )
{int i;
for (i=0; i<len; i++) {
printf("*");
}
printf("\n");
return;
}
void bar_graph( int len, int x[] )
{int i;
for (i=0; i<len; i++) {
bar( x[i] );
}
return;
}
int main()
{int a[7]={6,4,7,1,5,3,2};
bar_graph( 7, a);
return 0;
}
「整数の配列の先頭要素の
メモリアドレス」を受け取って,
配列 x として使う.
配列の先頭要素のメモリアドレスは
すでに受け取ったので,x[i] のように
書いて配列の要素を使える
配列 a の先頭要素の
メモリアドレス( &a[0] の省略形)
23
棒グラフを表示する関数
実行結果の例
*****
****
*******
*
*****
***
**
24
関数呼び出しの流れ
bar_graph 関数
void bar_graph( int len, int x[] )
main 関数
int main()
bar_graph( 7, a );
bar 関数
void bar( int len )
bar( x[i] );
関数呼び出し
関数呼び出し
return;
return; 戻り
戻り
25
関数への配列の受け渡し
呼び出し側
配列変数名を書いて,配列の先頭メモリアドレス
を,関数に渡す
例) bar_graph( 7, a );
関数側
配列を受け取る(正確には,配列の先頭メモリア
ドレス)ことを宣言しておく
void bar_graph( int len, int x[] )
受け取った配列は,普通と同じに使える
配列 a の先頭要素のメモリアドレス
&a[0] の省略形)
「整数の配列の先頭要素の
メモリアドレス」を受け取って,
配列 x として使う.
26
配列とポインタ
プログラム中に配列名を単独(例えば「a」)で書
くと,配列の先頭要素のメモリアドレスという意味
プログラム例:bar_graph(7, a);
a
a[0] a[1] a[2] a[3] a[4] a[5] a[6]
配列の先頭要素
27
課題1.ベクトルの内積
2つの3次元ベクトルの内積を求める関数
product を作成しなさい.同時に, product
数を使う main 関数を作成し,正しく動作す
ることを確認すること.
2つの3次元ベクトルをproduct関数に渡すこと
product関数の返り値として,求めた内積を返すこ
第5回の講義資料の「ベクトルの内積」の部分を
参考にして下さい
28
例題5. 2次元配列の受け渡し
2次元配列の先頭要素のメモリアドレスと,
配列の大きさから,配列の中身を表示する関
数を作る.
29
2次元配列の受け渡し
#include <stdio.h>
#pragma warning(disable:4996)
void print_matrix( int *x, int n, int m ) {
int i;
int j;
for( i = 0; i < n; i++ ) {
for( j = 0; j < m; j++ ) {
printf( "%d, ", x[i*m+j] );
}
printf( "\n" );
}
return;
}
int main()
{
int a[2][2] = {{1,2},{3,4}};
print_matrix( a, 2, 2 );
return 0;
}
「整数データのメモリアドレス」
を受け取って,x として使う.
2次元配列 x ij 列目
配列 a の先頭要素のメモリアドレス 30
関数呼び出しの流れ
print_matrix 関数
void print_matrix( int *x, int n, int m);
main 関数
int main()
print_matrix( a, 2, 2 );
関数呼び出し
return;
戻り
31
2次元配列とポインタ
2次元配列の場合でも,プログラム中に配列名を単独で書
くと,配列の先頭要素のメモリアドレスという意味
プログラム例: print_matrix( a, 2 );
a
2次元配列の先頭要素(つまり a[0][0]
32
x[i*n+j] の意味
2次元配列 a
2次元配列 メモリ
x[ 1 * 2 + 1 ];
1
2
3
4
a[0][0] a[0][1]
a[1][0] a[1][1]
a[0][0]
a[0][1]
a[1][0]
a[1][1]
x[ 0 * 2 + 0 ];
x[ 0 * 2 + 1 ];
x[ 1 * 2 + 0 ];
配列の名前 a
使用する場合の書き方
main 関数内)
配列の先頭アドレスが,
ポインタ変数 x
入っている場合の書き方
33
課題2.2つの行列の和
2つの行列の和を求める関数 add_matrix
作成しなさい.同時に, add_matrix 関数を
使う main 関数を作成し,正しく動作するこ
とを確認すること.
add_matrix関数に渡されるのは次の通り
1. 和を求めるべき2つの行列
2. 行列の縦,横の大きさ
3. 求めた和を格納すべき行列
34
整数から,その長さだけの棒を表示するbar 関数
と,bar関数を呼び出すmain数を作る
局所変数と仮引数について,メモリアドレスを表
示することも行う
35
例題6.局所変数と仮引数のメモリアドレス
#include <stdio.h>
#pragma warning(disable:4996)
void bar( int len )
{int i;
for (i=0; i<len; i++) {
printf("*");
}
printf("\n");
printf("address(len) = %p\n", &len);
printf("address(i) = %p\n", &i);
return;
}
int main()
{int len;
printf( "len=" );
scanf( "%d", &len );
bar( len );
printf("address(len) = %p\n", &len);
return 0;
}
仮引数(パラメータ)
局所変数
局所変数
36
局所変数と仮引数のメモリアドレス
実行結果の例
len=10
**********
address(len) = 0065FDA4
address(i) = 0065FD98
address(len) = 0065FDF4
表示された
メモリアドレス
37
関数呼び出しの流れ
bar 関数
void bar( int len )
main 関数
int main()
bar( len );
関数呼び出し
return 0;
戻り
38
メモリアドレス
i
len
len
メモリ
メモリアドレス
0
10
10 0065FDF4
0065FD98
0065FDA4
bar 関数で使う部分
main 関数で使う部分
39
関数呼び出しに伴うメモリ使用状況
の変化
メモリ使用状況
の変化
main 関数
bar 関数 実行の
流れ
メモリ
main 関数
で使う部分
メモリ
main 関数
で使う部分
bar 関数
で使う部分
bar( len );
関数呼び出し
return 0;
戻り
40
呼び出し側の局所変数を書き換えてしまうような
関数を作る
41
例題7.関数へのポインタ渡し
#include <stdio.h>
#pragma warning(disable:4996)
void int_count(int *count_ptr)
{
*count_ptr = *count_ptr + 1;
return;
}
int main()
{
int count = 0;
while ( count < 10 ) {
int_count(&count);
}
printf( "count=%d\n", count );
return 0;
}
仮引数
局所変数
42
関数呼び出しの流れ
bar 関数
void int_count( int *count_ptr )
main 関数
int main()
int_count( &count );
関数呼び出し
return;
戻り
43
仕事の依頼
一方通行の場合 返事を受け取り
たい場合
○○の仕事を
頼む!
頼む人
頼まれる人
○○の仕事を
頼む. 結果は,
その「箱」に入れ
てくれ!
頼む人
頼まれる人
44
関数へのポインタ渡し
関数 int_count の呼び出しで,&count (変数
count へのポインタ)を渡す
変数 count
count へのポインタ
(&count で得られる)
int_count(&count)
呼び出し側
45
関数にローカルなポインタ変数
関数に渡された &count は,ポインタ変数 count_ptr
に格納される
変数 count
count へのポインタ
int_count(int *count_ptr)
呼び出し側 呼び出され側
count_ptr
count_ptr という名前の
付いたポインタ変数
(関数の中でのみ使用)
中身がコピーされる
46
ポインタ変数によるデータ操作
count_ptr が指している変数 count の値が1増える
*count_ptr = *count_ptr +1;
呼び出され側
count_ptr
関数の中から見ると,
*count_ptr はこれ
変数 count
呼び出し側
47
メモリ
count_ptr
count
int_count 関数で使う部分
main 関数で使う部分
メモリアドレス
が入る変数
整数データ
が入る変数
48
関数へのデータの受け渡し
ポインタ変数を
使わない場合
ポインタ変数を使
う場合
関数に受
け渡され
るもの
変数の「中身」
変数の値そのも
(call by valueという)
ポインタ変数の
「中身」
ある変数への ポイ
ンタ (call by reference
という)
性質 一方通行(渡さ
れた変数の書き
換え不可能)
渡された変数の書
き換え可能
49
ポインタ変数
変数
変数の
名前 ポインタ
変数の名前
18
107.75
rate
age
ポインタ
変数
age_ptr
rate_ptr
50
ポインタ変数
変数: 数や文字を格納
(例) int age;
double rate;
ポインタ変数: ポインタを格納
(例) int *count_ptr
ポインタ変数も名前を持つ
(普通の変数と同じ)
51
ポインタ変数の宣言
変数名の前に を付ける
例) int *age_ptr
52
ポインタ変数=&変数
ポインタ変数 age_ptr に,変数 age へのポイ
ンタをセットする
プログラム例: age_ptr = &age;
18
変数 age
age_ptr
53
変数=*ポインタ変数
変数 x に,ポインタ変数 age が指している変
数の値をセットする
プログラム例: x = age_ptr;
18
age
age_ptr
*
18
x
54
*ポインタ変数=「値」
ポインタ変数 age_ptr が指している変数 age
に,値「22」をセットする
プログラム例: age_ptr = 22;
22
age
age_ptr
値が
セットされる
*
55
ポインタ変数=ポインタ変数
ポインタ変数 x_ptr に,age_ptr と同じポイ
ンタをセットする
プログラム例: x_ptr = age_ptr;
18
age
age_ptr
x_ptr
56
ポインタを使ったプログラム例
age_ptr age を指しているから,
age は「22」に変わる
#include <stdio.h>
#pragma warning(disable:4996)
int main()
{
int age;
int *age_ptr;
age_ptr = &age;
*age_ptr = 22;
printf( "age = %d\n", age );
return 0;
}
を使用
&を使用
57
ポインタを使ったプログラム例
いくつかのポインタ変数が,同じものを指し
ていてもかまわない
#include <stdio.h>
#pragma warning(disable:4996)
int main() {
int age;
int *age_ptr;
int *second_ptr;
age_ptr = &age;
second_ptr = age_ptr;
*second_ptr = 22;
printf( "age = %d\n", age );
return 0;
}
&を使用
を使用
58
scanf & を付ける理由
scanf では,変数に & を付けることになって
いた
scanf は,データを読み込んだら,「メモリア
ドレス」を使って,読み込んだデータをメモ
リに置く
scanf("%lf", &teihen);
書式 読み込むべき変数名
&
scanf("%lf",&teihen);
teihen
浮動小数データを読み込み
59
課題3.スタック
スタックの push 関数,pop 関数及び中身を表
示する関数を作成しなさい.同時に, これら
関数を使う main 関数を作成し,正しく動作
することを確認すること.但し,大域変数は
使わないこと
main 関数の中で,配列及びスタックポインタの宣
言を行うこと
push 関数,pop 関数内では,スタックポインタの
増減を正しく行うこと(ポインタ変数を使用する
こと
60
ptr++ の意味
int ary[7];
int *ptr;
ptr = &a[0];
ptr++;
ptr++;
printf ( “%d”, *ptr );
ptr を1つ動かす。
a の次の要素 a[1] を指す)
ptr を1つ動かす。
a の次の要素 a[2] を指す)
ポインタ変数 ptr に,配列
ary へのポインタをセット
2次元配列での ptr++ の意味
int a[1000][1000];
int *ptr;
ptr = &a[99][0];
ptr++;
ptr++;
printf ( “%d”, *ptr );
ptr を1つ動かす。
a の次の要素 a[99][1] を指す)
ptr を1つ動かす。
a の次の要素 a[99][2] を指す)
ポインタ変数 ptr に,配列
ary へのポインタをセット
*を使って、値を取り出す
文字列とポインタ
ポインタ変数 ptr に,文字列 string へのポインタを
セットする (char* ptr = string; と書いてもよい)
プログラム例: char string[6] = “March”;
char *ptr = &string[0];
文字列 string への
ポインタがセットされる
string
ptr
M a r c h \0
文字列の終わり
を示す記号
typedef
typedef を使って,新しい型の名前を使えるよ
うになる
struct date {
int year;
int month;
int day
};
struct date a;
a.year = 2002;
a.month = 10;
a.day = 20;
typedef struct {
int year;
int month;
int day
} date;
date a;
a.year = 2002;
a.month = 10;
a.day = 20;
同じ意味