ブートキャンプ目次
(1)iPhone/iPadアプリケーションを作るには?
→無料開発者登録
(2)プログラミング言語とは何か?
(3)Xcodeを使ったC言語の学習
(4)C言語ソースの解析1/2
(5)C言語ソースの解析2/2
(6)自分で関数を作り、利用する1/2
(1)iPhone/iPadアプリケーションを作るには?
→無料開発者登録
(2)プログラミング言語とは何か?
(3)Xcodeを使ったC言語の学習
(4)C言語ソースの解析1/2
(5)C言語ソースの解析2/2
(6)自分で関数を作り、利用する1/2
ちょっとだけ横道にそれて、変数についての話をしましょう。
グローバル変数は、ローカル変数のように影響範囲が関数内に限られず、どの関数からも変更可能なので、関数をたくさん呼び出す複雑なプログラムを作った時に、どの関数で、どのグローバル変数が変更されるかを把握しなくてはならず、管理が大変です。
int result;
void a(void)
{
・・・ 何か複雑な処理。
result = 0; ← その課程でresultが0になる。
}
void factorial(void)
{
result = 3 * 2 * 1;
}
int main (int argc, const char * argv[])
{
factorial(); ← resultは3の階乗になる。
a(); ← 階乗とは無関係の別の処理をする関数。その課程で副次的にresultが0になる。
printf("factorial 3 =%d\n", result); ← resultは3の階乗ではなく0になっている。
return 0;
}
void a(void)
{
・・・ 何か複雑な処理。
result = 0; ← その課程でresultが0になる。
}
void factorial(void)
{
result = 3 * 2 * 1;
}
int main (int argc, const char * argv[])
{
factorial(); ← resultは3の階乗になる。
a(); ← 階乗とは無関係の別の処理をする関数。その課程で副次的にresultが0になる。
printf("factorial 3 =%d\n", result); ← resultは3の階乗ではなく0になっている。
return 0;
}
サンプルプロジェクト:study-9-factorial-global.zip
また、分割コンパイルのところで、別ファイルに記述された関数が使えると説明しましたが、グローバル変数も同じように宣言する事で、別ファイルの関数から利用できます。こうなるとますます管理が難しくなります。
![$テン*シー*シー-9](https://stat.ameba.jp/user_images/20120118/17/xcc/55/4b/j/o0550035611741745322.jpg?caw=800)
グローバル変数は、外部のファイルからも使える変数なので、外部変数とも呼ばれます。
スコープ
このような関数や変数の影響範囲をスコープと呼びます。
グローバル変数は、最大級のスコープを持つわけです。ローカル変数は関数内がスコープとなります。
static変数
グローバル変数も関数のように型の前にstaticを付ける事で、スコープをファイル内に制限する事が可能です。
![$テン*シー*シー-10](https://stat.ameba.jp/user_images/20120118/17/xcc/c4/52/j/o0552046911741750482.jpg?caw=800)
これで、少しだけグローバル変数が管理しやすくなります。
いずれにしろ、グローバル変数は、本当に必要かどうか(関数の引数、戻り値で解決できないかどうか等)、慎重に考えてから利用するべきです。
関数内static変数
関数内の変数宣言でstaticを使うと、関数内での宣言でありながら、ヒープ領域にメモリ区画が確保され、アプリケーションが終了するまでメモリに存在しつつ、スコープは関数内のみという変数を用意できます。
これを利用すると、以下のcountup関数のような呼び出すたびにカウントを1つ増やす関数も作れます。
void countup(void)
{
static int count = 0;
count = count + 1;
printf("count =%d\n", count);
}
int main (int argc, const char * argv[])
{
countup(); ←画面に count=1 を出力
countup(); ←画面に count=2 を出力
countup(); ←画面に count=3 を出力
return 0;
}
{
static int count = 0;
count = count + 1;
printf("count =%d\n", count);
}
int main (int argc, const char * argv[])
{
countup(); ←画面に count=1 を出力
countup(); ←画面に count=2 を出力
countup(); ←画面に count=3 を出力
return 0;
}
サンプルプロジェクト:study-10-countup.zip
![$テン*シー*シー-11](https://stat.ameba.jp/user_images/20120118/17/xcc/74/f5/j/o0516024911741750484.jpg?caw=800)
最初のcountへの代入はアプリケーション起動時に1度だけ実行されます。
グローバル変数やstaticの付いた変数は、このようにしてアプリケーション起動時の初期値を設定可能です。
static int count = 0;
もし、staticを付けていない、ただのローカル変数の場合は、関数が呼ばれるたびにcountへ0が代入されます。つまり
int count = 0;
だと
int count;
count = 0;
count = 0;
の省略形という意味になります。
次の
count = count + 1;
は
count ←count + 1
というcountの値に1を加算して、countに代入する処理です。
= による左側の変数への代入は最後におこなわれます。
例えば、以下のように書いた場合、= の右側の計算での変数aの値は すべて同じです。
![$テン*シー*シー-12](https://stat.ameba.jp/user_images/20120118/17/xcc/62/0f/j/o0374010111741750483.jpg?caw=800)
グローバル変数、static変数の適切な使い分けは、場数を踏んで体得するしかありません。いろいろなサンプルプログラムを読んだり、自分で書いたりして経験を積んでいってください。
それでは階乗計算の話に戻ります。
引数によるfactorial関数の拡張
今度は、factorial関数にint型の引数を一つ用意して、この値に対して階乗計算をおこない、その結果を戻すようにしてみましょう。
int factorial(int seed) ←引数seedを用意
{
int result;
seedの階乗計算をしてresultに代入。
return result;
}
int main (int argc, const char * argv[])
{
int result;
result = factorial(3); ←階乗したい値を引数で渡す
printf("factorial 3 =%d\n", result);
return 0;
}
{
int result;
seedの階乗計算をしてresultに代入。
return result;
}
int main (int argc, const char * argv[])
{
int result;
result = factorial(3); ←階乗したい値を引数で渡す
printf("factorial 3 =%d\n", result);
return 0;
}
サンプルプロジェクト:study-11-factorial-dowhile.zip
seedという引数を階乗するようにします。
ループ処理(do ~ while)
そのためには引数seedを1になるまで一つずつ減らし、1になったら 終わらせるというループ処理が必要です。
このようなループ処理制御には
![$テン*シー*シー-13](https://stat.ameba.jp/user_images/20120118/17/xcc/48/93/j/t02200014_0390002511741754735.jpg?caw=800)
という制御文を使います。
![$テン*シー*シー-14](https://stat.ameba.jp/user_images/20120118/17/xcc/da/37/j/o0235025411741754734.jpg?caw=800)
ここで現れた条件式は、条件分岐式で説明した条件式です。条件式の結果がfalseの時、ループが終了します。したがって今回なら条件式に
seed > 1
と書けば、seedの値が1より大きい場合にループし続ける処理が書けることになります。
このループの中で、引数seedの値を1減らす処理をおこなえば、やりたかったループ処理が実現でき ます。
引数seedの値を1減らすには、先にやった四則演算と代入処理を組み合わせを使います。
seed = seed - 1
これで
seed ← seed - 1の計算結果
という処理がおこなわれseedの値が更新されていくことになります。
![$テン*シー*シー-15](https://stat.ameba.jp/user_images/20120118/17/xcc/f2/54/j/o0525022711741754736.jpg?caw=800)
あとは、以下のようにresultの初期値をseedにしておいてから、ループ中のseedの値をresultに掛け合わせていけば、階乗の計算ができます。
![$テン*シー*シー-16](https://stat.ameba.jp/user_images/20120203/00/xcc/a4/0a/j/o0543026811770977919.jpg?caw=800)
さまざまな演算子
代入と四則演算を合成した演算子を使うと、factorial関数は、もう少し省略した記述も可能です。
int factorial(int seed)
{
int result = seed;
do {
seed--;
result *= seed;
} while (seed > 1);
return result;
}
{
int result = seed;
do {
seed--;
result *= seed;
} while (seed > 1);
return result;
}
まず、変数の宣言と直後の代入
int result;
result = seed;
result = seed;
は、static変数の時に説明したように合成でき
int result = seed;
とできます。
int型変数の値を1つ減らしたり、増やしたりする場合は
++ 1つ増やす
-- 1つ減らす
-- 1つ減らす
という演算子が利用でき
seed = seed - 1;
は
seed--;
と記述できます。
result = result * seed;
は
+= 左の変数に右の計算式の結果を足したものを代入。
-= 〃 引いたものを代入。
*= 〃 かけたものを代入。
/= 〃 割ったものを代入。
%= 〃 割ったあまりを代入。
-= 〃 引いたものを代入。
*= 〃 かけたものを代入。
/= 〃 割ったものを代入。
%= 〃 割ったあまりを代入。
の*=が使え
result *= seed;
とできます。
--演算子(++も同じ)は、少し特殊で 変数の前に付けるか、後ろに付けるかで意味が変わります。
seed--;
result *= seed;
result *= seed;
の場合は
result *= --seed;
と合成できます。
result *= seed--;
の場合、
result *= seed;
seed--;
seed--;
という意味になるので気をつけてください。
もう少しだけ短くなるわけです。
int factorial(int seed)
{
int result = seed;
do {
result *= --seed;
} while (seed > 1);
return result;
}
{
int result = seed;
do {
result *= --seed;
} while (seed > 1);
return result;
}
短さを求めるなら、フリーフォーマットを利用して以下のように書いても問題な くコンパイルできます。
int factorial(int seed){int result=seed;do{result*=--seed;}while(seed>1);return result;}
しかし、これが見た目に読みにくい事は一目瞭然です。
後で読み解くのに苦労するような書き方は避けるべきでしょう。
また、最初で説明 した注釈文を豊富に入れておく事も非常に意味のある事です。
それと、result *= --seed;より元の2行の方が見やすいかもしれません。
/*
自然数の階乗、引数seedの階乗計算の結果を返す。
*/
int factorial(int seed)
{
int result = seed; // 階乗計算のためにseedで初期化
do {
seed--; // ひとつ小さくする
result *= seed;
} while (seed > 1); // seedが1になるまでループ
return result;
}
自然数の階乗、引数seedの階乗計算の結果を返す。
*/
int factorial(int seed)
{
int result = seed; // 階乗計算のためにseedで初期化
do {
seed--; // ひとつ小さくする
result *= seed;
} while (seed > 1); // seedが1になるまでループ
return result;
}
// の他に /* と */ で囲んだ部分も注釈にできます。
/* と */の間は複数行にわけて記述もできます。どちらを使うかは自由です。
/* と */の間は複数行にわけて記述もできます。どちらを使うかは自由です。
factorial関数を、数学の定義
0!=1 0の階乗は1
にも対応させてみましょう。
これを計算式にする事は不可能です。このような時は、先の条件分岐を使います。
/*
自然数の階乗、引数seedの階乗計算の結果を返す。
*/
int factorial(int seed)
{
if (seed == 0) { // seedが0の時は無条件で1を返す
return 1;
}
int result = seed; // 階乗計算のためにseedで初期化
do {
seed--; // ひとつ小さくする
result *= seed;
} while (seed > 1); // seedが1になるまでループ
return result;
}
自然数の階乗、引数seedの階乗計算の結果を返す。
*/
int factorial(int seed)
{
if (seed == 0) { // seedが0の時は無条件で1を返す
return 1;
}
int result = seed; // 階乗計算のためにseedで初期化
do {
seed--; // ひとつ小さくする
result *= seed;
} while (seed > 1); // seedが1になるまでループ
return result;
}
サンプルプロジェクト:study-12-factorial-dowhile-2.zip
引数seedが0の時は、即座に関数を終わらせ1を返します。このようにreturn処理は関数の終わりだけでなく、どこにでも書く事ができます。
引数や戻り値で負の値を扱わないという事を明示するのもひとつの考えです。
unsigned int型
以下のようにintの前にunsignedを付ける事で、seedやresult、戻り値を負の数を扱わない型にできます。
![$テン*シー*シー-17](https://stat.ameba.jp/user_images/20120118/17/xcc/2c/d7/j/o0272006211741757409.jpg?caw=800)
unsigned int factorial(unsigned int seed)
{
if (seed == 0) { // seedが0の時は無条件で1を返す
return 1;
}
unsigned int result = seed; // 階乗計算のためにseedで初期化
do {
seed--; // ひとつ小さくする
result *= seed;
} while (seed > 1); // seedが1になるまでループ
return result;
}
{
if (seed == 0) { // seedが0の時は無条件で1を返す
return 1;
}
unsigned int result = seed; // 階乗計算のためにseedで初期化
do {
seed--; // ひとつ小さくする
result *= seed;
} while (seed > 1); // seedが1になるまでループ
return result;
}
ただし、Xcodeの初期設定ではunsigned int型の引数に、負の値を渡しても注意はされません。また、int型やunsigned int型で大きな数の階乗計算をすると、すぐに記憶できる値の範囲を超えてしまうでしょう。その場合はfloat型を使うことになります。
ループ処理(while)
do~whileループ以外に2つのループがあり、こちらを使う事も可能です。
whileループは判定がループの最初におこなわれます。
![$テン*シー*シー-18](https://stat.ameba.jp/user_images/20120118/17/xcc/e5/c5/j/o0399039411741757411.jpg?caw=800)
今回の場合、うまく工夫すると、最初に判定する点でdo~whileループよりスッキリした記述になりま す。
unsigned int factorial(unsigned int seed)
{
unsigned int result = 1; // 階乗計算のために1で初期化
while (seed > 1) { // seedが1より大きい間ループ
result *= seed;
seed--; // ひとつ小さくする
}
return result;
}
{
unsigned int result = 1; // 階乗計算のために1で初期化
while (seed > 1) { // seedが1より大きい間ループ
result *= seed;
seed--; // ひとつ小さくする
}
return result;
}
サンプルプロジェクト:study-13-factorial-while.zip
ループ処理(for)
初期化処理や、ループ1回ごとにおこなう処理を記述できるforループもあります。
![$テン*シー*シー-19](https://stat.ameba.jp/user_images/20120118/17/xcc/dc/51/j/o0558029911741761426.jpg?caw=800)
whileループによく似ていますし、ループ初期化、ループ1回ごとにおこなう処理が記述できるので、さらにシンプルに記述できそうですが、以下の記述だとスコープの問題が発生します。
unsigned int factorial(unsigned int seed)
{
for (unsigned int result = 1; seed > 1; seed--) {
result *= seed;
}
return result;
}
{
for (unsigned int result = 1; seed > 1; seed--) {
result *= seed;
}
return result;
}
上記のままだとreturnで戻すresult変数が見つからないというエラーになるのです。
![$テン*シー*シー-20](https://stat.ameba.jp/user_images/20120118/17/xcc/11/cd/j/o0484006411741761425.jpg?caw=800)
注意と同じようにアイコンをクリックすると詳細がわかる。
これは、変数宣言で説明したスコープの問題で、forループのループ初期化で
for (unsigned int result = 1;
とresultを変数宣言すると、このresultはforループ内だけのスコープになってしまうためです。
したがって、今回の場合、result変数宣言はforループ外でおこない、for ループ初期化処理は空にする必要があります。
for ループ初期化処理を空にするには
for (;
と記述します。
unsigned int factorial(unsigned int seed)
{
unsigned int result = 1;
for (; seed > 1; seed--) { ←初期化処理を何も書かずに ; を書く。
result *= seed;
}
return result;
}
{
unsigned int result = 1;
for (; seed > 1; seed--) { ←初期化処理を何も書かずに ; を書く。
result *= seed;
}
return result;
}
サンプルプロジェクト:study-14-factorial-for.zip
その時その時で最適なループを選んで利用してください。
ループ処理は、if同様、処理が1つなら{}を省略もできます。
for (; seed > 1; seed--)
result *= seed;
result *= seed;
以上で階乗関数の作成はおしまいです。
次のパートでは、ここまでにならったループ処理や条件分岐処理、変数を使って文字列を加工してみましょう。
forループに限らず、関数内で{}を使う事で、一時的なスコープを作り出せます。例えば以下のように記述された場合
![テン*シー*シー-21](https://stat.ameba.jp/user_images/20120118/17/xcc/8a/69/j/o0417020911741761428.jpg?caw=800)
引数のseedと、{}内のseedは、まったく別物になります。
とうぜん、{}内ののseed = 100は、forループのseedに何の影響を与える事もなく、factorial関数は正常に計算をおこなう事になります。
![テン*シー*シー-22](https://stat.ameba.jp/user_images/20120118/17/xcc/cb/aa/j/o0460028011741761427.jpg?caw=800)
上の例のような記述をする人はいないと思いますが、スコープによる変数の影響範囲には絶えず気を使うようにしてください。
![テン*シー*シー-21](https://stat.ameba.jp/user_images/20120118/17/xcc/8a/69/j/o0417020911741761428.jpg?caw=800)
引数のseedと、{}内のseedは、まったく別物になります。
とうぜん、{}内ののseed = 100は、forループのseedに何の影響を与える事もなく、factorial関数は正常に計算をおこなう事になります。
![テン*シー*シー-22](https://stat.ameba.jp/user_images/20120118/17/xcc/cb/aa/j/o0460028011741761427.jpg?caw=800)
上の例のような記述をする人はいないと思いますが、スコープによる変数の影響範囲には絶えず気を使うようにしてください。