C语言中结构体的构造函数

54

给定:

struct objStruct {
    int id;
    int value;
};

typedef struct objStruct Object;

有没有一种快捷的方法来分配和初始化对象,类似于 C++ 的构造函数?
它甚至可以是一个预处理器宏。任何能比下面这段代码更短、更易读的东西。

Object *newObj = malloc(sizeof(Object));
// successful allocation test snipped
newObj->id = id++;
newObj->value = myValue;

2
可能的重复问题 (C 中的默认构造函数在 C++ 代码中初始化 C 结构体) 都不是这个问题的真正重复,虽然它们有关联。自从上一条评论发布以来,它们已经更改了标题。 - Jonathan Leffler
6个回答

62

在C语言中,我通常会创建一个类似于构造函数的函数来完成这个操作。例如(为了简洁起见省略错误检查)

Object* Object_new(int id, int value) { 
  Object* p = malloc(sizeof(Object));
  p->id = id;
  p->value = value;
  return p;
}

...
Object* p1 = Object_new(id++, myValue);

6
可能有点过度,但我通常也会创建一个void objStruct_destroy(objStruct * obj)函数,用于释放内部结构体的内存分配,在以后需要添加任何释放内容时我可以使用它。 - Mike Axiak
13
是的;除了有时最好让“构造函数”以Object*作为参数来接受,这样你也可以进行堆栈分配(Object object; Object_initialise(&object);)。 - Porculus
2
一个中间方法是使用Object_Init对已分配的对象执行初始化(=放置构造函数),并使用Object_New进行分配和初始化(在内部应调用Object_Init)。对于“析构函数”也应该这样做。 - Matteo Italia
@Traubenfuchs 这是因为这将意味着在返回点处进行复制。虽然可以进行省略,但这并不是保证的。 - Ruslan
你需要记住的是使用assert()检查错误。 - Sayan Bhattacharjee
显示剩余2条评论

36
在C99及以后的版本中,您可以使用复合字面量,它看起来像一个转换操作符后跟着花括号内的初始化器。
int init_value = ...;
int init_id    = ...;
Object newObj1 = (Object){ .value = init_value, .id = init_id };
Object newObj2 = (Object){ .id = init_id, .value = init_value };

后两行代码实现的效果是一样的——字段的顺序不是关键的。这是使用“指定初始值”的另一个C99特性。你可以创建一个复合字面量而不使用指定的初始化器。


11
在C语言中,可以使用与结构体相同的名称来声明内联函数:
struct my
{
    int a;
};

inline struct my* my(int* a)
{
    return (struct my*)(a);
}

//somewhere in code
int num = 123;
struct my *sample = my(&num);
//somewhere in code

它看起来非常类似于C++构造函数。


1
return (struct my*)(a) 这句代码是如何设置 int a 的?难道不应该通过 pointer_to_my->a 进行赋值吗? - VimNing
这只是一个构造函数的模拟:num的地址被解释为指向一个结构体的指针,该结构体包含一个字段 - 整数。如果在结构体中添加另一个字段,例如整数或指针,这个技巧将不起作用:将地址传递到函数my()将导致额外字段中的垃圾数据。 - grekhss
好的,但是如果我从一个C文件中获取“Undefined symbol: _my”,如果我删除“inline”关键字,则不会出现错误。 - tontonCD

4
struct thingy {
   char * label;
   int x;
};

#define declare_thingy( name, label, val) struct thingy name = { label, val }

struct thingy * new_thingy(const char * label, int val) {
     struct thingy * p = malloc(sizeof(struct thingy));
     if (p) {
          p->label = label;
          p->val = val;
     }
     return p;
}

2
你真的需要区分静态或自动变量的初始化和动态分配。对于第一种情况,使用命名初始化器;对于第二种情况,则需要一个良好指定的初始化函数。
所有这些都可以很好地封装成宏,以便为你提供简单的静态/自动初始化和类似于C++中的new的功能。

1
如果你在寻找一个针对 C 的面向对象“仿真”技术,我强烈推荐 GObject 类型系统[1],它已经很成熟并且被 GTK 等广泛使用。
GLib [2] 还有一个不错的小对象分配器,目前被 GNOME 使用。
[1] GObject 参考手册 [2] GLib 内存分片

请注意,某些在C ++中通常在编译时检查的内容,在GObject中仅在运行时完成。 - Ruslan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接