结构体是为了描述复杂对象
结构体可以让c语言创建新的类型
一、结构体声明
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct
{
int a;
char b;
double c;
} s1;
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE
{
int a;
char b;
double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
//也可以用typedef创建新类型
typedef struct
{
int a;
char b;
double c;
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;
在上面的声明中,第一个和第二声明被编译器当作两个完全不同的类型,即使他们的成员列表是一样的,如果令 t3=&s1,则是非法的。
结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。
二、结构体初始化
可以直接在结构体声明后面定义,甚至在后面可以初始化。
struct book
{
char name[20];
char author[15];
float price;
}b1, b2;
struct book b3 = { 0 };
struct point {
int x;
int y;
};
struct point p1 = { x, y };
也可以在main函数中定义及初始化。
typedef struct human{
char name[20];
int age;
char sex[10];
char id[20];
}hu;
int main() {
struct human man1 = { "张三",20,"女装大佬","20203100" };
point p1 = { 1,2 };
return 0;
}
三、结构体成员的访问
结构体成员是通过操作符.进行访问的,.操作符具有两个操作数。左边是结构体变量名,右边是结构体成员名
//就用上面举得嵌套访问的例子
int main() {
struct T t = { {10, 'x', 1.00}, "yourfriendyo", 21 };
printf("%d %c %lf %s %d\n", t.s.a, t.s.c, t.s.d, t.name, t.num);
return 0;
}
s是t的成员变量,a,c,d分别是s的成员变量,所以是t.s.a,t.s.c,t.s.d 。
3.2->&*操作符
当然有的时候有可能我们得到的是一个指向该结构体的指针。这时候我们就需要操作符->,同样也是两个操作数,如:
struct T t = { {10, 'x', 1.00}, "yourfriendyo", 21 };
//1.
struct T* pt = &t;
printf("%d %c %lf %s %d\n", (*pt).s.a, (*pt).s.c, (*pt).s.d, (*pt).name, (*pt).num);
//2.
printf("%d %c %lf %s %d\n", pt->s.a, pt->s.c, pt->s.d, pt->name, pt->num);
//3.
struct S* ps = &(t.s);
printf("%d %c %lf %s %d\n", ps->a, ps->c, ps->d, pt->name, pt->num);
第一种方法是我们不知道->,只能对指针解引用时想到的方法。
第二种方法->是专门在访问结构体时使用指针的方法,更高效。
第三种方法是更加简单粗暴的方法,我们越过了结构体变量t,直接创建指针指向(t.s).
四、结构体作参数
struct S {
char arr[100];
int num;
float f;
};
void Print(struct S ss) {
printf("%c %c %c %d %f\n", ss.arr[0], ss.arr[1], ss.arr[2], ss.num, ss.f);
}
void Print2(struct S* ps) {
printf("%c %c %c %d %f\n", ps->arr[0], ps->arr[1], ps->arr[2], ps->num, ps->f);
}
int main() {
struct S s = { {'1','2','3','4'}, 22, 3.14};
Print1(s);
Print2(&s);
return 0;
}
既然讨论到函数传参,就会有传址调用和传址调用的问题。
如上述代码,我们直接把s传过去,显然是传值调用,但s本身就占用了非常大的空间,然后又创建了一份一样大的形参临时拷贝,时间空间上浪费的问题非常严重。这样的话,很明显我们还是传址调用最好,无论s的占用有多大,我们只传了4个字节的地址。
所以结构体传参通常使用传址调用。