Part one:基础操作(定义、初始化、变量访问与赋值)
话不多说,直接上代码。
#include<stdio.h>
struct son
{
char name[20];//姓名
char sex[4];//性别
unsigned char age;//年龄
int height;//身高cm
float weight;//体重kg
char hobby[30];//爱好
char career[50];//职业
};
int main()
{
struct son wbm =
{ "二狗子","女",66,209,70,"拉屎","效力于BROOKLYN女子篮球队" };
strcpy(wbm.name, "王饱明");
printf("姓名:%s,性别:%s,年龄:%d,身高:%d,体重:%f,爱好:%s,职业:%s",
wbm.name, wbm.sex, wbm.age, wbm.height, wbm.weight, wbm.hobby,wbm.career);
return 0;
}
从中我们可以窥探出
①struct的结构声明:struct+类型名+{成员列表}+分号
②在main函数往里面赋值时,一一对应,数组要用字符串带进去。当然此前要有一个变量名,此处为wbm。ps:须在初始化wbm的同时必须也要把值给赋上去,之后不能再单独赋值了
如:wbm={。。。}这个是错的。但wbm1=wbm2;是对的(一个结构体可以赋值给另一个结构体)
③进行访问时(打印),应该用变量名+.+成员列表里的变量名。如wbm.sex获得内部内容。
④当然,可以在赋值之后,再去修改,比如wbm.age=83;就能改掉年龄。
⑤于是,我们发现name和sex都是数组名,本质还是const指针,不好赋值,所以我们在这边借用字符串函数strcpy(const char*p,"字符串");进行修改。
很明显 printf太长 我们可以用函数对他进行封装。(很8错)
void say(struct son p)
{
printf("姓名:%s,性别:%s,年龄:%d,身高:%d,体重:%f,爱好:%s,职业:%s",
p.name, p.sex, p.age, p.height, p.weight,p.hobby,p.career);
}
int main()
{
struct son wbm = { "二狗子","女",66,209,70,"拉屎","效力于BROOKLYN女子篮球队" };
strcpy(wbm.name, "王饱明");
say(wbm);
return 0;
}
⑥其他的我们还可以试出,部分初始化的话,未初始化部分是0,或者{0}全部清零。
struct DateTime{
int year;
int month;
int day;
int hour;
}t1,t2;
⑦我们在枚举型的操作在此同样适用。在声明的同时直接定义。
typedef struct _DateTime
{
int year;
}Datetime;
⑧利用typedef起别名,简化。类型名仍为_DateTime,但此时struct _DateTime可更换为DateTime了,方便使用。
Part two.结构体数组
一个结构体变量可以存放一个学生的一组信息,可是如果有 10 个学生呢?难道要定义 10 个结构体变量吗?难道上面的程序要复制和粘贴 10 次吗?
很明显不可能,这时就要使用数组。结构体中也有数组,称为结构体数组。它与前面讲的数值型数组几乎是一模一样的,只不过需要注意的是,结构体数组的每一个元素都是一个结构体类型的变量,都包含结构体中所有的成员项。
定义结构体数组的方法很简单,同定义结构体变量是一样的,只不过将变量改成数组。或者说同前面介绍的普通数组的定义是一模一样的,如:
struct STUDENT stu[10];
这就定义了一个结构体数组,共有 10 个元素,每个元素都是一个结构体变量,都包含所有的结构体成员。
结构体数组的引用与引用一个结构体变量在原理上是一样的。只不过结构体数组中有多个结构体变量,我们只需利用 for 循 环一个一个地使用结构体数组中的元素。
下面编写一个程序,编程要求:从键盘输入 5 个学生的基本信息,如姓名、年龄、性别、学号,然后将学号最大的学生的基本信息输出到屏幕。
# include <stdio.h>
# include <string.h>
struct STU
{
char name[20];
int age;
char sex;
char num[20];
};
void OutputSTU(struct STU stu[5]); //函数声明, 该函数的功能是输出学号最大的学生信息
int main(void)
{
int i;
struct STU stu[5];
for (i=0; i<5; ++i)
{
printf("请输入第%d个学生的信息:", i+1);
scanf ("%s%d %c%s", stu[i].name, &stu[i].age, &stu[i].sex, stu[i].num);/*%c前面要加空格, 不然输入时会将空格赋给%c*/
}
OutputSTU(stu);
return 0;
}
void OutputSTU(struct STU stu[5])
{
struct STU stumax = stu[0];
int j;
for (j=1; j<5; ++j)
{
if (strcmp(stumax.num, stu[j].num) < 0) //strcmp函数的使用
{
stumax = stu[j];
}
}
printf("学生姓名:%s 学生年龄:%d 学生性别:%c 学生学号:%s\n", stumax.name, stumax.age, stumax.sex, stumax.num);
}
输出结果是:
请输入第1个学生的信息:小红 22 F Z1207031
请输入第2个学生的信息:小明 21 M Z1207035
请输入第3个学生的信息:小七 23 F Z1207022
请输入第4个学生的信息:小欣 20 F Z1207015
请输入第5个学生的信息:小天 19 M Z1207024
学生姓名:小明 学生年龄:21 学生性别:M 学生学号:Z1207035
(注意:scanf语句后面需要调用指针参数,是一个地址,而访问的age,sex均为变量名,故要在前面加上一个&取地址符)
结构体数组定义时初始化
结构体数组的初始化与前面讲的数值型数组的初始化是一模一样的,数值型数组初始化的方法和需要注意的问题在结构体数组的初始化中同样适用,因为不管是数值型数组还是结构体数组都是数组。下面就将前面的一个程序修改一下。
# include <stdio.h>
# include <string.h>
struct STU
{
char name[20];
int age;
char sex;
char num[20];
};
void OutputSTU(struct STU stu[5]); //函数声明
int main(void)
{
struct STU stu[5] = {{"小红", 22, 'F', "Z1207031"}, {"小明", 21, 'M', "Z1207035"}, {"小七", 23, 'F', "Z1207022"}, {"小欣", 20, 'F', "Z1207015"}, {"小天", 19, 'M', "Z1207024"}};
OutputSTU(stu);
return 0;
}
void OutputSTU(struct STU stu[5])
{
struct STU stumax = stu[0];
int j;
for (j=1; j<5; ++j)
{
if (strcmp(stumax.num, stu[j].num) < 0)
{
stumax = stu[j];
}
}
printf("学生姓名:%s 学生年龄:%d 学生性别:%c 学生学号:%s\n", stumax.name, stumax.age, stumax.sex, stumax.num);
}
输出结果是:
学生姓名:小明 学生年龄:21 学生性别:M 学生学号:Z1207035
注意,字符要用单引号括起来,字符串要用双引号括起来。
Part three.结构体指针
①定义:一个指向结构体的指针。
struct son
{
char name[20];//姓名
char sex[4];//性别
unsigned char age;//年龄
int height;//身高cm
};
struct son* pson = &wbm;
(*pson).height;//访问方式一
pson->height;//访问方式二
②两种访问方式用.或者->
void say2(struct son *p)
{
printf("姓名:%s,性别:%s,年龄:%d,身高:%d", p->name, p->sex, p->age, p->height);
}
int main()
{
struct son wbm = { "二狗子","女",66,209,70,"拉屎","效力于BROOKLYN女子篮球队" };
struct son* pson = &wbm;
say2(pson);//或者是say2(&wbm);
return 0;
}
③用函数返回一个指针,传指针!!
ps:为何要传指针?而非传参?
Answer:好处一:传指针(统统4个字节)传的是一份地址,而传参是直接拷贝,故拷贝量很大,传指针效率更高。
好处二:传指针可以修改值,而传参不行,改的只是拷贝的量, 是形参,对实参没影响。
Part four:typedef的具体应用
第一种:另起一行typedef
struct Date
{
int year;
int month;
int day;
};
typedef struct Date DATE;
第二种:定义结构的同时起别名。
typedef struct Person
{
char name[20];
int age;
int height;
float weight;
struct Date birthday;
}PERSON , *PPERSON;
注意:PERSON现在与struct Person等价
*PERSON与 struct Person*相等价。是个指针类型,那么我想定义一个指针的时候可以这样:
*PERSON pp;此时pp就是一个指针(指向结构体)
区别:
struct Person
{
char name[20];
int age;
int height;
float weight;
struct Date birthday;
}PERSON , *PPERSON;
这并非起了别名,而是直接在声明结构体的同时,定义了两个变量。
Part five:结构体的自引用
①(结构体里面含有结构体)
#include<stdio.h>
struct Person
{
char name[20];
int age;
}wbm;
struct Date
{
int year;
int month;
int day;
struct Person wbm;
};
typedef struct Date DATE;
int main()
{
struct Date j = { 2021,9,30,{"wbm",23} };
printf("人名:%s,年龄:%d", j.wbm.name, j.wbm.age);
return 0;
}
②真·自引用
错误写法:
struct Student
{
int num;
char name[20];
char sex;//性别
int age;
struct Student s1;
};
编译器无法确定结构体s1所占的内存空间大小。
正确写法:
typedef struct Student
{
int num;
char name[20];
char sex;//性别
int age;
struct Student *s1;//指针占四个字节
}STUDENT;
编译器能够确定结构体所占的内存空间大小(指针统一4个字节)
作用:链表的节点 如下:(简单的)
struct node
{
int date;
struct node* next;
};
int main()
{
struct node node1,node2;
node1.date = 90;
node1.next = &node2;
node1.next->date = 80;
node1.next->next;
}
Part six:不完整声明
struct Date;
typedef struct Student
{
int num;
char name[20];
char sex; //性别
int age;
struct Date *birthday;//出生日期
}STUDENT;
struct Date
{
int year;
int month;
int day;
};
由于自上而下的执行顺序,结构体嵌套下面的结构体时会不认识。
所以我们加上第一行struct Date;即为不完整声明。
struct B;
struct A
{
struct B *pb;//
};
struct B
{
struct A *pa;//
};
struct A a = {NULL};
struct B b = {&a};
a.pb = &b;
Part seven:结构体存储分配
struct A
{
char ch; //1
short s; //2
int i; //4
};
sizeof(struct A)==8
why?涉及到边界对齐的问题了。
Part eight:边界对齐:按照最大的内存对齐
第三种为正确存储方式。既对齐又省内存。
struct A
{
char ch; //1
double d; //8
int i; //4
short s; //2
}
所占字节数为16B
若想浪费内存:最大int占四个字节,在内存条里按顺序对齐,4*3B
仅仅只是换了下顺序。
特殊的,数组
但是如果下一变量为数组,则没有这个要求。
例如:
1 2 3 4 5 6 7 |
|
应该为 4 + 24 + 4 + 8 = 40。
1 2 3 4 5 6 7 |
|
应该为 4 + 20 + 8 + 8 = 40。
1 2 3 4 5 6 7 |
|
应该为 1 + 19 + 4 + 8 = 32。
1 2 3 4 5 6 7 |
|
应该为 1 + 19 + 4 + 8 = 32。
1 2 3 4 5 6 7 |
|
应该为 1 + 15 + 8 + 8 = 32。
对齐目的:以空间换速度,以2的n次方寻址,对齐后更加高效。
Part nine:结构体作为函数参数进行传递
在part 3结构体指针已经描述过了,以及为什么要用结构体指针来传递问题,此处不过多赘述。
end!!