3

次のような構造体があります。

typedef struct Node {
    void* data;
    unsigned long id;
    NodePtr next;
    NodePtr prev;
} Node;

これは、リンクされたリスト ADT 内のノードであることを意図しています。ノードがデータに保持する必要があるものに応じて、2 つの異なるコンストラクターがあります。1 つのコンストラクターは以下を使用します。

NodePtr TempNode;
TempNode = malloc( sizeof(Node) );
/* Other stuff */
TempNode->data = newList();
return (TempNode);

そして、これは (List->current->data) を返すことでそのリストにアクセスできるようにするのにうまく機能しているようです。ここで current は List Struct のノード ポインターです。

ただし、(data) が int を指すバージョンのコンストラクターを作成したいと考えています。次のようにすることでこれを行うことができると読みました

void* ptr;
int x = 0;
*((int*)ptr) = x;

しかし、私のコンストラクターの設定方法では、このようなことをしなければならないということですか?

*((int*)TempNode->data) = 1; // data starts at 1

そして、これは機能しません。私はCに非常に慣れていないので、用語の多くを理解していません。逆参照 (-> 表記を使用して?) は void* では実行できないことを読みましたが、私のリスト バージョンのコンストラクターでは問題なく動作するようです。この void* を int にキャストするように他のコンストラクターを書き直すにはどうすればよいですか?

4

3 に答える 3

2

これを行うことは強くお勧めしませんが、整数を保持するためにメンバーを本当に使用したい場合はvoid *、次のようにすることができます。

Node *constructor_int(int n)
{
    Node *tmp = malloc(sizeof(*tmp));
    /* Other stuff */
    tmp->data = (void *)n;
    return(tmp);
}

これにより、キャストの数が最小限になり、型の相対的なサイズに関するほとんどの問題が回避されます。

dataそれを行うための明白で論理的な方法は、メンバーが指す整数を割り当てることです。

Node *constructor_int(int n)
{
    Node *tmp = malloc(sizeof(*tmp));
    /* Other stuff */
    tmp->data = malloc(sizeof(int));
    *(int *)temp->data = n;
    return(tmp);
}

2 つのメモリ割り当てを解放することを忘れないでください。

コードは、結果を使用する前にメモリ割り当てが成功したことも確認する必要があります。

于 2013-05-19T22:00:16.633 に答える
1

これについて話しましょう

あなたが何かをするとき

NodePtr TempNode;
TempNode = malloc( sizeof(Node) );

に十分な大きさの動的ストレージを確保するようライブラリに依頼しましたNode。そのメモリの初期値は定義されていないため、現時点ではポインタはどこTempNode->dataを指してもかまいませんが、使用のために予約されたメモリを指していない可能性があります。

あなたがするとき

TempNode->data = newList();

newList()ポインターに有効な値を与えます (おそらく、合法的で賢明な何かを行う限り)。

代わりに

*((int*)TempNode->data) = 1;

コンパイラに指示する

  1. TempNode->Dataint へのポインターとして扱います。
  2. それを参照解除し、
  3. すべてのメモリが反対側にあるものの値を設定します(それが指しているものが何であれ、それ自体1を設定していないことに注意してください)data

しかし、あなたはそれが何を指しているのかわかりません!それを逆参照することは未定義の動作であり、厳密には悪いことです。

使用する権利があるメモリをポインタが指していない限り、ポインタを逆参照しないようにする責任は常にあります。

于 2013-05-19T21:59:52.357 に答える
1

使用できる領域を指すデータを作成するにはどうすればよいですか?」

以下で説明しようとしていることが何を意味するのかはわかりませんが(短くはなりません: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 * nelemsdatadataDT_ARR

いずれにせよ、上記の例では、 の呼び出し元が渡された を適切に割り当てていることにDT_PTR依存しており、一般的なコンテキストでは、これはまったく良いことではありません。list_add_nodedata

コードはもっと複雑ですが、リストに格納されているデータ型はわかっています。したがって、少なくともプリミティブ データ型については、出力を自動的にキャストする印刷ルーチンを追加できますlist->dt(非プリミティブ データ型については、通常はコールバック関数を介して、カスタム印刷ルーチンのサポートを提供する必要があります)。

dt極限まで行って、フィールドを からListに移動することもできますNode。その場合、ノードに異種データのリストを実装しますが、はるかに複雑になり、ほとんど役に立ちません (あったとしても)。

(void *) ポインターを介したこのすべての ADT には重大なパフォーマンスの問題があります。そのため、速度が重要な実装では、この種のものに代わりにプリプロセッサを使用 (または必要に応じて乱用) します。

于 2013-05-20T00:55:51.133 に答える