前文
この記事は 10 年以上前に私のサイトで私が書いて公開したものですが、サイト自体はすっかり忘れ去られてしまいました。以下に説明するものはすべて、20 歳の男性による言語としての C の研究の結果であり、したがって、プレゼンテーションのスタイルにかかわらず、教科書であると主張するものではありません。しかし、私がかつて行ったように、若い開発者が C の実験に飛び込むことを奨励することを心から願っています。
警告
この短い記事は、経験豊富なC / C ++ プログラマーにとってはまったく役に立たないことがわかりますが、初心者にとっては時間を節約できるかもしれません。C / C ++に関するほとんどの優れた本は、このトピックを十分にカバーしていることを強調したいと思います。
動的型付けと静的型付け
多くのインタープリター言語は動的型付けを使用します。このアプローチにより、異なるタイプの値を同じ名前の変数に保存できます。C言語は強い型付けを使用していますが、私の意見では、これは正しいというよりもむしろ正しいです。ただし、動的型付けを使用する方がはるかに便利な場合もあります (それほど頻繁ではありませんが)。多くの場合、そのようなニーズは貧弱なデザインに直接関係していますが、必ずしもそうとは限りません。Qtが type を持っているのは、何のためというわけではありませんQVariant
。
ここでは C 言語について説明しますが、以下で説明することはすべてC ++ にも当てはまります。
ボイドポインターマジック
実際、C には動的型付けはなく、またありえませんが、型が であるユニバーサル ポインターがありvoid *
ます。この型の変数を関数の引数として宣言すると、任意の型の変数へのポインタを関数に渡すことができ、非常に便利です。そして、ここにある - 最初の例:
#include <stdio.h>
int main()
{
void *var;
int i = 22;
var = &i;
int *i_ptr = (int *)(var);
if(i_ptr)
printf("i_ptr: %d\n", *i_ptr);
double d = 22.5;
var = &d;
double *d_ptr = (double *)(var);
if(d_ptr)
printf("d_ptr: %f\n", *d_ptr);
return 0;
}
出力:
i_ptr: 22 d_ptr: 22.500000
ここでは、型int
と の両方に同じポインター (トートロジーで申し訳ありません) へのポインターを割り当てていますdouble
。
: , void *
. , — , GCC . , , :
void *var;
int i = 22;
var = (void *)(&i);
.
. :
#include <stdio.h>
int lilround(const void *arg, const char type)
{
if(type == 0) // int
return *((int *)arg); //
// double
double a = *((double *)arg);
int b = (int)a;
return b == (int)(a - 0.5) // >= 0.5
? b + 1 //
: b; //
}
int main()
{
int i = 12;
double j = 12.5;
printf("round int: %d\n", lilround(&i, 0)); //
printf("round double: %d\n", lilround(&j, 1)); //
return 0;
}
:
round int: 12 round double: 13
, , ( , ), . , - , .
, — lilround()
:
int lilround(const void *arg, const char type)
{
return type == 0
? *((int *)arg)
: ((int)*((double *)arg) == (int)(*((double *)arg) - 0.5)
? (int)(*((double *)arg)) + 1
: (int)(*((double *)arg)));
}
, — — . 0
, int
, — double
. , , , - , .
, (struct
), . , . .
? : . , , ? : type
, , , . , , . . :
typedef struct {
char type;
int value;
} iStruct;
typedef struct {
char type;
double value;
} dStruct;
. :
typedef struct {
char type;
int value;
} iStruct;
typedef struct {
double value;
char type;
} dStruct;
, , , — , double value , , .
:
#include <stdio.h>
#pragma pack(push, 1)
typedef struct {
char type; //
int value; //
} iStruct;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
char type; //
double value; //
} dStruct;
#pragma pack(pop)
int lilround(const void *arg)
{
iStruct *s = (iStruct *)arg;
if(s->type == 0) // int
return s->value; //
// double
double a = ((dStruct *)arg)->value;
int b = (int)a;
return b == (int)(a - 0.5) // >= 0.5
? b + 1 //
: b; //
}
int main()
{
iStruct i;
i.type = 0;
i.value = 12;
dStruct j;
j.type = 1;
j.value = 12.5;
printf("round int: %d\n", lilround(&i)); //
printf("round double: %d\n", lilround(&j)); //
return 0;
}
: #pragma pack(push, 1)
#pragma pack(pop)
, . , . .
iStruct
type. , .
, , void-. , , , .. void
, C++ . , :
#include <stdio.h>
int main()
{
int i = 22;
void *var = &i; // void- i
(*(int *)var)++; // void- int-,
printf("result: %d\n", i); // i
return 0;
}
: (*(int *)var)
.
C
. "" , , , . , type
:
typedef struct {
void (*printType)(); // ,
int (*round)(const void *); // ,
} uMethods;
さまざまな構造に対するこれらの関数の実装と、さまざまなタイプの構造に対する初期化関数について説明しましょう。結果は次のとおりです。
#include <stdio.h>
typedef struct {
void (*printType)(); // ,
int (*round)(const void *); // ,
} uMethods;
#pragma pack(push, 1)
typedef struct {
uMethods m; //
int value; //
} iStruct;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
uMethods m; //
double value; //
} dStruct;
#pragma pack(pop)
void intPrintType() // iStruct
{
printf("integer\n");
}
int intRound(const void *arg) // iStruct
{
return ((iStruct *)arg)->value; // iStruct
}
void intInit(iStruct *s) // iStruct
{
s->m.printType = intPrintType; // printType iStruct
s->m.round = intRound; // round iStruct
s->value = 0;
}
void doublePrintType() // dStruct
{
printf("double\n");
}
int doubleRound(const void *arg) // dStruct
{
double a = ((dStruct *)arg)->value;
int b = (int)a;
return b == (int)(a - 0.5) // >= 0.5
? b + 1 //
: b; //
}
void doubleInit(dStruct *s)
{
s->m.printType = doublePrintType; // printType dStruct
s->m.round = doubleRound; // round dStruct
s->value = 0;
}
int lilround(const void *arg)
{
((iStruct *)arg)->m.printType(); // , iStruct,
return ((iStruct *)arg)->m.round(arg); //
}
int main()
{
iStruct i;
intInit(&i); //
i.value = 12;
dStruct j;
doubleInit(&j); //
j.value = 12.5;
printf("round int: %d\n", lilround(&i)); //
printf("round double: %d\n", lilround(&j)); //
return 0;
}
出力:
integer
round int: 12
double
round double: 13
注: void ポインターの引数として使用する必要がある構造のみをコンパイラ ディレクティブで囲む必要があります。
結論
最後の例では、OPP との類似点が見られますが、これは一般的に当てはまります。ここでは、構造体を作成して初期化し、キー値フィールドに設定して丸め関数を呼び出します。これは、非常に単純化されていますが、ここでは引数の型推論を追加しています。それで全部です。そして、そのような構造を賢明に使用する必要があることを覚えておいてください。ほとんどのタスクでは、それらの存在は必要ないからです。