访问结构体成员
定义结构体,表示向程序引入了一种类型。
struct Cat
{
char name[64];
int color;
int age;
float weight;
};
同内置类型一样,我们需要先定义此类型的变量,也称为创建结构体对象,之后才能使用它。
struct Cat cat; // 定义Cat变量cat
struct Cat foo, bar; // 定义Cat变量foo, bar
struct Cat cats[9]; // 定义Cat数组,可饲养9只猫
结构体的成员可称为字段、成员变量、属性;可以通过(.)运算符对这些属性进行读写:对象 . 属性。
strcpy(cat.name, "laohu"); // 为cat对象的name字段设置值
foo.color = 0; // 设置foo对象的color成员变量
cats[3].weight = 7.0f; // 为cats[3]对象的weight属性设置值
这里是一个完整的示例程序。
#include <stdio.h>
#include <string.h>
struct Cat
{
char name[64];
int color;
int age;
float weight;
};
const char *colors[] =
{
"white",
"yellow",
"milk",
"gray"
};
void print_cat(struct Cat my_cat)
{
printf("%s - color: %s \t age: %d\n", my_cat.name, colors[my_cat.color], my_cat.age);
}
int main()
{
// 创建Cat类型变量my_cat
struct Cat my_cat;
// 使用(.)运算符访问结构体成员
strcpy(my_cat.name, "laohu");
my_cat.color = 2;
my_cat.age = 7;
my_cat.weight = 15.2f;
// 结构体变量作为函数参数传递
print_cat(my_cat);
return 0;
}

我们可以在定义结构体变量的同时依次对成员初始化:对于未指定的字段,将默认以0值初始化。
- 字符就是'\0'
- int就是0
- double就是0.0
- 指针就是NULL
- 字符数组就是所有元素为'\0'
对于数组:
- 在完全指定字段的情况下,组与组之间的数据用','分隔;
- 对于部分初始化,组与组之间用"{}"分隔,以界定当前对象初始化范围。
对于数组,不管在什么情形下,建议全部采用{}进行多个对象的分隔。
注意,未初始与初始化为0值是两个不同的概述。
int main()
{
struct Cat cat1 = { "cat1", 1, 7, 15.2f };
struct Cat cat2 = { "cat2", 2 };
struct Cat cats[5] =
{
"cat1", 1, 7, 15.2f, // 初始化所有字段
"cat2", 2, 3, 10.0f, // 两两之间以','分隔
{"cat3"}, // 仅初始化名称字段,以{}分隔两个对象之间的边界。
{"cat4", 0 } // 否则名称"cat4"会被当作为cat3.color的初始值,进而构造类型错误。
// 没有为cat[4]指定任何值,默认初始化为0值。
};
/*
* 同上,对象与对象之间使用{}限定,是良好编程的风格之一。
struct Cat cats[5] =
{
{ "cat1", 1, 7, 15.2f },
{ "cat2", 2, 3, 10.0f },
{ "cat3" },
{ "cat4", 0 }
};
*/
print_cat(cat1);
print_cat(cat2);
print_cat(cats[2]);
print_cat(cats[3]);
print_cat(cats[4]);
// 没有初始化,随机值,导致程序中断。
struct Cat cat6;
print_cat(cat6);
return 0;
}

克隆体
同内置类型一样,传递给函数的结构体变量,也是实参的一个拷贝,即在函数内部改变结构体的成员,不会改变原数据。
#include <stdio.h>
#include <string.h>
struct Foo
{
int a;
float b;
char s[64];
};
void change_foo(struct Foo foo)
{
foo.a = 9;
foo.b = 3.14f;
strcpy(foo.s, "hello");
}

int main()
{
struct Foo foo;
foo.a = 0;
foo.b = 0;
change_foo(foo);
printf("foo.a: %d\n", foo.a);
printf("foo.b: %f\n", foo.b);
printf("foo.s: %s\n", foo.s);
return 0;
}

在change_foo()内部使用的foo,只是main()中foo的一个副本,由断点截图可以看到,函数内部对此副本的修改是有效的,但这不会影响main()中原始foo的值。这是C语言函数传递参数的特性:函数参数(形参)是实参的拷贝!对于结构体而言,这就涉及到效率问题。当一个结构体有大量的成员时,对所有成员的拷贝成本可能很高,解决的方法是使用结构体指针。
结构体指针
定义一个结构体指针的方法与内置指针类型相似,不过需要struct类型关键字:
struct Foo foo; // 定义一个Foo对象foo
struct Foo *foo1 = &foo; // 定义一个Foo指针foo1,初始化让其指向foo
struct Foo *foo2; // foo2未初始化
foo2 = &foo; // 通过赋值操作让foo2指向foo
结构体指针同样可以作为函数参数。把指针传递给函数时,在32位环境下仅需要拷贝4个字节,对于大的结构体,这在效率上有很大的提高。
void foo(struct Foo *foo); // 参数foo是结构体Foo的指针
void bar(const struct Bar *bar); // const代表我们在函数bar中不修改Bar的成员
通过结构体指针访问成员时,使用箭头 -> 运算符。
struct Foo *foo;
foo->a = 3;
foo->b = 2.0f;
也可以对指针解引用指针,进行成员访问。
(*foo).a = 3; // *foo解引用取得foo对象,对对象就要使用(.)运算符。
示例程序将这些串联起来。
#include <stdio.h>
#include <string.h>
struct Foo
{
int a;
float b;
char s[64];
};
void change_foo(struct Foo *foo)
{
foo->a = 9;
foo->b = 3.14f;
strcpy(foo->s, "hello");
}
void print_foo(const struct Foo *foo)
{
printf("Foo(%d, %.2f, %s)\n", (*foo).a, (*foo).b, (*foo).s);
}
int main()
{
struct Foo foo;
foo.a = 0;
foo.b = 0;
change_foo(&foo);
print_foo(&foo);
return 0;
}
