こんにちは､ そしてお帰りなさい｡
この講義では､ ポインターをいつ使うのか､
どのように使うのか､ ポインターの使用例についてお話したいと思います｡
を宣言し､ それを定義する｡
既存の変数のアドレスを､ address of 演算子を使って代入する方法は知っています｡
また､ ポインタの参照を解除する方法､ ポインタの指す値を取得する方法も知っています｡
さて､ 実際にどのように使い始めるか｡
そこで､ cにはポインタに対して実行可能ないくつかの基本的な操作が用意されています｡
すでに学習したポインタにアドレスを割り当てることができます｡
これは､ 変数がアドレスを持つ既存の変数です｡
そして､ その変数のアドレスを演算子を使って見るだけで､ そこにポイントがあるんです｡
また､ 既存のポインタを別のポインタに代入することもできる｡
つまり､ ポインタにアドレスを割り当てる方法は2つあるのです｡
だから､ それだけで配列名となることもある｡
配列の話はまだしていませんが､ ポインタにraiseを代入するときは名前を指定するだけでよいのです｡
オペレーターのアドレスも使わなくていいんです｡
ここまでは､ 既存の変数を使うことと､ 変数そのもののアドレスを渡すことに焦点をあててきました｡
しかし､ そこには3つの方法が記載されています｡
また､ ポインタの再参照も可能です｡ アスタリスクは間接演算子で､
先ほど習いました｡
へのポインタに格納されている値を得ることができます｡ ポインター ポインターの値は､ ポイントが指しているアドレスであり､
それを使用するだけです｡
また､ ポインターアドレスも取ることができます｡
これは､ ポインタそのものが格納されているポインタを作成したときの実際のアドレスです｡
そのポインタにも､ アドレスi haveオペレータが使える｡
そして､ ポインタ演算も可能です｡ これについては､ 別途レクチャーを受ける予定です｡
ここで､ プラス演算子､ マイナス演算子を使って､
ポインタに整数を足したり､ ポインタから整数を引いたりすることができる｡
そして､ これによって具体的な表記が可能になり､ 特に配列を使っている場合は､
より早くメモリ内を飛び回ることができるようになります｡
つまり､ プラス演算子を使ってポインタに整数を足すと､
そのポインタのアドレスが何バイトも増えるというのが一般的なやり方です｡
ポインタから数値を引くと､
そのアドレスをあるバイト数だけマイナスに設定することになる｡
また､ 1つずつインクリメント・デクリメントすることもでき､
これは配列で使うときに非常によく見かけます｡
つまり､ 配列の中で次の要素に移動するとき､ インデックスを使う代わりにポインタを使い､
それを1つだけインクリメントすればいいのです｡
インデックスを使用せずに前の要素に移動したい場合は､ インデックスを1つ減じるだけです｡
だから､ ポインタ演算は､ 専ら話をすることになるのですが､
それはポインタでできることなのです｡
そしてまた､ メモリ内を素早く移動し､ より効率的にするための特別な記法です｡
また､ 2ポインターの違いもわかります｡
つまり､ 2つのポインタがあり､ その要素が同じ配列にある場合､
それらの要素がどのくらい離れているかを調べたいかもしれません｡
これが2ポインターの差になるし｡
つまり､
インデックス5で､ インデックス5を指すポインタポイントがあって､ インデックス15を指す別のポインタポイントがあって､
それらをマイナスすると､
16進数アドレスから､ 連続メモリ内の配列を想定した別の16進数アドレスを引いたものになるので､ それらが何要素離れているかが表示されるわけです｡
関係演算子を使って､ 2つのポインタの値を比較することができます｡
ポインターは同じ型でなければならない｡ つまり､ 両方が整数を指すのであれば､ 両方とも同じ型でなければならない｡
これは､ アドレスがより大きいかより小さいかを教えてくれるだけです｡
ここでも､ 配列のどの部分かを特定することができます｡
また､ 引き算には2つの形式があります｡
先ほど､ 引き算の話をしましたが､
実際にあるポインタから別のポインタを引いてポインタを得たり､
ある整数からあるポインタを引いてポインタを得たりすることができるのです｡
つまり､ あるポインタから別のポインタを引いて整数を得たり､ ポインタから整数を引いてポインタを得たりすることができるのです｡
以上､ 2つのケースをご紹介しました｡
そのことを念頭に置きながら､ もう少し例を見ていきましょう｡
ポインタのインクリメントやデクリメントを使用すると､ 配列の境界外を引き起こすことがあるので注意が必要です｡
つまり､
ポインタ演算のほとんどは､ 配列を操作するときに行われるのです｡
なぜなら､ これもまた､ 素早く動くことができ､ シンプルな構文だからです｡
しかし､ 配列にはout of boundsのエラーがあることを忘れないでください｡
そのため､ あまりに大きくインクリメントすると､ 配列の枠外に出ることになります｡
デクリメントしすぎるとアンダーアンダーになり､
負の数に入ってしまいます｡
コンピュータ自身は､ ポインタがまだ配列の要素を指しているかどうかを追跡するつもりはないのです｡
だから､ 配列の演算を使うときは､ 注意が必要なんだ｡
ポインタを式の中で使いたい場合も､ それが可能です｡
先ほど少しお話しましたが､ ポインタをデリファレンスして15を追加したわけです｡
20個くらいかな？
そのため､ πポインタの値は演算式で使用することができる｡
したがって､ もし変数が整数へのポインタ型と定義されていれば､
その変数は整数演算のルールで評価される｡
そして､ それをデリファレンスで示しました｡ つまり､ 変数があり､ ポインタがある場合､
それをnullに初期化し､ ポインタにその変数のアドレスを署名するのです｡
そうすると､ star pnumber plus equals 25と言えばいいのです｡
star pnumberが指している値を取得していることを忘れないでください｡
つまり､ pnumberの値はpnumberに25を足した値に等しいと言うことです｡
だから､ その値は35に等しくなる｡
これは､ アスタリスクを使用しているので､ 指し示すアドレスが変わるわけではなく､
operatorのアドレスが分からないので､
アドレスそのものが変わるわけではありません｡
指し示す値を変えているのです｡
つまり､ number変数の値を25ずつ増やしていくことで､ pnumberという変数が指している内容を尋ねていることを星印で表しているわけです｡
ポインタが変数xを指す場合､ そのポインタはxと同じデータ型へのポインタであると定義されています｡
つまり､ エンドポインタをfloatポインタにすると､
データ型がfloatかintか､
どちらを宣言したかによって､ そのアドレスしか持てないということです｡
つまり､ ある式でインダイレクト演算子を使うことは､
同じ式でxを使うことと同じなのです｡
つまり､ floatやintを使うときにインダイレクト演算子を使って参照解除すれば､
floatを使った場合と同じように､ それが指すものが何であれ､
何でもできるようになる､ ということです｡
int型へのポインタとして定義された変数には､ in型の任意の変数のアドレスが格納できることが分かっています｡
つまり､ ここではint型の値が999に等しいので､ pnumberがint型へのポインタであれば､
そのint型をpnumberに代入し､ それに足し算することができる｡
このステートメントは新しい変数値で動作し､
新しいコンテンツ値は1024となります｡
Pnumberはvalueを指しており､
valueのアドレスです｡ つまり､ valueを指しているポインタを変更すれば､
この変数valueの値も変更されることになるわけだ｡
つまり､ これはポインタの一ヶ所の変数を変更するという概念で､ 複数の変数で変更するということを覚えておいてください｡
ポインターは､ 適切な型の任意の変数のアドレスを格納することができます｡
つまり､ intやdouble､ floatがあれば､ 1つのポインタ変数で多くの異なる変数の値を変更することができるのです｡
ポインターの型と互換性のある型であれば､
その値を変更することができます｡ pnumberがfloatを指していて､
その値が整数の場合､ pnumberの値を変更することはできません｡
そんなの無理でしょう｡ コンパイルエラーになります｡
ここでもう一つ例を挙げます｡ ここでは､ いくつかの変数のアドレスを取得する方法を紹介します｡
つまり､
このメインでは､ 実際の式に対してポインタ演算を行っているのです｡
つまり､ 配列に対して何もしていないので､ その中でポインタを増やしたり減らしたりしているわけではありません｡
だから､ 値をデクリメントしているのです｡ そこで､
num1とnum2の2つのlong型があり､ long型のポインタがあるので､ それをnullに初期化します｡
num1のアドレスをポインタpnumに代入しています｡
そして､ pnumの値をLに代入しているので､
num1は2lになったということです｡
num2をインクリメントすると､ 今度はnum2がまだ何もしていないことになります｡
つまり､ pnumの値は2lであり､ num1の値は2lということになる｡
Lはあくまでロングであることを示す接尾語です｡
今､ 長いnum2の値をインクリメントすると､ 0lですが､
1lになります｡
num2 plus equals star pnumとすると､
pnumは2lですから､ 1l plus 2lとなり､
num2は3lに等しくなりました｡
そして､ num2のアドレスをpnumに代入すると､ 今度はpnumがnum2のアドレスと等しくなる｡
つまり､ pnumをインクリメントすれば､
4lになるわけです｡
そして､ コールドブロックの内側で見ていきますが､
私はそれを歩いているのです｡ Num1は2lに相当することになる｡
Num2は､ pnumを指しているのと同じになる予定です｡
Pnumが3Lだから､ 3Lになると思うんだけど｡
Pnumは3lになり､ そしてpneum＋num2は6lになる｡
では､ 私が正しいかどうか､ コードブロック単位で見ていきましょう｡
ここで､ コードブロックの中で､ 手作業で行ってみましたが､ 今､ 正しいかどうかを確認したいと思います｡
デバッガーを使ってみよう
これをここに置いて､ プログラムを進めていくうちに､ 価値観がどう変化していくかを見ていきたいと思います｡
では､ これをビルドして､ デバッグしてみましょう｡
これが起動し､ 10行目で停止します｡
そして､ ウォッチウィンドウを表示させ､ windows watchesのdebugからwatchesに移動させると､
値をそのまま見ることができるようになります｡
つまり､
現在､ num1 はゼロ､ num2 はゼロ､ pnum はヌルを指しています｡
そのため､ pnumberではnullが0を表すことが分かっているので､
0x0となっているのです｡
そして､ それをif文の中で比較するときに使うことができるのです｡
つまり､ 10行目を実行した後､ pnumはnum1のアドレスを持つようになるはずです｡
だから､ 踏み越えよう｡
これでpnumの実アドレスは0x60ff08になりました｡
11行目を実行した後､ pnumの値は2lに等しいと言うことになりますが､ 実際にはそのためのウォッチウィンドウには何も表示されません｡
しかし､
実行してみると､ pnumはまだ同じアドレスを持っています｡
pnumがnum1のアドレスを持っていたので､ それを変更したのですが､
num1は変更されました｡
これでnum1が2になったわけです｡ さて､ 12行目では､ num2をインクリメントしています｡
つまり､ num2が1lになるだけです｡
1になりそうです｡ pnumのアドレスはそのままです｡
さて､ pnumが何であったにせよ､ num2に加えます｡
Pnum
remember は num1 を指しているので､ num2 は 3 になるはずです｡
次に14へ行くと､ num2は3､ num1はまだ2です｡
さて､ 15行目では､ pnumにnum2の新しいアドレスを代入しています｡
つまり､
これ以降は0x60ff08ではなくなるわけです｡
060ff04になるんです｡
そこで､ pnumが指す値をインクリメントすると､
2番は3､ 2番は4になるはずです｡
そして､ 4であることがわかる｡
さて､ これらの値をすべてプリントアウトしてみると､ num1の値は2であることがわかります｡
num2 の値は 4 である必要があります｡
Pnumはnum2を指しているので､ やはり4であるべきです｡
そして､ 4であるpnumの値＋4であるnum2の値を8とする｡
だから､ 実は6で外れていたかもしれないんです｡
だから今は､ ウォッチウィンドウで見ることができない｡ そこで､ とりあえず再生して､ printf文としての出力を見てみましょう｡
2448が見えてくるはずだ｡
では､ 非デバッグモードで実行してみると､
2 4 4 8 と表示されます｡
そうそう､ そうなんです｡ ご理解いただけたでしょうか？
戻って､ 議論を続けましょう｡
では､ 入力を受けるときはどうかというと､ そのようにポインターを使うことができます｡
これまでscanfを使って値を入力する場合､ interやfloatのデータ型があると必ずアンパサンド演算子を使わなければなりませんでした｡
その理由は､
文字aが実は文字ポインタであるためです｡
しかし､ scanfを使って値を入力し､ 文字配列ではない普通のデータ型に使う場合は､
いつもこのaddやsub演算子を使わなければならなかったのです｡
そしてその理由は､ scanfの第2引数がポインタであることです｡
だから､ アドレスを送らなければならないのです｡ ポインターはアドレスの値しか持ちません｡
そのため､ すでにアドレスを含むポインタがある場合､ ポインタ名をscanfの引数として使用することができます｡
これで､ 実際にポインターを作った場合はアンパサンド演算子を使わなくても､
scanfの第2引数はポインターなので､ ポインター名と言えばいいわけです｡
以前は､ valueに値を格納したい場合､ scanfの第2引数にアンパサンドvalueと書いていた｡
しかし､ この例のように､ 作成したポインタがすでにあるアドレスを指している場合､
scanfを使うときに言わなければならないのは､ 値の名前とポインタの名前だけです｡
ここでは､ 整数がある場合ですが､ 整数へのポインタがあるので､
アンパサンドを使う必要はありません｡
私たちは､ パーセントd､ p値と言うだけです｡
つまり､ ポインタがあれば､ 文字配列以外にも通常の変数名をscanfに渡すことができる場合があるのです｡
また､ 入力された内容を後でプリントアウトすることもできます｡
そのアドレスを指しているため､ 値が実際に入力されたものに変更されることがわかります｡
ヌルに対するテスト｡
ひとつだけ､ 記憶に焼き付けるべきルールがある｡ これは非常に重要なことです｡
デリファレンスの話をしましたが､ 意図しないポインタ､ 何も指していないポインタ､
メモリが割り当てられていないポインタをデリファレンスするとどうなるのでしょう？
では､ こんな風にしたらどうでしょう｡ ポインターを宣言しておきながら､
実際には初期化しておらず､ それに代入しようとするのです｡
非参照にしたり､ 割り当てたり､ それは大きな間違いです｡
つまり､ 2行目は基本的にptが指す場所に値5を格納したいが､
ptは初期化されていないため何も指していない､
という意味です｡
つまり､ ptはランダムな値を持つようになったのです｡
だから､ 5がどこに配置されるかはわからないし､ メモリ内のどこに配置されるかもわからない｡
これは無害かもしれない｡ プログラム内の既存のデータや既存の状態変数を過大評価する可能性があります｡
プログラムがクラッシュする可能性があります｡ 非常に問題がありますね｡
そのため､ ポインターを作成し､ ポインター自体を格納するためにメモリを確保しても､ データを格納するためのメモリは確保されません｡
つまり､ instar ptとすることで､ ポインタのためのメモリは確保されますが､
ポインタの値であるアドレスのためのメモリは確保されていないのです｡
ポインターはアドレスを含んでいることを忘れないでください｡
ポインターは作成されましたが､ ポインターの指す先のメモリは作成されていません｡
だから､ デリファレンスできない｡
そのため､ データを保存するためのメモリは確保されない｡
ポインタを使用する前に､ すでに割り当てられているメモリ位置にポインタを割り当てるか､
これから説明するmallocを使用して動的メモリ割り当てを行う必要があります｡
しかし､ ポインターを使用するときは常にこのことを覚えておいてください｡ もしポインターを作成してそれがNULLだった場合､
事前にNULLかどうかをチェックしたいものです｡
しかし､
あなたが作成したポインタが初期化されていなかった場合､ それはそのように初期化された別の変数を指すか､
メモリが割り当てられていない限り､ それに何かを代入しないでください｡
そこで､ NULLの出番となるわけです｡
実際にnullをテストして､ 参照外エラーがないことを確認することができます｡
プログラムがクラッシュしないことを確認する｡
つまり､ nullは基本的に何も指していないことをすでに知っているので､
すべての変数とポインタをnullに初期化する必要があるのです｡
だから､ いつもこんな感じでやっていこうと｡
Nullはcの特殊記号で､ 通常の数値では0に相当するポインタを表します｡
また､ 以下ではゼロを使ってポインタをヌルにセットしています｡
つまり､ nullを渡したり代入したりしたくない場合は､ 単に0と言えば､ 何も指していないのと同じことになるのです｡
つまり､ nullは0と等価なので､ p値がnullかどうかを検定したい場合は､
明示的検定を使うような方法が考えられます｡
pointer equals nullと言えばいい｡
そして､ ポインタがゼロを意味するヌルと等しい場合､
実際に何かをすることができます｡
つまり､ ゼロやヌルでなく､ 1つの真やその他の値であれば､ クラッシュが発生しないことを確認する方法です｡
実際に何かを参照解除しようとする前に､ メモリが割り当てられているかどうかを確認することができます｡
そこで､ ポインタをデリファレンスする前に､ NULLかどうかをチェックしたいわけです｡
また､ 関数はポインターをパラメーターとして受け取るので､
関数の中でこれを行うことがよくあります｡
また､ 呼び出し側が不正なデータを渡していたり､
ポインタを渡してもメモリが割り当てられていなかったりすると､ コードがクラッシュする可能性があります｡
つまり､
呼び出し元のアドレスが有効であることを確認し､ NULLかどうかをチェックすることができます｡
基本的には､ これらのチェックで､ p値でないか､ ポインタでないかを常に確認することを意味します｡
また､ ポインタでない場合は､ デリファレンスすることができます｡
ポインターの話が多いですね｡ かなり議論しましたね｡
ちょっと無理をしているのでは？ 一休みして､ これまでの講義を振り返りながら､
このコンセプトを頭に叩き込むとよいでしょう｡
ポインターはアドレスを格納するだけです｡
でも､ 3つのパーツがあるので､ 問題になることもありますね｡
でも､ ポジティブでいてください｡ ついてきて､ 混乱するのはわかります｡
アドレスとの連携については､ これまでも話してきました｡
価値観で仕事をするという話をしました｡ これまで､ ポインタそのものを操作することや､ ポインタを指す変数を操作することについて説明してきましたが､
今回は､ ポインタを指す変数を操作することについて説明します｡
そのため､ 覚えなければならないことがたくさんあり､
圧倒され､ イライラすることがあります｡
そして､ その状況を把握しようとすると､ いつも大変で､ かなりの練習が必要になります｡
この概念と今まで話したことをすべて理解するには､ 練習して､
今まで話した概念をすべて使う短いプログラムを書くのが一番です｡
そこで､ ポインタを用いて値を取得するプログラムを作成します｡
これは､ 間接演算子やデリフェレンシングを用いることになるでしょう｡
ポインターを使用して値を変更するプログラムを作成します｡
つまり､ これはデリファレンスであり､ ポインタを使った値の取得は､
アドレスとなるポインタの名前だけとなる｡ 印刷用アドレス､ すべてのシンタックスに目を通しました｡
何度も何度も繰り返し練習することで､ 上手になるんだ｡
自信をつけなければならないし､ これらのコンセプトをマスターするためには､ 細かく勉強しなければならないのです｡
ポインターの話はまだまだ続きますが､ 一旦離れて､ 今までの話を振り返って､
頭に叩き込んでおくのもいいかもしれませんね｡
ありがとうございます｡