户自定义数据类型
9.1 结构体
9.1.1结构体的定义
在实际的处理对象中,有许多信息是由多个不同类型的数据组俣在一起进行描述,而且这些不同类型的数据是互相联系组成了一个有机的整体.此时,就要用到一种的构造类数据 ---结构体(structure),简称结构体.
结构体的使用为处理复杂的数据结构(如动态数据结构等)提供了有效的手段,而且,它为函数间传递不同类型的数据提供了方便.
结构体和数组一样,也是一种构造型数据类型,是用户自定义的新数据类型,在结构体中可以包含若干个不同数据类型和不同意义的数据项(当然也可以相同),从而使些数据项合起来反映某一个信息.
c
struct 结构体名
{
数据类型 成员名1;
数据类型 成员名2;
...
数据类型 成员名n;
};
在花括号中的内容也称为"成员列表"或"域表"
其中,每个成员名命名规则与变量名相同;
数据类型可以是基本变量类型和数据类型,或者是一人结构类型;
用分号 ";"作为结束符,整人结构的定义也用分号作为结束符.
c
struct worker
{
long number;
char name[20];
char gender;
int age;
float salary;
char address[80]
};
int age = 10;
结构体类型中的成员名可以与程序中的变量名相同,二者并不代表同一对旬,编译程序可以自动对它们进行区分.
由于结构体的成员的数据类于是可以是任何类型,可能是基本变量类型,数据类型,结构体类型,联合类型或枚举类型,因此,结构体的定义形式可能非常复杂
c
/* Struture describing an Internet (Ip)socket address*/
#define __SOCK_SIZE__ 16 /*sizeof(struct sockaddr)*/
struct sockaddr in{
sa_family_t sin_family; /* Address family */
__be26 sin_port; /*Internet address*/
struct in_addr sin_addr; /*Internet address*/
/*Pad to size of 'struct sockaddr'*/
unsigned char __pad[__SOCK_SIZE__-sizeof(short int)-sizeof(unsigned short int)-sizeof(struct in_addr)];
};
上面是linux 内核中,网络地址结构体 struct sockaddr_in 的定义,可以看到,结构体中的成员sin_addr依然是结构体.
(2) 结构体的成员含结构体类型和联合体类型
c
struct icmp
{
u_int8_t icmp_type; /*type of message,see below*/
u_int8_t icmp_code; /*type sub code*/
u_int16_t icmp_cksum;/*ones complement checksum of struct*/
union
{
u_char ih-pptr; /*ICMP_PARAMPROB*/
struct in_addr ih_gwaddr; /*gateway address*/
struct ih_idseq
{
u_int16_t icd_id;
u_int16_t icd_seq;
}ih_idseq;
u_int32_t ih_void;
/*ICMP_UNREACH_NEEDFRAG --Path MTU Discovery (RFC1191)*/
struct ih_pmtu
{
u_int16_t ipm_void;
u_int16_t ipm_nextmtu;
}ih_pmtu;
struct ih_rtradv
{
u_int8_t irt_num_addrs;
u_int8_t irt_wpa;
u_int16_t irt_lifetime;
}ih_rtradv;
}icmp_hun;;
#define icmp_pptr icmp_hun.ih_pptr
#define icmp_gwaddr icmp_hun.ih_gwaddr
#define icmp_id icmp_hun.ih_idseq.icd_id
#define icmp_seq icmp_hun.ih_idseq.icd_seq
#define icmp_void icmp_hun.ih_void
#define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void
#define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu
#define icmp_num_addrs icmp_hun.ih_rtradv.irt_num_addrs
#define icmp_wpa icmp_hun.ih_rtradv.irt_wpa
#define icmp_lifetime icmp_hun.ih_rtradv.irt_lifetime
union
{
struct
{
u_int32_t its_otime;
u_int32_t its_rtime;
u_int32_t its_time;
}id_ts;
struct
{
struct ip idi_ip;
/* options and then 64 bits of data */
}id_ip;
struct icmp_ra_addr id_radv;
u_int32_t id_mask;
u_int8_t id_data[1];
}icmp_dun;
#define icmp_otime icmp_dun.id_ts.its_otime
#define icmp_rtime icmp_dun.id_ts.its_rtime
#define icmp_ttime icmp_dun.id_ts.its_ttime
#define icmp_ip icmp_dun.id_ip.idi_ip
#define icmp_radv icmp_dun.id_radv
#define icmp_mask icmp_dun.id_mask
#define icmp_data icmp_dun.id_data
};
上面的结构体是linux内核中关于icmp协议的结构体的定义.读者可以看到,该结构体的定义特别复杂,其成员有基本类型,结构体类型,联合体类型及宏定义.
结构体类型的特点
- 结构体类型是开发者自行构造.
- 它由若干不同的基本数据类型的数据构成.
- 它属于C语言的和种数据类型,与整型,浮点型相当.因此,定义它时不分配空间,只有用它定义变量时才分配空间.
9.1.2 结构体变量的声明,使用及初始化
- 结构体变量的声明
在定义结构体类型后,就可以声明结构体类型的变量.
1先定义结构体类型,再定义变量名.
c
struct struct_name
{
类型 成员名;
类型 成员名;
.....
};
struct struct_name struct_val_name;
例:
c
struct employee
{
char name[8];
int age;
char gender;
char address[20];
float salary;
};
struct employee e2,e2;
struct employee是代表类型名,不能分开写
2在定义类型同时,定义变量
c
strct employee
{
char name[8];
int age;
char gender;
char address[20];
float salary;
} e2,e2;
3直接定义结构体变量
c
struct
{
char name[8];
int age;
char gender;
char address[20];
float salary;
}e2,e2;
sizeof(运算量)
c
sizeof(struct employee);
sizeof(e2);
- 结构体变量的使用
结构体变量是不同数据类型的若干数据的集合体,在程序中使用结构体变量时,一般情况下不能把它作为一个整体参数据处理,而参加各种运算和操作的是结构体变量的各个成员项数据.
结构体变量的成员用以下一般形式表示:
结构体变量名.成员名
例如:
e2.name;e2.age;e2.gender;e2.address;e2.salary
在定义了结构本变量后,就可以用不同的赋值方法对结构体变量的每个成员赋值
c
strcpy(e2.name,"Zhang San");
e2.age = 26;
strcpy(e2.address,"Beijing city!");
e2.salary = 6000.72;
(1) 不能将一个结构体类型变量作为一个整体中以引用,而只能对结构体类型变量中的各个成员分别引有.
c
#include <stdio.h>
#include <string.h>
#define N 64
struct employee
{
char name[N];
int age;
char gender;
char address[N];
float salary;
};
int main()
{
struct employee e2;
memset(&e2,0,sizeof(e2));
printf("name:");
scanf("%s",e2.name);
printf("age:");
scanf("%d",&e2.age);
getchar();
printf("gender:");
scanf("%c",&e2.gender);
printf("address:");
scanf("%s",e2.address);
printf("salary:");
scanf("%f",&e2.salary);
printf("\ninformation:%s %d %c %s %f\n",e2.name,e2.age,e2.gender,e2.address,e2.salary);
return 0
}
(2) 如果成员本身又属一个结构体类型,则要用若干个成员运算符,一级一级地找到最低的一级成员只能对最低级的成员进行赋值或存取以及运算.
例:
c
#include <stdio.h>
#include <string.h>
#define N 64
struct employee
{
char name[N];
struct
{
int year;
int month;
int day;
}birthday;
char gender;
char address[N];
float salary;
};
int main()
{
struct employee e2;
memset(&e2,0,sizeof(e2));
printf("name");
scanf("%s",e2.name);
printf("year:");
scanf("%d",&e2.birthday.year);
printf("month:");
scanf("%d",&e2.birthday.month);
printf("day:");
scanf("%d",&e2.birthday.month);
getchar();
printf("gendear:");
scanf("%c",&e2.gender);
printf("address:");
scanf("%s",e2.address);
printf("salary:");
scanf("%f",&e2.salary);
printf("\ninformation:%s\n%d-%d-%d\n%c\n%s\n%f\n",e2.name,e2.birthday.year,e2.birthday.month,e2.birthday.day,e2.gender,e2.address,e2.salary);
return 0;
}
(3)对成员变量可以像普通变量一样进行各种运逄(根据基类型决定可以进行的运算)
c
e2.age = e2.age;
sum=e2.age+e2.age;
e2.age++;
(4) 结构体类型变量可以相互赋值.
e.g:
c
#include<stdio.h>
#include<string.h>
#define N 64
struct employee
{
char name[N];
struct
{
int year;
int month;
int day;
}birthday;
char gender;
char address[N];
float salary;
};
int main()
{
struct employee e1,e2;
memset(&e1,0,sizeof(e1));
strcpy(e1.name,"zhangsan");
e1.birthday.year = 1989;
e1.birthday.month = 11;
e1.birthday.day = 10;
e1.gender = 'w';
strcpy(e1.address,"Suzhou");
e1.salary = 6100.9;
e2 = e1;
printf("\ninformation e1:%s\n%d-%d-%d\n%c\n%s\n%f\n",e1.name,e1.birthday.year,e1.birthday.month,e1.birthday.day,e1.gender,e1.address,e1.salary);
printf("\ninformation e2:%s\n%d-%d-%d\n%c\n%s\n%f\n",e2.name,e2.birthday.year,e2.birthday.month,e2.birthday.day,e2.gender,e2.address,e2.salary);
if(memcmp(&e1,&e2,sizeof(struct employee)) == 0 )
{
printf("e1 = e2 \n");
}
else
{
printf("e1!=e2\n");
}
return 0;
}
- 结构体变量的初始化
与其他类型变量一样,也可以给结构体的每个成员赋初值,这称为结构体的初始化.一种是定义结构体变量时进行初始化,语法格式如下
struct 结构体名 变量名={初始数据表}
另一种是定义结构本类型时进行结构变量的初始化.
c
struct 结构体名
{
类型 成员名;
类型 成员名:
...
}变量名 = {初始数据表}
c
struct employee e1 = {"Wan Jun",20,'m',"Suzhou Road No.100",5600};
strcpy(e1.name,"Wan Jun");
e1.age = 20;
e1.gender = 'm';
strcpy(e1.address,"Suzhou Road No.100");
e1.salary = 5600;
e.g:
c
#include <stdio.h>
#include <string.h>
#define N 64;
struct employee
{
char name[N];
struct
{
int year;
int month;
int day;
}birthday;
char gender;
char address[N];
float salary;
}e1 = {"zhangsan",{1980,9,4},'w',"Shanghai",3400};
int main()
{
struct employee e2 = {"lisi",{1900,12,4},'w',"Guangzhou",8400};
printf("\ninformation e1:%s\n%d-%d-%d\n%c\n%s\n%f\n",e1.name,e1.birthday.year,e1.birthday.month,e1.birthday.day,e1.gender,e1.address,e1.salary);
printf("\ninformation e2:%s\n%d-%d-%d\n%c\n%s\n%f\n",e2.name,e2.birthday.year,e2.birthday.month,e2.birthday.day,e2.gender,e2.address,e2.salary);
return 0;
}
9.1.3 结构体数组
- 结构体数组的定义
具有相同结构类型的结构体变量也可组成数组,称它们为结构体数组.结构体数组的每一个数组无素都是结构体类型的数据,它们都分别包括积压个成员(分量)项
定义结构体数组的方法和定义结构体变量的方法相仿,只需说明其为数组即可.可以采用以下方法.
(1) 先定义结构体类型,再用它定义结构体数组,
c
#define N 64
struct employee
{
char name[N];
int age;
char gender;
char address[N];
float salary;
};
struct employee[10];
(2)定义结构体类型的同时,定义结构体数组,定义形如下:
c
#define N 64
struct employee
{
char name[N];
int age;
char gender;
char address[N];
float salary,
}e[10];
(3)直接定义结构体数组,定义形式如下:
c
#define 64 N;
struct
{
char name[N];
int age;
char gender;
char address[N];
float salary;
}e[10];
-
结构体数组的初始化.
结构体数组在定义的同时也可以进行初始化,并且与结构体变量的初始化规定义相同.
-
结构体数组的使用
一个结构体数组的元素相当于一个结构体变量,因此前面介绍的有关结构体变量的规则也适用于结构体数组元素.以上面定义义的结构体数组e[2]为例,说明对构体数组的引用.
(1) 引用某一元素中的成员.
若要引用数组第二个元素的name成员,则可写为:e[1].name.
(2) 可以将一个结构体数组元素值赋给同一结构体类型的数组中的另一个元素,或赋给同一类型的变量如
struct employee e[2]现定义义了一个结构体类型的数组,它有2个元素,又定义了一个结构体类型变量e1,则下面的赋值是合法的.
c
e1 = e[0];
e[0] = e[1];
e[1] = e1;
(3) 不能把结构体数组元素作为一个整体直接进行输入(scanf)输出printf,只能以单个成员为对象进行输入输出.
e.g:
c
#include <stdio.h>
#include <string.h>
#define N 64
struct employee
{
char name[N];
struct
{
int year;
int month;
int day;
}birthday;
char gender;
char address[N];
float salary;
}e1[2] = {{"zhangsan",{1980,9,4},'w',"shanghai",3400},{"lisi",{1992,10,24},'w',"Hebei",3400}};
int main()
{
struct employee e2[2] = {{"wangwu",{1986,2,24},'w',"Hubei",6400},"Lucy",{1986,8,14},'w',"Henan",3421};
int i;
for(i=0;i<sizeof(e1)/sizeof(struct employee);i++)
{
printf("information:%s\n%d-%d-%d\n%c\n%s\n%f\n",e1[i].name,e1[i].birthday.year,e1[i].birthday.month,e1[i].birthday.day,e1[i].gender,e1[i].address,e1[i].salary);
printf("\n");
}
for(i=0;i<sizeof(e2)/sizeof(struct employee);i++)
{
printf("information:%s\n%d-%d-%d\n%c\n%s\n%f\n",e2[i].name,e2[i].birthday.year,e2[i].birthday.month,e2[i].birthday.day,e2[i].gender,e2[i].address,e2[i].salary);
printf("\n");
}
return 0;
}
9.1.4 结构体指针
可以设定一个批针变量用来指向一个结构体变量.此时该指针变量的值是结构体变量的起始地址,该指针称为结构指针.
结构体指针与前面介绍的各种指针变量在特性变量在特性和方法上是相同的.与前述相同,在程序中结构体指针也是通过访问目标运算* 访问它的对象
struct 结构名 *结构指针名
struct employee *p
c
#include <stdio.h>
#include <string.h>
#define N 64
struct employee
{
char name[N];
int age;
char gender;
char address[N];
float salary;
};
void input(struct employee *p)
{
printf("name:");
scanf("%s",p->name);
printf("age:");
scanf("%d",&p->age);
getchar();
printf("gender:");
scanf("%c",&p->gender);
printf("address:");
scanf("%s",p->address);
printf("salary:");
scanf("%f",&p->salary);
}
void output(struct employee *p)
{
printf("information:%s\n%d\n%c\n%s\n%f\n",p->name,p->age,p->gender,p->address,p->salary);
}
int main()
{
struct employee e1;
memset(&e1,0,sizeof(e1));
input(&e1);
output(&e1);
return 0;
}
9.2 位域
9.2.1 位域的定义
位域 是把一个字节中的二进制位划分为几个不同的区域,并说明每个区域的位数.每个域有一个域名,允许在程序中按域名进行操作.这样就可以氢几个不肉中刺的对象用一定节的二时制全域来表示.
c
struct data
{
unsigned int a:2;
unsigned int b:3;
unsigned int c:3;
};
linux源代码(include/linux/tcp.h)
c
struct tcphdr
{
__be16 source;
__be16 dest;
__be32 seq;
__be32 ack_seq;
#if define(__LITTLE_ENDIAN_BITFIELD)
__u16 res1:4;
doff:4,
fin:1,
syn:1,
rst:1,
psh:1,
ack:1,
urg:1,
ece:1,
cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
__u16 doff:4,
res1:4,
cwr:1,
ece:1,
urg:1,
ack:1,
psh:1,
rst:1,
syn:1,
rst:1,
syn:1,
fin:1;
#else
#error "Adjust your<asm/byteorder.h> defines"
#endif
__be16 window;
__sum16 check;
__be16 urg_ptr;
};
(1) 各位域必须存储在同一个字节中,不能跨两个字节
c
struct data
{
unsigned int a:2;
unsigned int b:4;
unsigned int c:3;
};
在这个例子中,域名a,b占用6个位,域c占3个位,这样1个完整的字节就没法存储a,b和c,c就得跨两个字节,就是不允许的.当一个字节所剩空间不够存放另一个位域时,应从下一单元起存放该位域.也可以有意使某位域从下一单元开始.可以使用下面的方式,来解决这个问题
c
struct data
{
unsigned int a:2;
unsigned int b:4;
unsigned int:0;// /*空域*/
unsigned int c:3;
}
这样修改后,a占第一个字节的2位,b占接下的4位,剩下的2位用0填充,表示不用,c会从第二个字节开始存储.
(2) 允许位域无域名.
可以用无名的域来填充,但不能使用.
9.2.2 位域变量的说明
(1)先定义位域类型,再声明变量
c
struct data
{
unsigned int a:2;
unsigned int b:3;
unsigned int c:3;
};
struct data t1,t2;
(2) 定义位域类型的同时,声明变量
c
struct data
{
unsigned int a:2;
unsigned int b:3;
unsigned int c:3;
}t1,t2;
(3)直接定义位域类型的变量.
c
struct
{
unsigned int a:2;
unsigned int b:3;
unsigned int c:3;
}t1,t2;
9.2.3 位域的使用
位域的使用和结构成员的使用相同,其一般形式为
位域变量名.位域名
c
#include <stdio.h>
struct data
{
unsigned int a:2;
unsigned int b:4;
unsigned int:0;
unsigned int c:3;
}t;
int main()
{
struct data *p
t.a = 3;
t.b = 5;
t.c = 6;
printf("t.a=%d\tt.b=%d\tt.c=%d\n",t.a,t.b,t.c);
p = &t;
p->a = 2;
p->b &= 0;
p->c |=1;
printf("t.a=%d\tt.b=%d\tt.c=%d\n",t.a,t.b,t.c);
return 0;
}
由于位域中的各个域是以二进制位为单位,因此,大部分的位域程序,都有位运算.
9.3 共用体
在C语言中,不同数据类型的数据可以使用的存储区域,这种数据构造类型称为共用体,简称共用,又称联合体.共用体在定义\说明和使用形式与结构体相似,两者的本质上的不同在于使用内存方式上.
定义一个共用体类型的一般形式为
c
union gy
{
int i;
char c;
float f;
}
这里定义了一个共用体类型union gy,它由3个成员组成,这3个成员在内存中使用共同的存储空间,由于共用体中各成员的数据长度往往不同,所以共用体变量在存储时总是按其成员中数据长度最大的成员占用内存空间.在这一点上共用体与结构体上不同,结构体类型变量在存储时总是按各成员的数据长度之和占用内空间.因此,当多个数据需要共享内存或者多个数据每次只取其一是,可以用共用体.
c
struct gy
{
int i;
char c;
float f;
}
则结构体类型struct gy 变量占用的内存大小为4+1+4=9个字节(不考虑字节点齐).下面通过一个例子
c
#include <stdio.h>
union gy
{
char a;
short b;
int c;
};
int main()
{
union gy t;
t.c = 0x12345678;
printf("sizeof(union gy)=%d\n",sizeof(union gy));
printf("%p %p %p\n",&t.a,&t.b,&t.c);
printf("%#x %#x %#x\n",t.a,t.b,t.c);
return 0;
}
c
union semun
{
int val; /*Value for SETVAL*/
struct semid_ds *buf; /*Buffer for IPC_STAT,IPC_SET*/
unsigned short *array; /*Array for GETALL,SETALL*/
struct seminfo *__buf; /*Buffer for IPC_INFO(linux-specific)*/
}
联合体中的成员共内存,是因为特定情况下,往往只用到其中的一个成员,上面提到的联合体,当cmd的值是SETVAL时,使用联合体中的buf成员等
共用体也可以和位域结合起来使用.
c
#include <stdio.h>
union A
{
struct
{
unsigned int a:2;
unsigned int b:4;
unsigned int c:2;
}t;
char m;
}data;
int main()
{
data.t.a = 2;
data.t.b = 5;
data.t.c = 1;
printf("%d\n",data.m);
return 0;
}
在该程序中,共用体的一个成员是位域类型,含有3个域,占1个字节,a是最低位的2位,b是中间的4位,c是最高2位,则整个字节里存的二进制数01010110,转换成十进制,是86,char型的共用体成员和位域黄用内存,因此执行结果86.
9.4 枚举
在c语言中还有一种构造类型,即枚举.在实际问题中,有些变量只有几种可能的取值.例:一周七天.在枚举定义中,会将变量的值一一列出来,当然,枚举类型的变量的值也就只限于列举出来的值的范围内.
9.4.1枚举类型的定义
枚举类型的定义形式
enum 枚举名{枚举成员列表};
在枚举成员列表中列出所有可能的取值,以分号结尾.注意和结构体,联合体类似
c
enum TimeOfDay
{
morning,
afternoon,
evening
};
枚举成员是该枚举类型的命名常数,任意两个枚举成员不能具有相同名称,每个枚举成员均具有相关联的常数值,此值的类型就是int型.每个枚举成员的常数值必须在该枚举的基础的范围之内.
c
enum TimeOfDay
{
morning = 22.2,
afternoon = "hello",
evening
};
产生编译错误,因为22.2,hello不是整型.
在枚举类型中,声明的第一个枚举成员的默认值为零.
以后的枚举员值是将当前一个枚举成员(按照文本顺序)的值加1得到的.这样增加后的值必须在整型可表示的值的范围内否则,会出现编译时错误.
也可以在定义枚举类型时,为枚举成员显示赋值,允许多个枚举成员有相同的值.没有显示赋值的枚举成员的值,总是前一个枚举成员的值+1.
c
enum TimeOfDay
{
morning = 1,
afternoon,
evening = 1
};
morning 的值为1,afternoon的值为2,evening的值为1.注意:以上给定的默认值都不能超过int型的范围.
9.4.2 枚举变量的声明和使用
- 枚举变量的声明
一
c
enum TimeOfDay
{
mornig,
afternoon,
evening
};
enum TimeOfDay a,b;
二
c
enum TimeOfDay
{
morning,
afternoon,
evening
}a,b;
三
c
enum
{
morning,
afternoon,
evening
}a,b;
- 枚举变量的使用
枚举成员都是常量,因此,一旦定义了枚举类型,在程序中,就不能用赋值语句再对它赋值
c
#include<stdio.h>
enum TimeOfDay
{
morning = 2;
afternoon,
evening
}a;
int main()
{
enum TimeOfDay a,b,c;
a = morning;
b = afternoon;
c = evening;
printf("a=%d b=%d c=%d\n",a,b,c);
c = 10;
printf("a=%d b=%d c=%d\n",a,b,c);
return 0;
}
c
#include <stdio.h>
enum TimeOfDay
{
morning,
afternoon,
evening
};
int main()
{
int i,j;
enum TimeOfDay a[10];
j = morning;
for(i=0;i<10;i++)
{
a[i] = j;
j++;
if(j>evening)
{
j = morning;
}
}
for(i = 0;i<10;i++)
{
switch(a[i])
{
case morning: printf("%d morning\n",a[i]);break;
case afternoon: printf("%d afternoon\n",a[i]);break;
case evening:printf("%d evening\n",a[i]);break;
default:break;
}
}
return 0;
}