-1

これは、やや標準的なリンク リストの演習です。別の関数を取り、リストをループする関数があります。具体的には_node_loop. 使用している構文が機能するのはなぜですか? そのように見える関数ポインタ構文を見たことがありません。

テストの便宜のために完全なコードが含まれていますが、私の質問は特に関数_node_loop_del_node、およびに関するもの_print_nodeです。

#include<stddef.h>
#include <iostream>

template <class T>
class Node {
public:
    T value;
    Node<T>* next;
    Node(T value_, Node<T>* next_) : value(value_), next(next_) {}
    Node(T value_) : value(value_), next(NULL) {}
};



using namespace std;

template <class T>
class List {
public:
    List() : head(NULL) {}

    void prepend(T val) {
        Node<T>* node_ptr = new Node<T>(val, head);
        head = node_ptr;
    }

    void print() {
        cout << "[";
        _node_loop(_print_node);
        cout << "]" << endl;
    }

    ~List() {
        _node_loop(_del_node);
    }

    static List<T> from_array(T tarray[], int N) {
        List<T> ls = List<T>();
        for (int i = 0; i < N; i++) {
            ls.prepend(tarray[i]);
        }
        return ls;
    }

private:
    Node<T>* head;

    void _node_loop(void kernel(Node<T>*)) {
        Node<T>* node_ptr = head;
        Node<T>* tmp;
        while (node_ptr != NULL) {
            tmp = node_ptr;
            node_ptr = node_ptr->next;
            kernel(tmp);
        }
    }

    static void _print_node(Node<T>* node_ptr) {
        if (node_ptr->next == NULL) {
            cout << node_ptr->value;
        }
        else {
            cout << node_ptr->value << ", ";
        }
    }

    static void _del_node(Node<T>* node_ptr) {
        delete node_ptr;
    }
};



int main() {
    int my_array[] = {1, 2, 3, 4, 5};
    List<int> my_list = List<int>::from_array(my_array, 5);
    my_list.print();
    return 0;
}
4

1 に答える 1

1

私が行ったコメントを少し拡張します。

ここで何が起こっているのですか?

関数パラメーターの型は、関数宣言を解析した直後に調整 (または減衰) されます。

  • の配列はへのsome_typeポインタに調整されますsome_type
  • 関数の戻り値はsome_type、関数の戻り値へのポインターに調整されますsome_type
  • 最上位のcv 修飾子は削除されます (例int const-> int)

[dcl.fct]/5 を参照してください。関数型のパラメーターは保持されます。つまり、関数へのポインターには、元の関数型と同じパラメーターが含まれます。

この調整は、関数パラメーター タイプでのみ行われます。これは、式に適用できるいくつかの暗黙的な変換を反映しています。

  • の (左辺値)配列は、配列の最初の要素を指す へのsome_typeポインターにsome_type変換できます(「配列からポインターへの変換」、[conv.array])。
  • 関数を返すsome_type(の左辺値) は、関数を返すポインターへsome_typeのポインターに変換できます(「関数からポインターへの変換」、[conv.func])。
  • 修飾変換、たとえば from intto const int[conv.qual]

関数の種類について

関数型は関数ポインター型とは異なりますが、どちらも配列やポインターと同様に関連しています。たとえば、関数型を使用して関数を宣言できます (定義はできません)。

typedef void my_function_type(int, double);

my_function_type f0;  // declare the function `void f0(int, double)`
my_function_type f1;  // declare the function `void f1(int, double)`

int main()
{
    f0(42, 1.2);
    f1(42, 1.2);
}

#include <iostream>
void f0(int, double) { std::cout << "f0\n"; }

// illegal:
//my_function_type f1 { std::cout << "f1\n"; }

void f1(int, double) { std::cout << "f1\n"; }

関数型を使用して、関数への参照とポインターを持つことができます。

my_function_type& my_ref = f0;
my_function_type* my_ptr = f1;

最後の行では、関数からポインターへの変換が使用されます。それは以下と同等です:

my_function_type* my_ptr = &f1;

関数ポインターを呼び出すには、 unary を使用する必要はありません*。これは、意味が明確であるため、構文糖衣です。

my_ptr(42, 3.14);

したがって、次のすべてが有効です。

my_ptr(42, 3.14);
(*my_ptr)(42, 3.14);
my_ref(42, 3.14);
f0(42, 3.14);

NB ()(関数呼び出し) は unary よりも優先順位が高い*ため、括弧で囲む*my_ptr必要があります。

単項*にはポインター型が必要なため、好きなだけ追加できます。なぜこれらすべてのクレイジーな関数ポインター定義がすべて機能するのですか?*を参照してください。本当に何が起こっているのですか?


OPの例への適用

void _node_loop(void kernel(Node<T>*));

という名前のこのメンバー関数は、 type のパラメーターを_node_loop返し、受け取ります。このパラメータ タイプは に調整されるため、 の宣言は次と同等です。voidvoid(Node<T>*)void (*) (Node<T>*)_node_loop

void _node_loop(void (*kernel)(Node<T>*));

この関数内では、パラメーターkernelが使用されます。

kernel(tmp);

を使用せずに、関数ポインターを介して関数を呼び出すことができます*。しかし、もっと明確にすることもできます (私はお勧めしません):

(*kernel)(tmp);
于 2013-11-03T14:18:44.217 に答える