C语言——结构体进阶

一:结构体基本定义

引入:数据类型的局限性

   我们经常使用的的数据类型有很多很多,比如int ,char,float等等,我们可以根据不同大小的数据给他分配不同的数据类型,这相比于MATLAB这种语言单调的数据类型C语言对内存的分配更加合理,C语言严格的数据类型可以极大程度的提高程序的运行速度等等。

  但是随着我们学习的深入,官方给定我们的数据类型只能定义一些指定的数据,适合单一的变量,但是在我们的生活中,每一个实物肯定不止一个特征,就那工资来说,每一个员工都会有一些固有属性,比如,姓名,具体地址,年龄,工资等等。我们要是使用单一的数据类型对其分别定义。在统计少数几个人的时候这种方法看样子还行,然而实际上一个公司的职员少则几百多则上千,一个一个这样单独定义无疑会让代码变的特别复杂。所以C语言就给我们提供了一个可以自己来定义的结构。(这个结构在我看来本质上就是一种数据类型,和int,char地位差不多,只不过他的大小不是固定的,是根据我们的需求进行改变的)

一:结构体

  结构体是一个或者多个变量的集合,这些变量是不同的类型,把他们都集合在了一起,这样更方便我们使用一些,就拿上文工资来距离,我们使用结构体就会变的特别简洁。

struct gongzi
{

   char  name[10];//定义姓名
   int age;  //定义年龄
   int num[10];    //定义号码

}

以后我们在定义关于工资的问题就可以直接使用这个数据类型。

结构体成员的访问:

    语法:结构体名.结构成员

struct gongzi
{

   char  name[10];//定义姓名
   int age;  //定义年龄
   int num[10];    //定义号码

}


gongzi.name=;//通过结构名.成员来调用

补充:两个不同的结构体可以嵌套,但是一个相同的结构体不能嵌套,至于原因吗,我们在结构体的嵌套里面解答。(小透露一下,同一种结构体嵌套就是无限套娃)。

二:结构体数组

      我们在之前的学习里面数组一般都是数据类型+数组名[]来定义的


//数据类型 数组名[] 

int arr[];
char arr[];
float arr[];

  既然是数据类型+数组名,那我们的结构体也是一种数据类型,自然也就有结构体数组。使用方法和普通数组基本一模一样。

struct gongzi //定义方式一
{

   char  name[10];//定义姓名
   int age;  //定义年龄
   int num[10];    //定义号码

} gz[5];


struct gongzi //定义方式二
{

   char  name[10];//定义姓名
   int age;  //定义年龄
   int num[10];    //定义号码

} ;

struct gongzi gz[5];
结构体数组访问内部成员
         结构体数组名[N].内部成员

二:结构体构体指针

      他依旧是一个指针变量所以他在内存里面的大小依旧是四个字节,调用方式和一般的指针基本一致,唯一不同的就是结构体指针调用内部成员有一种新的写法。

假设P是一个指向结构体的指针

  我们可以用p->结构体成员来引用相应的结构体

他等价于(*p).结构体成员

调用方法一://调用方式一(*p.结构体成员)

调用方法二:调用方式二(p->结构体成员)

一:结构体指针嵌套

   我们之前学过函数嵌套,那么在结构体里面自然也会有嵌套结构的产生。在上文我们介绍同名结构体不可以进行正常嵌套但是异名结构体可以嵌套,这是为什么呢。世间万物都是有限的,这也就决定了嵌套的一个前提是你需要有限个,不能层层套娃。

一:同名结构体嵌套

   我们同名结构体就会出现这么一个问题。下面我们来看下面的代码

这里我们定义了一个学生结构体,他的成员包括了年龄,姓名,新别,以及同桌,和班级。安装C语言内存的计算,这里我们先假设这个结构体内部没有同桌这个成员。

我们不难得到他所占的内存大小为4+10+4+4=22字节,现在我们把同桌这个结构体加上。

那么现在这个结构体的大小是多少呢,有些人可能会说了,这不就是之前的22字节+这个同桌结构体的大小吗。问题就出现在了这个同桌结构体的上,这个同桌结构体大小到底是多少呢?,因为他们是同一个结构体,所以在同桌结构体里面依旧是22字节+他的另外一个同桌结构体,这样不断的进行递推,我们会发现我们根本无法定义这个结构体的内存大小,按照逻辑推理他的内存大小应该是无穷大的。(下面这个图片应该会好理解一点)

那么我要是非要同一个结构体嵌套改怎么办呢,C语言也给我们提供了解决办法,即通过结构体指针调用。把他从一个递推结构编程一个并行结构。

(这里使用重定义会报错,后续已经更改了)

那么问题来了,现在结构体的大小是多少呢,自然就是我们一直强调的一个知识点,C语言中指针变量的大小不管是什么数据类型他的大小都是四个字节。这里无限大内存的问题就解决了。

那么我们该如何在一个结构体内部调用第二个结构体呢,这就用到了我们上面讲的通过结构体指针访问内部成员。具体讲述我们通过这段代码来介绍。

我们定义了两个结构体分别是张三和李四,在初始化过程中把结构体成员定义为空指针,这里

lisi.classmate = &zhan;需要特殊说明一下,因为lisi.classmate是指针变量,所以他应该对于zhan结构体的地址名。 lisi.classmate->age这段代码就是标准的通过结构体指针调用结构体成员

(一个注意的点,此时结构体定义里面的结构体指针还是使用struct Student 可能是因为此时的重定义还没有生效吧,笔者能力有限,无法解释)

二:异名结构体调用

   相比于上述的同名结构体通过指针调用来说,异名结构体调用就会简单许多,下面会用一个实例来介绍异名结构体的调用。(因为不会出现内存上无限的问题)

就比如调查一个人,会分为生活方面和工作方面,我们把生活和工作单独定义一个结构体,在定义一个大的结构体来存放这个人信息。

在C语言与程序设计是使用矩形的定义来介绍此嵌套的

即首先我们定义一个直角坐标系的结构体

struct point
{

   int x;
   int y;
};

然后我们用一个结构体嵌套来定义一个矩形(即通过两个点来定义一个矩形)

struct rect{

  struct point pt1;
  struct point pt1;
};

二:结构体调用函数

      我们正常人一般调用函数都是使用if-else哪里不够哪里凑,(大致思路就是通过if'-else对结构体成员的值进行一个判断,然后进行下一步操作)在数据量小的情况下这是没问题的,但是一旦涉及到大工程就极易形成屎山代码,不容易维护且一旦有了新操作就会需要不断的加if-else.

下面由一个产品显示来介绍结构体调用函数。

在实际应用中一家公司会有不同的屏幕驱动函数,不同的产品就会使用到不同的屏幕,正常流程是读取这个产品的ID,然后进入if else进入判断,然后在执行相应的操作函数。如下所示。

(屏幕驱动函数)

代码逻辑就是判断ID然后执行不同的函数(这里我们假设ID1为使用LCD,这里我们假设ID2为使用LCD2)

void LCD_USE1(void)
{
	
	printf("hello\n");

}
void LCD_USE2(void)
{

	printf("你好\n");

}

typedef struct PM  //重定义屏幕
{
	int ID;  //使用ID
	int name;
	
}pm1, * pm2;  //重定义
int main()
{
	    
	pm1 chanp1,chanp2;
	chanp1.ID = 1;
	chanp2.ID = 2;

	if (chanp1.ID == 1)
	{
		LCD_USE1();
	}
	else if (chanp1.ID == 2)
	{
		LCD_USE2();
	}

   return 0;
}

这样我们的代码逻辑确实非常简单,但是确定就是太过于繁琐了,不容易维护。而且一旦个数代码中的if-else就会特别特别多。这个时候我们就可以使用指针函数,在结构体内部直接定义一个指针函数,这样就会自己去执行(而且也不占用空间感,因为指针变量的大小永远都是4字节)最后我们可以现的效果就是每一次修改只用修改驱动函数即可。在学习这部分之前我们写学习一下函数与指针的关系。

三:函数与指针

   我们都学过数学中的排列组合,那么函数和指针两个每次可以组成两个词语,即函数指针,和指针函数。那么他们两个代表的含义是一样的吗。只是简单的位置改变但是他们的意义缺少截然不同,对于初学者来说淘宝容易造成两者混淆。

  实际上搞清楚这两者的区别特别简单,我们只要专注于最后面的两个字就好,其中函数指针最后两个字是指针,所以函数指针的本质是一个指针,指向的是一个函数。 

指针函数,最后两个字是函数,所以他本质就是一个函数,只不过他的返回值是一个指针而已

//指针函数
(void*)play_ball(void)//这里的括号可以不带
{
}

//函数指针
void (*play_ball)(void)
{
}

一:函数指针

  偏向于指针,他反馈回来的是一个函数的指针,我们通过它就可以直接调用函数,这也就是我们学习的重点。

  函数指针的定义:


//函数指针
void (*play_ball)(void)
{
}

实现效果

代码:

typedef struct PM  //重定义屏幕
{
	int ID;  //使用ID
	void (*xianshi)(void);//定义函数指针

}pm1, * pm2;  //重定义

void LCD_USE1(void)
{
	
	printf("hello\n");

}
void LCD_USE2(void)
{

	printf("你好\n");

}

int main()
{
	int i;
	pm1 chanp[10];
	
	chanp[0].xianshi = LCD_USE1;
	chanp[1].xianshi = LCD_USE2;
	for (i = 0; i < 2; i++)
	{
	  chanp[i].xianshi();
	}

对比:

这里对下面这个表述要求不严格,可以+&符号也可以不加,不影响程序编译。

二:指针函数

   偏向于函数,他反馈回来的是一个指针值,我们通过它就可以直接调用函数。

指针函数的定义:


//函数指针
void *play_ball(void)
{
}

就只是相当于返回了一个指针类型的变量而已。

int* add(int x, int y)
{
	int p;
	p = x + y;
	
	int* j=NULL;
	j = &p;
	return j;
}

int main()
{
	int i=10;
	int a = 100;
	int *z;
	z = add(a, i);
	printf("%d/n", (*z));
   return 0;
}

四:typedef类型重定义

一:#define宏定义

二:typedef重定义

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值