C -结构函数指针数组

s71maibg  于 2023-08-03  发布在  其他
关注(0)|答案(2)|浏览(109)

下面的第一个程序使用指针成功地调用了一个函数。我现在尝试使用结构体数组来实现相同的机制。最后一行(注解掉)将不起作用,我没有看到任何差异。我做错了什么?
有没有可能我在第二个程序中把这一行的格式搞错了?

  • void(func_ptr)(struct book,int)= printBook;

我使用了这个格式:第一个月

这个管用

// C call function using pointer
#include <stdio.h>
#include <string.h>

void func(int a)  {   // normal function
  printf("Value of a is %d\n", a);
}

int main() {
  void (*func_ptr)(int) = func;         // assign ptr to func address
  func_ptr(10); // call func
  return 0;
}

字符串

这行不通

// Looks the same but does not work
#include <stdio.h>
#include <string.h>

// data structure
struct book {                          // struct data
  int     book_id;                     // book id
  int     (*prtFunc) ( struct book  ); // eventually, addr of print function
  char    title[50];                   // book title
  char    author[50];                  // book author
};

struct book arBookList[10];

void printBook ( struct book arBookList[], int id ) {           // test struct access
  printf ( "book_id       : %d\n", arBookList[id].book_id );    
  printf ( "func addr     : %p\n", arBookList[id].prtFunc );    // eventually this will call function
  printf ( "title         : %s\n", arBookList[id].title   );    // string data test
  printf ( "author        : %s\n", arBookList[id].author  );    // string
  printf ( "\n" );
}

int main (  ) {

  arBookList[0].book_id =  0 ;
  arBookList[0].prtFunc = printBook;
  strcpy ( arBookList[0].title, "This Little Piggy" );
  strcpy ( arBookList[0].author, "Bad Wolf" );
  printBook (arBookList, 0 );                             // show data using function call

  arBookList[1].book_id =  1 ;
  arBookList[1].prtFunc = printBook;
  strcpy ( arBookList[1].title, "Mary Had a Lamb" );
  strcpy ( arBookList[1].author, "Snow Fleece" );
  printBook (arBookList, 1 );                             // show data using function call

  // call function using pointer
  void (*func_ptr)(struct book, int) = printBook;         // assign ptr to func address
 
  func_ptr (arBookList, 1 );                              // <--- does not work, why ???
  // commenting out the above line compiles ok 
  // but it looks ok to me except for different call parameters

  return 0;
}

**供将来参考:**通过下面的'O'的更正列表,我达到了我的最终目标,即能够在struct中调用保存的函数指针来调用函数(在本例中为printBook),如下所示:arBookList[1].prtFunc (arBookList, 1 );

icnyk63a

icnyk63a1#

对于函数指针,参数错误。你传递了一个指针,它需要的是结构体。
修正后的程序:

struct book {                          // struct data
  int     book_id;                     // book id
  void     (*prtFunc) (struct book  *, int); // eventually, addr of print function
  char    title[50];                   // book title
  char    author[50];                  // book author
};

struct book arBookList[10];

void printBook ( struct book *arBookList, int id ) {           // test struct access
  printf ( "book_id       : %d\n", arBookList[id].book_id );    
  printf ( "func addr     : %p\n", arBookList[id].prtFunc );    // eventually this will call function
  printf ( "title         : %s\n", arBookList[id].title   );    // string data test
  printf ( "author        : %s\n", arBookList[id].author  );    // string
  printf ( "\n" );
}

int main (  ) {

  arBookList[0].book_id =  0 ;
  arBookList[0].prtFunc = printBook;
  strcpy ( arBookList[0].title, "This Little Piggy" );
  strcpy ( arBookList[0].author, "Bad Wolf" );
  printBook (arBookList, 0 );                             // show data using function call

  arBookList[1].book_id =  1 ;
  arBookList[1].prtFunc = printBook;
  strcpy ( arBookList[1].title, "Mary Had a Lamb" );
  strcpy ( arBookList[1].author, "Snow Fleece" );
  printBook (arBookList, 1 );                             // show data using function call

  // call function using pointer
  void (*func_ptr)(struct book *, int) = printBook;         // assign ptr to func address
 
  func_ptr (arBookList, 1 );                              // <--- does not work, why ???
  // commenting out the above line compiles ok 
  // but it looks ok to me except for different call parameters

  return 0;
}

字符串
而且你的函数指针在结构中的类型也不对。
https://godbolt.org/z/5E1E8v7j3

4sup72z8

4sup72z82#

struct book中,您将prtFunc声明为

int (*prtFunc)(struct book);  // eventually, addr of print function

字符串
main中,您在

arBookList[0].prtFunc = printBook;


printBook

void printBook(struct book arBookList[], int id)


因此,acual参数与声明不匹配。

另一问题

有点离题,但如果函数对所有书籍都相同,则不应在阵列中的每个book中复制其地址。

示例:使用集合而非数组

typedef struct
{                     // struct data
    int  book_id;     // book id
    char title[50];   // book title
    char author[50];  // book author
} Book; // a single book

typedef struct
{
    size_t limit;  // capacity
    size_t size;   // actual size
    Book   book_list[10];
    int (*print_book)(Book*);
} Books; // a collection


这样,我们就有了一个集合Books,其中包含sizelimit。也是一个指向打印一本书的函数的指针。而且书都在struct里面,所以你可以把它作为一个整体来使用。
您的测试集合可以用现代的方式声明为

Books coll = {
        .limit = 10,
        .size  = 2,
        .book[0] =
            {.id     = 42,
             .title  = "This Little Piggy",
             .author = "Bad Wolf"},
        .book[1] =
            {.id     = 4242,
             .title  = "Mary Had a Lamb",
             .author = "Snow Fleece"},
        .print_book =
            printBook  // this function prints a book
    };


这更易于阅读和测试。

print_book的一个版本

void print_book(Book* book)
{
    printf(
        "\
    book_id       : %d\n\
    title         : %s\n\
    author        : %s\n\
\n",
        book->id, book->title, book->author);
};


单个printf(或puts)的读取和键入速度更快、更容易

打印所有图书

如果有一个函数可以方便地打印完整的Books结构:

int print_all(Books* col)
{
    if (col == NULL) return -1;
    printf(
        "    there are %llu of %llu books\n\n", col->size,
        col->limit);
    for (size_t i = 0; i < col->size; i += 1)
        col->print_book(&col->book[i]);
    printf("\n");
    return 0;
};


由于它在Books结构中使用了嵌入式指针,因此只需更改结构中的指针就可以轻松地更改样式

使用2个函数

coll.print_book(&coll.book[0]);
    print_book(&coll.book[1]);
   // print all
    printf("test: print the collection\n\n");
    print_all(&coll);


在这里,我们调用print_book来打印每本书,使用集合中的指针,按名称和引用来打印。然后打印集合。

完整的示例

// Looks the same but does not work
#include <stdio.h>
#include <string.h>

typedef struct
{                     // struct data
    int  id;          // book id
    char title[50];   // book title
    char author[50];  // book author
} Book;               // a single book

typedef struct
{
    size_t limit;  // capacity
    size_t size;   // actual size
    Book   book[10];
    void (*print_book)(Book*);
} Books;                          // a collection

void print_book(Book* book);      // single book
void print_book_alt(Book* book);  // single book,one line
int  print_all(Books*);           // collection

int main(void)
{
    Books coll = {
        .limit = 10,
        .size  = 2,
        .book[0] =
            {.id     = 42,
             .title  = "This Little Piggy",
             .author = "Bad Wolf"},
        .book[1] =
            {.id     = 4242,
             .title  = "Mary Had a Lamb",
             .author = "Snow Fleece"},
        .print_book =
            print_book  // this function prints a book
    };

    coll.print_book(&coll.book[0]);
    print_book(&coll.book[1]);
    // print all
    printf("test: print the collection\n\n");
    print_all(&coll);
    // test alternate print f.
    printf("test: alternate print\n\n");
    print_book_alt(&coll.book[0]);
    print_book_alt(&coll.book[1]);
    // adds a new book
    Book other = {
        .id     = 17,
        .title  = "My time with Ms. Lane",
        .author = "Super Man"};
    printf("\ntest: new book\n\n");
    print_book_alt(&other);
    // updates collection
    coll.book[2] = other;
    coll.size += 1;
    print_all(&coll);
    // changes printing
    coll.print_book = print_book_alt;
    printf("\ntest: print the collection again\n\n");
    print_all(&coll);
    return 0;
}

void print_book(Book* book)
{
    printf(
        "\
    book_id       : %d\n\
    title         : %s\n\
    author        : %s\n\
\n",
        book->id, book->title, book->author);
};

int print_all(Books* col)
{
    if (col == NULL) return -1;
    printf(
        "    there are %llu of %llu books\n\n", col->size,
        col->limit);
    for (size_t i = 0; i < col->size; i += 1)
        col->print_book(&col->book[i]);
    printf("\n");
    return 0;
};

void print_book_alt(Book* book)
{
    printf(
        "%8d, \"%s\", [%s]\n", book->id, book->title,
        book->author);
}

示例的输出

book_id       : 42
    title         : This Little Piggy
    author        : Bad Wolf

    book_id       : 4242
    title         : Mary Had a Lamb
    author        : Snow Fleece

test: print the collection

    there are 2 of 10 books

    book_id       : 42
    title         : This Little Piggy
    author        : Bad Wolf

    book_id       : 4242
    title         : Mary Had a Lamb
    author        : Snow Fleece

test: alternate print

      42, "This Little Piggy", [Bad Wolf]
    4242, "Mary Had a Lamb", [Snow Fleece]

test: new book

      17, "My time with Ms. Lane", [Super Man]
    there are 3 of 10 books

    book_id       : 42
    title         : This Little Piggy
    author        : Bad Wolf

    book_id       : 4242
    title         : Mary Had a Lamb
    author        : Snow Fleece

    book_id       : 17
    title         : My time with Ms. Lane
    author        : Super Man


test: print the collection again

    there are 3 of 10 books

      42, "This Little Piggy", [Bad Wolf]
    4242, "Mary Had a Lamb", [Snow Fleece]
      17, "My time with Ms. Lane", [Super Man]


在这个例子中

  • printing函数被调用两次,一次是按名称调用,另一次是使用Books中的指针
  • 一本书被添加到收藏中
  • 增加了交替打印功能
  • Books内的函数指针发生变化
  • 再次打印集合,

带类的C:使用函数指针转换Container泛型

下面是上一个示例中 * 集合 * 的struct

typedef struct
{
    size_t limit;  // capacity
    size_t size;   // actual size
    Book   book_list[10];
    int (*print_book)(Book*);
} Books; // a collection


它解决了很多问题,因为它可以作为一个整体来对待,并封装了当前和总大小限制。
但是:

  • 如果Book分配了内存,则无法释放它。
  • *collecion的 * 项目本身在struct
  • 难以调整大小
  • 删除项目是困难,
  • 这是同一个文件,因此容器中的更改或项目中的更改会触发同一代码的更改

容器的代码最好独立于物料的代码。

示例:更通用的容器

确定是物品清单。但也可能是链表、集合、队列、数组...... C++将其称为 * 容器 *。java和其他人称之为 * 集合 *。
考量

typedef struct
{
    size_t limit;             // capacity
    size_t size;              // actual size
    void** item;              // an array of pointers to items
    void* (*copy)(void*);     // copy
    void* (*destroy)(void*);  // destructor
    int (*show)(void*);       // print
} Container;                  // a collection


现在,我们有了一个指向项目的指针数组,并且:

  • 调整大小很容易:我们只有对该项目的引用
  • 我们没有参考该项目的实际定义
  • 我们知道如何复制项目以便将其插入容器
  • 我们知道如何在销毁集装箱或管理集装箱内容物时销毁物品。
  • 我们知道如何在屏幕上显示一个项目

但集装箱却不知道它的实际内容:是容器的用户生成代码,而容器只是保存函数指针。

甚至不需要重新编译container.c的代码

> now we can implement the container code with no mention to the items: a container can contain anything: books, SDL screens, SDL renderers, invoices, movie tickets...


最小函数集:

Container* ctn_create(
  size_t, void* (*copy)(void*), void* (*destroy)(void*),
  int (*show)(void*));
Container* ctn_destroy(Container*);
int        ctn_insert(void*, Container*);
int        ctn_show(Container*);


这是写一个例子的最低限度,就像下面这个例子。
item.h中将Book作为Item

#pragma once
#include <stdio.h>

typedef struct
{                   // struct data
    size_t id;      // book id
    char*  title;   // book title
    char*  author;  // book author
    size_t n_pages;   
} Book;             // a single book

int   print_book(void*);
int   print_book_alt(void*);  // one liner
void* copy_book(void*);
void* create_book(void);
void* delete_book(void*);


由于函数指针的存在,容器代码的更改不会导致物料代码的更改。在C++中,它们将被称为Item的 * 复制构造函数 析构函数 * 和 * 运算符<<重载 *。

执行:Book

项目. h

#pragma once
#include <stdio.h>

typedef struct
{                   // struct data
    size_t id;      // book id
    char*  title;   // book title
    char*  author;  // book author
    size_t n_pages;   
} Book;             // a single book

int   print_book(void*);
int   print_book_alt(void*);  // one liner
void* copy_book(void*);
void* create_book(void);
void* delete_book(void*);


函数现在接受并返回void,但Book有一个定义,因此所有函数都可以根据需要转换指针。这与qsort中使用的方法(例如,从stdlib开始)相同。例如javajavascript中的 * 回调 *。
请注意,现在Book使用指向titleauthor的指针,并有一个新的页数字段。因此,内存被分配到所需的确切大小,而不是第一个示例中的固定数组。
问题是,现在要销毁容器,代码必须知道如何释放这些字段和Book本身。这里是一个 callback(函数指针)的用法和需要。

item.c的简单实现

#include "item.h"

#include <stdlib.h>
#include <string.h>

void* copy_book(void* book)
{
    if (book == NULL) return NULL;
    Book* one     = (Book*)book;
    Book* other   = (void*)malloc(sizeof(Book));
    other->id     = one->id;
    other->author = malloc(1 + sizeof(one->author));
    strcpy(other->author, one->author);
    other->title = malloc(1 + sizeof(one->title));
    strcpy(other->title, one->title);
    other->n_pages = one->n_pages;
    return (void*)other;
};

void* create_book(void)
{
    static size_t id         = 1000;
    char          author[40] = {0};
    char          title[40]  = {0};
    Book*         one        = (void*)malloc(sizeof(Book));

    sprintf(&author[0], "Author %llu", id);
    one->author = malloc(1 + strlen(author));
    strcpy(one->author, author);

    sprintf(&title[0], "Title %llu", id);
    one->title = malloc(1 + strlen(title));
    strcpy(one->title, title);

    one->id      = id++;
    one->n_pages = 200 + rand() % 155;
    return (void*) one;
};

void* delete_book(void* book)
{
    if (book == NULL) return NULL;
    Book* one = (Book*)book;
    free(one->author);
    free(one->title);
    free(one);  // simple, void does not allocate
    return NULL;
}

int print_book(void* book)
{
    if (book == NULL) return 0;
    Book* one = (Book*)book;
    printf(
        "\
    Book ID       : %llu\n\
    title         : %s [%llu pages]\n\
    author        : %s\n\
\n",
        one->id, one->title, one->n_pages, one->author);
    return 0;
};

int print_book_alt(void* book)
{
    if (book == NULL) return 0;
    Book* one = (Book*)book;
    printf(
        "%8llu, \"%s [%llu pages]\", [%s]\n", one->id,
        one->title, one->n_pages, one->author);
    return 0;
}


没有对Container的引用:仅包含item.h以获得Book和函数原型。
请注意,添加了create_book(),这是一个方便的 * 工厂函数 *,每次调用它时都会生成一本新书。这种方法很容易在没有输入文件的情况下使用任意数量的项进行测试。

泛型Container的代码

请注意,该容器甚至不包括item.h

container.h的一个字符串

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct
{
    size_t limit;             // capacity
    size_t size;              // actual size
    void** item;              // an array of pointers to items
    void* (*copy)(void*);     // copy
    void* (*destroy)(void*);  // destructor
    int (*show)(void*);       // print
} Container;                  // a collection

Container* ctn_create(
    size_t, void* (*copy)(void*), void* (*destroy)(void*),
    int (*show)(void*));
Container* ctn_destroy(Container*);
int        ctn_insert(void*, Container*);
int        ctn_show(Container*);


Container现在是指向项的指针数组。任意项,因为用户在调用ctn_create()时传递了函数的地址。
通过这种方式,程序可以轻松地管理一组项目,甚至是不同的项目。

container.c个字符

#pragma once
#include "container.h"

Container* ctn_create(
    size_t size, void* (*copy)(void*),
    void* (*destroy)(void*), int (*show)(void*))
{
    Container* ctn =
        (Container*)malloc(sizeof(Container) * size);
    if (ctn == NULL) return NULL;
    ctn->limit = size;  // limit
    ctn->size  = 0;     // now empty, of course
    ctn->item = (void**)malloc(size * sizeof(void*));
    if (ctn->item == NULL)
    {  // could not allocate
        free(ctn);
        return NULL;
    }
    ctn->copy    = copy;
    ctn->destroy = destroy;
    ctn->show    = show;
    return ctn;
}

Container* ctn_destroy(Container* ctn)
{  // to destroy a container we need to know how to
    // delete the voids: they can allocate memory
    if (ctn == NULL) return NULL;
    for (size_t ix = 0; ix < ctn->size; ix += 1)
        ctn->destroy(ctn->item[ix]);
    return NULL;
}

int ctn_insert(void* item, Container* ctn)
{  // it is not wise to insert just a pointer
    // that can be free'd elsewhere:
    // it must be a copy. But an void can allocate
    // memory so we need to rely on user supplied
    // method
    if (item == NULL) return -1;       // no void
    if (ctn == NULL) return -2;        // no container
    if (ctn->size == ctn->limit)
        return -3;                     // container full
    if (ctn->copy == NULL) return -4;  // no copy function
    ctn->item[ctn->size] = ctn->copy(item);
    if (ctn->item[ctn->size] == NULL)
        return -5;  // error on copy
    ctn->size += 1;
    return 0;
}

int ctn_show(Container* col)
{
    if (col == NULL) return -1;
    printf(
        "    there are %llu of %llu items\n\n", col->size,
        col->limit);
    if (col->show == NULL) return -1;  // no show function
    for (size_t i = 0; i < col->size; i += 1)
        col->show(col->item[i]);
    printf("[end of listing]\n");
    return 0;
}

main.c进行简单测试

#include <stdio.h>
#include <stdlib.h>

#include "container.h"
#include "item.h"

int main(void)
{
    const int  test_size = 6;
    Container* books =
        ctn_create(30, copy_book, delete_book, print_book);
    for (size_t id = 1; id <= test_size; id += 1)
    {
        Book* newb = create_book();
        ctn_insert(newb, books);
        delete_book(newb);
    };
    ctn_show(books);
    printf(
        "\n\t***** now print again, using new layout\n\n");
    books->show = print_book_alt;
    ctn_show(books);             // now using _alt
    books = ctn_destroy(books);  // delete all
    ctn_show(books);             // now empty
    return 0;
}


注一:

ctn_show(books);
    printf(
        "\n\t***** now print again, using new layout\n\n");
    books->show = print_book_alt;
    ctn_show(books);             // now using _alt

在这里使用books->show的要点是它封装了行为,作为PythonjavaC++中的对象:通过更改此设置,下一次对cnt_show的调用将继承新的打印布局:见下面的程序输出
注2:

ctn_show(books);             // now using _alt
    books = ctn_destroy(books);  // delete all
    ctn_show(books);             // now empty

通过从cnt_destroy返回NULL,容器的指针在同一行代码中无效:没有办法有一个无效的指针。这很好,所以下一行对ctn->show的调用不会得到已经删除的容器的指针。

示例输出

there are 6 of 30 items

    Book ID       : 1000
    title         : Title 1000 [241 pages]
    author        : Author 1000

    Book ID       : 1001
    title         : Title 1001 [222 pages]
    author        : Author 1001

    Book ID       : 1002
    title         : Title 1002 [334 pages]
    author        : Author 1002

    Book ID       : 1003
    title         : Title 1003 [350 pages]
    author        : Author 1003

    Book ID       : 1004
    title         : Title 1004 [304 pages]
    author        : Author 1004

    Book ID       : 1005
    title         : Title 1005 [269 pages]
    author        : Author 1005

[end of listing]

        ***** now print again, using new layout

    there are 6 of 30 items

    1000, "Title 1000 [241 pages]", [Author 1000]
    1001, "Title 1001 [222 pages]", [Author 1001]
    1002, "Title 1002 [334 pages]", [Author 1002]
    1003, "Title 1003 [350 pages]", [Author 1003]
    1004, "Title 1004 [304 pages]", [Author 1004]
    1005, "Title 1005 [269 pages]", [Author 1005]
[end of listing]

代码在https://github.com/ARFNeto-CH/soc23-0720-example

相关问题