C – 结构体数组函数指针

huangapple go评论142阅读模式
英文:

C - Array of structs function pointer

问题

以下是翻译好的部分:

第一个程序在成功使用指针调用函数。我现在正在尝试使用结构体数组使相同的机制工作。最后一行(已注释)不起作用,我看不出任何区别。我做错了什么?

在第二个程序中,这一行的格式可能有问题吗?
void (*func_ptr)(struct book, int) = printBook;

我使用了这个格式:<返回类型>(<*变量名>)(<参数类型>,<参数类型>,...)=函数

这个可以正常工作...

  1. // C使用指针调用函数
  2. #include <stdio.h>
  3. #include <string.h>
  4. void func(int a) { // 正常函数
  5. printf("a的值为%d\n", a);
  6. }
  7. int main() {
  8. void (*func_ptr)(int) = func; // 将指针分配给函数地址
  9. func_ptr(10); // 调用func
  10. return 0;
  11. }

这个不工作...

  1. // 看起来相同但不工作
  2. #include <stdio.h>
  3. #include <string.h>
  4. // 数据结构
  5. struct book { // 结构体数据
  6. int book_id; // 书籍ID
  7. int (*prtFunc) ( struct book ); // 最终是打印函数的地址
  8. char title[50]; // 书名
  9. char author[50]; // 作者
  10. };
  11. struct book arBookList[10];
  12. void printBook ( struct book arBookList[], int id ) { // 测试结构体访问
  13. printf ( "book_id : %d\n", arBookList[id].book_id );
  14. printf ( "func地址 : %p\n", arBookList[id].prtFunc ); // 最终这将调用函数
  15. printf ( "title : %s\n", arBookList[id].title ); // 字符串数据测试
  16. printf ( "author : %s\n", arBookList[id].author ); // 字符串
  17. printf ( "\n" );
  18. }
  19. int main ( ) {
  20. arBookList[0].book_id = 0 ;
  21. arBookList[0].prtFunc = printBook;
  22. strcpy ( arBookList[0].title, "This Little Piggy" );
  23. strcpy ( arBookList[0].author, "Bad Wolf" );
  24. printBook (arBookList, 0 ); // 使用函数调用显示数据
  25. arBookList[1].book_id = 1 ;
  26. arBookList[1].prtFunc = printBook;
  27. strcpy ( arBookList[1].title, "Mary Had a Lamb" );
  28. strcpy ( arBookList[1].author, "Snow Fleece" );
  29. printBook (arBookList, 1 ); // 使用函数调用显示数据
  30. // 使用指针调用函数
  31. void (*func_ptr)(struct book, int) = printBook; // 将指针分配给函数地址
  32. func_ptr (arBookList, 1 ); // <--- 不起作用,为什么?
  33. // 注释掉上面的行可以正常编译
  34. // 但我觉得除了不同的调用参数外,看起来都没问题
  35. return 0;
  36. }

供以后参考: 使用'O'的修正清单下面,我达到了我的最终目标,即能够调用保存在结构体中的函数指针来调用函数(在这种情况下是printBook),就像这样:arBookList[1].prtFunc (arBookList, 1 );

英文:

The first program below calls a function using a pointer successfully. I am now trying to get the same mechanism to work using an array of structs. The last line (commented out) will not work, and I do not see any differences. What am I doing wrong?

Is it possible I have the wrong format for this line in the second program?
> *void (func_ptr)(struct book, int) = printBook;<br>
I used this format: &lt;ret type&gt; (&lt;*varname&gt;) (&lt;parm type&gt;, &lt;parm type&gt;,...) = function

This works...

  1. // C call function using pointer
  2. #include &lt;stdio.h&gt;
  3. #include &lt;string.h&gt;
  4. void func(int a) { // normal function
  5. printf(&quot;Value of a is %d\n&quot;, a);
  6. }
  7. int main() {
  8. void (*func_ptr)(int) = func; // assign ptr to func address
  9. func_ptr(10); // call func
  10. return 0;
  11. }

This does not work...

  1. // Looks the same but does not work
  2. #include &lt;stdio.h&gt;
  3. #include &lt;string.h&gt;
  4. // data structure
  5. struct book { // struct data
  6. int book_id; // book id
  7. int (*prtFunc) ( struct book ); // eventually, addr of print function
  8. char title[50]; // book title
  9. char author[50]; // book author
  10. };
  11. struct book arBookList[10];
  12. void printBook ( struct book arBookList[], int id ) { // test struct access
  13. printf ( &quot;book_id : %d\n&quot;, arBookList[id].book_id );
  14. printf ( &quot;func addr : %p\n&quot;, arBookList[id].prtFunc ); // eventually this will call function
  15. printf ( &quot;title : %s\n&quot;, arBookList[id].title ); // string data test
  16. printf ( &quot;author : %s\n&quot;, arBookList[id].author ); // string
  17. printf ( &quot;\n&quot; );
  18. }
  19. int main ( ) {
  20. arBookList[0].book_id = 0 ;
  21. arBookList[0].prtFunc = printBook;
  22. strcpy ( arBookList[0].title, &quot;This Little Piggy&quot; );
  23. strcpy ( arBookList[0].author, &quot;Bad Wolf&quot; );
  24. printBook (arBookList, 0 ); // show data using function call
  25. arBookList[1].book_id = 1 ;
  26. arBookList[1].prtFunc = printBook;
  27. strcpy ( arBookList[1].title, &quot;Mary Had a Lamb&quot; );
  28. strcpy ( arBookList[1].author, &quot;Snow Fleece&quot; );
  29. printBook (arBookList, 1 ); // show data using function call
  30. // call function using pointer
  31. void (*func_ptr)(struct book, int) = printBook; // assign ptr to func address
  32. func_ptr (arBookList, 1 ); // &lt;--- does not work, why ???
  33. // commenting out the above line compiles ok
  34. // but it looks ok to me except for different call parameters
  35. return 0;
  36. }

For future reference: With 'O's corrected listing below I reached my final goal which was to be able to call a saved function pointer in struct to call a function (in this case printBook) like this: arBookList[1].prtFunc (arBookList, 1 );

答案1

得分: 4

Here's the translated code portion:

  1. 对于函数指针传递了错误的参数。您传递了指针,但它期望的是结构体。
  2. 修正后的程序:
  3. ```c
  4. struct book { // 结构体数据
  5. int book_id; // 书籍ID
  6. void (*prtFunc) (struct book *, int); // 最终,打印函数的地址
  7. char title[50]; // 书籍标题
  8. char author[50]; // 书籍作者
  9. };
  10. struct book arBookList[10];
  11. void printBook (struct book *arBookList, int id) { // 测试结构体访问
  12. printf("book_id : %d\n", arBookList[id].book_id);
  13. printf("func addr : %p\n", arBookList[id].prtFunc); // 最终这将调用函数
  14. printf("title : %s\n", arBookList[id].title); // 字符串数据测试
  15. printf("author : %s\n", arBookList[id].author); // 字符串
  16. printf("\n");
  17. }
  18. int main() {
  19. arBookList[0].book_id = 0;
  20. arBookList[0].prtFunc = printBook;
  21. strcpy(arBookList[0].title, "This Little Piggy");
  22. strcpy(arBookList[0].author, "Bad Wolf");
  23. printBook(arBookList, 0); // 使用函数调用显示数据
  24. arBookList[1].book_id = 1;
  25. arBookList[1].prtFunc = printBook;
  26. strcpy(arBookList[1].title, "Mary Had a Lamb");
  27. strcpy(arBookList[1].author, "Snow Fleece");
  28. printBook(arBookList, 1); // 使用函数调用显示数据
  29. // 使用指针调用函数
  30. void (*func_ptr)(struct book *, int) = printBook; // 将指针分配给函数地址
  31. func_ptr(arBookList, 1); // <--- 不起作用,为什么???
  32. // 注释掉上面的行,编译没有问题
  33. // 但在我看来,除了调用参数不同以外,一切看起来都还好
  34. return 0;
  35. }

此外,您的结构体中的函数指针类型也有问题。

英文:

For function pointer takes wrong parameter. You pass pointer and it is expecting the struct.

The corrected program:

  1. struct book { // struct data
  2. int book_id; // book id
  3. void (*prtFunc) (struct book *, int); // eventually, addr of print function
  4. char title[50]; // book title
  5. char author[50]; // book author
  6. };
  7. struct book arBookList[10];
  8. void printBook ( struct book *arBookList, int id ) { // test struct access
  9. printf ( &quot;book_id : %d\n&quot;, arBookList[id].book_id );
  10. printf ( &quot;func addr : %p\n&quot;, arBookList[id].prtFunc ); // eventually this will call function
  11. printf ( &quot;title : %s\n&quot;, arBookList[id].title ); // string data test
  12. printf ( &quot;author : %s\n&quot;, arBookList[id].author ); // string
  13. printf ( &quot;\n&quot; );
  14. }
  15. int main ( ) {
  16. arBookList[0].book_id = 0 ;
  17. arBookList[0].prtFunc = printBook;
  18. strcpy ( arBookList[0].title, &quot;This Little Piggy&quot; );
  19. strcpy ( arBookList[0].author, &quot;Bad Wolf&quot; );
  20. printBook (arBookList, 0 ); // show data using function call
  21. arBookList[1].book_id = 1 ;
  22. arBookList[1].prtFunc = printBook;
  23. strcpy ( arBookList[1].title, &quot;Mary Had a Lamb&quot; );
  24. strcpy ( arBookList[1].author, &quot;Snow Fleece&quot; );
  25. printBook (arBookList, 1 ); // show data using function call
  26. // call function using pointer
  27. void (*func_ptr)(struct book *, int) = printBook; // assign ptr to func address
  28. func_ptr (arBookList, 1 ); // &lt;--- does not work, why ???
  29. // commenting out the above line compiles ok
  30. // but it looks ok to me except for different call parameters
  31. return 0;
  32. }

Also your function pointer in the struct had a wrong type.

https://godbolt.org/z/5E1E8v7j3

答案2

得分: 1

In struct book you have prtFunc declared as

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

And in main you have error on

  1. arBookList[0].prtFunc = printBook;

But printBook is

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

So the actual parameters do not match the declaration.

another issue

A bit off-topic but if the function is the same for all books its address should not be replicated in every book in the array.

Example: using a collection instead of an array

  1. typedef struct
  2. { // struct data
  3. int book_id; // book id
  4. char title[50]; // book title
  5. char author[50]; // book author
  6. } Book; // a single book
  7. typedef struct
  8. {
  9. size_t limit; // capacity
  10. size_t size; // actual size
  11. Book book_list[10];
  12. int (*print_book)(Book*);
  13. } Books; // a collection

This way we have a collection Books that has a size and limit. And also a pointer to a function that prints a single book. And the books are inside the struct so you can use it as a whole.

Your test collection can be declared in a modern way as

  1. Books coll = {
  2. .limit = 10,
  3. .size = 2,
  4. .book[0] =
  5. {.id = 42,
  6. .title = "This Little Piggy",
  7. .author = "Bad Wolf"},
  8. .book[1] =
  9. {.id = 4242,
  10. .title = "Mary Had a Lamb",
  11. .author = "Snow Fleece"},
  12. .print_book =
  13. printBook // this function prints a book
  14. };

This is easier to read and test.

a version of printBook

  1. void printBook(Book* book)
  2. {
  3. printf(
  4. "\
  5. book_id : %d\n\
  6. title : %s\n\
  7. author : %s\n\
  8. \n",
  9. book->id, book->title, book->author);
  10. };

A single printf (or puts) is way faster and easier to read and type.

printing all books

It can be convenient to have a function to print the complete Books struct:

  1. int print_all(Books* col)
  2. {
  3. if (col == NULL) return -1;
  4. printf(
  5. " there are %llu of %llu books\n\n", col->size,
  6. col->limit);
  7. for (size_t i = 0; i < col->size; i += 1)
  8. col->print_book(&col->book[i]);
  9. printf("\n");
  10. return 0;
  11. };

Since it uses the embedded pointer in the Books struct, it is easy to change style just by changing the pointer inside the struct.

using the 2 functions

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

Here we call printBook to print each book, by name and by reference using the pointer inside the collection. Then the collection is printed.

A complete example

  1. // Looks the same but does not work
  2. #include <stdio.h>
  3. #include <string.h>
  4. typedef struct
  5. { // struct data
  6. int id; // book id
  7. char title[50]; // book title
  8. char author[50]; // book author
  9. } Book; // a single book
  10. typedef struct
  11. {
  12. size_t limit; // capacity
  13. size_t size; // actual size
  14. Book book[10];
  15. void (*print_book)(Book*);
  16. } Books; // a collection
  17. void print_book(Book* book); // single book
  18. void print_book_alt(Book* book); // single book, one line
  19. int print_all(Books*); // collection
  20. int main(void)
  21. {
  22. Books coll = {
  23. .limit = 10,
  24. .size = 2,
  25. .book[0] =
  26. {.id = 42,
  27. .title = "This Little Piggy",
  28. .author = "Bad Wolf"},
  29. .book[1] =
  30. {.id = 4242,
  31. .title = "Mary Had a Lamb",
  32. .author = "Snow Fleece"},
  33. .print_book =
  34. print_book // this function prints a book
  35. };
  36. coll.print_book(&coll.book[0]);
  37. print_book(&coll.book[1]);
  38. // print all
  39. printf("test: print the collection\n\n");
  40. print_all(&coll);
  41. // test alternate print function
  42. printf("test: alternate print\n\n");
  43. print_book_alt(&coll.book[0]);
  44. print_book_alt(&coll.book[1]);
  45. // adds a new book
  46. Book other = {
  47. .id = 17,
  48. .title = "My time with Ms. Lane",
  49. .author = "Super Man"};
  50. printf("\ntest: new book\n\n");
  51. print_book_alt(&other);
  52. // updates collection
  53. coll.book[2] = other;
  54. coll.size += 1;
  55. print_all(&coll);
  56. // changes printing
  57. coll.print_book = print_book_alt;
  58. printf("\ntest: print the collection again\n\n");
  59. print_all(&coll);
  60. return 0;
  61. }
  62. void print_book(Book* book)
  63. {
  64. printf(
  65. "\
  66. book_id : %d\n\
  67. title : %s\n\
  68. author : %s\n\
  69. \n",
  70. book->id, book->title, book->author);
  71. };
  72. int print_all(Books* col)
  73. {
  74. if (col == NULL) return -1;
  75. printf(
  76. " there are %llu of %llu books\n\n", col->size,
  77. col->limit);
  78. for (size_t i = 0; i < col->size; i += 1)
  79. col->print_book(&col->book[i]);
  80. printf("\n");
  81. return 0;
  82. };
  83. void print_book_alt(Book* book)
  84. {
  85. printf(
  86. "%8d, \"%s\", [%s]\n", book->id, book->title,
  87. book->author);
  88. }

the output of the example

  1. book_id : 42
  2. title : This Little Piggy
  3. author : Bad Wolf
  4. book_id : 4242
  5. title : Mary Had a Lamb
  6. author : Snow Fleece
  7. test: print the collection
  8. there are 2 of 10 books
  9. book_id : 42
  10. title : This Little Piggy
  11. author : Bad Wolf
  12. book_id : 4242
  13. title : Mary Had
  14. <details>
  15. <summary>英文:</summary>
  16. In `struct book` you have `prtFunc` declared as
  17. ```C
  18. int (*prtFunc)(struct book); // eventually, addr of print function

And in main you have error on

  1. arBookList[0].prtFunc = printBook;

But printBook is

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

So the acual parameters does not match the declaration.

another issue

A bit off-topic but if the function is the same for all books its address should not be replicated in every book in the array.

Example: using a collection instead of an array

  1. typedef struct
  2. { // struct data
  3. int book_id; // book id
  4. char title[50]; // book title
  5. char author[50]; // book author
  6. } Book; // a single book
  7. typedef struct
  8. {
  9. size_t limit; // capacity
  10. size_t size; // actual size
  11. Book book_list[10];
  12. int (*print_book)(Book*);
  13. } Books; // a collection

This way we have a collection Books that has a size and limit. And also a pointer to a function that prints a single book. And the books are inside the struct so you can use it as a whole.

Your test collection can be declared in a modern way as

  1. Books coll = {
  2. .limit = 10,
  3. .size = 2,
  4. .book[0] =
  5. {.id = 42,
  6. .title = &quot;This Little Piggy&quot;,
  7. .author = &quot;Bad Wolf&quot;},
  8. .book[1] =
  9. {.id = 4242,
  10. .title = &quot;Mary Had a Lamb&quot;,
  11. .author = &quot;Snow Fleece&quot;},
  12. .print_book =
  13. printBook // this function prints a book
  14. };

This is easier to read and test.

a version of print_book

  1. void print_book(Book* book)
  2. {
  3. printf(
  4. &quot;\
  5. book_id : %d\n\
  6. title : %s\n\
  7. author : %s\n\
  8. \n&quot;,
  9. book-&gt;id, book-&gt;title, book-&gt;author);
  10. };

A single printf (or puts) is way more faster and easier to read and type

printing all books

It can be convenient to have a function to print the complete Books struct:

  1. int print_all(Books* col)
  2. {
  3. if (col == NULL) return -1;
  4. printf(
  5. &quot; there are %llu of %llu books\n\n&quot;, col-&gt;size,
  6. col-&gt;limit);
  7. for (size_t i = 0; i &lt; col-&gt;size; i += 1)
  8. col-&gt;print_book(&amp;col-&gt;book[i]);
  9. printf(&quot;\n&quot;);
  10. return 0;
  11. };

Since it uses the embedded pointer in the Books struct it is easy to change style just by changing the pointer inside the struct

using the 2 functions

  1. coll.print_book(&amp;coll.book[0]);
  2. print_book(&amp;coll.book[1]);
  3. // print all
  4. printf(&quot;test: print the collection\n\n&quot;);
  5. print_all(&amp;coll);

here we call print_book to print each book, by name and by reference using the pointer inside collection. Then the collection is printed.

A complete example

  1. // Looks the same but does not work
  2. #include &lt;stdio.h&gt;
  3. #include &lt;string.h&gt;
  4. typedef struct
  5. { // struct data
  6. int id; // book id
  7. char title[50]; // book title
  8. char author[50]; // book author
  9. } Book; // a single book
  10. typedef struct
  11. {
  12. size_t limit; // capacity
  13. size_t size; // actual size
  14. Book book[10];
  15. void (*print_book)(Book*);
  16. } Books; // a collection
  17. void print_book(Book* book); // single book
  18. void print_book_alt(Book* book); // single book,one line
  19. int print_all(Books*); // collection
  20. int main(void)
  21. {
  22. Books coll = {
  23. .limit = 10,
  24. .size = 2,
  25. .book[0] =
  26. {.id = 42,
  27. .title = &quot;This Little Piggy&quot;,
  28. .author = &quot;Bad Wolf&quot;},
  29. .book[1] =
  30. {.id = 4242,
  31. .title = &quot;Mary Had a Lamb&quot;,
  32. .author = &quot;Snow Fleece&quot;},
  33. .print_book =
  34. print_book // this function prints a book
  35. };
  36. coll.print_book(&amp;coll.book[0]);
  37. print_book(&amp;coll.book[1]);
  38. // print all
  39. printf(&quot;test: print the collection\n\n&quot;);
  40. print_all(&amp;coll);
  41. // test alternate print f.
  42. printf(&quot;test: alternate print\n\n&quot;);
  43. print_book_alt(&amp;coll.book[0]);
  44. print_book_alt(&amp;coll.book[1]);
  45. // adds a new book
  46. Book other = {
  47. .id = 17,
  48. .title = &quot;My time with Ms. Lane&quot;,
  49. .author = &quot;Super Man&quot;};
  50. printf(&quot;\ntest: new book\n\n&quot;);
  51. print_book_alt(&amp;other);
  52. // updates collection
  53. coll.book[2] = other;
  54. coll.size += 1;
  55. print_all(&amp;coll);
  56. // changes printing
  57. coll.print_book = print_book_alt;
  58. printf(&quot;\ntest: print the collection again\n\n&quot;);
  59. print_all(&amp;coll);
  60. return 0;
  61. }
  62. void print_book(Book* book)
  63. {
  64. printf(
  65. &quot;\
  66. book_id : %d\n\
  67. title : %s\n\
  68. author : %s\n\
  69. \n&quot;,
  70. book-&gt;id, book-&gt;title, book-&gt;author);
  71. };
  72. int print_all(Books* col)
  73. {
  74. if (col == NULL) return -1;
  75. printf(
  76. &quot; there are %llu of %llu books\n\n&quot;, col-&gt;size,
  77. col-&gt;limit);
  78. for (size_t i = 0; i &lt; col-&gt;size; i += 1)
  79. col-&gt;print_book(&amp;col-&gt;book[i]);
  80. printf(&quot;\n&quot;);
  81. return 0;
  82. };
  83. void print_book_alt(Book* book)
  84. {
  85. printf(
  86. &quot;%8d, \&quot;%s\&quot;, [%s]\n&quot;, book-&gt;id, book-&gt;title,
  87. book-&gt;author);
  88. }

the output of the example

  1. book_id : 42
  2. title : This Little Piggy
  3. author : Bad Wolf
  4. book_id : 4242
  5. title : Mary Had a Lamb
  6. author : Snow Fleece
  7. test: print the collection
  8. there are 2 of 10 books
  9. book_id : 42
  10. title : This Little Piggy
  11. author : Bad Wolf
  12. book_id : 4242
  13. title : Mary Had a Lamb
  14. author : Snow Fleece
  15. test: alternate print
  16. 42, &quot;This Little Piggy&quot;, [Bad Wolf]
  17. 4242, &quot;Mary Had a Lamb&quot;, [Snow Fleece]
  18. test: new book
  19. 17, &quot;My time with Ms. Lane&quot;, [Super Man]
  20. there are 3 of 10 books
  21. book_id : 42
  22. title : This Little Piggy
  23. author : Bad Wolf
  24. book_id : 4242
  25. title : Mary Had a Lamb
  26. author : Snow Fleece
  27. book_id : 17
  28. title : My time with Ms. Lane
  29. author : Super Man
  30. test: print the collection again
  31. there are 3 of 10 books
  32. 42, &quot;This Little Piggy&quot;, [Bad Wolf]
  33. 4242, &quot;Mary Had a Lamb&quot;, [Snow Fleece]
  34. 17, &quot;My time with Ms. Lane&quot;, [Super Man]

In this example

  • the printing function is called twice, one by name and other using the pointer in Books
  • a book is added to the collection
  • an alternate printing function is added
  • the function pointer inside Books is changed
  • the collection is again printed

C with classes: turning the Container generic using function pointers

Here is the struct for the collection in the previous example:

  1. typedef struct
  2. {
  3. size_t limit; // capacity
  4. size_t size; // actual size
  5. Book book_list[10];
  6. int (*print_book)(Book*);
  7. } Books; // a collection

It solves many things, since it can be treated as a whole and encapsulates the current and total size limits.

But:

  • if Book allocates memory there is no way to free it.
  • The collecion's items are themselves inside the struct
    • resize is difficult
    • delete an item is difficult
  • It is the same file so a change in the container OR in the items triggers a change in the same code

>It would be better to have the code for the container independent from the item's code.

Example: a more generic container

Sure it is a list of items. But could be a linked list, a set, a queue, an array... C++ calls it a container. java and others call them collection.

Consider

  1. typedef struct
  2. {
  3. size_t limit; // capacity
  4. size_t size; // actual size
  5. void** item; // an array of pointers to items
  6. void* (*copy)(void*); // copy
  7. void* (*destroy)(void*); // destructor
  8. int (*show)(void*); // print
  9. } Container; // a collection

Now we have an array of pointers to items and:

  • it is easy to resize: we have just references to the item
  • we have no reference to the actual definition of the item
  • we know how to copy an item in order to insert it into the container
  • we know how to destroy an item when destroying the container or managing the container's contents.
  • we know how to show one item at the screen

But the container have no idea about its actual contents: it is the user of the container that produces the code, and the container's just saves the function pointers.

There is no need to even recompile the code for container.c

  1. &gt; 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...

A minimum set of functions:

  1. Container* ctn_create(
  2. size_t, void* (*copy)(void*), void* (*destroy)(void*),
  3. int (*show)(void*));
  4. Container* ctn_destroy(Container*);
  5. int ctn_insert(void*, Container*);
  6. int ctn_show(Container*);

This is the mininum to write an example, like this one down here.

Book as an Item in item.h

  1. #pragma once
  2. #include &lt;stdio.h&gt;
  3. typedef struct
  4. { // struct data
  5. size_t id; // book id
  6. char* title; // book title
  7. char* author; // book author
  8. size_t n_pages;
  9. } Book; // a single book
  10. int print_book(void*);
  11. int print_book_alt(void*); // one liner
  12. void* copy_book(void*);
  13. void* create_book(void);
  14. void* delete_book(void*);

A change in container code does not lead to a change in the item code, thanks to the function pointers. In C++ they would be called copy constructor, destructor and operator << overload of Item.

Implementation: Book

item.h

  1. #pragma once
  2. #include &lt;stdio.h&gt;
  3. typedef struct
  4. { // struct data
  5. size_t id; // book id
  6. char* title; // book title
  7. char* author; // book author
  8. size_t n_pages;
  9. } Book; // a single book
  10. int print_book(void*);
  11. int print_book_alt(void*); // one liner
  12. void* copy_book(void*);
  13. void* create_book(void);
  14. void* delete_book(void*);

The functions now accept and return void, but there is a definition for Book so all of them can cast the pointers as needed. This is the same approach used in qsort from stdlib for example, since ever. Like callbacks in java or javascript.

Note that now Book uses pointers to title and author and has a new field for # of pages. So memory is allocated to the exact size needed, instead of the fixed arrays of the first example.

Problem is that now to destroy a container the code has to know how to free these fields and the Book itself. Here is the use )and need) for a callback, a function pointer.

item.c a simple implementation

  1. #include &quot;item.h&quot;
  2. #include &lt;stdlib.h&gt;
  3. #include &lt;string.h&gt;
  4. void* copy_book(void* book)
  5. {
  6. if (book == NULL) return NULL;
  7. Book* one = (Book*)book;
  8. Book* other = (void*)malloc(sizeof(Book));
  9. other-&gt;id = one-&gt;id;
  10. other-&gt;author = malloc(1 + sizeof(one-&gt;author));
  11. strcpy(other-&gt;author, one-&gt;author);
  12. other-&gt;title = malloc(1 + sizeof(one-&gt;title));
  13. strcpy(other-&gt;title, one-&gt;title);
  14. other-&gt;n_pages = one-&gt;n_pages;
  15. return (void*)other;
  16. };
  17. void* create_book(void)
  18. {
  19. static size_t id = 1000;
  20. char author[40] = {0};
  21. char title[40] = {0};
  22. Book* one = (void*)malloc(sizeof(Book));
  23. sprintf(&amp;author[0], &quot;Author %llu&quot;, id);
  24. one-&gt;author = malloc(1 + strlen(author));
  25. strcpy(one-&gt;author, author);
  26. sprintf(&amp;title[0], &quot;Title %llu&quot;, id);
  27. one-&gt;title = malloc(1 + strlen(title));
  28. strcpy(one-&gt;title, title);
  29. one-&gt;id = id++;
  30. one-&gt;n_pages = 200 + rand() % 155;
  31. return (void*) one;
  32. };
  33. void* delete_book(void* book)
  34. {
  35. if (book == NULL) return NULL;
  36. Book* one = (Book*)book;
  37. free(one-&gt;author);
  38. free(one-&gt;title);
  39. free(one); // simple, void does not allocate
  40. return NULL;
  41. }
  42. int print_book(void* book)
  43. {
  44. if (book == NULL) return 0;
  45. Book* one = (Book*)book;
  46. printf(
  47. &quot;\
  48. Book ID : %llu\n\
  49. title : %s [%llu pages]\n\
  50. author : %s\n\
  51. \n&quot;,
  52. one-&gt;id, one-&gt;title, one-&gt;n_pages, one-&gt;author);
  53. return 0;
  54. };
  55. int print_book_alt(void* book)
  56. {
  57. if (book == NULL) return 0;
  58. Book* one = (Book*)book;
  59. printf(
  60. &quot;%8llu, \&quot;%s [%llu pages]\&quot;, [%s]\n&quot;, one-&gt;id,
  61. one-&gt;title, one-&gt;n_pages, one-&gt;author);
  62. return 0;
  63. }

No reference to Container: just includes item.h to get Book and function prototypes.

Note the addition of create_book(), a handy factory function that generates a new book for each time it is called. This way is easy to test with any number of items with no input files.

Code for a generic Container

Note that the container does no even include item.h

container.h

  1. #pragma once
  2. #include &lt;stdio.h&gt;
  3. #include &lt;stdlib.h&gt;
  4. #include &lt;string.h&gt;
  5. typedef struct
  6. {
  7. size_t limit; // capacity
  8. size_t size; // actual size
  9. void** item; // an array of pointers to items
  10. void* (*copy)(void*); // copy
  11. void* (*destroy)(void*); // destructor
  12. int (*show)(void*); // print
  13. } Container; // a collection
  14. Container* ctn_create(
  15. size_t, void* (*copy)(void*), void* (*destroy)(void*),
  16. int (*show)(void*));
  17. Container* ctn_destroy(Container*);
  18. int ctn_insert(void*, Container*);
  19. int ctn_show(Container*);

And the Container is now an array of pointers to items. Any item, since the user when calling ctn_create() passes the addresses of the functions.

This way a program can easily manage an array of items, even of diferent ones.

container.c

  1. #pragma once
  2. #include &quot;container.h&quot;
  3. Container* ctn_create(
  4. size_t size, void* (*copy)(void*),
  5. void* (*destroy)(void*), int (*show)(void*))
  6. {
  7. Container* ctn =
  8. (Container*)malloc(sizeof(Container) * size);
  9. if (ctn == NULL) return NULL;
  10. ctn-&gt;limit = size; // limit
  11. ctn-&gt;size = 0; // now empty, of course
  12. ctn-&gt;item = (void**)malloc(size * sizeof(void*));
  13. if (ctn-&gt;item == NULL)
  14. { // could not allocate
  15. free(ctn);
  16. return NULL;
  17. }
  18. ctn-&gt;copy = copy;
  19. ctn-&gt;destroy = destroy;
  20. ctn-&gt;show = show;
  21. return ctn;
  22. }
  23. Container* ctn_destroy(Container* ctn)
  24. { // to destroy a container we need to know how to
  25. // delete the voids: they can allocate memory
  26. if (ctn == NULL) return NULL;
  27. for (size_t ix = 0; ix &lt; ctn-&gt;size; ix += 1)
  28. ctn-&gt;destroy(ctn-&gt;item[ix]);
  29. return NULL;
  30. }
  31. int ctn_insert(void* item, Container* ctn)
  32. { // it is not wise to insert just a pointer
  33. // that can be free&#39;d elsewhere:
  34. // it must be a copy. But an void can allocate
  35. // memory so we need to rely on user supplied
  36. // method
  37. if (item == NULL) return -1; // no void
  38. if (ctn == NULL) return -2; // no container
  39. if (ctn-&gt;size == ctn-&gt;limit)
  40. return -3; // container full
  41. if (ctn-&gt;copy == NULL) return -4; // no copy function
  42. ctn-&gt;item[ctn-&gt;size] = ctn-&gt;copy(item);
  43. if (ctn-&gt;item[ctn-&gt;size] == NULL)
  44. return -5; // error on copy
  45. ctn-&gt;size += 1;
  46. return 0;
  47. }
  48. int ctn_show(Container* col)
  49. {
  50. if (col == NULL) return -1;
  51. printf(
  52. &quot; there are %llu of %llu items\n\n&quot;, col-&gt;size,
  53. col-&gt;limit);
  54. if (col-&gt;show == NULL) return -1; // no show function
  55. for (size_t i = 0; i &lt; col-&gt;size; i += 1)
  56. col-&gt;show(col-&gt;item[i]);
  57. printf(&quot;[end of listing]\n&quot;);
  58. return 0;
  59. }

main.c for a simple test

  1. #include &lt;stdio.h&gt;
  2. #include &lt;stdlib.h&gt;
  3. #include &quot;container.h&quot;
  4. #include &quot;item.h&quot;
  5. int main(void)
  6. {
  7. const int test_size = 6;
  8. Container* books =
  9. ctn_create(30, copy_book, delete_book, print_book);
  10. for (size_t id = 1; id &lt;= test_size; id += 1)
  11. {
  12. Book* newb = create_book();
  13. ctn_insert(newb, books);
  14. delete_book(newb);
  15. };
  16. ctn_show(books);
  17. printf(
  18. &quot;\n\t***** now print again, using new layout\n\n&quot;);
  19. books-&gt;show = print_book_alt;
  20. ctn_show(books); // now using _alt
  21. books = ctn_destroy(books); // delete all
  22. ctn_show(books); // now empty
  23. return 0;
  24. }

Note 1:

  1. ctn_show(books);
  2. printf(
  3. &quot;\n\t***** now print again, using new layout\n\n&quot;);
  4. books-&gt;show = print_book_alt;
  5. ctn_show(books); // now using _alt

The point of using books-&gt;show here is that it encapsulates behavior, as an object in Python or java or C++: by changing this, the next call to cnt_show inherits the new print layout: see the program output below

Note 2:

  1. ctn_show(books); // now using _alt
  2. books = ctn_destroy(books); // delete all
  3. ctn_show(books); // now empty

By returning NULL from cnt_destroy the pointer of the container is invalidaded in the same line of code: no way to have an invalid pointer. This is great, so the call to ctn-&gt;show on the next line will not get the pointer of a container already deleted.

output for the example

  1. there are 6 of 30 items
  2. Book ID : 1000
  3. title : Title 1000 [241 pages]
  4. author : Author 1000
  5. Book ID : 1001
  6. title : Title 1001 [222 pages]
  7. author : Author 1001
  8. Book ID : 1002
  9. title : Title 1002 [334 pages]
  10. author : Author 1002
  11. Book ID : 1003
  12. title : Title 1003 [350 pages]
  13. author : Author 1003
  14. Book ID : 1004
  15. title : Title 1004 [304 pages]
  16. author : Author 1004
  17. Book ID : 1005
  18. title : Title 1005 [269 pages]
  19. author : Author 1005
  20. [end of listing]
  21. ***** now print again, using new layout
  22. there are 6 of 30 items
  23. 1000, &quot;Title 1000 [241 pages]&quot;, [Author 1000]
  24. 1001, &quot;Title 1001 [222 pages]&quot;, [Author 1001]
  25. 1002, &quot;Title 1002 [334 pages]&quot;, [Author 1002]
  26. 1003, &quot;Title 1003 [350 pages]&quot;, [Author 1003]
  27. 1004, &quot;Title 1004 [304 pages]&quot;, [Author 1004]
  28. 1005, &quot;Title 1005 [269 pages]&quot;, [Author 1005]
  29. [end of listing]

Code is on https://github.com/ARFNeto-CH/soc23-0720-example

huangapple
  • 本文由 发表于 2023年7月18日 06:56:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/76708554.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定