「使用できる領域を指すデータを作成するにはどうすればよいですか?」
以下で説明しようとしていることが何を意味するのかはわかりませんが(短くはなりません:P)、に格納されているデータのタイプをどのように区別できるかを尋ねている場合はNode->data
、その実装でできません。
リストに保存したデータの種類をエンドプログラマーに任せます(これは悪いことではありません..逆に、それは標準です)。言い換えれば、あなたはエンドプログラマーがNode->data
.
何らかの理由で、より管理された API をリストに提供したい場合は、List
構造体にもう 1 つのフィールドを追加して、リストに格納されているデータのタイプを識別することができます。
例えば...
enum DataType {
DT_INVALID = 0,
DT_PTR
DT_CHAR,
DT_INT,
DT_FLOAT,
DT_DOUBLE,
...
/* not a data-type, just their total count */
DT_MAX
};
#define DT_IS_VALID(dt) ( (dt) > DT_INVALID && (dt) < DT_MAX )
typedef struct List List;
struct List {
enum DataType dt;
Node *head;
};
もちろんenum
、上記の にリストしたデータ型よりも少ないまたは多いデータ型をサポートすることは自由です (文字列などのカスタム型や、プロジェクトに応じて適切と思われるものであっても)。
したがって、最初に、リストのコンストラクター (または初期化子) が必要になります。次のようなものです...
List *new_list( enum DataType dt )
{
List *ret = NULL;
if ( !DT_IS_VALID(dt) )
return NULL;
ret = malloc( sizeof(List) );
if ( NULL == ret )
return NULL;
ret->dt = dt;
ret->head = NULL;
return ret;
}
そしてそれをインスタンス化し、次のように言います...
int main( void )
{
List *listInt = new_list( DT_INT );
if ( NULL == list ) {
/* handle failure here */
}
Node
目的のデータ型をリスト メタデータに格納したので、コンストラクタの実装方法を自由に選択できます。たとえば、一般的なものは次のようになります...
int list_add_node( List *list, const void *data )
{
Node *node = NULL;
size_t datasz = 0;
/* sanity checks */
if ( !list || !data || !DT_IS_VALID(list->dt) )
return 0; /* false */
node = malloc( sizeof(Node) );
if ( NULL == node )
return 0; /* false */
/* when data points to mem already reserved say for an array (TRICKY) */
if ( DT_PTR == list->dt ) {
node->data = data;
}
/* when data points to mem reserved for a primitive data-type */
else {
datasz = dt_size( list->dt ); /* implement dt_size() according to your supported data-types */
node->data = malloc( datasz );
if ( NULL == node->data ) {
free( node );
return 0; /* false */
}
memcpy(node->data, data, datasz );
}
/* add here the code dealing with adding node into list->head */
...
return 1; /* true */
}
for DT_PTR
(例ではTRICKYNode
としてフラグを立てました)別のコンストラクターを実装する方が安全です。おそらく2つの追加の引数を受け入れます.andと言うelemsz
と、配列を指す場合、関数は内容をコピーするためのバイトをnelems
割り当てることができます、構造体、またはその他の非プリミティブ型。または、配列専用の追加の列挙値を指定できます。何をするのも自由です。elemsz * nelems
data
data
DT_ARR
いずれにせよ、上記の例では、 の呼び出し元が渡された を適切に割り当てていることにDT_PTR
依存しており、一般的なコンテキストでは、これはまったく良いことではありません。list_add_node
data
コードはもっと複雑ですが、リストに格納されているデータ型はわかっています。したがって、少なくともプリミティブ データ型については、出力を自動的にキャストする印刷ルーチンを追加できますlist->dt
(非プリミティブ データ型については、通常はコールバック関数を介して、カスタム印刷ルーチンのサポートを提供する必要があります)。
dt
極限まで行って、フィールドを からList
に移動することもできますNode
。その場合、ノードに異種データのリストを実装しますが、はるかに複雑になり、ほとんど役に立ちません (あったとしても)。
(void *) ポインターを介したこのすべての ADT には重大なパフォーマンスの問題があります。そのため、速度が重要な実装では、この種のものに代わりにプリプロセッサを使用 (または必要に応じて乱用) します。