ps:在文章末尾有用实验所用到的所有代码,不同部分在讲解的时候只是代码的节选,一般而言是不能够单独运行的,望周知;
便于同一个类型的数据传输,运算;在单片机中,运行复杂的程序的时候,一个对数据有很好的储存;
struct Student{
int id; //学号
char *name; //名字
int age; //年龄
float score;//成绩
//,,,,,
};
id,name,age,score
struct Student{
};
struct{
int id; //学号
char *name; //名字
int age; //年龄
float score;//成绩
//,,,,,
}stu1 stu2;
注意这里的stu1,stu2都是变量名字,不是类型名
struct Student{
int id; //学号
char *name; //名字
int age; //年龄
float score;//成绩
//,,,,,
};
int main(){
//数据类型 + 变量名 : int a; float b; char c;
Student stu;
return 0;
}
注意这里用这个
Student stu;来定义的话,会报错的,原因是什么呢;
Student不是数据类型,注意一点Student为结构体名;而struct Student才是结构体类型名,所以应该这样定义:
int main(){
//数据类型 + 变量名 : int a; float b; char c;
struct Student stu;
return 0;
}
这样一来,每次都要多写一个 struct ,很复杂,所以我们考虑引入 typedef;
typedef 的作用就如他的名字一样,用于定义新的数据类型 type --> name
typedef type name
如下:
type struct Student Student;
int main(){
//数据类型 + 变量名 : int a; float b; char c;
Student stu;
return 0;
}
也可以直接在定义结构体的时候,定义:
typedef struct Student{//这里的Student可以省略
int id; //学号
char *name; //名字
int age; //年龄
float score;//成绩
//,,,,,
}Student;
int main(){
//数据类型 + 变量名 : int a; float b; char c;
Student stu;
return 0;
}
比如每个学生都有生日,但是生日又是由年,月,日组成的:
typedef struct Birthday{
int year;
int month;
int day;
}Birthday;
typedef struct Student{
int id; //学号
char *name; //名字
int age; //年龄
float score;//成绩
Birthday birthday;//生日
}Student;
int main(){
//数据类型 + 变量名 : int a; float b; char c;
Student stu;
stu.birthday.year;
return 0;
}
就以上面的例子,我们定义的 Student 还有 Birthday 结构体都是没有占用内存的,就好比是 int double float一样,没有定义明确的变量的时候,就相当于是一个蓝图;
Student stu1,stu2;
Birthday bir1,bir2;
以上面的 stu1 和 stu2为例:
Student stu1 = {1001,"xsw",27,100,{2003,10,2}};
Student stu2 = {1002,"zzz",27,80,{2001,1,24}};
根据结构体定义的前后顺序进行赋值,如果遇到像 “Birthday” 这样的嵌套,你需要多加一层括号即可;
printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",stu1.id,stu1.name,stu1.age,stu1.score,stu1.birthday.year,stu1.birthday.month,stu1.birthday.day);
printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",stu2.id,stu2.name,stu2.age,stu2.score,stu2.birthday.year,stu2.birthday.month,stu2.birthday.day);
一旦输入stu1. 后面就会跳出相关的选项,包括Birthday这样的嵌套的;需要注意的一点就是,前面的数据类型需要和结构体定义的时候保持一致,避免出错;
void printStudentinfo(Student stu1)//定义传入的数据类型
{
printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",stu1.id,stu1.name,stu1.age,stu1.score,stu1.birthday.year,stu1.birthday.month,stu1.birthday.day);
}
printStudentinfo(stu1);//printStudentinfo(Student stu1)这样传的话会报错哦
printStudentinfo(stu2);
刚刚在用函数解决问题的时候,计算机实际上是将stu1复制了一份,然后才传入函数的,如果定义的结构体比较的复杂,那么这样拷贝,然后传入的话,工作量很大(如果需要进行多个结构体的传入哈),所以用到指针,直接传入定义的时候的地址,加快代码的效率;
定义:
Student *pStu1 = &stu1;
Student *pStu2 = &stu2;
这里直接用调用函数的方式,来解释上述的两个知识点:
void printStudentinfo2(Student *pstu1)//传入指针类型的结构体
{
printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",pstu1->id,pstu1->name,pstu1->age,pstu1->score,pstu1->birthday.year,pstu1->birthday.month,pstu1->birthday.day);
}
printStudentinfo2(pStu1);
printStudentinfo2(pStu2);
实际上和一般的引用方式一致,但是,在声明、调用一个结构体的指针时候,往往用到的是“->”;
但是如果有嵌套的话,像这里的Birthday的话,Birthday和year,month,day之间还是用“.”连接的;注意体会他们之间的关系;
如果用中文来解释的话:
"."一般情况下读作"的”。
“->”一般读作"指向的结构体的"。
这两个在结构体虽然常用,但有时候很容易让人用混淆了,程序编译不通过。
1、一般情况下使用“.”,只需要声明一个结构体。这个一般用在结构体变量的使用。
格式是,结构体类型名+结构体名。
然后用结构体名加“.”加域名就可以引用域 了,因为自动分配了结构体的内存。
2、用“->”,则要声明一个结构体的指针,还需要手动开辟一个该结构体的内存空间,
然后把返回的指针给声明的结构体指针,才能用“->”正确引用。
这个用在结构体指针变量。如果内存中只分配了指针的内存,没有分配结构体的内存,
将会导致想要的结构体实际上是不存在。
这时候用“->”引用自然出错了,因为没有结构体,自然没有结构体的域了。
3、此外,(*a).b 等价于 a->b。
4、总结
"."一般情况下读作"的”。
“->”一般读作"指向的结构体的"。
计算方式
用sizeof来计算,注意一点的是,如果是指针类型的话,需要查明占用空间以后再进行计算;
还有就是如果,结构体都是一样的类型的数据,那么直接加和就行,但是如果类型不同的话,一般而言他们的储存空间也不一样,那么就需要按照内存对齐的方式进行计算;
printf("the size of char:%d\n",sizeof(char));
printf("the size of char*:%d\n",sizeof(char*));
printf("the size of int:%d\n",sizeof(int));
printf("the size of float:%d\n",sizeof(float));
printf("the size of double:%d\n",sizeof(double));
printf("the size of Birthday:%d\n",sizeof(Birthday));
printf("the size of Student:%d\n",sizeof(Student));
内存对齐
说白了就是必须按照储存大小的来倍数进行储存,什么意思呢,就比如说 int 我们都知道它占用4个字节,在储存的时候,就只能在第 4n 的位置开始储存;不能在第二个,第三个位置,,
struct S3
{
double d;
char c;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%d\n", sizeof(S4));
return 0;
}
S4 = char + S3 + double;
S3 = double + char + int
double:8,char:1,int:4
8+1=9,那么这个时候再加 int 的话,他需要在第三个位置开始储存的,(也就是前面说的n=4),所以最后就是 9+3+4=16 (12+4)
S3 = 16
S4 = 1 + 16 + 8;
1是任何数的倍数,所以不需要留位置对齐,那么只需要留位置给 8;
S4 = 1+16+7+8=32 (24+8)
如果要表示很多学生的信息的话,我们不可能想之前一样,stu1,stu2,stu3,这样一直下去,于是结构体数组就体现出他的便携性了
void printStudentInfo(Student *pStu, int len){
for(int i = 0; i < len; ++i){
printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",
pStu[i].id,(pStu+i)->name,(pStu+i)->age,(pStu+i)->score,(pStu+i)->birthday.year,(pStu+i)->birthday.month,(pStu+i)->birthday.day);
}
}
int main(){
Student students[] = {
{1001,"xsw",27,100,{2003,10,2}},
{1002,"zzz",27,80,{2001,1,24}}
};
printStudentInfo(students ,sizeof(students)/sizeof(students[0]));
printf("\n");
}
很清晰的看到,在调用结构体的时候,你会发现有这样的两种方式可以进行表达;那么这是不是和我们之前说的有矛盾呢
pStu[i].id, 相当于就是表示的是students[],是数组,所以用点号表示;
(pStu+i)->name,中的 pStu 单独来写的话,表示的是地址,所以需要用到箭头表示;
#include
typedef struct Birthday{
int year;
int month;
int day;
}Birthday;
typedef struct Student{
int id; //学号4
char *name; //名字8
int age; //年龄4
float score;//成绩4
Birthday birthday;//生日12
}Student;
typedef struct A{
int a; //4个字节
char c; //1个字节
char d[3];//3个字节
}A;
void printStudentinfo1(Student pstu1)//定义传入的数据类型
{
printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",pstu1.id,pstu1.name,pstu1.age,pstu1.score,pstu1.birthday.year,pstu1.birthday.month,pstu1.birthday.day);
}
void printStudentinfo2(Student *pstu1)//传入指针类型的结构体
{
printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",pstu1->id,pstu1->name,pstu1->age,pstu1->score,pstu1->birthday.year,pstu1->birthday.month,pstu1->birthday.day);
}
void printStudentInfo(Student *pStu, int len){
for(int i = 0; i < len; ++i){
printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",
pStu[i].id,pStu->name,(pStu+i)->age,(pStu+i)->score,(pStu+i)->birthday.year,(pStu+i)->birthday.month,(pStu+i)->birthday.day);
}
}
int main(){
//数据类型 + 变量名 : int a; float b; char c;
Student stu1 = {1001,"xsw",27,100,{2003,10,2}};
Student stu2 = {1002,"zzz",27,80,{2001,1,24}};
Student students[] = {
{1001,"xsw",27,100,{2003,10,2}},
{1002,"zzz",27,80,{2001,1,24}}
};
printStudentInfo(students ,sizeof(students)/sizeof(students[0]));
printf("\n");
Student *pStu1 = &stu1;
Student *pStu2 = &stu2;
//访问成员变量,选用点操作符号
printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",stu1.id,stu1.name,stu1.age,stu1.score,stu1.birthday.year,stu1.birthday.month,stu1.birthday.day);
printf("id:%d\t name:%s\t age:%d\t score:%.2f\t birthday:%d-%d-%d\n",stu2.id,stu2.name,stu2.age,stu2.score,stu2.birthday.year,stu2.birthday.month,stu2.birthday.day);
//调用函数,来输出成员信息
printStudentinfo1(stu1);//printStudentinfo(Student stu1)这样传的话会报错哦
printStudentinfo1(stu2);
//调用函数,传入指针,来输出成员信息
printStudentinfo2(pStu1);
printStudentinfo2(pStu2);
printf("\n");
printf("the size of char:%d\n",sizeof(char));
printf("the size of char*:%d\n",sizeof(char*));
printf("the size of int:%d\n",sizeof(int));
printf("the size of float:%d\n",sizeof(float));
printf("the size of double:%d\n",sizeof(double));
printf("the size of Birthday:%d\n",sizeof(Birthday));
printf("the size of Student:%d\n",sizeof(Student));
printf("the size of A:%d\n",sizeof(A));
return 0;
}
操作系统:Windows10
软件:Devc++
编译器:TDM-GCC 4.9.2 64-bit Release
附习题详解:
typedef的一些常见错误