web-dev-qa-db-ja.com

Cで変数のデータ型について言及する必要があるのはなぜですか

通常Cでは、変数宣言でデータのタイプをコンピューターに通知する必要があります。例えば。次のプログラムでは、2つの浮動小数点数XとYの合計を出力します。

_#include<stdio.h>
main()
{
  float X=5.2;
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}
_

変数Xのタイプをコンパイラーに通知する必要がありました。

  • コンパイラがXのタイプを独自に決定できないのですか?

はい、できます。

_#define X 5.2
_

これで、コンパイラーにXのタイプを次のように指示することなく、プログラムを作成できます。

_#include<stdio.h>
#define X 5.2
main()
{
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}  
_

したがって、C言語には何らかの機能があり、それを使用してデータのタイプを独自に判別できることがわかります。私の場合、Xはfloat型であると判断しました。

  • Main()で何かを宣言するときに、なぜデータのタイプについて言及する必要があるのですか? _#define_とは異なり、コンパイラがmain()で変数自体のデータ型を判別できないのはなぜですか。
19
user106313

変数宣言を_#define_ sと比較していますが、これは正しくありません。 _#define_を使用して、識別子とソースコードのスニペット間のマッピングを作成します。次に、Cプリプロセッサは、その識別子の出現を文字通り、提供されたスニペットで置き換えます。書き込み

_#define FOO 40 + 2
int foos = FOO + FOO * FOO;
_

コンパイラーにとって、書き込みと同じことになります

_int foos = 40 + 2 + 40 + 2 * 40 + 2;
_

自動化されたコピー&ペーストと考えてください。

また、通常の変数は再割り当てできますが、_#define_で作成されたマクロは再割り当てできません(ただし、_#define_は再割り当てできます)。 「rvalues」に割り当てることができないため、式_FOO = 7_はコンパイラエラーになります:_40 + 2 = 7_は不正です。

では、なぜ型が必要なのでしょうか。一部の言語は明らかに型を取り除きます。これは特にスクリプト言語で一般的です。ただし、変数には通常「動的型付け」と呼ばれるものがあり、変数には固定型がありませんが、値にはあります。これははるかに柔軟ですが、パフォーマンスも低下します。 Cはパフォーマンスが好きなので、変数の概念は非常にシンプルで効率的です。

「スタック」と呼ばれる一連のメモリがあります。各ローカル変数は、スタック上の領域に対応しています。ここで問題は、この領域は何バイト長でなければならないかということです。 Cでは、各型には明確に定義されたサイズがあり、sizeof(type)を介してクエリできます。コンパイラーは、スタック上に正しい量のスペースを予約できるように、各変数のタイプを知る必要があります。

_#define_で作成された定数に型注釈が必要ないのはなぜですか?それらはスタックに保管されません。代わりに、_#define_は、コピーと貼り付けよりもわずかに保守しやすい方法で、ソースコードの再利用可能なスニペットを作成します。 _"foo"_や_42.87_などのソースコード内のリテラルは、コンパイラによって、特別な命令としてインラインで、または結果のバイナリの個別のデータセクションに格納されます。

ただし、リテラルにはタイプがあります。文字列リテラルは_char *_です。 _42_はintですが、より短い型(ナロー変換)にも使用できます。 _42.8_はdoubleになります。リテラルがあり、それを別のタイプにしたい場合(例えば、_42.8_をfloat、または_42_を_unsigned long int_にするため)、サフィックスを使用できます–コンパイラーがそのリテラルを処理する方法を変更するリテラルの後の文字。この場合、_42.8f_または_42ul_と言うことができます。

一部の言語にはCのように静的型付けがありますが、型注釈はオプションです。たとえば、ML、Haskell、Scala、C#、C++ 11、Goなどです。それはどのように機能しますか?マジック?いいえ、これは「型推論」と呼ばれます。 C#とGoでは、コンパイラーは割り当ての右側を見て、その型を推測します。右側が_42ul_などのリテラルである場合、これはかなり簡単です。その後、変数の型がどうあるべきかは明らかです。他の言語にも、変数の使用方法を考慮したより複雑なアルゴリズムがあります。例えば。 _x/2_を実行する場合、xを文字列にすることはできませんが、数値型を指定する必要があります。

46
amon

2番目の例のXは、floatにはなりません。これはマクロと呼ばれ、ソースで定義されたマクロ値「X」を値で置き換えます。 #defineに関する読みやすい記事は here です。

提供されたコードの場合、コンパイルの前にプリプロセッサがコードを変更します

Z=Y+X;

Z=Y+5.2;

そしてそれがコンパイルされます。

つまり、これらの「値」を次のようなコードで置き換えることもできます

#define X sqrt(Y)

あるいは

#define X Y
4
James Snell

簡単な答えは、歴史/ハードウェアを表すため、Cには型が必要です。

歴史:Cは1970年代初頭に開発され、システムプログラミング用の言語として意図されていました。コードは理想的に高速で、ハードウェアの機能を最大限に活用します。

コンパイル時に型を推論することは可能でしたが、すでに遅いコンパイル時間は増加していたでしょう( XKCDの「コンパイル」漫画を参照してください。これは、Cが公開されてから少なくとも10年間は​​「hello world」に適用されていました) )。実行時に型を推測することは、システムプログラミングの目的に適合しなかったでしょう。ランタイム推論には、追加のランタイムライブラリが必要です。 Cは最初のPCよりずっと前に登場しました。 256 RAMがありました。ギガバイトやメガバイトではなく、キロバイト。

あなたの例では、タイプを省略した場合

   X=5.2;
   Y=5.1;

   Z=Y+X;

次に、コンパイラーはXとYが浮動小数点であり、Zを同じにすることを喜んで解決できたはずです。実際、最近のコンパイラーは、XとYが不要で、Zを10.3に設定するだけで済むようにしています。

計算が関数内に埋め込まれていると仮定します。関数の作成者は、ハードウェアの知識、または解決中の問題を使用する場合があります。

Doubleはfloatよりも適切でしょうか?より多くのメモリを必要とし、速度は遅くなりますが、結果の精度は高くなります。

Floatからintへの変換にはコストがないわけではありませんが、小数は重要ではなかったため、関数の戻り値はint(またはlong)である可能性があります。

Float + floatがオーバーフローしないことを保証して、戻り値をdoubleにすることもできます。

これらの質問はすべて、今日書かれたコードの大多数にとっては意味がないように見えますが、Cが作成されたときには不可欠でした。

1
itj

Cは古いため、型推論(コンパイラーが変数の型を推測するときに呼び出される型)はありません。それは 1970年代初頭に開発されました

新しい言語の多くには、型を指定せずに変数を使用できるシステムがあります(Ruby、javascript、pythonなど)。

0