Undergraduate
情報

【プログラミング入門】#8 ポインタの活用

11

514

0

たくのろじぃ

たくのろじぃ

ポインタなんて出来なくてもいいと思うかもしれませんが、どうしても必要になる時があります。
あと、よくある勘違いですがC言語には参照渡しはありません。代わりにポインタ渡しがあります。参照渡しはc++言語からの機能です。

PromotionBanner

ノートテキスト

ページ1:

プログラミング入門
#8 ポインタの活用
©たくのろじぃ

ページ2:

前回やったこと
•
N進数
•
コンピュータのメモリ
.
ポインタ
配列とポインタ
具体的な内容は前回のスライドを参照してください

ページ3:

今回やること
1. 文字列とポインタ
2. 関数とポインタ
3.変数の入れ替え(swap)
4. 配列を引数に渡す
ポインタを使うと何が嬉しいのかをまとめます。一見、 「なんだ、 変数でいいじゃん」と
思うかもしれませんが、実はポインタを使わないと上手くいかないことが... ?
キーワード:
null文字, 文字列リテラル, 関数ポインタ, ポインタ渡し, スワップ (swap)

ページ4:

1. 文字列とポインタ
文字列は配列に格納するものであった→ char str[] = "Hello";
配列の各要素に対して文字を割り当てることで、その変数を文字列として扱えた
また、文字列の最後には文字の終端を示す null文字(EOF, ¥0) が格納される
ポインタ変数を使用しても、 配列と同じように文字列として扱える
1
#include<stdio.h>
2
3
int main(){
4
int i;
5
// 要素数を指定する配列による初期化
6
char strA[5] = {'H', 'e', '1', '1', 'o'};
7
// 要素数を指定しない配列による初期化
8
char strB[] = "new";
9
// ポインタ変数による初期化
10
char *strC = "World";
11
12
13
14
15
}
16
17
18
19
20
}
for(i = 0; i < 5; i++){
// strA は確保している要素数の分だけ回す必要がある
printf("%c", stra[i]);
// 要素数を指定しなければそのまま出力可能
// %s は文字列を出力する指定子 (文字は %c)
printf("%s %s\n", strB, strC);
return 0;
PS F:\C> gcc Test.c
PS F:\C> .\a.exe
Hello new World
PS F:\C> [

ページ5:

1.1 文字列を指すポインタ
文字列の指すポインタがどの領域を指しているかを調べてみる
1
12345678910111213
#include<stdio.h>
int main(){
int i;
char str[] = "World";
printf("str 変数を指すポインタ %p\n", &str);
for(i = 0; i < sizeof(str); i++){
printf("%d 文字目を指すポインタ %p\n", i, &str[i]);
}
return 0;
}
PS F:\C> \a.exe
str変数を指すポインタ 0061FF16
0 文字目を指すポインタ 0061FF16
1 文字目を指すポインタ 0061FF17
2 文字目を指すポインタ 0061FF18
3 文字目を指すポインタ 8061FF19
4 文字目を指すポインタ 0061FF1A
5 文字目を指すポインタ 0061FF1B
PS F:\C>
文字列の指すポインタは文字の先頭のアドレスである
先頭のアドレスさえ分かればポインタ変数経由で読み書き可能

ページ6:

1.2 文字列リテラル
""
で囲まれた文字列を文字列リテラルといい、定数扱いとするルールがある
→ 定数なのでポインタ変数から文字列の値(文字)の内容を変更することは禁止
char str[] = "hoge";
str[3] = 'o'; として最後の文字を書きかえるのはOK
char *str = "huga";
str[3] = 'o'; として最後の文字を書きかえるのは NG(定義されていない領域の書き換えは危険)
*str W 0 r
d ¥0
文字列リテラル領域のコピー
ポインタ変数の場合は文字列リテラル領域に
直接アクセスできるため違反を起こす
str[]
Wo
r
d ¥0
(最近のコンパイラは優秀なのでシステムは保護される)
変数(配列)の場合、 文字列リテラルの領域が変数用領域にコピーされる
→ 実際に書き換わるのはコピーであるため適切な処理がなされる

ページ7:

1.3 文字列の上書き
変数名から上書きするには要素ごとのアクセスが必要であり、一気に書き込めない
→ ポインタ変数ではアドレスをもとにアクセスできるので書き込みが容易
変数名では要素ごとの書き込みが必要
ポインタ変数では書き込める
1
#include<stdio.h>
2
3
int main(){
4
char str[] = "Hello";
5
6
str = "World";
7
8
printf("%s\n", str);
9
return 0;
10
PROBLEMS 1 OUTPUT
DEBUG CONSOLE TERMINAL
1
2
3
4
5
6
7
8
9
#include<stdio.h>
int main(){
char *str = "Hello";
str = "World";
printf("%s\n", str);
return 0;
10
}
OUTPUT DEBUG CONSOLE TERI
PS F:\C> gcc Test.c
Test.c: In function 'main':
Test.c:6:9: error: assignment to expression with array type
str = "World";
PS F:\C>
PROBLEMS
PS F:\C> .\a.exe
World
PS F:\C>

ページ8:

1.4 上書きの本当の意味
文字列リテラルを変更することは禁じられているが、なぜポインタ変数で上書きできてしまうのか
1
#include<stdio.h>
2
3
int main(){
6
7
8
9
10
11
12
}
char *str = "hoge";
printf("書き換え前のポインタ %p\n", str);
//確保した str ポインタ変数に代入してみる
str= "huga";
printf("書き換え後のポインタ %p\n", str);
return 0;
PS F:\C> .\a.exe
書き換え前のポインタ 00405844
書き換え後のポインタ 0040506F
PS F:\C>
h
O g e ¥0
書き換え前の文字列リテラルを指す
ポインタ変数 *str
h U g a ¥0
書き換え後の文字列リテラルを指す
ポインタ変数 *str
実は上書きをしているのではなく、文字列リテラルを指すポインタが変わっているだけ
→ 参照するアドレスを変えているだけというトリック

ページ9:

1.5 ポインタ変数を指すポインタ
ポインタ変数自体を指すポインタのアドレス領域は変わっているだろうか?
1
2
3
#include<stdio.h>
int main(){
char *str = "hoge";
// & を付けるとそれ自体のアドレスを指す
printf("書き換え前のポインタ %p\n", &str);
// 確保した str ポインタ変数に代入してみる
str= "huga";
7
8
9
printf("書き換え後のポインタ %p\n", &str);
10
11
return 0;
PS F:\C> gcc Test.c
PS F:\C> .\a.exe
書き換え前のポインタ 0061FF1C
書き換え後のポインタ 0061FF1C
PS F:\C> [
*str
12
h
O
g e ¥0
hu
g a ¥0
&str
ポインタ変数自体のアドレス領域は変わらない
str から見れば文字列リテラルのアドレス領域はポインタのポインタ

ページ10:

1.6 複数の文字列の保持
文字列をポインタ変数として配列による宣言が可能→ 配列によって複数の文字列を扱える
#include<stdio.h>
int main(){
1
2
3
4
5
6
7
8
9
10
11
}
int i;
char *str[] = {"white", "blue", "red", "black"};
//ポインタ変数自体を配列に格納しているので * つきで参照
for(i = 0; i < sizeof(str); i++){
}
printf("%s\n", str[i]);
return 0;
PS F:\C> gcc Test.c
PS F:\C> .\a.exe
white
blue
red
black
PS F:\C>
配列で確保したアドレス領域は、その文字数 + null文字ごとに確保されている
PS F:\C> .\a.exe
7
for(i = 0; i < sizeof(*str); i++){
printf("%p\n", str[i]);
8911
10
}
return 0;
00405044
0040504A
0040504F
00405053
PS F:\C>

ページ11:

2. 関数とポインタ
関数:引数と返り値があり、様々な処理をまとめることができる (第3,4回を参照のこと)
関数ポインタ : 関数が確保しているアドレス領域を参照する
12
11 int main(){
13
printf( "main関数を指すポインタ p", main);
return 0;
14
PROBLEMS
OUTPUT
PS F:\C> gcc Test.c
PS F:\C> .\a.exe
DEBUG CONSOLE TERMINAL
main関数を指すポインタ 8040141A
PS F:\C>
変数のような具体的な値を持たないので、
関数そのものはポインタ
変数と関数は別の領域に保持されている
→ 関数もメモリのある領域に存在する
PS F:\C> .\a.exe
functionA関数を指すポインタ 00401410
functionB 関数を指すポインタ 0040141A
functionc関数を指すポインタ 00401424
main関数を指すポインタ 0040142E
PS F:\C>
他にも関数を作ってアドレスを見てみると・・・
関数を指すポインタは 10 [Byte] ごとに
確保されている

ページ12:

2.1 ポインタ変数から関数を呼び出す
呼び出される関数はそのまま作成して良いが、 呼び出すときは関数ポインタを使う
関数名にポインタ演算子を付けて宣言する→返り値の型 (*関数名) (仮引数の型);
あとは呼び出される関数のポインタを関数ポインタにコピーして、ここから呼び出す
int main(){
1
#include<stdio.h>
2
3
int functionA(int a, int b);
4
5
int functionA (int a, int b){
6
return a + b;
7
}
8
9
10
11
12
13
14
15
16
17
18
// 関数ポインタ
int (*funcp)(int, int);
//functionAを指すポインタ
funcp = functionA;
printf("functionAを指すポインタ %p\n", funcp);
printf("functionAから得た値 %d\n", funcp(2,3));
return 0;
functionA を直接指定しなくても
関数ポインタ funcp から呼び出しができる
間接的な呼び出し
PS F:\C> gcc Test.c
PS F:\C> .\a.exe
functionAを指すポインタ 80401410
functionAから得た値 5
PS F:\C>
19
}

ページ13:

1
2
2.2 関数の配列
関数ポインタを使えば関数そのものを配列に保持することができる
メリットとしては複数の関数を配列1つで管理できること
#include<stdio.h>
int Add(int a, int b);
24 int main(){
//関数ポインタの配列
//配列の要素には各関数を保持
int (*funcp[])(int, int) = {
Add, //加算
Sub, // 減算
Mult, // 乗算
Div //除算
for(i = 0; i < sizeof(*funcp); i++){
//配列の中に関数が入っている -> 呼び出される
printf("The Answer is %d\n", funcp[i](10, 5));
PS F:\C> gcc Test.c
PS F:\C> .\a.exe
The Answer is 15
The Answer is 5
The Answer is 50
The Answer is 2
PS F:\C>
int Sub(int a, int b);
25
int i;
5
int Mult(int a, int b);
26
6
int Div (int a, int b);
27
7
28
8
int Add(int a, int b){
29
9
return a + b;
30
10
}
31
11
32
12
int Sub(int a, int b){
33
};
13
return a - b;
34
14
}
35
15
36
16
17
int Mult(int a, int b){
return a ✶ b;
37
38
}
18
39
19
40
return 0;
20
int Div(int a, int b){
41
C
21
return a / b;
22
}

ページ14:

2.3 値渡しがうまくいかない例
値をそのまま引数として関数で2倍にしたかったが、値は変化しない
1
2
3
4
5
#include<stdio.h>
int function(int value);
int function(int value){
//ポインタ演算子でないので注意
return value * 2;
int main(){
7
8
}
9
10
11
int value = 5;
12
// value を関数で2倍にする
13
function (value);
14
15
16
// 関数によって value は2倍されているはずだが...
printf("The value is %d", value);
return 0;
17
PS F:\C> .\a.exe
The value is 5
PS F:\C> [
それぞれの関数の value を指すポインタが異なるため
→ 変数名は同じ value でも、参照するアドレスが異なる

ページ15:

2.4 各関数の指す変数へのポインタ
なぜうまくいかないかはポインタを見ればわかる
value を引数に入れると仮引数に値がコピーされる→しかしコピー先は異なるポインタ
コピーデータを受け取るための変数を指すポインタが定義される
1
#include<stdio.h>
2
3
int function(int value);
4
5
int function(int value) {
printf("function の value へのポインタ %p\n", &value);
return 0;
}
6789101112131415
int main(){
int value = 5;
function (value);
printf( "main の value へのポインタ %p\n", &value}};
return 0;
PS F:\C> \a.exe
function の value へのポインタ 0061FF00
main の value へのポインタ e061FF1C
PS F:\C> [

ページ16:

2.5 値渡しのポインタ
値渡しは値そのものをコピーしている→確保されるアドレスは関数によって異なる
main 関数内 value (関数内定義)
5,
値そのものは保護されるが、アドレスは破棄
function 関数内
value (仮引数定義)
'5
value * 2
10
printf 関数は main 関数内の value を表示しているので値が変わっていないように見える
→
function 関数内では適切な処理がなされているが、意図した処理ではない

ページ17:

2.6 ポインタ渡し
ポインタ渡し※:関数の引数にポインタ変数をわたすこと→ function(int *pt){… }
値ではなくポインタを渡すので、参照先のアドレス領域は変わらない
1
#include<stdio.h>
2
3
int function(int *value);
4
5
//引数をポインタ変数にする
6
7
8
9
}
10
11
12
13
14
int main(){
int value = 5;
// value のアドレスを渡す
function(&value);
int function(int *value){
printf("function の value へのポインタ %p\n", value );
return 0;
PS F:\C> .\a.exe
function の value へのポインタ 0061FF1C
main の value へのポインタ 8061FF1C
PS F:\C> [
どちらの関数も同じアドレスを指している
15
16
printf( "main の value へのポインタ %p\n", &value);
return 0;
17
※ 参考にした本には参照渡しと書いてあったが、C言語には参照渡しの機能はない。正確には「参照の値渡し」であり、
C言語にはアドレスを直接渡せる機能はない。 つまり、参照先のアドレスを値として保持しているポインタ変数に保持
アドレスを値として管理しているので、値渡しをしているように思えるが実体はポインタ (アドレス) なので正しくはポインタ渡し

ページ18:

2.7 ポインタ渡しのポインタ
ポインタ渡しはアドレスをコピーしている→確保されるアドレスは同じ領域
value (関数内定義)
main 関数
5
アドレスを渡すので、 値も参照可能
function 関数
value (アドレス代入)
5
value * 2
10
printf 関数で value を参照→その値を出力
ポインタ渡しを使えば1つの変数(アドレス)だけで動作するグローバル変数に近い

ページ19:

2.8 ポインタ渡しの実装
同一のアドレスを指すことを利用して実装してみる
#include<stdio.h>
//ポインタ変数の参照先は値であり、 それを2倍している
*value = value * 2;
1
2
3
void function(int *value);
4
5
//ポインタ変数に上書きするので返り値なし
6
void function(int *value){
7
8
9
}
10
11
int main(){
12
int value = 5;
13
// value のアドレスを渡す
14
function(&value);
15
printf("The value is %d\n", value);
16
return 0;
17
PS F:\C> gcc Test.c
PS F:\C> .\a.exe
The value is 10
PS F:\C> |
ポインタ変数 value の初期値は5
このアドレスを参照して2倍しているので
結果として 10 となる
*value でなく value とすると、 そのアドレス自体の値を2倍しようとしてエラーになる
→ アドレスの値は16進数なので16進数×10進数は型が合わない

ページ20:

3. 変数の入れ替え (swap)
Q. 変数を入れ替えて何の意味があるのか
A. データの昇順・降順並び替え (sort) を行うのに必要
→ データ整理のためのアルゴリズムの基礎になる
考え方としては元のデータを一時保存しておき、入れ替え先と交換する
同じ関数内での入れ替え → 変数の上書きで実装
異なる関数内での入れ替え→ポインタ変数の上書きで実装
変数 a, b を入れ替えて b, a とする流れ
b
b
a
b
a
a
b
a

ページ21:

OCR失敗: NoMethodError undefined method `first' for nil:NilClass

ページ22:

OCR失敗: NoMethodError undefined method `first' for nil:NilClass

ページ23:

OCR失敗: NoMethodError undefined method `first' for nil:NilClass

ページ24:

OCR失敗: NoMethodError undefined method `first' for nil:NilClass

ページ25:

OCR失敗: NoMethodError undefined method `first' for nil:NilClass

ページ26:

OCR失敗: NoMethodError undefined method `first' for nil:NilClass

ページ27:

OCR失敗: NoMethodError undefined method `first' for nil:NilClass

ページ28:

OCR失敗: NoMethodError undefined method `first' for nil:NilClass

ความคิดเห็น

ยังไม่มีความคิดเห็น

News