6: 2007-03-22 (木) 19:20:56 |
現: 1970-01-01 (木) 18:00:00 |
- | COLOR(red){えーっと!専門家に見てもらったら案の定このページの内容は大間違いだということがわかりましたー!(泣 そんなわけで再びお勉強&修正開始〜!} | |
| | | |
- | ---- | |
- | | |
- | | |
- | | |
- | | |
- | C言語の変数っていろいろ型がある。で、それらを相互に変換することを''型キャスト''あるいは単純にキャストと言う。 | |
- | | |
- | | |
- | *変数の型 [#p06e0e0f] | |
- | | |
- | そもそも、「型」ってなに?? | |
- | | |
- | 普通のコンピュータの素子は1bit(ビット)。0と1しか記憶できない。で、それを8個集めた単位が1byte(バイト)。(つまり8bit=1byteになる) | |
- | | |
- | |>|>|>|>|>|>|>|CENTER:1バイト| | |
- | |0|0|1|0|1|0|0|0| | |
- | | |
- | CENTER:↑ | |
- | CENTER:0と1の二つしか表現できない最小単位bit(ビット)。8個集まって1Byte(バイト)になる | |
- | | |
- | ~ | |
- | ~ | |
- | COLOR(blue){経験則だが、普通多くの言語はこの1バイトを最低単位とする。それ以下のビットは特殊な用途で使用することが多い。ちょうどお金の円と銭みたいな感じ} | |
- | ~ | |
- | | |
- | ~ | |
- | | |
- | | |
- | C言語でも、''基本的には''変数の最低単位はこの1バイトになる。 | |
- | | |
- | | |
- | 1バイトは2進数で00000000〜11111111。10進数に直すと0〜255までとなる。逆に言えば、たった255しか表現できない困った単位ともいえる。 | |
- | | |
- | でもこれじゃ不便なので、複数のバイトで1個という単位の変数が生まれた。 | |
- | | |
- | C言語では大体こんな感じ。 | |
- | | |
- | |int型|整数|4バイト|どうも通常は動作している環境って単位みたい。つまり16bitCPUなら16bit、32bitCPUなら32bitとか(Cコンパイラに依存?)| | |
- | |float型|小数|4バイト|文字通り少数を扱いたい場合使うらしい| | |
- | |char型| 文字(一文字単位)|1バイト|「A」だの「i」だの| | |
- | |double型|少数|8バイト|より桁数が多い少数?| | |
- | |ポインタ型|4バイト|アドレス変数|アドレスを記憶する| | |
- | | |
- | ・・・こうして見ると、intやfloat、ポインタはみな同じバイト数である。なのでおそらく、実際にメモリ内に記憶されている状態ではまったく同じ状態だと思われる。 | |
- | | |
- | int a; | |
- | float b; | |
- | char *c; | |
- | | |
- | a=0x00FF00FF; | |
- | b=0x00FF00FF; | |
- | c=0x00FF00FF; | |
- | | |
- | /*この状態で各変数の実際の場所を覗いてみるときっとまったく同じだと思われる*/ | |
- | | |
- | しかし、''C言語上では''おのおのは型(タイプ)が違う変数という解釈がされるのだろう。 | |
- | | |
- | **豆知識〜 [#t245203f] | |
- | 枝豆ってなぁ・・・大豆やねんでぇ? | |
- | | |
- | -たとえばchar型。1バイトである。二進数でいえば8桁。扱える範囲は 00000000〜11111111までだ。これを16進数に直すと0x00〜0xFF。10進数だと0〜255までの値を使えるということになる。 | |
- | | |
- | ヨミちゃんアカンわ・・・・ | |
- | | |
- | -しかしこれは間違い。C言語の通常の考え方では、頭の(一番左)のビットは''符号用の印''として扱うようだ。頭の1ビットが0なら正数。1なら負の数ということ。っていうことは、char型が数字の値として扱えるのは残りの7ビットだけということになるわけ。7ビット(0000000〜1111111)だと、0〜127まで。で、符号があるから、char型が扱える数値の範囲は -127〜127 ということになる。ポインタを除く普通の変数はみなこの法則になる。 | |
- | | |
- | それは豆知識やのーて豆の知識や〜て言わなアカンやろ | |
- | | |
- | -しかしそれだと不便だという場合もあるよね?。50+40+80なんて計算をしたい場合、char型では正の数は最大で128。たかが170なんて値を計算したのにint型(4バイト)を使わなくてはいけなくなる。これじゃメモリの無駄だよね。なので、''unsigned''というオプションがある。ただの char a; だと、変数aは-127〜127という範囲になるけど、unsigned char a; って宣言すると、最初の1ビットを符号とするのをやめてくれる。なので変数aは、 0〜255 を扱える変数になるわけ。 | |
- | | |
- | | |
- | COLOR(BLUE){多くのコンピュータの変数等は「0」という値をもっている。なので大抵''0〜xxx''という感じになる。16進数で0x00〜0xFFは、10進数で0〜255となる。最大の値は255だが、0も1個と数えられるので、個数・種類という目でみると1個増える。} | |
- | -COLOR(BLUE){''例:'' 1バイトは0〜255までの数字を扱える。なので、''256種類''の色を表現できる} | |
- | | |
- | | |
- | | |
- | | |
- | *違う型同士の演算 [#db169d62] | |
- | COLOR(red){ここは厳密には間違っているが、概念の理解のため大雑把に端折って考える} | |
- | | |
- | 当たり前だが現実の世界の計算でも、型やタイプがバラバラなもの同士の演算はできないし、困る。 | |
- | | |
- | X = sing60(ピタゴラス+マジンガーZ) | |
- | | |
- | とか。 | |
- | | |
- | 同じようにC言語の変数でも、違う型同士だと演算や代入はできない。intとfloatがたまたま同じ4バイトだからと言っても、とにかく型が違うんだからできない。ましてやchar型+int型なんて、バイト数も違うんだからなおさらである。 | |
- | | |
- | int a,aa,aaa; | |
- | unsegned int b; | |
- | char c; | |
- | float d; | |
- | double e; | |
- | | |
- | aaa = aa + a; /*同じint同士。足し算も代入もOK。*/ | |
- | | |
- | a = b - aa; /*おいおい! bとaaは型が違うだろ同じ4バイトでも!*/ | |
- | | |
- | aaa = e; /*うわ!aaaとeじゃ型が違うしそもそもバイト数も・・・*/ | |
- | | |
- | aa = e + d -b; /*もうなにがなんだか・・・*/ | |
- | | |
- | ''そこで!!!'' | |
- | | |
- | 型キャスト(型の変換)が必要になってくるというわけ。 | |
- | | |
- | | |
- | *キャストの実際 [#r83bdef1] | |
- | **単純なもの [#h2426baf] | |
- | こんな式を妄想してみます。 | |
- | | |
- | char a; | |
- | int b; | |
- | int c: | |
- | | |
- | a = 18; | |
- | b = 38632; | |
- | | |
- | c = a + b; | |
- | | |
- | さて、これは問題です。まず、 a+b の時点で、char型+int型です。型が合いません。 | |
- | | |
- | これを暗算すると、38650になります。計算の結果はint型の変数cに収まります。代入はまあ、問題ないでしょう。 | |
- | | |
- | じゃあどうしたらいいかというと、こんなふうにしてchar型のaをint型に変換します。 | |
- | | |
- | char a; | |
- | int b,ab; | |
- | int c: | |
- | | |
- | a = 18; | |
- | b = 38632; | |
- | | |
- | ab = (int)a; /*こうすると、char型aの値(18)はそのままに、int型に変換してくれる*/ | |
- | | |
- | c = ab + b; /*当然int同士の足し算。そして結果をint型に代入なので、問題なし!*/ | |
- | | |
- | さて、上の式を見ると、変数abは一時的に型キャストした変数aを収めているだけですね。こんなことでいちいち変数を使うのはもったいないし行数も増える。ちょっといや〜ん。 | |
- | | |
- | そこでこんなふうに書くことができます。 | |
- | | |
- | | |
- | char a; | |
- | int b; | |
- | int c: | |
- | | |
- | a = 18; | |
- | b = 38632; | |
- | c = (int)a + b; | |
- | | |
- | なるほど!。簡単で単純ですね! | |
- | | |
- | ・・・ちなみに、この型キャストはあくまでも一時的に式の内部で変換をしただけです。なので、変数aそのものがint型に変化したわけではないことに注意。 | |
- | | |
- | | |
- | | |
- | **ポインタ型のキャスト [#aa70e23e] | |
- | これがややこしいですね。整理して単純化して考えてみます。 | |
- | | |
- | たとえばですが、メモリの0x0010番地〜0x0014番地になんらかの方法で22〜26までの値を代入してあるとします。(どうやるかはちょっとここでは考えない) | |
- | | |
- | とりあえず今はこういう状態だと仮定する。 | |
- | : : | |
- | ├──────┤ | |
- | 0x0010番地 │ 22 (0x16)│ | |
- | ├──────┤ | |
- | 0x0011番地 │ 23 (0x17)│ | |
- | ├──────┤ | |
- | 0x0012番地 │ 24 (0x18)│ | |
- | ├──────┤ | |
- | 0x0013番地 │ 25 (0x19)│ | |
- | ├──────┤ | |
- | 0x0014番地 │ 26 (0x1A)│ | |
- | ├──────┤ | |
- | : : | |
- | | |
- | ここでたとえば、「0x0010番地〜0x0014番地に入っている値を表示しなさい!」という指令を受けたとします。どういうプログラムになるでしょう? | |
- | | |
- | char *p; | |
- | char a; | |
- | int i; | |
- | | |
- | | |
- | for(i = 0x0010; i = 0x0014; i = i+1) | |
- | { | |
- | p = i; | |
- | printf("データは %d 16進数だと %x だよーん!\n",*p); | |
- | | |
- | } | |
- | | |
- | これで、0x0010番地〜0x0014番地の内容が次々と表示されるはずです。 | |
- | | |
- | データは 22 16進数だと 0x16 だよーん! | |
- | データは 23 16進数だと 0x17 だよーん! | |
- | データは 24 16進数だと 0x18 だよーん! | |
- | データは 25 16進数だと 0x19 だよーん! | |
- | データは 26 16進数だと 0x1A だよーん! | |
- | | |
- | | |
- | さて、このプログラム。コンパイルするとエラーというか、警告が出てきます。型が合わないんです。いったいどこが???? | |
- | | |
- | p = i; | |
- | | |
- | なんと!ここです。おかしいですね?。pはアドレス変数として宣言しています。pに数値を入れればその数値の番地の内容が*pで取り出せるはずです。だからアドレス変数pに、変数i(0x0010〜0x0014まで変化してくれる値)を代入したのですが・・・ | |
- | | |
- | | |
- | 実はC言語では、同じ4バイトの変数や値と''アドレス変数''というのはまったく別で特別なものと考えられているようです。 | |
- | | |
- | | |
- | | |
- | 普通の型の変数でもバイト数が違ったりすると問題なのはわかりますよね?。アドレス変数はもっとまったく別で、''数値ですらない''くらいまったく別なんです。 | |
- | | |
- | | |
- | こんなふうに考えるとわかります。アドレス。文字通り、''住所・番地''です。 | |
- | | |
- | -たとえばアナタの住んでいる所。住所がありますよね?。で、その住所。数字も使われているでしょう? | |
- | -日本国 東京都 葛飾区 亀有 5丁目 18番地 とか。 | |
- | -こういう住所の数字に対して、「なあ?お前の家の住所の三倍ってなに?」ともし聞かれたらどう答えますか? | |
- | -「まずよく寝ろ!話はそれからだ」が正しいですね? <はぁ??? | |
- | -つまり、住所にはたまたま数字が使われていますが、これは倍にしたり割り算したりするようなものではない。つまり、''数字は使っているけど数値ではない''となります。 | |
- | | |
- | 上記の例がエラーになるのはこれが理由です。int(4バイト)だろうがchar(1バイト)だろうが、これらは数値なのです。なので、「住所の番地に数を代入しようとしてるぞ!おかしいぞ!」と、こういうことでエラーになってしまうのです。 | |
- | | |
- | | |
- | そのため、こういう代入をしたい場合は、数値であるiを''型キャスト''して、一時的にアドレス変数タイプに転換してから代入してやる必要があります。 | |
- | | |
- | この場合、何型にキャストしてあげればいいでしょう。pと同じにしてあげればいいのです。変数pはchar型です。しかも、*がついています。char型のアドレス変数(ポインタ)です。なので、 | |
- | | |
- | char *p; | |
- | char a; | |
- | int i; | |
- | | |
- | | |
- | for(i = 0x0010; i = 0x0014; i = i+1) | |
- | { | |
- | p = (char *)i; /* ←ここに注目!*/ | |
- | printf("データは %d 16進数だと %x だよーん!\n",*p); | |
- | | |
- | } | |
- | | |
- | | |
- | こうしてあげれば、int変数であるiは一時的にアドレス変数pと同じ型に変化します。よって代入が可能になり、エラーが出なくなります。 | |
- | | |
- | | |
- | もう一度書きますが、アドレス変数(ポインタ)は、たまたま数字を使っているだけの住所番地です。よって、足したり割ったり掛け算したりできる''数値''とはまったく違う型なのです。 | |
- | | |
- | -COLOR(BLUE){ちょっとまて!それはおかしいぞ! この状態で p = p + 1 等の演算をしてもエラーにならないはず。アドレス変数だって計算が出来る''数値''じゃないのか??} | |
- | | |
- | -COLOR(red){大変すばらしい問いです。ほんとですね。アドレス変数であるpに対して、足し算・引き算を行うことはできますしエラーにはなりません。やっぱりアドレス変数も''数値''なのでしょうか???} | |
- | -COLOR(red){では、実験してください。p = p * 3 等、掛け算や割り算をしてみてください。ほらね?エラーになるでしょう??。これはいったいどういうことなのでしょうか?} | |
- | -COLOR(red){難しく考えることじゃありません。たとえば最初に出した、実際の住所・番地で考えてください。} | |
- | --COLOR(red){あなたの住所は「日本国 東京都 葛飾区 亀有 5丁目 18番地」です。この住所に対して、「お前の住所の2倍」というのは成り立ちません。} | |
- | --COLOR(red){しかし、「お前の住所の次」はどうですか?} | |
- | --COLOR(red){「お前の住所の3軒前の家」ならどうでしょうか?。} | |
- | --COLOR(red){そう。成り立ちますね?} | |
- | -COLOR(red){つまり、アドレス変数が足し算引き算はできても掛け算割り算ができないのはこういう理由なのです。そしてこの理屈なら、足し算ができたってやっぱりアドレス変数は''数値''ではないということも矛盾しないでしょう?} | |
- | -COLOR(red){もっと言えば、アドレス変数(ポインタ)を p = p + 2 などとしているのは「2軒後のお家」と言っているだけで、そもそも''足し算ではない''とさえ言えます} | |
- | | |
- | | |
- | | |
- | | |
- | *現実の型キャスト [#x0b20a5e] | |
- | さて、これまで書いたことは、あくまでも概念と理屈の理解のためです。実際のCでは上記そのままではありません。(もちろん理屈そのものは合っているはずです) | |
- | | |
- | **数値の型キャスト [#k48737ef] | |
- | | |
- | たとえばこんな式を考えます。 | |
- | | |
- | char z; | |
- | char c; | |
- | | |
- | z = 0x44; | |
- | c = 0x22; | |
- | | |
- | c = c + z; | |
- | | |
- | printf("data is %x\n",c); | |
- | | |
- | 答えは暗算できますね。結果は0x66になります。もちろんエラーも出ません。同じ型同士の足し算を同じ型に代入したのですから当然ですね。 | |
- | | |
- | ではこんな式はどうでしょう? | |
- | | |
- | int i; | |
- | | |
- | char z; | |
- | char c; | |
- | | |
- | z = 0x44; | |
- | c = 0x22; | |
- | | |
- | i = c + z; | |
- | | |
- | printf("data is %x\n",i); | |
- | | |
- | これまで説明してきた「概念」ではこれはおかしいはずです。だって、変数zと変数cはchar型。しかし代入しているiはint型。型が合わないはずです。''ところが・・・'' | |
- | | |
- | あれれれ〜? これをコンパイルすると、ちゃんと''エラーもなく''、さらに実行してもちゃんと答えが出てきます。どういうこと??? | |
- | | |
- | 実はこれは、厳密には概念通り、型が合わないのです。しかしCコンパイラは、「この程度ならよく使うし、いちいち型キャストの警告出しててもうっとぉしいだろうなぁ。この程度は容認してあげよう」と考えるように調整されているのです。 | |
- | | |
- | -COLOR(blue){もちろん、 i = (int)c + (int)z; と書いても当然エラーにはなりません。むしろこれが正しいのです。''本来の概念''で言えば。} | |
- | | |
- | もちろん「許容」にもちゃんと法則があります。これには順番があります。以下を順番に適用していくのです。 | |
- | -最後は左辺の型に自動調整される | |
- | | |
- | | |
- | です。 | |
- | | |
- | こんな式を妄想してみます。 | |
- | | |
- | char c; | |
- | int i; | |
- | short s; | |
- | double d; | |
- | | |
- | c = 0x11; | |
- | i = 0x22; | |
- | s = 0x33; | |
- | d = 0x44; | |
- | | |
- | i = c + i + s + d; | |
- | | |
- | printf("data is 0x%x\n",i); | |
- | | |
- | さあ、この計算は型が混在もいいところですね。でもエラーは出ません。Cコンパイラが「許容」しているためです。 | |