int型
引数名argcに指定されているint型は、通常メモリ上で4区画(4バイト)を占有し、-2147483648~2147483647までの整数を表現します。
2進数での負の値の扱いは、ここでは説明しません。32ビットは、負の領域まで加味するなら-2147483648~2147483647、加味しないなら0~4294967295の数値を表現できると考えてください。
興味のあるかたは「2の補数」をキーワードにしてインターネットを検索してみてください。
興味のあるかたは「2の補数」をキーワードにしてインターネットを検索してみてください。
char型
引数名argv側はもう少し複雑です。
まず、char型はメモリ上で最小単位1区画を占有し、主に文字1つ分の記憶に利用されます。
C言語の文字列
char型は、これを連続で確保し、最後のchar型メモリ区画に0を付ける事で文字列を表現する事にも使います。
例えば以下のようにchar型の区画を4つ、メモリに用意し、65、66、67、0を入れる事で
ABC
という文字列を表現します。これはC言語での文字列表現のルールです。
このルールに従ったchar型の連続したメモリ区画をC文字列と呼びます。
ポインタ型
そして、int型にもchar型にも、後ろに「*」(アスタリスク)を付けたint*型、char*型という型が存在します。これはその型の大きさで占有したメモリ区画の先頭番地を記憶する型です。
そのため、どの型に*を付けても占有区画の大きさは、番地を記憶するために必要な8バイト(Mac OS Xの場合)になります。
メモリ区画の番地を指し示す値が入る事から、最後に*が付いた型を総称してポインタ(指示棒)型と呼びます。
2系統の引数の渡し方
int型やchar型の引数は、スタック領域に型が必要とする大きさの区画が確保され、関数に渡したい値が格納され、呼び出された関数はその値を参照します。
引数はスタック領域なので関数から戻ると同時に取りはぶかれます。
これに対し、ポインタ型であるint*型やchar*型の引数は、スタック領域に番地を記憶するために必要な大きさの区画が確保され、メモリ上のどこかをさす番地が格納されます。呼び出された関数は引数に設定された番地を探し、そこに設定された値を参照します。
引数はスタック領域なので関数から戻ると同時に取りはぶかれますが、引数が指し示していた番地は、取りはぶかれません。
このため、ポインタ型引数は、関数で加工させたいデータを渡すのに利用されたりします。
また、先のC文字列を引数で渡す時にもポインタ型が利用されます。
C文字列は終端を意味する0をどこに置くかにより、100文字、時には1万文字という大きさのものになるので、直接引数として渡すより、char*型で文字列の先頭番地を渡した方が効率がいいからです。
実際の利用方法は、後のパートで学習します。
const修飾子
当然、ポインタ型を引数にもらった関数は、ポインタ型が指し示す番地の値を書き換える事ができるわけです。
しかし、呼び出した関数側には値を見る事だけ許し、書き換えるのは許したくない場合(長文のC文字列のように、単に効率の点からポインタ型にした等)もあります。
その場合につけるのがconst修飾子です。
main関数のargv引数は、const修飾子が付いているので、main関数は、その番地の内容を見ることができるが、変更する事はできないわけです。
const修飾子がついたポインタ型が示す番地に対し、書き込みをおこなおうとすると、コンパイラが注意してきます。ただし、あくまでコンパイラが注意するだけであり、物理的に書き込みができなくなるわけではありません。
↓
↓
配列
ところで、argvは後ろに[](ブラケット)が付いています。これはargvは単独ではなく、const char*型のデータが、連続して複数存在する事を意味します。
このようにメモリ上に順に並んだ同じ型の記憶領域群を配列と呼びます。
それぞれの配列要素には、以下のように[]の中に添字(0から始まる)を付ける事で順にアクセス可能です。
そして、このargv配列要素数がいくつであるかを示しているのが、argcというわけです。
argv[1]にターミナルでstudyの後に続けた文字列が入っていた事を思いだしてください。
…/study myname
条件分岐式
argv[1]が用意されているのは、argcが1より大きい時だけです。argcが1以下の時のargv[1]の値は、でたらめです。そのため条件分岐式を使ってargcが1より大きい時だけargv[1]を利用するようにしたのが
if (argc > 1) printf("%s ", argv[1]);
という処理です。
条件分岐式は、以下のような形式で記述します。
本来、この記述法に従うなら{}で処理を囲み
if (argc > 1) {printf("%s ", argv[1]);}
とするべきですが、処理が1つだけの場合は、{}を省略可能なので省略しています。
条件式
条件式とは、以下のように2つの値を比較する演算子で記述した式を意味しま す。
bool値
条件式の結果はbool(ブール)値と呼ばれ
真:true(ツルー) 条件式が成り立った
偽:false(フォルス) 〃 成り立たなかった
どちらかの値を取ります。偽:false(フォルス) 〃 成り立たなかった
1 > 2 → false
1 <= 2 → true
といった具合です。1 <= 2 → true
条件合成用の演算子
条件式は、以下の条件合成用の演算子を組み合わせる事で、より複雑な条件を記述でき ます。
&& 左と右の条件式がどちらもtrueの時だけtrue
|| 〃 どちらか一方がtrueならtrue
|| 〃 どちらか一方がtrueならtrue
例)
(a > 0) || (a < 0) aは0より小さいか大きい
(a > b) && (b > 0) aがbより大きく、なおかつ、bは0より大きい
()による優先順位指定もできます。
例)
((a > b) && (b > 0)) || (a >= 100) aがbより大きく、なおかつ、bは0より大きい、またはaが100以上
また、bool値は、以下の演算子で値を逆転させる事ができます。
! trueならfalae、falseならtrueにする。
例)
!(a == 0) aは0と等しい場合の逆(aが0と等しくない)
今回のmain関数ではargcの値が1より大きいかを比較しています。
argc > 1
argcの値が1より大きければ
printf("%s ", argv[1]);
が実行され、そうでなければ何もおこりません。
if (argc > 1)
printf("%s ", argv[1]);
else
printf("nobody ");
printf("%s ", argv[1]);
else
printf("nobody ");
と記述すれば、studyの後ろに何も付けずに実行した時に
nobody Hello. World!
と表示されるようになります。フリーフォーマットなので
if (argc > 1) printf("%s ", argv[1]); else printf("nobody ");
と書いてもかまいませんし、{}を省略せずに
if (argc > 1) {
printf("%s ", argv[1]);
} else {
printf("nobody ");
}
printf("%s ", argv[1]);
} else {
printf("nobody ");
}
と書いてもかまいません。
ifを入れ子にする事もできます。
if (argc > 1) {
if (argc > 2) {
printf("%s %s ", argv[1], argv[2]);
} else {
printf("%s ", argv[1]);
}
} else {
printf("nobody ");
}
if (argc > 2) {
printf("%s %s ", argv[1], argv[2]);
} else {
printf("%s ", argv[1]);
}
} else {
printf("nobody ");
}
サンプルプロジェクト:study-2-else.zip
この場合、elseが、どの条件に対するelseになるかに注意が必要です。elseは省略可能なので{}まで省略して記述していると、思わぬ分岐になる事があります。
Xcodeでアプリケーションに追加文字列を渡す方法
そのさい、いちいちターミナルを使うのは面倒でしょうから、Xcodeでアプリケーションにターミナルから実行したように、追加文字列を送れるようにする方法を紹介しておきます。
まず、Scheme(スキーム:構成)メニューから"Edit Scheme…"を選びます。
表示された画面で、左のリストから"Run study"を選択し、右に表示される画面の"Arguments"タグを選択してください。右の画面が以下のようになり、その中に"Arguments Passed On Launch"という項目が見えるはずです。
この項目が、ターミナルのようにアプリケーションに文字列を渡すための設定です。
項目の+ボタンを押してください。
項目が追加され、テキスト入力状態になるので、ターミナルでアプリケーション名の後にスペースを空けてから入力した
myname
を入力します。
これで、Runするとアプリケーションに文字列"myname"が渡ようになりました。
OKボタンを押して画面を閉じ、Runボタンで実行してみてください。デバッグエリアのコンソールにはターミナルと同じようにmynameが出力されます。
同じ要領で項目を追加していけば、argcの数が増え、
ターミナルで
と打ち込むのと同じ効果が得られます。
switch文
数字の比較で複数の分岐を一気に記述したい場合はswitch文を使ってもいいでしょう。
先ほどのifの入れ子による記述はswitchだと以下のようになります。
サンプルプロジェクト:study-3-switch.zip
ifやswitchを使った条件分岐はプログラマによって多彩な使われ方をします。いろいろなサンプルプログラムを見て、使い方を把握していってください。
printf関数
画面に文字列を表示する関数です。
この関数は先に紹介したC標準ライブラリに含まれています。非常に多機能で、文字だけでなく数値やメモリの番地を表示する事も可能です。
また、引数の数が固定されていない事も特徴の一つです。
if (argc > 1) printf("%s ", argv[1]); ←引数が2つ。
printf("Hello, World!\n"); ←引数が1つ。
注意)\は実際のXcodeでは半角。ブログ上で表示できないので全角にしているだけ。printf("Hello, World!\n"); ←引数が1つ。
ただし、第1引数には必ずconst char*型(C文字列)を渡さなければいけません。
文字列リテラル
つまり
printf("Hello, World!\n");
の"Hello, World!\n"はconst char*型としてprintf関数に渡っている事になります。
「"」(ダブルクォーテーション)で囲まれた文字は、メモリ上にC文字列で記憶するようコンパイラに指示するもので、文字列リテラルと呼ばれます。
そしてprintfは、この受け取った文字列を解析し、引数が続くかどうか、続くなら引数がどのような型かなどを決定します。
引数が続くかどうかを意味するのは、第1引数で渡した文字列中の
%s
という部分です。
printf("%s ", argv[1]);
これはconst char*型の引数(すなわちC文字列)が続き、その引数の内容で %s 部分を置き換えて表示せよという指示になります。
数字(int型引数)を埋め込む事も可能です。この場合は
%d
を使います。
試しにargcを画面に出力させてみましょう。
int main (int argc, const char * argv[])
{
// insert code here...
if (argc > 1) {
printf("argc = %d %s ", argc, argv[1]);
} else {
printf("argc = %d ", argc);
}
printf("Hello, World!\n");
return 0;
}
{
// insert code here...
if (argc > 1) {
printf("argc = %d %s ", argc, argv[1]);
} else {
printf("argc = %d ", argc);
}
printf("Hello, World!\n");
return 0;
}
サンプルプロジェクト:study-4-printf.zip
としてRunさせてみてください。
コンソールには
argc = 1 Hello, World!
Program ended with exit code: 0
Program ended with exit code: 0
と出力されます。
ターミナルでmynameを加えて実行すれば
argc = 2 myname Hello, World!
となります。
%d、%s以外にも、いくつかの変換指定があります。
float型
%fの引数として指定されるfloat型は、小数点を扱えるようにした数値(数学でいう実数)データ型です。
ただし、メモリが記憶できるのはあくまで整数値なので、文字の時のようにいろいろと取り決めをして実数を表現します。このためfloat型による実数値には誤差という問題が発生します。
興味のわいた人は「浮動小数点 誤差」というキーワードでインターネットを検索してみてください。
エスケープ文字
また、文字列リテラル中に書かれているバックスラッシュとその後に続く1文字は、エスケープ文字と呼ばれ特殊な文字を意味します。
\n
このうち「\n」は改行を意味する文字で、printfに
"ABCDEF"
というC文字列を渡せば画面には
ABCDEF
と出力されますが
"ABC\nDEF"
とすると
ABC
DEF
DEF
と出力されます。この改行文字は少し特殊で、Mac OS XやiOSの場合、メモリ上の数値は
10
を使いますが、OS Xより前のMac OS 9では
13
を使います。Windowsは
13,10
と2つの連続したバイト列にする必要があったりもして統一されていません。
ちなみに13にはキャリッジリターン(CR)、10にはラインフィード(LF)という呼び名が付いています。
他にもいくつかのエスケープ文字があります。興味のある人は「エスケープ文字」、「ASCII制御文字」や「改行コード」などでインターネットを検索してみてください。
「\」(バックスラッシュ)
そしてバックスラッシュ自体がC言語ソースコードの中で、特殊な役割りを持っており、現在の行と次の行を一つの行とみなせとコンパイラに指示する時にも使われます。
#define Test(a) if (a) { \
printf("A"); \
printf("B"); \
}
注意)ブログ上で半角バックスラッシュが表示できないので全角の\で代用している。printf("A"); \
printf("B"); \
}
という記述は、コンパイラ側には
#define Test(a) if (a) {printf("A");printf("B");}
相当になります。
プリプロセス指令の#define(後のパートで紹介)は複数行をサポートしないので、1行で記述しなければならないのですが、それだと人間が読みにくくなる場合などに、このような使われ方をします。
これからC言語のサンプルソースコードを読む時に、上記のような記述にぶつかったら、この事を思いだしてください。
次のパートでは、コンピュータ(計算機)らしく階乗計算をおこなう関数を作り、その結果を画面に出力させてみましょう。
「\」(バックスラッシュ)はどうやって出すのか?
ブートキャンプを読んでる大半の人はJISキーボードを使っていると思います。
その場合、初期状態では簡単にバックスラッシュが打ち込めません。
optionキーを押しながら、以下の¥マークの付いたキーを押す事で入力できるにはできるのですが、C言語では結構バックスラッシュを打ち込む機会があるので、optionキー抜きでバックスラッシュを打ち込みたい人は、ことえりの環境設定を変更してください。
ことえりの環境設定は、ことえりメニューから"環境設定を表示"を選ぶと表示されます。
入力文字タグを選び「JISキーボードの¥キーで入力する文字」を\(バックスラッシュ)に変更する事で、¥マークの付いたキーを押すだけでバックスラッシュが打ち込めるようになります。