结构体与数组的区别
数组:相同数据类型的数据构成的一种数据结构。
结构体:不同类型的数据成员组成的(用户自定义的)一种新的数据类型。
下面利用图片形象的解释两者存储差别,实现以下数据的存储。
可以看出,使用结构体等于说创建了一种新的数据类型,其他操作并无两异,尤其是到后面的结构体数组中会体现的更加明显。总的,结构体是一个整体。
结构体变量的定义
首先声明结构体模板,标准格式如下:
struct 结构体名
{
数据类型 成员1名字;
数据类型 成员2名字;
......
数据类型 成员n名字;
};
在声明结构模板(只是声明了一种新的数据类型),接着需要定义结构体变量。
简单示例
struct student
{
char i;
char x;
char y;
};
主要有一下三种方式:
先声明结构体模板,在定义结构体变量。
struct student stu1;
在声明结构体模板的同时定义结构体变量。
struct student //此种方法可以省略student,但是复用困难,所以不常用
{
char i;
char x;
char y;
}stu1; //此时定义的结构体变量为全局变量
typedef定义结构体
相信你已经会使用typedef定义别的数据类型。
如
typedef int INTEGER;
typedef的实质就是给数据类型起别名。
下面我们介绍使用typedef定义结构体:(与上述大差不差)
当你已经声明了结构体模板后,只需要简单的typedef语句就可完成
typedef struct student STUDENT;
或者在声明模板时就可以使用typedef
typedef struct student
{
char i;
char x;
char y;
}STUDENT;
上述两者是等价的,在使用它们定义时也是相同的。
STUDENT stu1,stu2;
//一下等价语句为
struct student stu1,stu2;
结构体变量的初始化
初始化语句为:
STUDENT stu1={‘a’,‘b’,‘c’};//在前面的结构体中我们定义的是三个char,所以现在初始化三个字符就🆗
//一下等价语句为
struct student stu1={‘a’,‘b’,‘c’};
嵌套结构体
简单来说就是一个结构体中包含了另外一个结构体的内容。(也可以是同一个结构体间的嵌套,但是实用价值不大)
下面简单举一个嵌套结构体的例子
typedef struct data
{
int year;
char month[10];
int day;
}DATE; //注意嵌套在内部的结构体需要先声明
typedef struct student
{
long studentID;
char studentName[10];
char studentSex;
DATE brithday; //嵌套的结构体,定义的DATE型结构体变量为brithday
int score[4];
}STUDENT;
简单的介绍一下嵌套结构体的初始化方式:
STUDENT stu1={100310121,"王刚",'M',{1991,'May',19},{98,99,100,86}};
结构体变量的引用
使用成员选择运算符 (圆点运算符)
结构体变量名.成员名
可以使用此语句对于成员进行赋值操作
stu1.studentID = 100310121;
当结构体为嵌套结构体时,采用级联的方式进行引用
stu1.brithday.year = 1991;
结构体数组
在上面我们提到过结构体只是相当于定义了一种新的数据类型,其他操作与已有的数据类型别无两异
结构体数组的定义与初始化
STUDENT stu[30];
//初始化就是数据的重叠
STUDENT stu1={{100310121,"王刚",'M',{1991,'May',19},{98,99,100,86}},
{100310121,"张三",'M',{1991,'March',18},{73,62,88,86}},
{100310121,"李四",'W',{1991,'June',17},{99,76,66,90}}
};
//上述只初始化了三个数据,其他的系统自动赋值为0
结构体指针
指向结构体变量的指针
STUDENT *pt;
pt = &stu1;
//上述语句与下列语句等价
STUDENT *pt = &stu1;
使用指针对与结构体成员的引用不可以直接使用成员运算符(因为指针只是一个存储地址的变量,无法直接使用指针对于成员的应用,需要使用该指针保存的地址寻找到该地址,再利用该地址对成员进行引用)
所以,对于指针,我们有指向运算符 ->
指向结构体的结构体指针变量名 -> 成员名
//以下示例
pt -> studentID = 100310121;
那么如何通过成员运算符与指针来引用结构体成员呢
我们可以通过*运算符来寻址,从而使用成员运算符来访问
(*pt).studentID = 100310121;
指向结构体数组的指针
STUDENT *pt=stu;
//与下面一条语句等价
STUDENT *pt=&stu[0]
//也等价与以下语句
STUDENT *pt;
pt=stu;
是结构体变量还是结构体指针变量?
(更新补充于23.4.1)
在使用指针与变量本身时很容易混淆
当结构体嵌套时,这个问题更明显
下面根据一个数据结构中经典的迷宫问题来辨析一下
这是定义了一个BOX类型的结构体变量,含有i,j,di三个成员
然后又定义了一个StType类型结构体变量,嵌套了BOX类型的变量
我们看看在程序中或者函数中如何使用它们吧
上述语句中,st为定义的StType类型的结构体的指针类型变量(有点拗口,,ԾㅂԾ,,)
很容易犯得错误是忘记st是指针型变量,使用成员运算符 . 来引用内部成员,很显然应该使用指向运算符来引用内部成员。然而对于其内部嵌套的结构体类型数组data[k]是BOX类型结构体变量,直接使用成员运算符即可引用内部成员。
在函数中也一样,函数传过来的一般多是指针,所以下面的例子也一样
stu与&stu[0]与&stu的关系
在这里有一个隐含的概念,和数组也是一样的
即stu与&stu[0]是等价的
但是对于&stu是具有不一样的含义的
首先我们从数组开始解释
有以下简单代码:
char a[4];
printf("%p\n",a);
printf("%p\n\n",a+1);
printf("%p\n",&a[0]);
printf("%p\n\n",&a[0]+1);
printf("%p\n",&a);
printf("%p\n\n",&a+1);
预计执行结果为:
2,4两行结果相同,3,5两行在前一种结果的基础上+1(char类型占一个字节)
那么6,7两行的结果呢?
第6行的结果和2,4的结果相同
第七行结果为此结果基础上+4
很显然,对于&a来说,是将整个数组当作一个整体,所以对于&a进行+1的操作,即是像后挪动一整个数组。
下面以结构体数组来进行演示,看看的预测的结果是否正确吧?
typedef struct Teacher
{
char i;
char x;
char y;
}TEACHER;
printf("%p\n",teac); //结构体首地址(可以进行+1操作) 与&teac[0]等价
printf("%p\n",teac+1); // 往后挪一个结构体
printf("%p\n",&teac[0]); //结构体首地址
printf("%p\n",&teac[0]+1); //结构体地址加一
printf("%p\n",&teac); //也是首地址,但是是整个结构体数组首地址
printf("%p\n",&teac+1); //往后诺挪一个结构体数组
printf("%p\n",teac[0]); //数据 (不可以进行+1操作)
结构体不一样的只是结构体内部还有成员,但是你可以将它看作一个新的数据类型,不需要管内部的数据成员。只是在引用的时候将看成内部的细分。
向函数传递结构体
将结构体单个成员作为函数参数,向函数传递单个成员
此处和普通类型的变量作为函数实参无区别,传值调用。较少使用。
结构体变量作为函数参数,向函数传递结构体的完整结构
由于我们前面提到的,结构体可以看作一种新的数据类型,所以此方式还是传值调用。与第一中传递方法并无两异。
用结构体指针或者结构体数组作为函数参数,向函数传递结构体的地址
按地址调用。
结构体也可以作为函数的返回值。
一个贯彻的原理是,结构体相当于定义的一个新的数据类型。所以上述与函数传参中的知识点,语法是相同的。但不要忘记在第三种中,无论是通过指针还是结构体数组,在函数体内部访问结构体成员,由于函数得到的都是地址,所以都需要使用指向运算符。(如果结构体数组是全局变量,就不需要传参,并且可以使用成员运算符)
结构体的知识介绍就到此,希望你在接下来的编程中找到属于自己的船,扬帆远航!
——23.3.17