structstruct 类型struct + 名字。C 中 struct 关键字不可省略,C++ 中必须省略。
struct Student student;
struct Record records[1000];
typedef 定义类型别名typedef long long LL;
typedef struct { double x, y, z; } Point3d;
LL llval = 0; // llval is long long
Point3d p;
不要用 #define 代替 typedef!!!
structstruct 的成员name.mem
struct Student student;
student.name = "Alice";
student.id = "2023533000";
student.entrance_year = 2023;
student.dorm = 8;
printf("%d\n", student.dorm);
++student.entrance_year;
puts(student.name);
structstruct 的成员ptr->mem:等价于 (*ptr).name。不是 *ptr.name !!(. 优先级高于 *)
struct Student *ptr = &student;
ptr->name = "Alice";
ptr->id = "2023533000";
(*ptr).entrance_year = 2023; // equivalent to ptr->entrance_year = 2023;
ptr->dorm = 8;
printf("%d\n", ptr->dorm);
++ptr->entrance_year;
puts(ptr->name);
structstruct 的初始化老生常谈的问题:不显式初始化时会发生什么?
struct Student gs;
int main(void) {
struct Student ls;
}
structstruct 的初始化老生常谈的问题:不显式初始化时会发生什么?
struct Student gs;
int main(void) {
struct Student ls;
}
static:空初始化:结构体的所有成员都被空初始化。static:不初始化,所有成员都具有未定义的值。structstruct 的初始化Initializer list:
struct Record r = {p, cnt * each_size, __LINE__, __FILE__};
隔壁 C++20 才有的 designators,C99 就有了!(是不是非常像 PythonVerilog?)
struct Record r2 = {.ptr = p, .size = cnt * each_size,
.line_no = __LINE__, .file = __FILE__};
C 允许 designators 以任意顺序出现,C++不允许。
structstruct 的初始化我们可以使用 Initializer List 来初始化结构体:
struct Record r = {p, cnt * each_size, __LINE__, __FILE__};
struct Record r2 = {.ptr = p, .size = cnt * each_size,
.line_no = __LINE__, .file = __FILE__};
但对结构体的赋值却不能这样使用:
r = {p, cnt * each_size, __LINE__, __FILE__}; // Error
records[i] = {.ptr = p, .size = cnt * each_size,
.line_no = __LINE__, .file = __FILE__}; // Error
structstruct 的初始化我们可以使用 compound literal(复合字面值),在表达式中临时构造一个匿名结构体:
r = (struct Record){p, cnt * each_size, __LINE__, __FILE__}; // OK
records[i] = (struct Record){.ptr = p, .size = cnt * each_size,
.line_no = __LINE__, .file = __FILE__}; // OK
structstruct一个 struct 可以被拷贝,其行为是逐成员拷贝。
struct Student a = {"Alice", "2023533000", 2023, 8};
struct Student b = a; // OK,初始化
struct Student c = something();
c = a; // OK,赋值
// struct Student b = a; 就如同下面的代码
struct Student b = {a.name, a.id, a.entrance_year, a.dorm};
// c = a; 就如同下面的代码
c = (struct Student){a.name, a.id, a.entrance_year, a.dorm};
structstructstruct A {
int array[10];
// other members ...
};
虽然编译器拒绝直接拷贝数组,但它其实有能力做到。
拷贝一个 struct A 时,编译器会自动逐元素拷贝数组。
int a[10];
int b[10] = a; // Error!
struct A a;
struct A b = a; // OK
structstruct传参的语义是拷贝。
void print_info(struct Record r) {
printf("%p, %zu, %d, %s\n", r.ptr, r.size, r.line_no, r.file_name);
}
print_info(records[i]);
structstruct事实上,在传参时发生的是这样的初始化,也就是逐成员拷贝:
struct Record r = records[i];
就如同
struct Record r = {records[i].ptr, records[i].size,
records[i].line_no, records[i].file_name};
structstruct返回一个 struct,严格按照语法来说,也是拷贝:
struct Record fun(void) {
struct Record r = something();
some_computations(r);
return r;
}
records[i] = fun();
return r;:发生了形如 struct Record tmp = r; 的拷贝,临时对象 tmp 是表达式 fun() 的求值结果。然后发生了形如 records[i] = tmp; 的拷贝。
但实际上这个过程会被编译器优化,标准也是允许这种优化的。(我们以后在 C++ 里进一步讨论这个问题)
structstruct 的大小struct A {
int x;
char y;
double z;
};
sizeof(struct A) 是多少?
structstruct 的大小struct A {
int x;
char y;
double z;
};
sizeof(struct A) >= sizeof(int) + sizeof(char) + sizeof(double)。由于内存对齐的问题,编译器可能会在某些地方插入一定的空白。
一般地,对于一个 struct X ,有
structstruct 的大小struct A {
int x;
struct A a;
};
sizeof(struct A) 是多少?
structstruct 的大小struct A {
int x;
struct A a; // Error: use of incomplete type 'struct A'.
};
sizeof(struct A)
struct??? minmax(int *array, int n) {
int min = INT_MAX;
int max = INT_MIN;
for (int i = 0; i < n; ++i) {
// ...
}
return /* (min, max) */
}
??? read_string(void) {
char *buffer = /* ... */;
size_t size = 0;
// read the string ...
buffer[size] = '\0';
return /* (buffer, size) ? */
}
“返回一个值”:将信息从函数内传给调用者。
return ,还有什么办法可以传递这个信息?struct除了 return ,还有什么办法可以把信息传给调用者?
让调用者把存放结果的内存地址传进来,我往那儿写。
int add(int a, int b) {
return a + b;
}
int main(void) {
int sum = add(1, 2);
}
void add(int a, int b, int *result) {
*result = a + b;
}
int main(void) {
int sum;
add(1, 2, &sum);
}
struct——传递存放结果的内存地址!
void minmax(int *array, int n, int *min, int *max) {
*min = INT_MAX;
*max = INT_MIN;
for (int i = 0; i < n; ++i) {
// 对 *min 和 *max 进行修改
}
}
int main(void) {
int a[] = {1, 2, 3};
int min, max;
minmax(a, 3, &min, &max);
}
struct——传递存放结果的内存地址!
想“返回”多少个值都可以:
void minmax(int *array, int n, int *min, int *max, int *secmin, int *secmax);
例如:在 CUDA 编程中,Kernel 函数(__global__函数)不能有返回值,因此只能通过指针参数写入结果。
struct——传递存放结果的内存地址!
还可以与 return 结合使用(但像 minmax 这样的函数,返回的两个值“地位对等”,不建议用两种不同的方式。):
char *read_string(size_t *size) {
char *buffer = /* ... */;
*size = 0;
// read the string, ++*size whenever a new character is read and stored.
buffer[*size] = '\0';
return buffer;
}
int main(void) {
size_t size;
char *string = read_string(&size);
}
struct更直接的方式: return 一个 struct。
C 没有内置的“元组” (tuple) ,但它可以轻易用 struct 造出来:
struct minmax_result { int min; int max; };
struct minmax_result minmax(int *array, int n) {
int min = INT_MAX;
int max = INT_MIN;
// ...
return (struct minmax_result){min, max};
}
int main(void) {
int a[] = {1, 2, 3};
struct minmax_result minmax = minmax(a, 3);
int min = minmax.min, max = minmax.max;
}
struct更直接的方式: return 一个 struct。
Exercise:
用这种方式改写
read_string
struct一些来自其他语言的选手可能会想这么写:
int minmax(int *array, int n) {
// ...
return min, max;
}
它真的能通过编译,但效果就是 return max;
expr1, expr2 中的 , 是逗号运算符:先对 expr1 求值,再对 expr2 求值,返回 expr2 的值。
func(a, b, c) 中的逗号是函数调用传参的语法的一部分, int a, b; 中的逗号是声明多个变量的语法的一部分。structstrtol :将一个以字符串形式存在的整数的数值提取出来(得到一个 long)。
long strtol(const char *str, char **str_end, int base);
char str[] = "1234abcdef";
char *next_pos;
long value = strtol(str, &next_pos, 10);
// value 是 1234 , next_pos 指向 'a'
The functions set the pointer pointed to by
str_endto point to the character past the last numeric character interpreted. Ifstr_endis a null pointer, it is ignored.
strtoll, strtod 等函数也是这样。
struct要么 x = f(x) ,要么 f(&x) 。
const char *move_forward(const char *s)
{
while (*s != '\0' && condition(*s))
++s;
return s;
}
// call site:
ptr = move_forward(ptr);
void move_forward(const char **s)
{
while (**s != '\0' && condition(**s))
++*s;
}
// call site:
move_forward(&ptr);
enum定义一个新的类型,其值是某几个特定的(有名字的)值之一。
struct Text {
int color; // 0 黑色, 1 红色, 2 绿色
char *contents;
};
// in some function
struct Text text = something();
text.color = 0; // 设为黑色
enum TextColor { Black, Red, Green };
struct Text {
enum TextColor color;
char *contents;
};
// in some function
struct Text text = something();
text.color = Black;
enum 。enumenum 实际上也是一种整数!const char *colorToString(enum TextColor color) {
switch (color) { // 可以放在 switch 里
// enum items 是整型编译期常量,可以放在 case label 上
case Black: return "Black";
case Red: return "Red";
case Green: return "Green";
default: return "unknown color";
}
}
const char *colorToString(enum TextColor color) {
const char *colors[] = {"Black", "Red", "Green"};
return colors[color]; // color 可以作为下标
}
enum
注意:以下代码的行为是 implementation-dependent 的:
struct Instruction {
unsigned imm : 7;
unsigned r2 : 3;
unsigned r1 : 3;
unsigned opcode : 3;
}; // sizeof(struct Instruction) == 7 + 3 + 3 + 3

unsigned short x; scanf("%hx", &x);
struct Instruction i= *(struct Instruction *)&x;
// 现在可以直接使用 i.opcode, i.r1, i.r2, i.imm
unionunion 和 struct 一样,也有一系列成员。不同的是, union 是这些成员的“叠加”,这些成员共用一片内存。
struct: "Cartesian product"
union: (almost) "disjoint union"
使用 union 要非常小心。
union// 定义一个 Union,让 float 和 uint32_t 共享内存
typedef union {
float f; // 32-bit 浮点数
uint32_t i; // 32-bit 无符号整数(用于位操作)
} FloatIntUnion;
float 和 uint32_t 共享同一块内存(4 字节)。u.i 会直接影响 u.f 的二进制表示,反之亦然。union通过修改 u.i 的对应 Bit 来修改 u.f 的 Exponent:
uint32_t new_exponent = 127 + 1; // 指数 +1(相当于乘以 2)
u.i = (u.i & 0x807FFFFF) | (new_exponent << 23);
这样 3.14 会变成 6.28(因为指数 +1 相当于 ×2)。