こんにちは､ そしてお帰りなさい｡
この講義では､ 参照渡しという概念についてお話したいと思います｡
最初にポインタの話をしたときに､ グローバル変数をあまり使わなくてもいいという利点があると言いました｡
なぜなら､ 関数にポインタを渡すと､ その関数はそのパラメータを使用することができるからです｡
そして､
そのパラメータを変更すると､ 関数の外側でデータが変更されることになります｡
だから､ これは貴重な､ 貴重なコンセプトなんです｡
という話をするんです｡ これは､ どちらかというとコンセプトのようなものです｡ Cは実はこれを採用していない｡
そして､ この方法がどのように機能するのか､ 基本的にいくつかの例を挙げて説明します｡
パス・バイ・バリューという考え方があるんですね｡ これは機能に関係することです｡
関数にデータを渡すという話をしましたが､ コンパイラの言語的には実際にどのように渡しているのでしょうか｡
パス・バイ・バリューという考え方があるんですね｡ そして､ これが今までの話なのです｡
scanf関数以外でも､
関数を呼び出すたびに､ 関数内でそのデータを修正しても､
関数外では修正されなかったのです｡
ローカル変数の話をしましたが､ これはちょっと違う概念です｡
しかし､ 関数にデータを渡すとき､ 2つの方法がある｡
値で渡すことも､ 参照で渡すこともできます｡
Cは専ら値渡しで､ javaも同様で､
すべて値渡しです｡
しかし､
アドレスをコピーするため､ 参照渡しをシミュレートしています｡
そして､ この参照渡しの概念は､ cでは特に採用されていないにもかかわらず､
それをシミュレートしているわけです｡
そこで､
参照渡しの意味とその重要性を理解するために､ この点についてお話します｡
しかし､ 値渡しとは､ 関数が引数の実際の値を関数の正式なパラメータにコピーすることである｡
整数､ 浮動小数点数､ あるいは二重小数点数を引数として関数に渡した場合､
実際の変数はコピーされるため､ 関数の外では変更されないのはこのためです｡
そのため､ 関数内部でパラメータに加えられた変更は､ 引数には影響しません｡
渡される引数は変更されない｡
つまり､ コピーなのでほとんどローカル変数と同じような働きをします｡
実際にどうなっているかというと､ コピーされて関数に渡されているのです｡
Cプログラムでは､ 引数の受け渡しにcall by valueを使用します｡
全てはバリューパスで呼び出す｡
これらの用語は､ 同じ意味で使われています｡
つまり､ 関数内のコードは､ 関数を呼び出すために使用される引数を変更できないことを意味します｡
関数内部で値が変更されていますが､ 変更はありません｡
ローカル変数のようですね｡
そのため､ ある関数から次の関数へデータを共有する場合､
グローバルデータを作成する必要がありました｡
そして､ これがcの仕組みです｡
しかし､ 実際にアドレスのコピーを渡すとどうなるか｡
そうすれば､ 参照渡しを模倣することができる｡
しかし､ ここでは値による受け渡しをどのように使うか､ その例を示します｡
このスワップルーチンは､ 値による受け渡しのデモンストレーションによく使われます｡
なぜなら､ このルーチンが行うのは､ tempを作成し､ 引数に渡されるパラメータを交換することだからです｡
つまり､ 2つの整数を受け取るswapという関数がある場合､
ローカル変数tempを作成する｡
パラメータxをtempに代入すると､ tempはxと等しくなる｡
xとyが等しいと言えば､
今度はyがxと等しくなる｡ yがtempに等しいと言えば､ yはtempに等しくなる｡
しかし､ これらの値を渡してこの関数を呼び出すと､ xとyはこの関数のローカルなものになります｡
これらの変数は､ この関数の外では変更されないようになっています｡
この関数は､ yに代入し､ xに代入しているので､
非常に便利です｡
そして､ xとyは値渡しなのでこの関数のローカルなもので､ この関数の外では変更されないので､
グローバル変数を使う必要があります｡
しかし､ それらは整数である｡ 2つの整数xとyである｡ アドレスではなく､ 整数であることに意味があるのです｡
swapを呼び出して､ これがどのように見えるかを見てみると､
値が変化していないことに気がつくと思います｡
つまり､ a 100 b with 200という変数を作り､
100と200を入れ替える前に変数をプリントアウトしてからswapを呼び出すと､
再びプリントアウトした後に変数が変更されることはないのです｡
では､ コードブロックで見てみましょう｡ これは､ 値による受け渡しを示すものです｡
では､ その例をご紹介しましょう｡ 交換のルーチンがあるんです｡
xとyを渡したデータの値を入れ替えているのです｡
100 200と呼んでいます｡
関数を呼び出す前にプリントアウトするのです｡
後にプリントアウトすることになります｡
そしてお気づきのように､ 変数aとbは決して変化しないのです｡
そして､ その理由は､ 値で渡すことを行っているからです｡
そこで､ このプログラムを実行すると､
スワップ前はaの値が100だったのが､ スワップ後はbの値が200と200で同じであることがわかります｡
そして､ その理由は､ 値で渡したからです｡ 参照渡しをした場合､ それらの変数aやbはswapの呼び出し後に変更されることになります｡
では､ 同じように値による受け渡しではなく､ ポインタをコピーする場合はどうでしょうか｡
つまり､ ポインタと関数はかなり相性がいいのです｡
ポインターは関数の引数として渡すことができ､
また､ 関数はその結果としてポインターを返すことができます｡
その方法も紹介します｡
しかし､ 参照渡しでは､ 引数のアドレスが正式なパラメータにコピーされます｡
このアドレスは､ 呼び出しで使用される実際の引数にアクセスするために使用される｡
つまり､ 関数内部のパラメータに加えられた変更は､
呼び出し側の引数に影響を与えるということです｡
つまり､ 技術的には参照渡しではないのですが､ 参照渡しを模倣､
あるいはシミュレートしているのです｡
アドレスをコピーしています｡ アドレスをコピーすると､ 新しいアドレスとそのアドレスの値のメモリができます｡
しかし､ そのアドレスに割り当てようとすると､ そのアドレスに割り当てられてしまう｡
だから､ 値で渡されても､ コピーしているんです｡
また､ 通常の整数をコピーする場合は､ 変更を加えることはありません｡ アドレスをコピーしている場合､ アドレスはメモリ上にあるので､
変更を加えるとそのアドレスに変更を加えることになります｡
そこで､ 値を参照渡しするために､
他の値と同じように引数ポインタを関数に渡します｡
関数のパラメータをアスタリスク付きのポインタ型として宣言する必要があります｡
関数内部の変更は､ 関数外部にも反映される｡
グローバル変数をあまり使わなくてもいいんです｡
変更が関数の外に反映されない値による呼び出しとは異なります｡
まだコピーしている......値で渡している......アドレスのコピーの値だけだ｡
だから､ これは理解すべき重要な概念です｡
さて､ swap関数を作るときに､
関数内の宣言をアスタリスク､ スター､ スターとして作成することにします｡
それは､ この関数はポインターを受け取るということです｡
さて､ この関数内で代入を行う場合､ 値を代入したいので､ 実際にはインダイレクト演算子を使って参照を解除しなければなりません｡
そのxが指し示す値をtempに割り当てることになる｡
xが指す値をyが指す値に代入して､
yが指す値をtempに代入するんだ｡
Tempは整数なので､ デリファレンスする必要はない｡
ここで､ a100とb200にaとbを渡して呼び出すと､
aとbはポインタではないので､ 関数はポインタを受け取るのでアドレスを渡さなければなりません｡
ポインターを受け取る関数で､ ポインターを宣言しない場合は､ 必ずアドレス演算子を使わなければなりませんが､
これはscanfの使い方と同じことです｡
しかし､ swapを呼び出した後にこれを呼び出すと､
変数aとbが変更され､ スワップされていることにお気づきでしょう｡
Aは200 Bは100になる｡
では次に､ コードブロック内のポインタへのスワップメソッド変更でこの例を見てみましょう｡
コードブロックになった swap関数がポインタを取るように修正した｡
そのため､ 代入する際には参照を解除する必要があります｡
また､ a,bのアドレスは整数となり､ swapはポインタを取るようになったので､
そのアドレスも渡すようにしました｡ そこで､ これを実行すると､ call and
afterが変更されるようになったことがわかります｡
つまり､ swapを呼び出す前のaは100に等しく､ swapを呼び出した後は200になります｡
bのスワップを呼ぶ前に､ 200円です｡ その後､ 100です｡
つまり､ アドレスのコピーを作っているので､ 関数内の変数の値を変更することができるのです｡
非常に重要であり､
ポインタの大きな利点の一つです｡
では､ 構文をまとめてみましょう｡ もうすでにやったが､ もっと詳しく見ていこう｡
関数には､ 変数に関する2種類の情報を伝達することができます｡
xの値を送信することができ､
関数はxと同じ型で宣言する必要があります｡
それは分かっています｡ 関数にデータを渡すときは､
呼び出すときに必ず同じ型を持っていなければならない｡
そして､ ここではそれを呼び出すためのint function1があり､ それはint numとして定義されています｡
そのため､ 関数内でxの値を送信する際には､ 同じ型で宣言する必要があります｡
そして､ 実際にxのアドレスを呼び出す場合は､ 関数定義に正しい型へのポインタを含める必要があります｡
ですから､ ポインターを使うときはいつでも､ データを渡すときにそのデータがポインターでない場合は､
そのアドレスを取得しなければならないことを確認しなければなりません｡
これが要約すると､ ポインタを取らない関数を定義すれば､ それを呼び出してデータを渡せばいいということです｡
ポインターでないことを確認する｡
アスタリスクポインタを取る関数がある場合､ それを呼び出すときは､
addまたはsub演算子で呼び出す必要があります｡
アドレスを渡す必要があります｡
それが構文です｡
定数キーワードを使用して､ 関数パラメータを修飾することもできるようになりました｡
これは､ この関数がこのパラメータに渡された引数を定数として扱うことを示しています｡
パラメータがポインタのときのみ有効です｡
なぜなら､ 値で渡しているのであって､ ポインターを渡していないのであれば､
関数の外側の変更には影響しないので､ なぜ定数にするのか､
変更しても誰も気にならないからです｡
ポインタを定数にするのは､ 関数外の値が変化するのが嫌だからです｡
そして､ ポインタである引数にconstantキーワードを適用すると､
その引数が指す値を関数が変更しないことを指定することができるのです｡
とても重要なことです｡
この例では､ メッセージの送信関数があります｡ pメッセージポインタを取る｡
char starの前に定数キーワードを入れています｡
つまり､ このタイプのパラメータpメッセージは､ 定数文字へのポインタである｡
不変なのは文字値であって､ そのアドレスではありません｡
つまり､ この場合､ 私たちがやっていることは､ 指されているポインタを変更することはできないということです｡
アドレスは変更できても､ 値を指しているものは変更できない｡
構文は､ 他のポインタに定数を作成するときと同様です｡
一定のキーワードは最初に表示されるもので､ その後､ 値を変更することはできません｡
ここで､ ポインタそのものを定数と指定することもできますが､
アドレスは値で渡されるため､ これはほとんど意味を持ちません｡
呼び出し側の関数でレンダーポインターを変更することはできません｡
だからこれは､
アドレスが変更できないようにしようとしたら､ 値で渡されるから意味がない､ というだけの話です｡ つまり､ その特定のアドレスを保持するために､
実際には新しいメモリロケーションを作っているわけです｡
だから､ 変えても外見上は変わらないから気にしないでね｡
パラメータを定数にできるのは､
それが指すものの値を変えないようにしたいだけで､ 元のポインタと呼び出し側の関数はコピーで渡される値で渡されるので､ どうせ変えられないのです｡
コンパイラは､ 定数データへのポインタである引数が安全であることを知っているので､
これには利点がある｡ 値が指し示すものを誤って変更することはありません｡
そのため､ 関数の外側に変更を反映させたくない場合は､ 再度この操作を行うことがあります｡
パラメータの引数として定数データへのポインタを渡した場合､
上記のようにパラメータを使用する必要があります｡
関数を呼び出して定数に渡す場合､ 関数宣言も定数である他のポインタと同じように､
定数文字ポインタを取らなければならないのです｡
それらは定義されなければならず､ 定義されたものはプロトタイプと一致しなければならない｡
関数からポインタを返す｡
関数からポインタを返すのは､ 特に強力な能力です｡
単一の値だけでなく､ 一連の値全体を返す方法を提供します｡
ポインターの利点は､ 関数からより多くのデータを返せるようになったことです｡
従来は､ 1つの値しか返せませんでした｡
しかし､ ポインタの場合は､
ポインタがまだ説明していない構造体を指していたり､
他のものを指していたりするので､
複数の値を返すことができます｡
ポインターを返す関数を宣言する必要があります｡ だから､ 関数の宣言は一致させなければならない｡
それはとても簡単なことなんです｡ 関数を宣言するときや定義するときにアスタリスクを使い､
そのポインタが指しているものを戻り値として持つ､ というだけのことです｡
そのため､ ポインタの返送には特有の危険性がありますが､ 注意してください｡
引数が指す変数に干渉しないよう､ ローカル変数を使用する｡
ポインターを返すときにやや問題があるのは､ 関数内部でそのポインターを変更してから返すと､
意図していない動作をする可能性があるからです｡
そのため､ 引数が指す変数に影響を与えたくない場合は､ 常にローカル変数を使用するようにしてください｡
そして､ この講義では､ 関数にポインタを渡す方法と､
関数からポインタを返す方法について説明しました｡
そして､ このポインタを関数に渡すという仕組みを使うことで､
アドレスをコピーしているので､ 参照渡しを実演したり､
模倣したりすることができます｡
そして､ アドレスをコピーしたときに､ そのアドレスに書き込むと､
そのアドレスを指しているものが更新されます｡
これは､ 関数内のデータの値を変更すると､ 関数外に反映されることを効果的に意味します｡
スワップルーチンを使って､ それを実証するのです｡
これは非常に強力なコンセプトです｡ そして､ 参照渡しを真似したい場合は､
ポインタを使えばいいし､ 関数の外で変更したい場合は､ そうしたいですよね｡
ありがとうございます｡