结构体(struct)
1、基本概念
结构体-----将不同类型的数据成员组织到统一的名字之下,适用于对关系紧密,逻辑相关、具有相同或不同类型的数据进行处理
2、结构体定义格式
struct 结构名(也可称作结构标识符)
{
类型 变量名;
类型 变量名;
······
};
struct 结构名 结构变量;
或者
struct 结构名
{
类型 变量名;
类型 变量名;
······
}结构变量;
例:声明结构体类型的同时定义变量名
1 struct student
2 {
3 int num;
4 }teacher;
(声明结构体类型仅仅是声明了一个类型,系统并不为之分配内存,就如同系统不会为类型 int 分配内存一样。只有当使用这个类型定义了变量时,系统才会为变量分配内存。所以在声明结构体类型的时候,不可以对里面的变量进行初始化。)
定义了一个结构名为student的结构体和一个结构变量teacher,如果省略变量名(teacher),就变成了对结构的声明,上述结构体声明也可分开写
1 struct student
2 {
3 int num;
4 };
5
6 struct student teacher;
与上面效果相同,可理解为struct student类似于int,而我们用的是teacher类似于变量,如果省略结构名,则称之为无名结构,这种情况常常出现在函数内部,或者说你只需要teacher这一个变量,
后面不需要再使用结构体名定义其他变量,那么在定义时也可以不给出结构体名
1 struct
2 {
3 int num;
4 }teacher;
(在声明结构体时常常与typedef函数配合使用)
3、结构体成员的访问
访问结构体变量的成员必须使用成员选择运算符(也称圆点运算符),格式为:结构体变量名.成员名
若使用指针对结构体成员进行访问,格式为:指针->成员名 等价于 (*指针).成员名
4、typedef函数
为一种数据类型定义一个新名字。这里的数据类型包括内部数据类型(int,char等)和自定义的数据类型(struct等),
(注意与#define的区别,typedef 是用来定义一种类型的新别名的,它不同于宏#define,宏是简单的字符串替换)
例:
1 typedef int INTEGER;
为int定义了一个新的名字INTEGER,也就是说INTEGER与int是同义词,也可以为结构体定义一个别名
1 typedef struct student STUDENT;
或者
1 typedef struct student
2 {
3 int num;
4 }STUDENT;
上述两条语句是等价的,二者都是为struct student结构体类型定义了一个新的名字STUDENT,即STUDENT与struct student是同义词,所以下列两条语句等价
1 1 STUDENT stu1,stu2;
2 2 struct student stu1, stu2;
补充:
1 typedef struct tagNode
2 {
3 char *pItem;
4 pNode pNext;
5 } *pNode;
上述代码编译阶段会报错,原因:
在上面的代码中,新结构建立的过程中遇到了 pNext 声明,其类型是 pNode。这里要特别注意的是,pNode 表示的是该结构体的新别名。
于是问题出现了,在结构体类型本身还没有建立完成的时候,编译器根本就不认识 pNode,因为这个结构体类型的新别名还不存在,所以自然就会报错。
因此,我们要做一些适当的调整,比如将结构体中的 pNext 声明修改成如下方式:
1 typedef struct tagNode
2 {
3 char *pItem;
4 struct tagNode *pNext;
5 } *pNode;
或者将 struct 与 typedef 分开定义
1 typedef struct tagNode *pNode;
2 struct tagNode
3 {
4 char *pItem;
5 pNode pNext;
6 };
在上面的代码中,我们同样使用 typedef 给一个还未完全声明的类型 tagNode 起了一个新别名。不过,虽然 C 语言编译器完全支持这种做法,但不推荐这样做,建议改为
1 struct tagNode
2 {
3 char *pItem;
4 struct tagNode *pNext;
5 };
6 typedef struct tagNode *pNode;
5、typedef函数与#define函数
#define函数格式:
#define 标识符 字符串,标识符成为宏名,宏替换时不做任何语法检查
1 typedef char* pStr1;
2 #define pStr2 char*
3 pStr1 s1,s2;
4 pStr2 s3,s4;
在上述的变量定义中,s1、s2、s3都被定义为char *,而s4则定义成了char,不是我们所预期的指针变量,根本原因就在于#define只是简单的字符串替换而typedef则是为一个类型起新名字。
上例中#define语句应该写成 pStr2 s3, *s4;
6、结构体嵌套
就是在一个结构体内包含了另一个结构体作为其成员
1 typedef struct date
2 {
3 int year;
4 int month;
5 int day;
6 }DATE;
7
8 typedef struct student
9 {
10 long studentID;
11 char studentName[10];
12 char studentSex;
13 DATE birthday;
14 int score[4];
15 }STUDENT;
16 STUDENT pp;
上面代码中,定义了结构体变量birthday和pp,并给struct date和struct student分别取别名为DATE和STUDENT,
当出现结构体嵌套时,必须以级联方式访问结构体成员,即通过成员选择运算符逐级找到最底层的成员时再引用
1 pp.birthday.day = 10;
2 printf("%d", pp.birthday.day);
上面是使用了typedef的结构体嵌套,也可以不使用,代码如下
1 struct STUDENT
2 {
3 long studentID;
4 char studentName[10];
5 char studentSex;
6 int score[4];
7 struct DATE
8 {
9 int year;
10 int month;
11 int day;
12 }birthday;
13 }student;
定义了结构体变量student和birthday,引用成员的的方法和上面类似
1 student.birthday.day = 100;
2 printf("%d", student.birthday.day);
C语言允许对具有相同结构体类型的变量进行整体赋值,注意:对字符数组型结构体成员进行赋值时一定要使用strcpy()
1 strcpy(stu1.studentName, “王刚”);
而不能写成
1 stu2.studentName = stu1.studentName
因为结构体成员studentName是一个字符型数组,studentName是该数组的名字,代表字符型数组的首地址,是一个常量,不能作为赋值表达式的左值,
结构体所占内存的字节数,不是简单的相加, 因为对多数计算机而言,为了提高内存寻址的效率,很多处理器体系结构为特定的数据类型引入了特殊了内存对齐需求,
不同的系统和编译器,内存对齐的方式有所不同,为了满足处理器的对其要求,可能会在较小的成员后加入补位,例:
1 Typedef struct sample
2 {
3 Char m1;
4 Int m2;
5 Char m3;
6 }SAMPLE;
字节长度为12而不是1+4+1=6字节长度,即sizeof(struct sample)==12;
7、结构体指针的定义和初始化
1 typedef struct student
2 {
3 long studentID;
4 char studentName[10];
5 char studentSex;
6 DATE birthday;
7 int score[4];
8 }STUDENT;
9 STUDENT stu1;
10 STUDENT *pt;
11 pt = &stu1;
或者
1 STUDENT *pt = &stu1;
上面的代码中,定义了结构体变量stu1,和结构体指针变量pt,并将stu1变量的地址赋值给了指针变量pt
假设已声明了STUDENT结构体类型,并且已定义了一个有30个元素的结构体数组stu,则定义结构体指针变量pt并将其指向结构体数组stu的方法为:
1 STUDENT *pt = stu;
等价于
1 STUDENT *pt = &stu[0];
等价于
1 STUDENT *pt; 2 pt = stu;
8、向函数传递结构体(参数传递)
将结构体传递给函数的方式有如下3种:
1.用结构体的单个成员作为函数参数,向函数传递结构体的单个成员(属于传值调用,不会影响相应的实参结构体的值)
2.用结构体变量做函数参数,向函数传递结构体完整结构(属于传值调用,不会影响相应的实参结构体的值)
3.用结构体指针或结构体数组作函数参数属于模拟按引用调用,会影响相应的实参结构体的值,向函数传递结构体地址,因为仅复制结构体首地址一个值给被调函数,相对于第二种方式,这种传递效率更高
9、补充:按值调用与模拟按引用调用(参数传递)
按值调用:将程序将函数调用语句中的实参的一份副本传给函数的形参
模拟按引用调用:指针作为函数的参数,虽然实际上也是传值给被调用函数,但是传给被调用函数的这个值不是变量的值,而是变量的地址,
通过向被调用函数传递某个变量的地址值可以在被调函数中改变主调函数中这个变量的值,相当于模拟C++中的按引用调用因此称为模拟按引用调用
参考链接:
https://www.cnblogs.com/ktao/p/8578074.html