通过声明某一数据类型的变量,可以描述某一事物的某一特性。越复杂的事物特性越多,这时候用单一的变量来描述事物的特性会比较麻烦,所以C语言引入了结构体类型,结构体类型可以对事物整体进行封装。
如描述学生的特性
以前:
char id[8]; //id
char names[8] //名字
int age;// 年龄
char sex; //性别
… … …
现在:
struct student //特性之间不在分离,而是一个整体
{
char id[8];
char names[8];
int age;
char sex;
};
1. 结构体类型
注:结构体类型属于我们构造的类型
1.1 结构体类型定义
定义格式:
struct 结构体类型标识符
{
成员变量列表; //可以是基本数据类型,或构造类型
};
struct student
{
char id[8];
char names[8];
int age;
char sex;
};
struct class
{
struct student yuanyuan;//成员为结构体类型
struct student wenwen;
struct student wenwen; //error 不能同名
struct class A_senior_class; //error 不能嵌套定义相同类型的结构体变量
struct class *A_senior_class;
//correct 成员变量可以指向本类型
};
注:
可以在结构体中定义别的结构体,但是不能在本结构体定义本身,或者说不能在本结构体声明本结构体的变量。
可以在本结构体中定义指向本结构体的指针。
小结:
①struct student 和struct class是结构体类型名,student和class不是结构体类型名
②成员名称不能相同
③C语言不允许嵌套定义相同类型的结构体变量,允许成员变量可以指向本类型
④结构体的有效范围和在源程序文件中定义的位置有关。
1.2 结构体类型声明和初始化
声明
声明方式①
struct student yuanyuan,wenwen;
声明方式②
struct student
{
char id[8];
char names[8];
int age;
char sex;
}yuanyuan,wenwen;//定义的同时进行声明
声明方式③
//可以这样用
struct class
{
... ... ...
struct //没有标识符,意味着无法在别处声明
{
char id[8];
char names[8];
int age;
char sex;
}yuanyuan, wenwen;
... ... ...
};
初始化
格式:
结构体类型名 变量名 = {成员1的初值,成员1的初值…}
初值列表中的初值顺序必须和结构成员顺序一致。
初始化也有三种方式,分别对应声明的三种方式。
注意:
若是部分初始化,则未初始化的为对应类型的零值。
1.3 结构体变量的引用
引用格式:
结构体类型变量.成员变量
代码演示:
#include<stdio.h>
struct student
{
char id[8];
char names[8];
int age;
char sex;
};
int main()
{
struct student yuanyuan = {
"1907020",
"wenwen",
18,
'W'
};
struct student wenwen = yuanyuan;
//对结构体变量的操作和对普通变量和数组的操作类似
printf("names = %s\n", wenwen.names);
printf("age = %d\n", wenwen.age);
printf("id = %s\n", wenwen.id);
printf("sex = %c\n", wenwen.sex);
/*运行结果:
names = wenwen
age = 18
id = 1907020
sex = W*/
return 0;
}
注:相同结构的类型可以赋值(有字符数组也可以)。
1.4 结构体数组
结构体数组是一组结构体数据集合,其定义和引用和基本数据类型的数组相同。
代码演示:
#include<stdio.h>
struct student
{
char id[8];
char names[8];
int age;
char sex;
};
int main()
{
//对应上面说的三种,这里也有三种。这里只说一种
struct student a[3] = {
{"1907020","wenwen",18,'W'},
{"1907020","wenwen",18,'W'},
{"1907020","wenwen",18,'W'},
};
for (int i = 0; i < 3; ++i)
{
printf("%s,%s,%d,%c\n", a[i].id, a[i].names, a[i].age, a[i].sex);
}
/*运算结果:
1907020, wenwen, 18, W
1907020, wenwen, 18, W
1907020, wenwen, 18, W
*/
return 0;
}
结构体数组的存储和,基本类型类似,但是得遵循内存对齐原则,下面将简单介绍一下。
代码演示:
#include<stdio.h>
struct student
{
char id[8]; //8B
char names[8]; //8B
char sex; //1B
int age; //4B
};
int main()
{
struct student a;
//按理论a的大小应为8+8+4+1=21
printf("a的大小为: %d\n", sizeof(a));
printf("id的首地址:%p\n", a.id);
printf("names的首地址:%p\n", a.names);
printf("sex的首地址:%p\n", &a.sex);
printf("age的首地址:%p\n", &a.age);
/*运行结果:
a的大小为: 24
id的首地址:0019FA54 //偶数
names的首地址:0019FA5C // C(十进制的12)偶数
sex的首地址:0019FA64 //偶数
age的首地址:0019FA68 //偶数
*/
}
}
内存对齐原则是指为了提高可移植性以及减少CPU读取内存的次数,系统在分配变量地址空间时,都是以2B(16位CPU)或4B(32位CPU)的整数倍为起始地址进行分配,即各变量的起始地址都对齐在内存空间的偶数地址上。
注意:虽然给1个字节的sex分配了4个字节的空间,但是,sex只可以用第一个字节,后面三个字节不能用。
结构体与函数
与基础类型和数组的参数传递类似,结构体类型作函数参数也遵循单向值传递原则。结构体数组也类似。当然,函数的返回值也可以是结构体类型。
代码演示:
#include<stdio.h>
struct student
{
char id[8];
char names[8];
int age;
char sex;
};
struct student fun(struct student x)
{
x.age = 999999;
return x;
}
int main()
{
struct student wenwen = {
"1907020",
"wenwen",
18,
'W'
};
struct student yuanyuan = fun(wenwen);
printf("%s,%s,%d,%c\n", wenwen.id, wenwen.names, wenwen.age, wenwen.sex);
printf("%s,%s,%d,%c\n", yuanyuan.id, yuanyuan.names, yuanyuan.age, yuanyuan.sex);
//按值传递并不改变原有数据
/*
运行结果:
1907020, wenwen, 18, W
1907020, wenwen, 999999, W
*/
return 0;
}
1.5 结构体类型指针
除了引用成员要使用->之外就没什么特别的地方.
代码演示:
#include<stdio.h>
struct student
{
char id[8];
char names[8];
int age;
char sex;
};
int main()
{
struct student a;
struct student *p=&a;
p->age = 18;
p->sex = 'W';
printf("%d,%c", p->age, p->sex);
/*运行结果:
18, W
*/
return 0;
}
2. 共用体
概述:共用体也是用户自定义得类型,共用体类型的定义,变量声明以及引用方式于结构体类似,不同的是共用体可以将不同类型的变量存放在同意内存区域内。
定义格式:
union 共用体标识符
{
成员变量列表;
}
共用体变量的赋值和引用同结构体
代码演示:
#include<stdio.h>
union variant
{
char a;
int b;
double c;
};
//所有变量共用一段内存,即当声明变量时,系统只为其分配8B的空间
int main()
{
union variant val; // 声明共用体变量
printf("val的大小为: %d\n", sizeof(val));
printf("val.a的大小为: %d\n", sizeof(val.a));
printf("val.b的大小为: %d\n", sizeof(val.b));
printf("val.c的大小为: %d\n", sizeof(val.c));
val.a = 'W'; //给共用体赋值,只使用了1B的空间,其余的空间空闲
printf("%c\n", val.a);
val.c = 12.15; //给共用体赋值,使用全部的空间,即8B
printf("%lf\n", val.c);
return 0;
/*
运行结果:
val的大小为: 8
val.a的大小为: 1
val.b的大小为: 4
val.c的大小为: 8
W
12.150000
*/
}
注:共同体分配的空间大小为:共同体中最大一个成员变量的大小,如果使用的是较小的变量,那么空间肯定会剩余
①这里的的大小指的是变量在内存中占字节的大小
②剩余的空间其他变量不能使用
3. 枚举类型
生活中的很多问题所描述的状态仅为有限个,如东南西北,上下左右,周一到日,一月到十二月等等。
如,我们可以定义定义一组符号常量来描述上下左右
//后面将详细讲解这个方式
#define UP 1
#define DOWN 2
#define LEFT 3
#define RIGNT 4
尽管这4个变量为同一类型,但是他们不能作为一个完整的逻辑整体。故C语言提供了枚举类型,为具有少量可列举状态的变量定义一种新的数据类型。枚举类型同样是用户自定义类型。
定义格式:
enum 枚举标识符
{
常量列表;
}
代码演示1:
#include<stdio.h>
int main()
{
enum text1{a,b,c,d,e}; //a,b,c,d,e叫枚举常量,
//此后不得其他的变量的名字不能和枚举常量一致
enum text2
{
f = 2,
g,
h = 1,
i,
j
};
printf("a=%d b=%d c=%d d=%d e=%d\n", a,b,c,d,e);
printf("f=%d g=%d h=%d i=%d j=%d\n", f,g,h,i,j);
// 运行结果:
// a=0 b=1 c=2 d=3 e=4
// f = 2 g = 3 h = 1 i = 2 j = 3
//故,如果不给枚举量值,他将从0开始,依次加1
// 如果给枚举值,则从这个枚举值开始依次加1
return 0;
}
代码演示2:
#include<stdio.h>
enum text { UP=1,DOWN=2,LEFT=3,RIGNT=4}; //定义枚举类型
int main()
{
enum text a;//声明一个enum text类型的变量
a = UP;
printf("%d ", a);
a = DOWN;
printf("%d ", a);
a = LEFT;
printf("%d ", a);
a = RIGNT;
printf("%d ", a);
a = 1999; //不会出错
printf("\n%d", a);
/*
运行结果:
1 2 3 4
1999
*/
return 0;
}