结构体的定义
结构体定义一般有以下几种:
直接定义
struct Stu
{
char name[20];
int age;
} ;
类型名称定义
typedef struct {
char name[20];
int age;
} Student;
另一种分开写的格式:
struct Stu {
char name[20];
int age;
};
typedef struct Stu Student;
结构体变量定义
使用结构体名称定义:
struct Stu student = {"张三", 12};
使用类型名称定义:
Student student = {"张三", 12};
使用赋值定义:
Student student;
student.age = 12;
// 由于 name 字段使用的是 char[20] 所以必须复制内容到 name 的预开辟内存
memcpy(student.name, "李四", sizeof("李四") + 1 );
// 如果 name 字段使用的是 char* 类型 则可以直接赋值
// student.name = "李四";
使用无须指定字段定义:
Student student = {
.name = "李四",
.age = 12,
};
结构体数组定义
直接定义
Student student[2] = {
{"张三", 12},
{"李四", 11},
};
或者不写数量也可以,默认会根据定义的数量自动推断:
Student student[] = {
{"张三", 12},
{"李四", 11},
};
结构体的基本理解
内存开辟
当我们使用以下命令定义一个 结构体的时候,同时等于开辟了整个结构体的内存空间,所有的结构体属性都类似这样连续的预开辟在空间中(事实上并不是所有的属性都会连续对齐,中间可能会预留一些间隔空间,至于原因可以度娘,这里不做展开);
Student student;
因此很明显, name char[20]
已经在内存上预留了 20
长度的空间,所以无法直接使用 student.name = "李四"
的方法赋值,必须将内容通过 memcpy
等内存复制方法将内容拷贝到当前空间。
可以通过 sizeof
来验证:
// 类型验证
printf("%s\n", sizeof(Student)); // 24 (char[20] + int(4))
printf("%d\n", sizeof(Student*)); // 4 (指针长度为 int(4))
// 实际取值验证
Student stu;
printf("%s\n", sizeof(stu)); // 24 (char[20] + int(4))
printf("%d\n", sizeof(&stu)); // 4 (指针长度为 int(4))
结构体变量与结构体变量指针
事实上结构体变量和变量指针,可以类似的理解成 char[20]
和 char*
的区别;
当 char[20] str;
数组开辟之后, 虽然 str
事实上也是一个指针,但是由于指针与预开辟空间的头部地址已经绑定,因此无法再修改 str
地址, 也就是 str
是只读状态,不能通过简单的 =
赋值;
结构体也是类似 Student a
和 Student b
之间 无法使用 a = b
进行赋值;
Student stu1 = {"张三", 12};
Student stu2 = {"李四", 11};
stu1 = stu2; // 编译无法通过!!!
需要使用指针操作来解决以上问题
Student stu1 = {"张三", 12};
Student stu2 = {"李四", 11};
Student* stu1p = &stu1;
stu1p = &stu2; // 编译通过, stu1p 从原来指向 stu1 变为 指向 stu2
读取结构体属性
对于结构体,使用 .
符号读取, 例如: stu.name
;
对于结构体指针,使用 ->
符号读取, 例如: stu->name
;
Student stu = {"张三", 12};
// 对于结构体,可以直接使用 . 符号读取内容
printf("name : %s\n", stu1.name);
printf("age : %d\n", stu1.age);
// 对于结构体指针,可以使用 -> 读取内容
Student* stup = &stu;
printf("name : %s\n", stup->name);
printf("age : %d\n", stup->age);
结构体传参
单个结构体参数传递
结构体直接传参
Student changeAge(Student s, int age){
s.age = age;
return s;
}
Student stu = {"李四", 14};
Student stu2 = changeAge(stu, 100);
printf("%d\n", stu2.age); // 100
指针传参
Student changeAge(Student *s, int age){
s->age = age;
return *s;
}
Student stu = {"李四", 14};
Student stu2 = changeAge(&stu, 100);
printf("%d\n", stu2.age); // 100
也可以返回结构体指针
Student* changeAge(Student *s, int age){
s->age = age;
return s;
}
Student stu = {"李四", 14};
Student *stu2 = changeAge(&stu, 100);
printf("%d\n", stu2->age); // 100
结构体数组传参
数组传参需要注意的是结构体数组下标获取的不是结构体指针,而是结构体,类似 stu[1]
得到的下标为 1
的 结构体;
我们尝试定义一个数组,之后我们遍历数组并进行打印;
// 这里可以有几种写法,但结果最终都会被编译成 Student *stu 的样子
// 所以喜欢用什么语法就用什么语法
// void each( Student stu[], int n)
void each( Student *stu , int n){
for( int i = 0 ; i < n ; i ++){
// 这里得到的 stu[i] 是结构体,因此用 . 获取属性
Student s = stu[i];
printf("%s\n", s.name);
// 也可以使用指针做
Student *sp = &stu[i];
printf("%s\n", sp->name);
}
}
Student stu[] = {
{"张三", 12},
{"李四", 11},
};
// 传入的时候可以直接传入 stu , 因为 stu 本身就是数组起始地址
// 也可以传入 &stu 指针,最终没有任何区别
// each(&stu, 2)
each(stu, 2);
总结
C 语言结构体虽然有点绕,但在实际开发过程从,它可以最大限度的降低面向过程开发中的大量传参问题,减小变量规模,无论是可读性还是可维护性上,都能得到很大提升;
同时基于结构体,我们可以完成更多的数据结构,比如:树、图、链表、hashmap 等,因此认真和仔细了解结构体特性对于学习 C 语言来讲,是非常有必要的,他是必须克服的关卡!