目录
一、结构体
1、初次见面
(1)举个栗子先认识一下
上图定义一个结构体4~10行,赋值时需要细化到结构体里的元素即boy1.num=007,不可以整体赋值,输出时也一样。
可以如上图只在结构体部分声明boy1和boy2(第10行),在后面(第12、13行)赋值,也可以如下图在结构体声明的同时赋值(第9行)
(2)以下是一个小练习,即输入几个名字和电话号码生成一个汇集好的电话簿
使用宏定义#defin NUM 3 意思是下方的所有NUM的值都代表着3,如果我们想改变这个数值,直接在表头改变即可,就不用在下方逐个改,方便实用
struct person man[NUM] 可以这样理解:有一个写法是【 int a;】代表定义一个a为整型数据,现在这个就是定义一个man[NUM]为struct person型数据
2、结构体数据输出的三种方式
这个故事告诉我们,这种情况下的结构体数据输出有三种方式,boy1.num、(*pstu).num、pstu->num输出结果是一样的
3、用结构体变量做函数参数
4、用指向结构体变量的指针做实参
5、加深理解
1、结构体变量可以直接用加号进行加法(错)
结构体之间直接加号进行运算是没有定义的
2、结构体做函数参数的效率不如直接传结构体的地址,在函数参数中用指针变量接收高效。(对)
当结构体很大的时候,以指针的方式传递函数参数的效率会明显的比传递值的方式更高效。
3、p->x这种写法访问的是p这个结构体的x成员元素(错)
使用->时,p必然是个指针变量,不可能是一个结构体
二、用typedef定义类型
1、基本内容
当我们需要使用结构体的时候,都会需要写一次struct关键字。c语言中提供了—种为某一已知类型添加别名的方式—— typedef
typedef 原类型 类型别名
例如,对于一个已经定义好的结构体模板,可以给其一个别名,使它可以像int 、float这些基础类型—样被方便地使用
typedef struct point Point;
在这里被设置别名的原类型为struct point ,类型别名为Point 。完成了设置别名后,如果再进行变量的声明时,就可以不写
struct point point1;
而直接写:
Point point1;
其实,也可以在定义结构体的时候,就同时在前面加上typedef,那样就可以把这两步二合一,得到
typedef struct point
{
float x;
float y;
}Point;
同样的,将结构体定义和起别名一次性完成后,也可以直接通过
Point point1;
的方式声明新的符合结构体类型定义的变量了。
不过,注意这并不是只能对结构体类型进行别名的设置。如果会大量的用整数型的数字表示尺寸,也可以使用typedef定义一个int类型的别名size
typedef int size;
定义了这—类型别名之后,在关于长度一些场合,就可以直接使用size声明变量了。举例来说,直接声明变量时就可以写:
size rule_length;
在定义一个描述盒子的结构体时,就可以写
typedef struct box
{
size width;
size height;
size length;
}Box;
另外请再注意一个风格习惯的问题。进行类型别名的定义时,被定义出的类型的首字母一般会使用大写。这是一个约定俗成的良好习惯。在实际的使用过程中,通过使用大写开头的这种做法,可以尽可能减小和C语言中的关键字和程序中定义的变量名、函数名发生冲突的可能性。
还有,typedef不可以用于给非结构体类型设置类型别名
练习一:
#include <stdio.h>
#include <math.h>
typedef struct point
{
float x;
float y;
} Vector;
Vector vector_add(Vector v1,Vector v2)
{
v1.x += v2.x;
v1.y += v2.y;
return v1;
}
int main()
{
Vector v1 = {2.4f , 2.5f};
Vector v2 = {3.7f , 4.4f};
Vector v_result;
v_result = vector_add(v1, v2);
printf("(%f, %f)\n",v_result.x , v_result.y);
return 0;
}
对比练习:
#include <stdio.h>
#include <math.h>
typedef struct point
{
float x;
float y;
} Vector;
Vector vector_add(Vector *v1, Vector *v2)
{
Vector v_result;
v_result.x = v1->x + v2->x;
v_result.y = v1->y + v2->y;
return v_result;
}
int main()
{
Vector v1 = { 2.4f, 2.5f };
Vector v2 = { 3.7f, 4.4f };
Vector v_result;
v_result = vector_add(&v1, &v2);
printf("(%f, %f)\n", v_result.x, v_result.y);
return 0;
}
2、三种情况
(1)声明NUM为整型数组类型
(2)声明P为字符指针类型
(3)声明P为指向函数的指针类型,该函数返回整型值
三、共用体
1、共用体的内存
定义共用体的关键字是union,一个共用体可以包括多个合法的类型描述成员,例如:
union register
{
struct
{
unsigned char byte1;
unsigned char byte2;
unsigned char byte3;
unsigned char byte4;
}bytes; //占4个字节
unsigned int number; //占4个字节
}; //选最大的,所以该共用体占内存空间为4个字节
这个共用体所占用的内存空间是被公用的,可以通过struct类型的bytes和unsigned int类型的number两种不同的类型描述成员进行访问。
无论通过哪一种描述成员访问这一共用体,访问的都会是同一块内存空间。
对于这样的一个共用体,它在内存中将会被描述成下面这样:
如果我们用这个union register类型声明一个变量reg。我们将可以通过reg.bytes按字节访问或者通过reg.number整体访问两种不同的方式获得或修改同一片内存。
2、举栗子
共用体在涉及到直接操作内存的嵌入式编程、需要极度节省空间的通信协议设计中都会有它的优势。在之前的内容中,我们看到了一种通过共用体实现的可以整体修改,也可以按字节修改的类型。类似的,我们也可以定义一个既可以按位访问,也可以按字节访问的类型:
union
{
struct
{
unsigned char b1:1;
unsigned char b2:1;
unsigned char b3:1;
unsigned char b4:1;
unsigned char reserved:4;
}bits;
unsigned char byte;
}
在这里有一个冒号是用来定义变量使用内存的“位长”的。这里:1、:4表示冒号前的变量只需要占1个和4个二进制位,而不按照char类型默认的字节数占用内存。这样,用这个类型生成的变量就可以被我们就按字节或者按位进行访问和使用了(这个概念被称为位域(bit field),在其它场景下也可以被使用)。
再举一个被设计出来专门储存IP地址的共用体结构。使用了它的变量,既可以存储IPv4的IP地址,也可以存储IPv6的IP地址,这些地址既可以作为一个整体被操作,也可以分几个部分分别操作。
3、应用
使用共用体实现IP转整数的功能
#include <stdio.h>
union IP
{
struct {
unsigned char a1;
unsigned char a2;
unsigned char a3;
unsigned char a4;
} ip;
unsigned int num;
};
int main()
{
union IP p;
char str[100] = {0};
int arr[4];
while (~scanf("%s", str))
{
sscanf(str,"%d.%d.%d.%d",arr,arr1,att+2,arr+3);
p.ip.a1 = arr[3];
p.ip.a2 = arr[2];
p.ip.a3 = arr[1];
p.ip.a4 = arr[0];
printf("%u\n",p.num);
}
preturn 0;
}
运行输出:
如果将以上改为
p.ip.a1 = arr[0];
p.ip.a2 = arr[1];
p.ip.a3 = arr[2];
p.ip.a4 = arr[3];
将输出:
会发现转为整数的值相差很大,这是由于大端机和小端机的因素造成的
小端机的数字低在低地址
168.192.0.1存储时是10168192
168.192.0.2存储时是20168192
所以相差甚远
大端机的数字低在高地址
其余在这里不做更多的赘述,想检验自己的电脑是什么端可以用以下代码检测一下
int is_little()
{
static int num = 1;
return ((char*)(&num))[0];
}
printf("%d\n",is_little());
4、加深理解
1、共用体类型变量的成员在内存中地址可能不同。(错)
共用体类型变量的成员在内存中地址相同。
2、共用体类型中不可以含有共用体类型成员。(错)
并不是,和其他类型—样,共用体类型也可以被用于共用体类型定义。
3、共用体类型不可以设置类型别名。(错)
类型别名在共用体类型上的使用方式与在其他类型上相同,没有区别。
4、结构体类型中不可以含有共用体类型成员。(错)
并不是,和其他类型一样,共用体类型也可以被用于结构体类型定义。
5、共用体类型的变量大小取决于其中最大的一个成员类型。(对)
如果共用体的类型大小小于共用体中最大的一个成员类型,那么当这个共用体用于存这个类型时就会出现内存错误了。
四、枚举
1、基本认知
在C语言中,提供了一种数据类型,叫枚举(enumeration)。它由一系列的整数成员组成,表示了这—数据类型的变量可以取的所有可能值;但是,这些值都不直接以字面量形式存在,每一个值都被单独给予了一个名字。例如:
当一个枚举类型被定义以后,可以直接用这一类型声明变量。如:
enum week
{
SUNDAY,
MONDAY,
TUESDAY,
wEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY
};
enum week exam_date;
声明了一个enum week类型的变量exam_date,它只能取定义过的枚举类型中的成员名作为值,如exam_date =TUESDAY ;
与struct、union以及其它类型一样,也可以给枚举类型通过typedef设置类型别名。
#include <stdio.h>
typedef enum week
{
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY
} Week;
int main()
{
Week meeting_date;
meeting_date = FRIDAY;
printf("%d\n",meeting_date);
return 0;
}
运行结果为5
2、练习:排班
3、加深理解
1、枚举类型中可以有多个成员被编号。(对)
2、枚举类型中可以包含结构体类型成员。(错)
枚举类型中成员类型并不能由结构体和共用体变量。
3、枚举类型中成员的编号只能是整数。(对)
枚举类型由整数成员组成,编号不可能是非整数。
4、枚举类型不可以设置类型别名。(错)
枚举类型和其他类型—样,都可以设置类型别名。