【问题标题】:Coding Style -- Pass by Reference or Pass by Value?编码风格——通过引用传递还是通过值传递?
【发布时间】:2015-02-24 06:05:22
【问题描述】:

为了简化未来学校作业的开发,我决定为我常用的两种数据结构(链表和哈希表)创建一个 API(你会这么称呼它吗?)。

在开发其中的每一个时,我最终得到了以下两个插入函数:

int list_insert(list *l, char *data, unsigned int idx);
int hash_insert(hash_table **ht, char *data);

list_insert() 函数(以及所有列表函数)最终成为按值传递,因为除非我正在 malloc'ing 或 free'ing 它,否则我不需要直接修改 list * 本身。但是,因为我想在我的哈希表中包含自动重新哈希,我发现我必须在任何可能强制重新哈希的函数中通过引用而不是按值传递表。现在我得到如下语法:

list_insert(l, "foo", 3);
hash_insert(&ht, "foo");

这种差异让我觉得有点奇怪,我发现自己想知道是否应该将列表函数也更改为按引用传递以保持一致性——即使我的任何函数都不需要利用它。这里的典型共识是什么?如果我的函数确实需要修改它的参数,我应该只通过引用传递,还是为了一致性而应该通过引用传递?

结构定义:

typedef struct list_node list_node;
struct list_node {
    char *data;
    list_node *next;
    list_node *prev;
};

typedef struct list list;
struct list {
    list_node *head;
    list_node *tail;
    size_t size;
};

typedef struct hash_table hash_table;
struct hash_table {
    list **table;
    size_t entries;
    size_t buckets;
    float maxLoad;
    unsigned int (*hash)(char*, unsigned int);
};

列出函数:

list *list_createList();
list_node *list_createNode();
void list_destroyList(list *l);
void list_destroyNode(list_node *n);
int list_append(list *l, char *data);
int list_insert(list *l, char *data, unsigned int idx);
int list_remove(list *l, char *data, int (*compar)(const void*, const void*));
void list_push(list *l, char *data);
char *list_pop(list *l);
int list_count(list *l, char *data, int (*compar)(const void*, const void*));
int list_reverse(list *l);
int list_sort(list *l, int (*compar)(const void*, const void*));
int list_print(list *l, void (*print)(char *data));

哈希函数:

hash_table *hash_createTable(size_t buckets, float maxLoad, unsigned int (*hash)(char*, unsigned int));
void hash_destroyTable(hash_table *ht);
list *hash_list(const hash_table **ht);
int hash_checkLoad(hash_table **ht);
int hash_rehash(hash_table **ht);
int hash_insert(hash_table **ht, char *data);
void hash_stats(hash_table *ht);
int hash_print(hash_table *ht, void (*print)(char*));

【问题讨论】:

  • hash_table 分别有什么信息。 list 包含?也许修改通过引用传递的list* 与修改hash_table* 一样有意义...
  • 您能否告诉我们您的 API 到底是做什么的以及您的结构是什么样的?
  • 建议Code Review 而不是这里。
  • 为什么要在堆上分配根?只需按创建值返回它(如果您坚持使用 {0} 进行聚合初始化不会削减它),并始终通过引用传递它,您将获得两者相似的 API。
  • @gopi 我添加了结构和函数原型。我认为它们是相对不言自明的,但如果需要更多信息,我很乐意提供。

标签: c styles pass-by-reference pass-by-value


【解决方案1】:

这是一般的经验法则:

  • 如果 typdef 是本机类型(char、short、int、long、long long、double 或 float),则按值传递
  • 如果是联合、结构或数组,则通过引用传递

通过引用传递的其他注意事项:

  • 如果不修改,请使用 const
  • 如果指针不指向同一个地址,则使用限制

有时结构/联合似乎是合适的类型,但如果类型相似,可以用数组替换。这有助于优化(例如循环矢量化)

【讨论】:

  • 您是否建议不需要列表结构?而是声明一个由三个指针组成的数组会更好吗?另外,您对在堆而不是堆栈上分配列表/哈希表的根有什么想法?
  • 虽然我使用数组来表示列表,但我更多地指的是带有 r,g,b,a 或 x,y,dx,dy 的结构,它们都是相同的类型。至于堆与堆栈,我尝试组织新代码以尽可能使用堆栈,这样我就不会忘记释放,但有时 *alloc 是不可避免的,尤其是对于现有代码。无论哪种方式,能够分析汇编输出以进行比较都是很好的,因为现代编译器可以优化 malloc 调用以使用有效的内置内在函数。
【解决方案2】:

这取决于你,需要一点直觉。当传递大型结构时,我通过引用传递,这样我就不会占用额外的堆栈空间并烧毁复制结构的周期。但是对于像你这样的小支柱,根据你的目标处理器、你使用这些值的频率以及你的编译器做什么,使用堆栈可能会更有效。您的编译器可能会分解该结构并将其值放入寄存器中。

但是,如果您确实通过引用传递并且不打算修改值,则最好将指针传递给 const,例如:const list * l。这样就不会有任何意外修改值的风险,并且它使界面更清晰——现在调用者知道值不会改变。

一致性很好,我个人会倾向于这个方向,尤其是在大界面上,因为从长远来看它可能会使事情变得更容易,但我肯定会使用 const。这样一来,您就可以让编译器发现任何意外分配,这样您以后就不需要追踪难以发现的错误。

另请参阅:Passing a struct to a function in C

【讨论】:

  • 他没有传递结构体,只是传递了结构体的指针(list *)和指向结构体的指针(hash_table **)。
  • @netcat 他问他是否应该直接传递结构列表,如果他不打算修改它,或者他是否应该传递一个指向该结构的指针,这就是我理解这个问题的方式。这不正确吗?
猜你喜欢
  • 2020-04-01
  • 2017-01-27
  • 2014-07-26
  • 1970-01-01
  • 2014-05-18
  • 2011-05-06
  • 2013-04-21
  • 1970-01-01
  • 2016-06-11
相关资源
最近更新 更多