结构体
(一)结构体的设计
结构体是由程序开发人员自己设计的类型
结构体的本质就是数据库中的表
不要说定义了一个结构体,而应该是设计了一个结构体
注意:结构体后面的分号不能省略,是设计结构体的一个结束符号;区别于函数:函数结束花括号后面的分号可以省略,编译器认为该语句是一个空语句
(二)结构体的初始化
结构体变量中元素的初始化顺序必须和设计顺序一致
struct Student s1;//s1,s2是自己设计的变量(结构体变量).c或.cpp
Student s2; //.cpp
结构体变量与数组的相同处:
- 都用花括号初始化
- 元素未初始化都用 ’ 0 ’ 填充
不同处:
- 数组元素类型都是相同的
- 结构体中元素类型可能不相同
- 结构体变量初始化按照属性声明的顺序依次给定赋值
s1 = { "0901","xsy","man",20 };
struct Student
{
char s_id[10];
char s_name[10];
char s_sex[6];
int s_age;
};
int main()
{
int a = 0, b = 20;//a和b是内置类型变量
struct Student s1;//s1,s2是自己设计的变量(结构体变量).c或.cpp
Student s2; //.cpp
s1 = { "0901","xsy","man",20 };
s2 = { "0902","zhao" };
int ar[10] = { 1,2,3,4,5 };
return 0;
}
结构体变量的赋值:
struct Student
{
char s_name[20];
int s_age;
};
int main()
{
int a = 10;
struct Student s1 = { "xsying",23 };
Student s2 = {"zhang",19};
Student s3 = s1;//等价于:int b=a;
Student s4;//s4中都是随机值
s4 = s1;//结构体之间可以作为整体相互赋值,和其中的属性类型没有关系
//访问结构体变量时,只有在初始化,或者赋值时可以作为整体,其余情况下访问就要用 .or->访问变量的属性
}
结构体变量成员的规范化使用:
struct Student
{
char s_name[20];
int s_age;
};
int main()
{
struct Student s1 = { "xsying",23 };
char name[20];
strcpy(name, s1.s_name);
int age = s1.s_age;
char* p = s1.s_name;
return 0;
}
指针访问结构体:
- 星号+指针+(). 成员
- 指针->成员
struct Student * sp = &s1;
//sp=>&s1
//*sp=>s1;
//*sp.s_name;考虑优先级,所以加括号
(*sp).s_name;
(*sp).s_age;
sp->s_age;
(三)为结构体变量分配空间
注意:类型是“图纸”,不占空间,拿类型定义的变量才占空间,只有结构体变量可以存放数据
特例分析
以下程序在vs2019中编译不通过,vs2012可以
vs2019严格遵循C++标准;vs2012兼容C语言标准
原因如下:
char *s_id=“0901”;
"0901"是字符串常量,可以用指针指向它,但是不可以修改它
- s_id[0]= ‘1’ ;//error;
vs2019中,当一个普通指针指向一个字符串常量,编译器认为你想修改它,故:编译不通过,直到加 const
const char *s_id=“0901”;
正确写法:
(四)结构体的嵌套
嵌套一:不同结构体间的嵌套
struct Date //12字节
{
int year;
int month;
int day;
};
struct Student //36字节
{
char s_name[10];
struct Date birthday;//12
float score;
};
int main()
{
struct Student studa = { "xsying",2000,12,23,999.9 };
Student studb = { "xsying",{2000,12,23},999.9 };
return 0;
}
嵌套二:相同结构体内部的嵌套(不完整类型)
该结构不能编译通过,一直到内存空间耗费完,程序停止
因此不能 sizeof(struct StudentB )
C语言中凡是不能够计算大小的类型称为不完整类型
基本数据类型中也有不完整类型:void也不能用sizeof(void)
嵌套三:嵌套结构体指针
#include<stdio.h>
#include<assert.h>
struct Student
{
char s_name[20];
int s_age;
struct Student* next;
};
void Print_Student(struct Student* sp)
{
assert(sp != NULL);
while (sp != NULL)
{
printf("%s \n", sp->s_name);
printf("%d \n", sp->s_age);
sp = sp->next;
}
}
int main()
{
struct Student a = { "yhping",12 };
struct Student b = { "zhang",11 };
struct Student c = { "wang",10 };
struct Student* head = &a;
a.next = &b;
b.next= &c;
c.next= NULL;
Print_Student(head);
return 0;
}
(五)结构体变量属性的访问
p->a==(*p).a
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<limits.h>
//结构体的打印
struct Student
{
char s_id[10];
char s_name[10];
char s_sex[8];
int s_age;
};
void Print_Student(struct Student const stud[], int n) //数组名的退化
//void Print_Student(struct Student const *stud, int n)//指针的指向不能改
{
//printf("%d \n",sizeof(stud));//4
assert(stud != nullptr);
printf("%-10s %-10s %-8s %-5s \n", "id", "name", "sex", "age");
for (int i = 0;i < n;++i)
{
//printf("%-10s %-10s %-8s %-5d \n",
// stud[i].s_id, stud[i].s_name, stud[i].s_sex, stud[i].s_age);
//printf("%-10s %-10s %-8s %-5d \n",
//(*(stud+i)).s_id, (*(stud + i)).s_name, (*(stud + i)).s_sex, (*(stud + i)).s_age);
printf("%-10s %-10s %-8s %-5d \n",
stud->s_id, stud->s_name, stud->s_sex, stud->s_age);
stud++;
}
printf("\n");
}
int main()
{
struct Student studx[10] = {
{"190601","xiao","man",22},
{"190602","sun","man",21},
{"190603","zhao","woman",17},
{"190604","qian","man",19},
{"190605","li","woman",24},
{"190606","zhou","woman",25},
{"190607","liming","man",24},
};
Print_Student(studx, 7);
return 0;
}
(六)结构体与引用
内置类型引用
引用:就是别名
引用的特点:
- 定义引用必须给予其初始化
- 不存在空引用
int& c; c = b;
- 不存在引用的引用,引用不存在分级
引用的好处:
真正意义上的对形参的改变改变实参
int Swap_int(int& a, int& b)
{
int c = a;
a = b;
b = c;
}
int main()
{
int a = 10;
int b = 20;
Swap_int(a,b);
return 0;
}
自己设计类型的引用
struct Student
{
char s_name[20];
int s_age;
};
void Print_StudA(struct Student x)
{
}
void Print_StudB(struct Student const* sp)
{
assert(sp != nullptr);
}
void Print_StudC(const struct Student& x)//x是s1的别名,对x的改变就可以改变s1,所以,必须限定只能读取值,但是不能改变S1
{
printf("%d \n", x.s_age);
printf("%s \n", x.s_name);
}
int main()
{
struct Student s1 = { "xsying",23 };
Print_StudA(s1);
Print_StudB(&s1);
Print_StudC(s1);
return 0;
}
(七)结构体的大小
struct Node
{
char cha;
int ia;
char chb;
};
int main()
{
struct Node x = { 'a',23,'b' };
int size = sizeof(x);
printf("%d \n", size);
}
struct Node
{
char cha;
char chb;
int ia;
};
int main()
{
struct Node x = { 'a',23,'b' };
int size = sizeof(x);
printf("%d \n", size);
}
struct Node
{
char cha;
char chb;
char chc;
int ia;
};
int main()
{
struct Node x = { 'a',23,'b' };
int size = sizeof(x);
printf("%d \n", size);
}
结构体的对齐方式
由于存储变量地址对齐的问题,计算结构体大小的3条规则:
- 1.结构体变量的首地址, 必须是结构体变量中的“最大基本数据类型成员所占字节数”的整数倍。
- 2.结构体变量 中的每个成员相对于结构体首地址的偏移量,都是该成员基本数据类型所占字节数的整数倍。
- 3.结构体变量的总大小, 为结构体变量中“ 最大基本数据类型成员所占字节数"的整数倍。
0地址可以整除于任何基本类型的大小
示例1: 12字节
示例2: 8字节
示例3:
示例4: 结构体中含有数组(将数组元素看成基本数据类型)8字节
示例5:结构体中嵌套结构体(看成基本数据类型)40字节
示例6:结构体中含有指针 s1:8 | s2:8
s2示意图:
关心结构体对齐方式的原因
编写网络程序时,需要将数据写入结构体,将结构体的数据通过网络发送在远端,远端需要接受结构体中的数据,故,必须清楚结构体在内存的布局方案,以便于读取数据
(八)预处理指令对齐
指令对齐方式是2的幂次方:
struct Node
{
char ca;
double dx;
char cb;
};
int main()
{
Node x;
printf("%d \n", sizeof(x));
return 0;
}
#pragma pack(1)//指定按照1字节对齐
struct Node
{
char ca;
double dx;
char cb;
};
int main()
{
Node x;
printf("%d \n", sizeof(x));
return 0;
}
#pragma pack(2)//指定按照2字节对齐
struct Node
{
char ca;
double dx;
char cb;
};
int main()
{
Node x;
printf("%d \n", sizeof(x));
return 0;
}
特例分析:
基本类型都小于对齐指令,那么就按照最大基本类型偏移量对齐
#pragma pack(16)//指定按照16字节对齐
struct Node
{
char ca;
double dx;
char cb;
};
int main()
{
Node x;
printf("%d \n", sizeof(x));
return 0;
}
(九)计算结构体偏移量
方法一:定义变量
struct Node
{
short sx;
char str[3];
double dx;
char strb[7];
int age;
long int num;
};
int main()
{
Node x;
int dist = (int)((char*)&x.dx - (char*)&x);
printf("%d \n", dist);
return 0;
}
方法二:不定义结构体变量
(十)结构体的两种特殊用法:
1.柔性数组
操作系统管理内存是一页一页管理的,每个页面的大小为4K
1K=1024 4K=1024*4=4096 bit
柔性数组的定义:
柔性数组在结构体中声明时只能在所有变量最后声明,并且柔性数组只能声明一个。因为柔性数组的空间是可变的,用户想申请多少字节是不确定的,所以结构体偏移量也不确定,若不是在最后声明,那么柔性数组后面声明的变量地址就无法确定
struct kd_node
{
int num;
int size;
char data[];
};
int main()
{
struct kd_node* sp1 = (struct kd_node*)malloc(sizeof(struct kd_node) + 20);
if (sp1 == nullptr)
{
exit(EXIT_FAILURE);
}
strcpy_s(sp1->data, 20, "xsying");
sp1->num = strlen("xsying");
sp1->size = 20;
printf("size:%d \n", sp1->size);
printf("num:%d \n", sp1->num);
printf("data:%s \n", sp1->data);
free(sp1);
sp1 = nullptr;
return 0;
}
程序结束一定要释放sp1指向的空间,并将sp1赋值为空
柔性数组所在结构体变量的大小:
注意:柔性数组中的数据所占字节并不计算在结构体变量的大小中
因为 sizeof() 只计算类型的大小。sizeof(s1) s1是struct kd_node 类型,该类型只占8字节大小
sizeof() 是在编译时确定所要计算类型大小,编译时数据还没有给,所以编译时只能按照数据类型进行计算
举例:
struct kd_node
{
int num;
int size;
char data[];
};
int main()
{
struct kd_node s1 = { 6,7,"xsying" };
struct kd_node s2 = { 11,12,"xsyinghello" };
printf("s1=%d \n", sizeof(s1));
printf("s2=%d \n", sizeof(s2));
printf("kd_node=%d \n", sizeof(struct kd_node));
return 0;
}