「C言語/関数」の版間の差分

削除された内容 追加された内容
Ef3 (トーク | 投稿記録)
→‎構造体を使う方法: s/strcpyは、文字列をコピーするための命令/strcpyは、文字列をコピーするための関数/、Fix typo
タグ: 2017年版ソースエディター
Ef3 (トーク | 投稿記録)
→‎swap関数: 外部のスコープのインスタンスがより内側のスコープで同じ識別子を持つインスタンスによって隠されてしまう事を、シャドーイング(Shadowing)と呼ぶ。s/#include "stdio.h"/#include <stdio.h>/、{{コラム|新たに変数を用意せず2つの変数を入れ替える }}
タグ: 2017年版ソースエディター
646 行
<syntaxhighlight lang="C">
//例 誤ったswap関数
#include "<stdio.h">
 
// グロ-バル変数を使って、交換用の変数をswap外部と共有しようと意図しているが・・・
653 行
int b;
 
void swap(int a, int b)
{
x = a;
a = b;
659 ⟶ 660行目:
}
 
int main()
{
int a = 1, b = 2;
b = gb2;
printf("swap前のa=%d, b=%d\n", a, b);
swap(a, b);
675 ⟶ 678行目:
と表示され、なにも交換されていない。
 
こう失敗する原因は、呼び出され側のswap()の引数で用いられている値を格納する変数(上記コード例の<code> swap(int a,int b) </code> のaおよびb )は、必ず型をつけて再宣言しないといけないのでローカル変数ってしまうのでその変数をグローバル変数に出来なの a ,b が隠されてしまってからである。
たとえグローバル領域で「a」「b」と同名の変数があっても、swap関数内では同名なだけの別変数としてのローカル変仮引数「a」「b」が新たに関数swap内では用意されてしまう。
 
:この様に、外部のスコープのインスタンスがより内側のスコープで同じ識別子を持つインスタンスによって隠されてしまう事を、シャドーイング(Shadowing)と呼ぶ。
こう失敗する原因は、呼び出され側のswap()の引数で用いられている値を格納する変数(上記コード例の<code> swap(int a,int b) </code> のaおよびb )は、必ず型をつけて再宣言しないといけないのでローカル変数になってしまうので、その変数をグローバル変数に出来ないから、である。
 
たとえグローバル領域で「a」「b」と同名の変数があっても、swap関数内では同名なだけの別変数としてのローカル変数「a」「b」が新たに関数swap内では用意されてしまう。
 
なお、swap関数の引数を定義する際に、(再宣言しないつもり等で)型をつけない事はできない。
 
 
ユーザ定義関数内で変数の値を交換させるためには、一例として、下記のようにポインタを使ってmain関数の変数a,bを書き換える方法がある。
687 行
<syntaxhighlight lang="C">
//例 正しいswap関数
#include "<stdio.h">
 
void swap(int *a, int *b)
{
int x = *a;
x = *a;
*a = *b;
*b = x;
}
 
int main()
{
int a = 1, b = 2;
printf("swap前のa=%d, b=%d\n", a, b);
711 ⟶ 712行目:
と表示される。
 
ポインタを使うと成功する理由は、main実引関数のローカル変数のアドレスが仮引数にコピーされたため、swap関数がmain関数のローカル変数a,bにアクセスできているからであるつまり、ポインタを介したswap関数内のa,bはもはやローカル変数だけなくグローバル変数などアクセスできる
 
<!-- ポインタを使った例は「参照渡し」ではなく「ポインタの値を用いた値渡し」である。(C言語に参照渡しはない)。これを間違えると C++ で本物の参照渡しに出会ったとき混乱する。
 
なお、
718 ⟶ 720行目:
:ポインタを使った例「正しいswap関数」のように実引数のアドレスを仮引数にコピーすることを''参照渡し''と呼ぶ。
<ref>値渡しと参照渡しという用語はよく使われるが、『JISX3010:2003』には出てこない。</ref>
---->
 
 
----
ユーザ定義関数を何も書かずに全てmain関数に書く方法を使えば、ポインタを使わずとも交換できる。
 
標準C言語では考える必要は無いが、別のプログラム言語だと、言語によってはポインタが標準設定では使用禁止されていたり(たとえばC#がそうである)、そもそもポインタに相当する機能の無い言語も考えられる<ref>最近は Fortran ですらポインタがあるのでポインタのない言語は非常に限られ、ポインタがなくてもVariant型に類する型がある場合が多い。</ref>。そのような場合でも、下記コードのようなアルゴリズムで、変数の交換は可能である。
 
<syntaxhighlight lang="C">
//例 swap関数を使わない場合
#include "<stdio.h">
 
int main()
{
int a = 1, b = 2;
printf("swap前のa=%d, b=%d\n", a, b);
int x = a;
x = a;
a = b;
b = x;
748 ⟶ 749行目:
</pre>
 
この処理を、マクロを使って一般化すると
 
----
なお、やや技巧的だが、下記コードのように書けば、ポインタも使わずに、ユーザ定義関数を使ったコードで、変数を交換できるが、処理の一部のmain関数にゆだねなければならなくなる欠点がある。
 
<syntaxhighlight lang="C">
//例 ポインタマクロを使わないswapった場合
#include "<stdio.h">
#define swap_int(a,b) do {int __temp=a;a=b;b=__temp;}while(0)
 
int x;main()
{
int ga; // swap内のローカル変数aとの区別のため、新しく用意する
int a = 1, b = 2;
int gb; // swap内のローカル変数bとの区別のため、新しく用意する
 
int a;
int b;
 
void swap(int a,int b) {
x = a;
ga = b;
gb = x;
 
int main(){
a = 1, b = 2;
printf("swap前のa=%d, b=%d\n", a, b);
swap(a, b);
swap_int(a,b);
a = ga;
b = gb;
printf("swap後のa=%d, b=%d\n", a, b);
}
 
</syntaxhighlight>
※ 一度も反復しない<syntaxhighlight lang="C" inline>do { ... } while(0)</syntaxhighlight>は、変数__temp のスコープを切るため。
 
<pre>
swapのa=1, b=2
swap後のa=2, b=1
</pre>
 
{{コラム|新たに変数を用意せず2つの変数を入れ替える|2=
上記コードの発想は、ユーザ定義関数swapでは、どうあがいても引数でa,bを再宣言してしまうので、別途、グローバル変数ga,gbを用意するという工夫で切り抜けている。
 
だが、上記コードのswap内では、どうあがいてもa,bに代入してからmainにa,bを持ち帰る事ができないので、main関数側でga,gbの内容をa,bに代入するという方法で切り抜けるという方法である。
 
 
しかし、上記の方法は、かなり技巧的である。いっそ全てmain関数に書いてしまうほうがラクだろう。
 
----
なお、ポインタも使わず、グローバル変数を使わずに下記のようにswap関数を書いても、交換できない。
 
<syntaxhighlight lang="C">
#include "<stdio.h">
//例 誤ったswap関数その2 (グローバル変数を用意しない場合)
#include "stdio.h"
 
int main(){
void swap(int a, int b){
{
int x;
int a = 1, b = 2; /* a の初期値を A、b の初期値を B として... */
x = a;
a = b;
b = x;
}
 
int main(){
int a = 1, b = 2;
printf("swap前のa=%d, b=%d\n", a, b);
a ^= b; /* a = A ^ B */
swap(a, b);
b ^= a; /* b = B ^ A ^ B ... A */
a ^= b; /* a = A ^ B ^ A ... B */
printf("swap後のa=%d, b=%d\n", a, b);
}
</syntaxhighlight>
※ <code>^</code>は、排他的論理和演算子。
 
結果
 
swap(前のa=1, b);=2
<pre>
swapのa=12, b=21
}}
swap後のa=1, b=2
</pre>
----
 
=== 関数の引数として配列を渡す ===