Document

プログラミング入門2
ポインタ
芝浦工業大学情報工学科
青木 義満、篠埜 功
連絡先
 篠埜 功(ささの いさお)
 電子メールアドレス:
[email protected]
 講義情報web page:
http://www.sic.shibaura-it.ac.jp/~sasano/lecture/lecture.html
プログラミング入門2
2
ポインタ
 C言語の理解を妨げる難敵の一つ
 更にプログラミングの勉強を進めるためには,
超えなければならない壁
 理解できれば,C言語の機能をフルに活用できるようになる
 これまで,流して説明してきた部分を理解できる




scanf関数でなぜ変数に&をつけるの?
文字列をscanfで読み込む場合にはなぜ&が不要?
関数に配列を渡すとき,どうして配列名だけを書いて渡すの?
ファイルポインタとは?
その他
変数のメモリ空間上での振舞いを意識!
プログラミング入門2
3
ポインタ
の必要性
 ソースファイル名: list1001.c (p.226)
 2つの整数の和と差を求める関数(間違い)
#include <stdio.h>
void sum_diff(int n1, int n2, int sum, int diff)
{
sum = n1 + n2;
diff = n1 - n2;
}
int main(void)
{
int na, nb;
int wa = 0, sa = 0;
puts("二つの整数を入力してください。");
printf("整数A:");
scanf("%d", &na);
printf("整数B:");
scanf("%d", &nb);
sum_diff(na, nb, wa, sa);
printf("wa = %d\n", wa);
printf("sa = %d\n", sa);
return (0);
和と差の値は表示されない!
どうして?
}
プログラミング入門2
4
関数呼び出しと引数の引渡し過程(復習)
main関数
int main(void)
重要
{
int na, nb;
実引数の値が,仮引数にそれぞれ
コピーされる
int sa=0, wa=0;
na = 10;
(変数そのものでなく,中に格納されている値
が受け渡される!)
nb = 20;
実引数
sum_diff( na, nb, wa, sa );
10
20
0
0
仮引数
printf("wa = %d\n", wa);
printf("sa = %d\n", sa);
void sum_diff( int n1, int n2, int sum, int diff )
{
}
sum = n1 + n2;
Main文のwa, saの値は
書き換えられない!
diff = n1 – n2;
この関数内では,sumは30,
diffは-10となるが。。。
}
解決にはポインタが必要!
プログラミング入門2
5
アドレスとは?
 オブジェクトのアドレス

オブジェクト(実体)が格納されているメモリ上の
番地
プログラミング入門2
6
アドレスを表示
 ソースファイル名: list1002.c (p.228)
 オブジェクトのアドレスを表示
#include <stdio.h>
int main(void)
{
int
double
int
nx;
dx;
vc[3];
printf("nxのアドレス:%p\n", &nx);
printf("dxのアドレス:%p\n", &dx);
printf("vc[0]のアドレス:%p\n", &vc[0]);
printf("vc[1]のアドレス:%p\n", &vc[1]);
printf("vc[2]のアドレス:%p\n", &vc[2]);
return (0);
}
プログラミング入門2
7
アドレス演算子 &
 オブジェクトの頭に&をつけると,そのオブジェクトの格納されてい
るアドレスを取り出すことが可能
 アドレスをprintfで表示するための変換指定は%p
int
m;
double x;
&m
1000番地
&x
m
1008番地
メ
モ
リ
空
間
x
アドレス(番地)
printf("nxのアドレス:%p\n", &nx);
プログラミング入門2
nxのアドレスを16進数で表示
8
ポインタとは?
 ポインタ変数

オブジェクトのアドレス(メモリ上の位置)を格納する
ためのもの
 ポインタの宣言
int nx;
int *p;
int型 : 整数の値を格納するための変数nx
int型のオブジェクトのアドレスを格納する
ポインタ型の変数pを宣言
ポインタ変数pの型は? → 「int *」型,int型オブジェクトへのポインタ型
プログラミング入門2
9
最初のポインタ・プログラム
 ソースファイル名:pointer1.c
 整数の値とポインタの値を表示
#include <stdio.h>
int main(void)
{
int nx;
int *pt;
int型オブジェクトのアドレスを格納するための
int型へのポインタ型=(int *)型
nx = 57;
pt = &nx;
printf("nxの値:%d\n", nx);
printf("nxのアドレス:%p\n", &nx);
printf("ptの値:%p\n", pt);
return (0);
表示される値を確認!
}
プログラミング入門2
10
プログラムの解説
int
int
nx;
*pt;
57
メモリ上の番地
1000番地
nx
=
nx = 57;
pt = &nx;
&nx
代入
pt
nxのアドレス
int型のオブジェクトのアドレスを格納するため
の変数
printf("nxの値:%d\n", nx);
printf("nxのアドレス:%p\n", &nx);
printf("ptの値:%p\n", pt);
プログラミング入門2
57
同じアドレス
11
int型とintへのポインタ型
int *型
int型
int
nx;
nx
nxの値(右辺値)は整数
int
*pt;
pt
ptの値(右辺値)は整数を格
納するオブジェクトのアドレス
&nx, pt は(int *)型
プログラミング入門2
12
ポインタがオブジェクトを「指す」
 ポインタptの値がオブジェクトnxのアドレスであるとき,
pt は nx を指す
という。
57
メモリ上の番地
int nx;
int *pt;
pt = &nx;
1000番地
=
nx
&nx
代入
nxのアドレス
pt
ptにはオブジェクトnxのアドレスが格納されているので,
ptはnxを指している といえる。
プログラミング入門2
13
間接演算子 *
 ソースファイル名:pointer2.c
 ポインタが指すオブジェクトの値を表示
#include <stdio.h>
int main(void)
{
int nx;
int *pt;
nx = 57;
pt = &nx;
ptに,int型の変数nxのアドレスを代入
pt は nx を指す
printf( "nxのアドレス:%p\n", &nx);
printf( "nxの値:%d\n", nx );
printf( "ptの値:%p\n", pt );
printf( "*ptの値:%d\n", *pt );
return(0);
}
プログラミング入門2
14
ポインタとエイリアス(別名)
int nx;
int *pt;
nx = 57;
pt = &nx;
pt は nx を指す
nx
pt
*pt はnxのエイリアス(別名)
nx
pt
プログラミング入門2
15
ポインタが指すオブジェクトへの代入


ソースファイル名:pointer3.c
ポインタを介して,オブジェクトの値を変更
#include <stdio.h>
int main(void)
{
int nx, ny;
int *pt;
ny
pt = &nx; ptはnxを指す
*pt = 100;
printf( "nxの値:%d\n", nx );
printf( "*ptの値:%d\n", *pt );
pt = &ny;
*pt = 300;
printf( "nyの値:%d\n", ny );
printf( "*ptの値:%d\n", *pt );
return(0);
}
nx
pt
この状況で*ptに100を代入することは,
nxに100を代入することと同じ
「*ptはnxのエイリアスである」ということも
ある。
プログラミング入門2
16
練習問題
1. ポインタの理解
 次の手順でプログラムを作成せよ。











Int型の変数 x, y を宣言
Intへのポインタ型 ptr を宣言
x, yにそれぞれ,100,500を代入
ptr がxを指すようにする
*ptrの値を表示
ptrを介して,xの値を400に変更
x, *ptrの値を表示
ptr がオブジェクトyを指すようにする
*ptrの値を表示
ptrを介して,yの値を200に変更
y, *ptrの値を表示
プログラミング入門2
17
ポインタと関数
 ソースファイル名:list1004-1.c
 関数の引数とポインタ(間違い)
#include <stdio.h>
void hiroko(int height)
{
if ( height < 180)
height = 180;
}
Height before : 179
Height after : 179
int main(void)
{
int masaki = 179;
printf("height before:%d\n", masaki);
値が変更されていない!
hiroko(masaki);
printf("height after:%d\n", masaki);
return (0);
}
プログラミング入門2
18
値の受け渡し
 実引数の値が仮引数の値にコピーされるだけ
#include <stdio.h>
void hiroko(int height)
{
if ( height < 180)
height = 180;
}
・heightには179という値がコピーされる
・hiroko関数内でheightの値は変更されるが,
main関数内のmasakiの値は変更されない
ポインタを利用して変更
int main(void)
{
int masaki = 179;
printf("height before:%d\n", masaki);
hiroko(masaki);
printf("height after:%d\n", masaki);
return (0);
}
プログラミング入門2
19
ポインタと関数
 ソースファイル名:list1004-2.c
 関数の引数とポインタ(正解)
#include <stdio.h>
void hiroko(int *height)
{
if ( *height < 180)
*height = 180;
}
height before : 179
height after : 180
int main(void)
{
int masaki = 179;
printf("height before:%d\n", masaki);
hiroko(&masaki);
printf("height after:%d\n", masaki);
return (0);
}
プログラミング入門2
20
関数の引数としてのポインタ
 ポインタを介して,間接的に変数の値を変更
int masaki;
masaki = 179;
masakiのアドレス(&masaki)
を関数に渡す
hiroko(&masaki);
int *height;
height = &masaki;
106番地
void hiroko(int *height)
{
if ( *height < 180)
*height = 180;
}
106番地
masaki
height
プログラミング入門2
21
関数におけるポインタの使用例(p.234)
ソースファイル名:list1005-1.c
 関数の引数とポインタ(間違い)

#include <stdio.h>
void swap(int nx, int ny)
{
int temp = nx;
nx = ny;
ny = temp;
}
int main(void)
{
int na, nb;
temp
nxの値を一時的に
tempに格納
nx
nyにtemp(元のnxの値)
を代入
ny
nyの値をnxに代入
puts("二つの整数を入力してください。");
printf("整数A:"); scanf("%d", &na);
printf("整数B:"); scanf("%d", &nb);
swap(na, nb);
変数の値を関数を使って変更したい
→ ポインタを使いましょう
puts("これらの値を交換しました。");
printf("整数Aは%dです。\n", na);
printf("整数Bは%dです。\n", nb);
return (0);
}
プログラミング入門2
22
関数におけるポインタの使用例(p.234)
ソースファイル名:list1005-2.c
 関数の引数とポインタ(正解)

#include <stdio.h>
void swap(int *nx, int *ny)
{
int temp = *nx;
*nx = *ny;
*ny = temp;
}
int main(void)
{
int na, nb;
puts("二つの整数を入力してください。");
printf("整数A:"); scanf("%d", &na);
printf("整数B:"); scanf("%d", &nb);
swap(&na, &nb);
puts("これらの値を交換しました。");
printf("整数Aは%dです。\n", na);
printf("整数Bは%dです。\n", nb);
return (0);
}
プログラミング入門2
23
関数とポインタの重要ポイント
 関数に対して,変数の値の変更を頼みたい時

関数にその変数へのポインタ(その変数のアドレス)
を渡す
ポインタ(オブジェクトのアドレス)を渡して,
関数側はそのポインタが指すオブジェクトに対して処理を行う
プログラミング入門2
24
関数におけるポインタの使用例(p.236)
ソースファイル名:list1007.c
 2つの整数の和と差を求める

#include <stdio.h>
void sum_diff( int n1, int n2, int *sum, int *diff )
{
*sum = n1 + n2;
*diff = (n1 > n2) ? n1 - n2 : n2 - n1;
}
int main(void)
{
int na, nb;
int wa = 0, sa = 0;
puts("二つの整数を入力してください。");
printf("整数A:"); scanf("%d", &na);
printf("整数B:"); scanf("%d", &nb);
sum_diff( na, nb, &wa, &sa );
何故,na, nbには&が不要?
printf("和は%dです。\n差は%dです。\n", wa, sa);
return (0);
}
プログラミング入門2
25
値の並べ替え(ソート,p.237)
 ソースファイル名: list1008.c
 2つの整数値を小さい順に並べる(昇順ソート)
#include <stdio.h>
/*--- nx・nyが指すオブジェクトの値を交換 ---*/
int main(void)
{
int
void swap(int *nx, int *ny)
puts("二つの整数を入力してください。");
printf("整数A:"); scanf("%d", &na);
printf("整数B:"); scanf("%d", &nb);
{
int temp = *nx;
*nx = *ny;
*ny = temp;
sort2( &na, &nb );
}
puts("これらの値を昇順に並べました。");
printf("整数Aは%dです。\n", na);
printf("整数Bは%dです。\n", nb);
/*--- *n1≦*n2となるように並べる ---*/
void sort2(int *n1, int *n2)
{
if (*n1 > *n2)
swap(n1, n2);
}
na, nb;
return (0);
}
*n1, *n2, n1, n2には何の値が入っている?
プログラミング入門2
26
scanf関数とポインタ
 printf関数とscanf関数
 printf(“x = %d\n”, x);

scanf(“%d”, &x);
 printfはただ変数の値を表示するだけ
 変数の値を変更する必要なし
→ そのまま変数を渡す
 scanfは,キーボードから読み込んだ値を変数に格納する
 変数に値を代入(値を変更)する必要
→ ポインタ(アドレス)で渡す!
プログラミング入門2
27
ポインタと配列 (p.240)
ソースファイル: list1010.c
 配列とポインタ

#include <stdio.h>
int main(void)
{
int
i;
int
vc[5] = {10, 20, 30, 40, 50};
int
*ptr = &vc[0];
for (i = 0; i < 5; i++)
printf("vc[%d] = %d ptr[%d] = %d *(ptr + %d) = %d\n",
i, vc[i], i, ptr[i], i, *(ptr + i) );
return (0);
}
プログラミング入門2
28
配列とポインタ
 配列 vc の先頭要素vc[0]のアドレス &vc[0] を
ptrに代入
int *ptr = &vc[0];
ptr + i
ptrが指すオブジェクトのi個後ろの要素
を指すポインタ
*(ptr + i)
ptrが指すオブジェクトのi個後ろの要素
のエイリアス
*(ptr + i) = ptr[i]
vc[0]
*ptr
ptr[0]
vc[1]
vc[2]
*(ptr+1)
ptr[1]
*(ptr+2)
ptr[2]
vc[3]
vc[4]
*(ptr+3)
ptr[3]
*(ptr+4)
ptr[4]
ptr
ptr[i]は、*(ptr + i) と同じ意味
プログラミング入門2
29
配列とアドレス (p.242)
ソースファイル: list1011.c
 配列のアドレスを表示

#include <stdio.h>
int main(void)
{
int
vc[3];
printf("vc :%p\n", vc);
printf("vc[0]のアドレス:%p\n", &vc[0]);
printf("vc[1]のアドレス:%p\n", &vc[1]);
printf("vc[2]のアドレス:%p\n", &vc[2]);
return (0);
}
単独に現れた配列名vcは,その配列の先頭
要素vc[0]へのポインタ( 型は、(int *)型)
ただし、vcの書き換えはできない。
プログラミング入門2
30
関数への配列の受け渡し
 配列そのものは渡せないので、配列の先頭のアドレスを渡す。
void int_set( int *vc ) {
…..
}
500番地
502番地
504番地
506番地
508番地
ary[0]
ary[1]
ary[2]
ary[3]
ary[4]
vc
int_set( ary );
または
void int_set( int vc[ ]) {
…..
}
int_set( ary );
プログラミング入門2
31
配列の受け渡し (p.244)
ソースファイル名:list1013.c
 関数への配列の受け渡し

#include <stdio.h>
void int_set( int
int vc[
*vc] )
{
int i;
for (i = 0; i < 5; i++)
vc[i] = 0;
}
void int_set (int vc[ ])
と書いても同じ意味。vcはポインタであ
って、配列ではない。(vcの値(アドレス)
は書き変えてもよい。)
int main(void)
{
int i;
int ary[ ] = {1, 2, 3, 4, 5};
int_set( ary );
配列名(ary)
=配列の先頭要素のアドレス
=&(ary[0]) int型のオブジェクトのアドレス
を渡す
for (i = 0; i < 5; i++)
printf("ary[%d] = %d\n", i, ary[i]);
return (0);
}
プログラミング入門2
32
scanf関数とポインタ
 scanfは,キーボードから読み込んだ値を変数に格納する
 変数に値を代入(値を変更)する必要
→ ポインタ(アドレス)で渡す!
 文字列の場合

文字配列の先頭アドレスを渡してやればよい
char name[256];
scanf( “%s”, name );
name は &(name[0]) と同じ。
プログラミング入門2
33
二次元配列の関数への渡し方
 ソースファイル名: 2jigen.c
 関数への二次元配列データの受け渡し
#include <stdio.h>
void print_name( char x[ ][256] )
{
int i;
for(i=0; i < 4; i++){
printf("name[%d] = %s\n", i, x[i] );
}
}
先頭の要素数は省略可能
(注)関数の引数に配列の表記が使われる
場合は、ポインタの意味になる。
void print_name ( char (*x) [256] )
と書いても同じ。
int main(void)
{
char name[4][256]={"aoki", "morishima", "tokunaga", "yanagisawa" };
print_name( name );
return(0);
配列名で関数へ受け渡し(配列の先頭の
アドレスが渡される)
}
プログラミング入門2
34
今日の課題

ファイル名:kadai10-1.c
ポインタと関数,配列

3名分の氏名,身長,体重データが格納されている配列を受け取って,肥満度(BMI値
)を計算し,全員の肥満度を表示する関数を設計せよ

氏名、身長、体重、BMI値を格納する配列はMain関数で宣言、関数にそれらのデータ
を渡して関数内でBMI値を計算して配列BMIに結果を格納。

Main関数中でBMI値に基づき、判定結果を表示。

氏名,身長,体重のデータはキーボード読込み,ファイル読込みのどちらでも良い。
void calculate_bmi( char name[ ][256], double *height, double *weight, double *bmi )
名前の配列
身長の配列
体重の配列
BMIの配列
<BMI(Body mass index)の計算方法>
身長の二乗に対する体重の比で体格を表す指数
BMI=体重(kg)/(身長m)2
BMI 25以上
BMI 18.5以上25未満
BMI 18.5未満
→ 肥満
→ 標準体重
→ 低体重
プログラミング入門2
35
今日の課題
ファイル名:kadai10-2.c
要素数noのint型配列データを受け取り,値の小さい順に要素を並べ替える(ソート)関数を設計し,
その動作を確認せよ。なお,並べ替えのアルゴリズムは以下の手順に従うこと。(他のアルゴリズムでも良い)
void select_sort( int *vc, int no )
セレクトソート法(最も単純なソート法)
※3, 5, 2, 4, 1 というデータを例に説明
A.まず、データ全体の中から、最小の値を探す
B.探し出した最小の値と、データの先頭の値を、入れ替える
C.次に、先頭から2番目より最後までの値の中から、最小の値を探す
D.探し出した最小の値と、先頭から2番目の値の値を、入れ替える
E.同様に、先頭から3番目より最後までの値の中から、最小の値を探す
F.探し出した最小の値と、先頭から3番目の値の値を、入れ替える
G.最後まで処理を繰り返す
この結果、順番が1, 2, 3, 4, 5 と並び替えられる
プログラミング入門2
3, 5, 2, 4, 1
1, 5, 2, 4, 3
1, 5, 2, 4, 3
1, 2, 5, 4, 3
1, 2, 5, 4, 3
1, 2, 3, 4, 5
36