花间一壶酒

举杯邀明月,对影成三人

0%

笨办法学C

Learn C the hard way

C语法

一些技巧

语法糖

使用宏定义构建语法糖。
"语法糖"是一种编程术语,用来描述那些没有给编程语言添加新功能,但是可以使代码更易读或易写的语法。这些语法通过提供更加方便、简洁的编写方式,使得程序员能够更加快速、更加直观地表达程序设计的意图。 在C语言中,也存在一些语法糖。例如,数组的下标运算就是一种语法糖。例如,array[i]实际上就是*(array + i)的简化写法。另一个例子是for循环,它实际上可以使用while循环和一些额外的语句来实现,但for循环提供了一种更加简洁、易读的方式来编写循环。 虽然语法糖可以使代码更简洁、易读,但并不会影响程序的功能或效率。使用或不使用语法糖,都不会影响程序的运行结果。

1
2
3
// 语法糖, 写入obj->_(N), 等价于 obj->proto.N
#define _(N) proto.N
#define NEW(T, N) Object_new(sizeof(T), T##Proto, N)

assert宏

<assert.h> 是 C 语言的一个标准库,主要提供了一个名为 assert 的宏,用于在程序开发和调试过程中进行条件测试。
assert 宏允许程序员在代码中插入诊断信息。如果在运行时,assert 的条件测试失败(即,测试的表达式结果为假),那么程序会立即终止,并显示一条错误消息。这条消息通常包括文件名、源代码行号以及测试失败的表达式。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
struct Person *Person_create(char *name, int age, int height, int weight)
{
struct Person *who = malloc(sizeof(struct Person));
assert(who != NULL);

who->name = strdup(name);
who->age = age;
who->height = height;
who->weight = weight;

return who;
}

这里使用assert宏来检查内存是否被成功分配。

继承

众所周知C不支持面向对象,但是可以通过一些技巧实现类似继承的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//定义一个基类 Object
//注意这里使用了函数指针,从而可以实现函数重载
typedef struct {
char *description;
int (*init)(void *self);
void (*describe)(void *self);
void (*destroy)(void *self);
void *(*move)(void *self, Direction direction);
int (*attack)(void *self, int damage);
} Object;


//基类的构造函数
void *Object_new(size_t size, Object proto, char *description)
{
// setup the default functions in case they aren't set
if(!proto.init) proto.init = Object_init;
if(!proto.describe) proto.describe = Object_describe;
if(!proto.destroy) proto.destroy = Object_destroy;
if(!proto.attack) proto.attack = Object_attack;
if(!proto.move) proto.move = Object_move;

// this seems weird, but we can make a struct of one size,
// then point a different pointer at it to "cast" it
Object *el = calloc(1, size);
*el = proto;

// copy the description over
el->description = strdup(description);

// initialize it with whatever init we were given
if(!el->init(el)) {
// looks like it didn't initialize properly
el->destroy(el);
return NULL;
} else {
// all done, we made an object of any type
return el;
}
}


//对每个需要继承基类的子类,用以下方法定义:
struct Map {
Object proto;
Room *start;
Room *location;
};

//指定需要重载的方法:
Object MapProto = {
.init = Map_init,
.move = Map_move,
.attack = Map_attack
};

//使用之前的语法糖进行初始化:
Map *game = NEW(Map, "The Hall of the Minotaur.");