こんにちは､ そしてお帰りなさい｡ この講義では､ ポイント演算についてお話したいと思います｡
配列とポインタの関係を理解したことで､ この概念はより理解しやすくなると思います｡
算術演算と言いますが､ 実際には､ ポインターそのものに対してプラスやマイナス､
インクリメントやデクリメントを行うことができるということです｡
そこで､ 配列へのポインタの真価が発揮されるのは､ 配列の要素を順番に並べたいときです｡
最初の要素から終わりまで､ 繰り返し行いたいのです｡
これぞ､ パワーポインター｡ ポインターはアドレスを指すので､
配列の各要素は単なるアドレスの1つです｡
だから､ ポインターでプラス・プラスをすれば､ 次のアドレスに行けるんです｡
したがって､ ここでは､ 値ポインタを使用して､ 値配列の最初の整数をアクセスすることができます｡
サブ 0 の値にアクセスするために使用することができます｡
つまり､ 値のポインタとは､ 整数の配列を指すポインタであることを思い出してください｡
そして､ そのポインタを配列の名前に代入するだけで､ 配列の最初の要素に割り当てました｡
必要であれば､ 演算子sub-0のアドレスも使うことができる｡
しかし､ 配列の最初の要素を指しているので､
値ポインタをデリファレンスすると､ 値サブ0配列の最初の整数が得られることになります｡
もし､ ポインタ変数から値サブ3を参照したい場合は､
値ポインタに3を追加し､ 間接演算子を適用して参照解除すればよい｡
だから､ こんな風にすればいいんです｡ 値ポインタプラス3と言って､ アスタリスクで非参照にすることができます｡
そして､ 普通は引用符の外にアスタリスクをつけて､ わかりやすくしますよね｡
しかし､ これは配列の最初の要素のアドレスであるポインタの値に､
3を足しているのです｡
そして､ 実際に整数の各バイトを加算し､ バイトと整数の掛け算をすることになります｡
しかし､ これは本質的には配列の4番目の要素であるサブ3を指すことになります｡
したがって､ 式中のアスタリスク値ポインタプラスiは､ 値配列に含まれる任意の値をアクセスするために使用することができます｡
つまり､ この式を理解すれば､ iに何かを足せば､ 特定の要素の配列を指し示すことができるのです｡
つまり､ value sub 10を27にしたい場合は､ ポインタを使用している場合､
values pointer plus 10と言い､ それをデリファレンスしてequal
27と言えばよいのです｡
つまり､ ここで構文が違うことがわかるわけです｡
これも表記上の都合ですが､ プログラム自体の効率も良くなります｡
ポインターは配列と密接に関係しているので､ ポインターをインクリメントしたりデクリメントしたり､
ポインターに値を加えたり減算したりするだけで､ 簡単に要素にアクセスすることができるわけですね｡
とても､ 便利です｡
値ポインタが値配列の2番目の要素を指すように設定するには､ 値サブ1に対してアドレス演算子を適用し､
その結果を値ポインタに代入すればよい｡
これを見たんです｡ これは､ 先ほどのアドレスサブオペレータと同じことをするための別の方法です｡
やろうと思えばいつでもできるのですが､
ポインタ演算の方がずっと便利なのです｡
値ポインタが値sub-0を指している場合､ 値ポインタの値に1を加えるだけで､
値1の時点で設定することができます｡
そして､ これがポイント演算の正体なのです｡
このとき､ values pointまたはplus equals
oneと言えば､ values pointerは配列の2番目の要素または最初のインデックスを指すようになります｡
とても､ 素敵な表記で､ とてもやりやすい｡
つまり､ これはcで完全に有効な表現であり､ 整数文字であろうと何であろうと､
あらゆるデータ型へのポインタに使用することができる｡
そして､ これがキャラクターでどれだけ役に立つか､ おそらく考えているはずです｡
文字ポインタがあれば､ ポインタのプラス・プラスを行うだけで､
文字列の各要素､ 各文字にアクセスできるようになったのです｡
このようなポインターを扱う場合､ 通常､ ある要素から次の要素へというように移動させたいので､
インクリメントとデクリメントの演算子プラスとマイナスマイナが特に有効である｡
22を足すとか､ 20を引くというのは､ 普通は順番に進んでいくので､
あまり一般的ではありませんね｡
つまり､ ポインターでインクリメント演算子を使うと､ ポインターに1つ足すのと同じ効果があるわけです｡
デクリメント演算子を使うと､ ポインタから1を引いたのと同じ効果が得られます｡
つまり､ プラスプラス値ポインタと言えば､
値配列の次の整数を指す値ポインタが設定されることになります｡
マイナスマイナスは値ポインタでなければならない
値配列の前の整数を指す値ポインタを設定するつもりです｡
でも､ マイナスマイナスをするときに気をつけたいのは､
少なくとも配列の中で0になっていないことを確認したいからです｡
また､ プラスアルファも同様です｡
配列の末尾にある場合､ out of bounds エラーにならないようにするためです｡
したがって､ valuesポインタをデクリメントする場合は､ それが最初にvalues配列の先頭を指していないことを確認してください｡
もうひとつの例として､
配列の全要素を合計する例を示します｡
この関数の内部で､ 整数を作成することになります｡
その整数の合計を0に初期化し､ 整数ポインタ､ int型へのポインタを作成することにします｡
また､ 定数であるポインターを作成することにします｡
つまり､ そのアドレスは変えられないということです｡
常にarray+nとなり､ array endという名前になります｡
これは､ 配列の末尾に移動してポインタを見るように設定すると､
ループがより効率的になるからです｡
つまり､ array endは配列の終端のアドレスを指しているわけです｡
渡されたものをarrayと言い､ nは配列の大きさを表します｡
さて､ forループはループ制御変数ポインタを初期化します｡
これは配列へのポインタです｡ そして､ 配列の最初の要素にポインタを割り当てる場合は､
pointer equals arrayと言えばいいだけです｡
そして､ ループの終了条件は､ ポインタが配列の末尾よりも小さい場合です｡
ポインタはアドレスであり､ 配列の終端もアドレスであることを忘れないでください｡
つまり､ ポインタのアドレスがポインタnより小さければ､ ループの本体を実行したいと言っているわけです｡
ポインターは単なる整数で､ 16進数のアドレスです｡
インクリメントするたびに､ 次のアドレスに移動する｡ エンディングアドレスがあるので､
ループを最後まで回せばいいのです｡
そして､ ポインタ自体をインクリメントして､
配列の次の要素に移動するためのアドレスをインクリメントします｡
そして､ ループの本文の中で､ sumとequalsのポインターを表示しています｡
値を取得するためにポインタを再参照しているのは､
値を取得するためにポインタのアドレスやポインタが指しているアドレスを取得したくないということを覚えているからです｡
そして､ 最後の最後に､ その合計を返す｡ つまり､ これは配列の全要素を合計することになります｡
mainでは､ ファイルの先頭とは対照的に､ 関数の宣言も行っていることにお気づきでしょう｡
私は通常､ ファイルの一番上に置くのが好きです｡
しかし､ メインでは､ 3 7 ネガティブ 9 といった10要素の配列を作成します｡
次に､ 配列または合計を渡し､ サイズを10と指定しています｡
そしてお気づきのように､ これは配列の合計値を返します｡
つまり､ このポイント演算を使って配列を繰り返し処理すると､ 次のアドレスにプラスするだけで､
終了アドレスを見てジャンプアウトすることができるのです｡
これは､ ポインタが終了アドレスを見ることで､ n未満のカウンタを見るのとは対照的に､
より効率的です｡
また､ 定数として作成したのは､ 定数が変化しないため､ コンパイラで効率よく処理できるためです｡
ここでの配列は､ アドレスが変更できない定数ポインタです｡
また､ この例では､ 関数に配列を渡す場合､
単に配列の名前を指定するだけであることにお気づきでしょう｡
だから､ 関数を上げる渡し方はわかっている｡ また､ 配列へのポインタを生成するには､
配列の名前を指定するだけでよい｡
配列の最初の要素をポインタに代入すると､ このようになることを確認しました｡
つまり､ 配列の合計関数を呼び出す際に渡されたのは､
実際には配列の値へのポインタであったということです｡
これは､ 配列とポインタが非常に密接に関係していること､ 配列はポインタ以外の何物でもないことを物語っています｡
また､ 関数の中から配列の要素を変更することができるのも､ このためです｡
なぜなら､ ここまでは関数にデータを渡しても､ ポインタでない限りそのデータはその関数の外では変更されないからです｡
なぜなら､ そのためにscanfのアドレスを渡さなければならないからです｡
関数の外で要素が変更されるのは､ 関数にポインタを渡している場合だけです｡
そして､ その関数の内部で要素が変更されるので､ これはポインタを渡していることを意味します｡
これは､ 配列とポインタが非常によく似ていて､
配列はポインタであり､ 異なる構文を使っているということを改めて証明するものです｡
これから､ 参照渡しの方法､ つまり参照のコピーを渡して､
関数のデータを関数の外側で変更する方法について説明します｡
以前の課題で､ 関数内のデータを変更したいときは､
常にグローバル変数を使用する必要があったのを覚えていますか？
参照渡しの概念を理解していなかったし､
ポインタの概念も理解していなかったからだ｡
しかし､ 先ほどもポインターの利点として､ 関数の内部でデータを変更しても､
その関数の外部で反映されるようになったことを述べました｡
でも､ それはまた別の講義で｡
しかし､ 前の例では､ 配列が本質的にポインタであることを実証しているため､
その概念自体がありません｡
では､ なぜ関数内部の正式なパラメータがポインタであると宣言されていないのか､ 不思議に思われるかもしれません｡
まあ､ そうかもしれませんね｡ int array sum, int star arrayのようにすればいいのです｡
それを配列として宣言する必要はない｡
配列とポインタはint star arrayと言えるほど同じものなので､ 全く問題ありません｡
本当に密接に関係しているとお話しました｡
したがって, 配列の型をint型の配列として配列和関数の中で宣言することもできますし,
int型へのポインタとして宣言することもできます．
そのため､ 引数として配列を受け取る関数や､ 引数が実際にはポインタである関数を多く見かけます｡
したがって､ 関数に渡される配列の要素をインデックス番号で参照する場合は､
パラメータを配列として宣言します｡
これも表記上の都合で､ 括弧と添え字を使うか､
ポインターを使うかの違いに過ぎない｡
配列そのものは､ 通常､ プログラムのメンテナンスの観点から読みやすく､
関数による配列の使用をより正確に反映します｡
でも､ ポインターの方が使いやすいんです｡
また､ 引数を配列へのポインタとして使用する場合は､ 正式なパラメータでポインタ型であることを宣言してください｡
そこで､ 前の例では配列記法を使っていたところを､ 関数のポインタ記法を使った例を紹介します｡
ここで､ int start
array と言うだけで､ この関数はすぐにかなり小さくなることに気づくでしょう｡
また､ 最初の文で代入を行う必要がないため､
ループもかなり小さくなります｡
配列が配列の末尾より小さくなるまで､ と言って配列をインクリメントすればいいのです｡
配列とポインタを理解していれば､
何が起こっているかは一目瞭然なのですが､ コード量は少ししかありません｡
int star配列で渡しているので､ もうその配列を初期化する必要はありません｡
ポインターで渡しているので､ もうポインターを初期化する必要はないのです｡
そして､ 実際に配列の終端のアドレスを定数ポインタである配列終端に設定しているのです｡
これを利用してループを抜け出し､
sum plus equals star arrayと言い､ 間接演算子を使って再び配列を参照し､
sumを返すだけです｡
そして､ main関数は､ 宣言が配列ではなくポインタになった以外は､ 全く同じです｡
このように､ 2つの例は異なる記法を用いていますが､ 同じ機能を提供しています｡
そのため､ 関数パラメータの内部でパラメータとしてポインタを使用することが多く見られます｡
そこで､ ポインターを使うときに有効なものと､ 有効でないものをまとめてみました｡
ここで変数を宣言すると､ earnという配列を宣言し､ 3つの要素が入っていて､
2つのポインタを宣言しています｡
このポインターで､ 演算ができるようになりました｡ ポインターワンプラスプラスと言えるでしょう｡
点や2が1点に等しいとか､ 1＋2が2点に等しいと言うことができるのです｡
ポインター2＝稼ぐ＋1」とも言える｡
ポインタがあって､ アドレスでポインタを修飾しているのだから､ ポイント演算をするのは全く問題ない｡
配列の点演算はできない｡ だから､ 稼ぐプラスアルファとは言えない｡
ポインター2 0と言うことはできません｡ 2プラス0｡ 1は､ それらがアドレスであるため｡
そして､ 実際には配列の中の何かを指しているわけではありません｡
また､ 獲得ポイントや1を掛けて､ ポイントや2を得ることはできません｡
それらは無効です｡
そのため､ ポインタ演算の正しい構文に注意し､ またポイント演算はポインタにのみ適用でき､
配列には適用できないことを理解してください｡
また､ 配列を処理する関数は､ 実際にはポインタを引数として使用します｡
配列処理関数を記述する際に､ 配列表記とポインタ表記のどちらかを選択することができます｡
ですから､ ランダム関数を処理するのであれば､ パラメータにポインタを使っても全く問題ありません｡
どちらかを選択することができます｡
しかし､ ポインターを使用する方が､ 通常はコードが少なく､ 効率的です｡
配列記法を用いると､ 関数が配列を扱っていることがより明確になり､
ある言語でプログラミングを学んだプログラマーにとって､ 配列記法はより親しみやすいものとなります｡
ですから､ 以前やFortran､ Pascalなどのプログラミングをする場合､
矢印表記を使うことがあります｡
他のプログラマーはポインターの扱いに慣れているので､ ポインター表記の方が自然で､
より理にかなっていると感じるかもしれません｡
ポイント演算やポインタ表記を用いることで､ より機械語に近く､
コンパイラによっては､ より効率的なコードになることも､ 利用する理由の一つです｡
では､ これらの例をコードブロックの中に入れて見てみましょう｡
では､ スライドにある例をご覧ください｡ 3行目で関数の宣言をしています｡
配列和である｡
そしてこの例では､ 配列記法を使うことにします｡
そこで､ インデックスを使用する際には､ 括弧を使用することにします｡
そして､ 値の配列を渡して､ サイズを渡して呼び出すことになります｡
つまり､ 配列和関数は､ 整数配列で受け取ります｡ さて､ まず最初にしなければならないことは､
14行目でポイント演算に使用するポインタを作成することです｡
そして､
アドレス配列にサイズを加えた配列の末尾を代入する必要があります｡
これで配列は終わりです｡ これでループを抜けることができます｡
しかし､ 配列へのポインタを代入しなければならない｡
配列の名前を使うだけで､ 配列をポインタに代入して最初の要素を指し示すことができることは知っています｡
operativeのアドレスは使わなくていいんです｡
やろうと思えば､ 実際にこんなことができるんです｡
そして､ これもまたいいのです｡
そして､ ポインターをプラスプラスする｡ ポインタをインクリメントしているのは､ 次のアドレスにインクリメントしているだけだからです｡
配列の次の要素を指しています｡
そして18行目では､ ポインタをデリファレンスしてsumを計算しています｡ sumはsumに等しく､
sumにポインタの値を足したものになります｡
というのも､ mainはint型ではないので､
エラー値を返すのは良いのですが､
単にreturn 0と言うだけだからです｡
mainでエラーがあった場合､
マイナスを返すとか｡ これを実行すると､ 配列の値の合計が実行されます｡
3 + 7 + マイナス 9 など､ 何だかよくわからないけど 21
になっています｡
さて､ この配列パラメータをポインタを取るように変更するには､ ポインタの前にアスタリスクを宣言するだけで､
簡単に変更できます｡
同じように呼んでいます｡ 私たちは修正するだけです｡ もうポインターを宣言する必要はありません｡
おそらく､
より簡単にするために名前を変えたいのでしょうが､ もうこのポインタを宣言する必要はありません｡
やはり配列の最後を取得したいので､
もう代入も必要ありません｡
そこに空っぽのモノがあればいいんです｡ そして､ 実はここでwhileループがあった方が便利かもしれません｡
しかし､ 割り当てがない場合は､ そこにセミコロンを置くだけでよいのです｡
そして今､ 私たちは元気です｡ 我々はまだそれを参照解除したいし､ まだそれを返したいのです｡
しかし､ これをビルドする場合､
関数宣言を変更したので､
ポインタとして定義することを確認したい｡
そして､ これはどんな名前でもいいのですが､ 私たちは一貫してそうしています｡
さて､ ビルドしてみましょう｡
変更したすべての変数を再び使用することを確認します｡
さて､ 無事にビルドできたので､ 実行してみると､ 同じように21という結果が得られました｡
ですから､ このポインタを配列の正式なパラメータとして使用することの違いを理解すれば､
両者を互換的に使用することができます｡
表記上の便宜を図っているだけです｡
しかし､ ポインターを使用すると､ 通常､ コードが少なくなります｡
そして､ より強力に操作でき､ コンパイラによってはより効率的でもあります｡
ありがとうございます｡