结 构 体 全 析

(一)结构体的设计

结构体是由程序开发人员自己设计的类型

结构体的本质就是数据库中的表

不要说定义了一个结构体,而应该是设计了一个结构体

在这里插入图片描述
注意:结构体后面的分号不能省略,是设计结构体的一个结束符号;区别于函数:函数结束花括号后面的分号可以省略,编译器认为该语句是一个空语句

(二)结构体的初始化

结构体变量中元素的初始化顺序必须和设计顺序一致

    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;
}

在这里插入图片描述

替代柔性数组的结构

在这里插入图片描述

2.结构体的位运算

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值