自定义类型----结构体、共用体与枚举
一、结构体
引入:
数组有两个重要的特性。首先,数组中所有元素具有相同的类型;其次,为了选择数组元素需要指明元素的位置(作为整数下标)。
结构体中的元素可能具有不同的类型。而且,每个结构成员都有名字,所以为了选择特定的结构成员需要指明结构成员的名字而不是它的位置。
目的:描述对象。
<1>结构体变量的定义及初始化
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
struct Point p3 = { 1,2 };//定义结构体变量p3的同时赋初值
数组可整体初始化,不允许整体赋值,结构体同理。
<2>结构体内存对齐
目的:解决效率问题,用牺牲空间来提高时间效率。
原则:
1>第一个成员在与结构体变量偏移量为0的地址处;
2>其他成员变量对齐到某个数字(对齐数)的整数倍的地址处。
对齐数:默认为自身大小。
3>结构体总大小为最大对齐数的整数倍,其中每个成员变量都有一个对齐数。
4>如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体整体的大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
示例1:
struct S1
{
char c1; //0
int i; //4,5,6,7
char c2; //8
};
printf("%d\n", sizeof(struct S1));
运行结果:
示例2:
struct S2
{
char c1; //0
char c2; //1
int i; //4
};
printf("%d\n", sizeof(struct S2));
运行结果:
示例3:
struct S3
{
double d; //0,1,2,3,4,5,6,7
char c; //8
int i; //12
};
printf("%d\n", sizeof(struct S3));
运行结果:
示例4:
struct S3
{
double d; //8,9,10,11,12,13,14,15
char c; //16
int i; //20,21,22,23
};
struct S4
{
char c1; //0
struct S3 s3;
double d; //24,25,26,27,28,29,30,31
};
printf("%d\n", sizeof(struct S4));
运行结果:
综合练习:
struct B
{
char a;
double* b;
short c; //12,其中4为最大对齐数
};
struct A
{
char a; //0
int b; //4,5,6,7
char c[3]; //8,9,10
double* x; //12,13,14,15
struct B object; 16+12=28,最大对齐数为4
char d; //28
char* e[3]; //32~44,最大对齐数为4,一个对齐,其余3个也对齐
};
printf("%d\n", sizeof(struct A));
运行结果:
<3>修改默认对齐数
#include<stdio.h>
#include<windows.h>
#pragma pack(1)
int main()
{
struct S1
{
char c1; //0
int i; //4
char c2; //5
}; //0~5为6
printf("%d\n", sizeof(struct S1));
system("pause");
return 0;
}
运行结果:
注:
#pragma pack(2^n)其中,()里面为对齐数,可以理解为自身的大小,将自身的大小与()里面数字进行比较,采用小的对齐数。
而且,()里面数字仅可为2^n,如:1,4,8,16……
<4>结构体传参
#include<stdio.h>
#include<windows.h>
struct A
{
char x[1024 * 1024 ];
int a;
char b;
double c;
};
void fun(struct A obj)
{
}
int main()
{
struct A x; //struct A* x;
int i = 0;
for (; i < 50000; i++)
{
fun(x); //fun(&x)
}
system("pause");
return 0;
}
注:
使用注释比较好。
函数传参时,参数是需要压栈的,会有时间和空间上的开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。
只有数组传参会发生降维,结构体传参不发生降维,所以结构体传参除非特殊需要,一律传指针。
<5>位段
本质:压缩存储,并不考虑效率。
位段的成员必须是int、unsigned int、signed int或char。位段的成员名后面有一个冒号和一个数字。
比如:(其中,A是位段类型)
#include<stdio.h>
#include<windows.h>
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
printf("%d\n", sizeof(struct A));
system("pause");
return 0;
}
运行结果:
位段的内存分配;
1>位段的成员可以是int、unsigned int、signed int或char。
2>位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的。
3>位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
位段的跨平台问题:
1>int位段被当成有符号数还是无符号数是不确定的。
2>位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32)。
3>位段中的成员从左向右分配,还是从右往左分配标准尚未定义。
4>当一个结构体中包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
:::cpu和内存,内存和硬盘间速度差距为数量级1000倍以上。
二、枚举
引入:枚举顾名思义就是一一列举。不考虑内存对齐。实为整型,占四个字节。
现实生活中:
性别有男、女和保密,可一一列举;
月份有12个月,也可一一列举。
1>枚举的定义:
typedef enum Color
{
BLUE,
GREEN,
WHITE,
BLACK,
PINK,
RED,
YELLOW
}Color_t;
或者:
enum Color
{
BLUE,
GREEN,
WHITE,
BLACK,
PINK,
RED,
YELLOW
};
2>枚举的优点
我们可以用#define定义常量,为什么要使用枚举?枚举有如下优点:
1.增加代码的可读性和可维护性;
2.和#define定义的标识符比较枚举有类型检查,更加严谨;
3.防止了命名污染;
4.便于调试;
5.使用方便,一次可以定义多个常量。
3>枚举的特点
特点一:
#include<stdio.h>
#include<windows.h>
typedef enum Color
{
BLUE,
GREEN,
WHITE,
BLACK,
PINK,
RED,
YELLOW
}Color_t;
int main()
{
Color_t c = BLUE;
c = YELLOW;
printf("%d\n", BLUE);
printf("%d\n", GREEN);
printf("%d\n", WHITE);
printf("%d\n", BLACK);
printf("%d\n", PINK);
printf("%d\n", RED);
printf("%d\n", YELLOW);
system("pause");
return 0;
}
运行结果:
特点二:
#include<stdio.h>
#include<windows.h>
typedef enum Color
{
BLUE,
GREEN,
WHITE,
BLACK,
PINK,
RED,
YELLOW
}Color_t;
int main()
{
Color_t c = BLUE;
c = YELLOW;
c = 100;
printf("%d\n", BLUE);
printf("%d\n", GREEN);
printf("%d\n", WHITE);
printf("%d\n", BLACK);
printf("%d\n", PINK);
printf("%d\n", RED);
printf("%d\n", YELLOW);
printf("%d\n", c);
system("pause");
return 0;
}
说明:
c是变量,但是给c的赋值必须是常量。
运行结果:
特点三:
#include<stdio.h>
#include<windows.h>
typedef enum Color
{
BLUE=5,
GREEN,
WHITE,
BLACK,
PINK,
RED=88,
YELLOW
}Color_t;
int main()
{
Color_t c = BLUE;
c = YELLOW;
printf("%d\n", BLUE);
printf("%d\n", GREEN);
printf("%d\n", WHITE);
printf("%d\n", BLACK);
printf("%d\n", PINK);
printf("%d\n", RED);
printf("%d\n", YELLOW);
system("pause");
return 0;
}
运行结果:
三、联合
引入:联合也是一种特殊的自定义类型,这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以联合也叫共用体)。联合体内所有成员共用一块空间。
1>联合类型的定义
#include<stdio.h>
#include<windows.h>
union Un
{
char c;
int i;
};
int main()
{
//联合体变量的定义
union Un un;
//计算联合变量的大小
printf("%d\n", sizeof(un));
system("pause");
return 0;
}
运行结果:
2>联合的特点
联合的成员是共用同一块内存空间的,这样一个联合体变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。联合体内所有成员都是联合体内第一成员。
<1>区分大小端:
#include<stdio.h>
#include<windows.h>
union Un
{
char c;
int i;
};
int main()
{
union Un un;
un.i = 1; //0x 00 00 00 01
printf("%d\n", un.c); //此计算机是小端,低权值位放于低地址处,为01 00 00 00.
system("pause");
return 0;
}
运行结果:
<2>
#include<stdio.h>
#include<windows.h>
union Un
{
char c;
int i;
};
int main()
{
union Un un;
//下面的输出结果是一样的吗
printf("%d\n", &(un.i));
printf("%d\n", &(un.c));
//是一样的。c和i都是联合体内第一个成员。在具体使用时,都是唯一的一个成员。
system("pause");
return 0;
}
运行结果:
<3>
#include<stdio.h>
#include<string.h>
#include<windows.h>
//#pragma warning(disable:4996)
union Un
{
char c;
int i;
};
int main()
{
union Un un;
un.i = 0x11223344; //此计算机是小端,低权值位放于低地址处,为44 33 22 11
un.c = 0x55; //此计算机是小端,低权值位放于低地址处,55f覆盖小端44的值,为55 33 22 11
printf("%x\n", un.i); //取值时,也要考虑小端问题,依照小端取值
system("pause");
return 0;
}
运行结果:
3>联合大小的计算
联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。
(联合体相对于枚举比较单纯,仅需考虑最大对齐数)
#include<stdio.h>
#include<windows.h>
union Un1
{
char c[5];
int i;
};
union Un2
{
short c[7];
int i;
};
int main()
{
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));
system("pause");
return 0;
}
运行结果: