198 Views
February 21, 22
スライド概要
C プログラミング入門 (スライド資料とプログラム例)(Visual Studio 2019 を使用)(全15回)
https://www.kkaneko.jp/pro/adp/index.html
金子邦彦研究室ホームページ
https://www.kkaneko.jp/index.html
金子邦彦(かねこくにひこ) 福山大学・工学部・教授 ホームページ: https://www.kkaneko.jp/index.html 金子邦彦 YouTube チャンネル: https://youtube.com/user/kunihikokaneko
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さんの家 福岡市東区 箱崎2丁目2番 Bさんの家 名前 家 住所 4
メモリアドレスとは メモリ age 18 rate 107.75 変数名 変数の中身 メモリアドレス 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
メモリアドレス メモリ menseki 6.000000 0065FDE0 takasa 4.000000 0065FDE8 teihen 3.000000 0065FDF0 変数名 メモリアドレス 10
メモリアドレスの取得と表示 printf("address(teihen) = %p\n", &teihen ); メモリアドレスメモリアドレス の表示 の取得 • 変数からメモリアドレスの取得 &: メモリアドレスを取得するための演算子 変数名(など)の前に付ける • メモリアドレスの表示のための書式 %p: メモリアドレスを表示せよという指示 printf 文などで使用 11
例題2.配列のメモリアドレス • 次の2つの配列を使って,ベクトル(1.9, 2.8, 3.7) と,ベクトル(4.6, 5.5, 6.4)の内積を求めるプログ ラムを作る. ベクトル(1.9, 2.8, 3.7) u 要素数3の浮動小数の 配列 ベクトル(4.6, 5.5, 6.4) v 要素数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] 4.6 5.5 6.4 1.9 2.8 3.7 0065FDC8 0065FDD0 0065FDD8 0065FDE0 0065FDE8 0065FDF0 メモリアドレス 15
配列とメモリアドレス 配列 u 配列 v (サイズは3) (サイズは3) 0 0 1 1 2 2 添字 添字 2つの配列 メモリアドレス v[0] v[1] v[2] u[0] u[1] u[2] 4.6 5.5 6.4 1.9 2.8 3.7 0065FDC8 0065FDD0 0065FDD8 0065FDE0 0065FDE8 0065FDF0 メモリ内の配置 (配列の並びはそのままで メモリに入る) 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] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2] 1 2 3 4 5 6 0065FDE0 0065FDE4 0065FDE8 0065FDEC 0065FDF0 0065FDF4 メモリアドレス 20
2次元配列とメモリアドレス メモリアドレス 2次元配列 a a[0][0] a[1][0] a[0][1] a[1][1] a[0][2] a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2] 1 2 3 4 5 6 0065FDE0 0065FDE4 0065FDE8 0065FDEC 0065FDF0 0065FDF4 a[1][2] 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("*");
メモリアドレス」を受け取って,
}
配列 x として使う.
printf("\n");
return;
}
void bar_graph( int len, int x[] )
{
int i;
for (i=0; i<len; i++) {
bar( x[i] );
}
return;
配列の先頭要素のメモリアドレスは
}
int main()
すでに受け取ったので,x[i] のように
{
int a[7]={6,4,7,1,5,3,2};書いて配列の要素を使える
bar_graph( 7, a );
return 0;
配列 a の先頭要素の
}
メモリアドレス( &a[0] の省略形)
23
棒グラフを表示する関数 実行結果の例 ***** **** ******* * ***** *** ** 24
関数呼び出しの流れ main 関数 int main() 関数呼び出し bar_graph( 7, a ); bar_graph 関数 void bar_graph( int len, int x[] ) 関数呼び出し bar( x[i] ); bar 関数 void bar( int len ) 戻り return; 戻り return; 25
関数への配列の受け渡し • 呼び出し側 • 配列変数名を書いて,配列の先頭メモリアドレス を,関数に渡す 例) bar_graph( 7, a ); • 関数側 配列 a の先頭要素のメモリアドレス ( &a[0] の省略形) • 配列を受け取る(正確には,配列の先頭メモリア ドレス)ことを宣言しておく void bar_graph( int len, int x[] ) 「整数の配列の先頭要素の メモリアドレス」を受け取って, 配列 x として使う. • 受け取った配列は,普通と同じに使える 26
配列とポインタ プログラム例:bar_graph(7, a); 配列の先頭要素 a a[0] a[1] a[2] a[3] a[4] a[5] a[6] • プログラム中に配列名を単独(例えば「a」)で書 くと,配列の先頭要素のメモリアドレスという意味 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;
を受け取って,x として使う.
for( i = 0; i < n; i++ ) {
for( j = 0; j < m; j++ ) {
printf( "%d, ", x[i*m+j] );
}
printf( "\n" );
}
return;
2次元配列 x の i 行 j 列目
}
int main()
{
int a[2][2] = {{1,2},{3,4}};
print_matrix( a, 2, 2 );
return 0;
30
配列 a の先頭要素のメモリアドレス
}
関数呼び出しの流れ main 関数 int main() 関数呼び出し print_matrix 関数 print_matrix( a, 2, 2 ); void print_matrix( int *x, int n, int m); 戻り return; 31
2次元配列とポインタ プログラム例: print_matrix( a, 2 ); 2次元配列の先頭要素(つまり a[0][0]) a • 2次元配列の場合でも,プログラム中に配列名を単独で書 くと,配列の先頭要素のメモリアドレスという意味 32
x[i*n+j] の意味 2次元配列 a a[0][0] a[0][1] 配列の名前 a で 使用する場合の書き方 (main 関数内) a[0][0] a[0][1] a[1][0] a[1][1] a[1][0] 配列の先頭アドレスが, ポインタ変数 x に 入っている場合の書き方 1 2 3 4 x[ 0 * 2 + 0 ]; x[ 0 * 2 + 1 ]; x[ 1 * 2 + 0 ]; x[ 1 * 2 + 1 ]; a[1][1] 2次元配列 メモリ 33
課題2.2つの行列の和 • 2つの行列の和を求める関数 add_matrix を 作成しなさい.同時に, add_matrix 関数を 使う main 関数を作成し,正しく動作するこ とを確認すること. • add_matrix関数に渡されるのは次の通り 1. 和を求めるべき2つの行列 2. 行列の縦,横の大きさ 3. 求めた和を格納すべき行列 34
例題6.局所変数と仮引数のメモリアドレス • 整数から,その長さだけの棒を表示するbar 関数 と,bar関数を呼び出すmain関数を作る • 局所変数と仮引数について,メモリアドレスを表 示することも行う 35
#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
関数呼び出しの流れ main 関数 int main() 関数呼び出し bar( len ); bar 関数 void bar( int len ) 戻り return 0; 38
メモリアドレス メモリ i 0 0065FD98 len 10 0065FDA4 bar 関数で使う部分 len 10 main 関数で使う部分 0065FDF4 メモリアドレス 39
関数呼び出しに伴うメモリ使用状況 の変化 メモリ メモリ bar 関数 で使う部分 main 関数 で使う部分 main 関数 で使う部分 メモリ使用状況 の変化 main 関数 関数呼び出し bar( len ); bar 関数 戻り return 0; 実行の 流れ 40
例題7.関数へのポインタ渡し • 呼び出し側の局所変数を書き換えてしまうような 関数を作る 41
#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
関数呼び出しの流れ main 関数 int main() 関数呼び出し int_count( &count ); bar 関数 void int_count( int *count_ptr ) 戻り return; 43
仕事の依頼 ○○の仕事を 頼む. 結果は, その「箱」に入れ てくれ! ○○の仕事を 頼む! 頼む人 頼む人 箱 頼まれる人 • 一方通行の場合 頼まれる人 • 返事を受け取り たい場合 44
関数へのポインタ渡し int_count(&count) count へのポインタ (&count で得られる) 変数 count 呼び出し側 • 関数 int_count の呼び出しで,&count (変数 count へのポインタ)を渡す 45
関数にローカルなポインタ変数 int_count(int *count_ptr) count へのポインタ 中身がコピーされる 変数 count 呼び出し側 count_ptr count_ptr という名前の 付いたポインタ変数 (関数の中でのみ使用) 呼び出され側 • 関数に渡された &count は,ポインタ変数 count_ptr に格納される 46
ポインタ変数によるデータ操作 *count_ptr = *count_ptr +1; count_ptr 変数 count 呼び出し側 関数の中から見ると, *count_ptr はこれ 呼び出され側 • count_ptr が指している変数 count の値が1増える 47
メモリ メモリアドレス が入る変数 count_ptr int_count 関数で使う部分 整数データ が入る変数 count main 関数で使う部分 48
関数へのデータの受け渡し 関数に受 け渡され るもの ポインタ変数を 使わない場合 変数の「中身」 変数の値そのも の (call by valueという) 性質 一方通行(渡さ れた変数の書き 換え不可能) ポインタ変数を使 う場合 ポインタ変数の 「中身」 ある変数への ポイ ンタ (call by reference という) 渡された変数の書 き換え可能 49
ポインタ変数 age rate 変数の 名前 18 age_ptr 107.75 rate_ptr 変数 ポインタ ポインタ 変数 変数の名前 50
ポインタ変数 • 変数: 数や文字を格納 (例) int age; double rate; • ポインタ変数: ポインタを格納 (例) int *count_ptr • ポインタ変数も名前を持つ (普通の変数と同じ) 51
ポインタ変数の宣言 • 変数名の前に * を付ける 例) int *age_ptr 52
ポインタ変数=&変数 プログラム例: age_ptr = &age; age_ptr 変数 age 18 • ポインタ変数 age_ptr に,変数 age へのポイ ンタをセットする 53
変数=*ポインタ変数 プログラム例: x * = age_ptr; x 18 age_ptr age 18 • 変数 x に,ポインタ変数 age が指している変 数の値をセットする 54
*ポインタ変数=「値」 プログラム例: age_ptr = 22; * age_ptr age 22 値が セットされる • ポインタ変数 age_ptr が指している変数 age に,値「22」をセットする 55
ポインタ変数=ポインタ変数 プログラム例: x_ptr = age_ptr; age_ptr x_ptr age 18 • ポインタ変数 x_ptr に,age_ptr と同じポイ ンタをセットする 56
ポインタを使ったプログラム例
#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;
}
• age_ptr は age を指しているから,
age は「22」に変わる
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("%lf", &teihen); 書式 & 読み込むべき変数名 • scanf は,データを読み込んだら,「メモリア ドレス」を使って,読み込んだデータをメモ リに置く teihen scanf("%lf",&teihen); 浮動小数データを読み込み 59
課題3.スタック • スタックの push 関数,pop 関数及び中身を表 示する関数を作成しなさい.同時に, これら 関数を使う main 関数を作成し,正しく動作 することを確認すること.但し,大域変数は 使わないこと • main 関数の中で,配列及びスタックポインタの宣 言を行うこと • push 関数,pop 関数内では,スタックポインタの 増減を正しく行うこと(ポインタ変数を使用する こと) 60
ptr++ の意味 int ary[7]; int *ptr; ptr = &a[0]; ポインタ変数 ptr に,配列 ary へのポインタをセット ptr++; ptr++; printf ( “%d”, *ptr ); ptr を1つ動かす。 (a の次の要素 a[1] を指す) ptr を1つ動かす。 (a の次の要素 a[2] を指す)
2次元配列での ptr++ の意味 int a[1000][1000]; int *ptr; ptr = &a[99][0]; ポインタ変数 ptr に,配列 ary へのポインタをセット ptr を1つ動かす。 ptr++; (a の次の要素 a[99][1] を指す) ptr を1つ動かす。 ptr++; (a の次の要素 a[99][2] を指す) printf ( “%d”, *ptr ); *を使って、値を取り出す
文字列とポインタ プログラム例: char string[6] = “March”; char *ptr = &string[0]; ptr 文字列 string への ポインタがセットされる string M a r c h \0 文字列の終わり を示す記号 ポインタ変数 ptr に,文字列 string へのポインタを セットする (char* ptr = string; と書いてもよい)
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;