自定义类型详解
1、结构体
1.1、结构体声明
结构体是一些值的集合,这些值称为成员变量,这些值可以是不同的类型
struct tag
{
member-list;
}variable-list;
#include <stdio.h>
#include <stdlib.h>
//结构体是一些值的集合,这些值称为成员变量,结构的每个成员可以是不同类型的变量。
//一般声明
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
};
//特殊声明
//匿名结构体类型,下面两个结构体的声明的时候省略掉了结构体标签(tag)
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
int main()
{
p = &x;
system("pause");
return 0;
}
当结构体声明的时候省略了结构体标签,编译器会把两个相同类型值组成的结构体声明为两个不同的类型,是非法操作。
1.2、结构体的自引用
#include <stdio.h>
#include <stdlib.h>
//结构体中包含一个类型为该结构体本身的成员
struct Node
{
int data;
struct Node *next;
};
//typedef struct Node
//{
// int data;
// struct Node *next;
//}Node;
int main()
{
system("pause");
return 0;
}
1.3、结构体变量的定义和初始化
#include <stdint.h>
#include <stdlib.h>
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
//赋初值
struct Point p3 = { 2, 3 };
struct Student //类型声明
{
char name[15];
int age;
};
struct Student s = { "zhangsan", 20 };//初始化
struct Node3 //结构体嵌套初始化
{
int data;
struct Point p;
struct Node *next;
}n1 = { 10, { 4, 5 }, NULL };
struct Node3 n2 = { 20, { 5, 6 }, NULL };//结构体嵌套初始化
int main()
{
system("pause");
return 0;
}
1.4、结构体内存对齐
结构体对齐规则:
1、第一个成员与结构体变量偏移量为0的地址处。
2、其他成员变量对齐到某个数字(对齐数)的整数倍的地址处
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值
注:vs默认对齐数为8,Linux的默认对齐数为4
3、结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
4、如果嵌套了结构体,嵌套结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
为什么存在内存对齐:
1、移植原因:不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因:数据结构应该尽可能在自然边界对齐,为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存访问仅需要一次访问。
结构体内存对齐选择空间换时间,提高性能
#include <stdio.h>
#include <stdlib.h>
/*
结构体的对齐规则:
1、第一个成员与结构体变量偏移量为0的地址处。
2、其他成员变量对齐到某个数字(对齐数)的整数倍的地址处
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值
注:vs默认对齐数为8,Linux的默认对齐数为4
3、结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
4、如果嵌套了结构体,嵌套结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
*/
struct s1
{
char c1;
int i;
char c2;
};
struct s2
{
char c1;
char c2;
int i;
};
struct s3
{
double d;
char c;
int i;
};
struct s4
{
char c1;
struct s3 s;
double d;
};
int main()
{
printf("%d\n", sizeof(struct s1));//12
printf("%d\n", sizeof(struct s2));//8
printf("%d\n", sizeof(struct s3));//16
printf("%d\n", sizeof(struct s4));//32
system("pause");
return 0;
}
我们在设计结构体的时候,我们既要满足对齐,又要节省空间
我们采取占用空间小的成员尽量集中在一起。
1.5、修改默认对齐数
使用 #pragma pack(字节数)修改默认对齐数
#include <stdio.h>
#include <stdlib.h>
//修改默认对齐数为8
#pragma pack(8)
struct s1
{
char c1;
int i;
char c2;
};
//取消设置默认对齐数,还原为默认
#pragma pack()
//修改默认对齐数为1
#pragma pack(1)
struct s2
{
char c;
int i;
char c2;
};
//取消设置默认对齐数,还原为默认
#pragma pack()
int main()
{
printf("%d\n", sizeof(struct s1));
printf("%d\n", sizeof(struct s2));
system("pause");
return 0;
}
1.6、结构体传参
结构体传参的时候传结构体地址,防止在参数压栈的时候系统开销过大,导致性能下降
#include <stdio.h>
#include <stdlib.h>
//结构体传参首选传地址,这样结构体过大的情况下可以防止参数压栈过程中开销过大,导致性能下降的问题
struct qwe
{
int data[2000];
int num;
};
struct qwe q = { { 1, 2, 3, 4 }, 1000 };
//结构体传参
void Print1(struct qwe q)
{
printf("%d\n", q.num);
}
void Print2(struct qwe *q)
{
printf("%d\n", q->num);
}
int main()
{
Print1(q); //传结构体
Print2(&q);//传地址
system("pause");
return 0;
}
2、位段
2.1、什么是位段
位段和结构体类似
1、未断电额成员必须是int、unsigned int或signed int
2、位段的成员名后面跟所占的位数
#include <stdio.h>
#include <stdlib.h>
//1、位段的成员必须是int、unsigned int或signed int
//2、位移的成员名后边有一个冒号和一个数字。
//3、与结构体类似。
struct A
{
int a : 2;//位段a占2位
int b : 5;//位段b占5位
int c : 10;//位段c占10位
int d : 30;//位段d占30位
};
int main()
{
printf("%d\n", sizeof(struct A));
system("pause");
return 0;
}
2.2、位段的内存分配
1、位段的成员可以是int、unsigned int、signed int或者char(属于整形家族)类型
2、位段的空间上是按照需要以4个字节(int)或1个字节(char)的方式来开辟的
3、位段的使用要避免跨平台
2.3、位段的跨平台问题
1、int位段在不同的编译器不能确定是不是有无符号类型
2、位段中最大位的数目不能确定。
3、位段的成员在内存中是从左向右还是从右向左定义尚未定义
4、当一个结构包含两个位段,第二个位段成员比较大,无法容纳第一个位段的剩余位段是利用还是舍弃不确定
使用位段可以达到和结构体相同的效果,比结构体节省空间,但是存在跨平台问题。
3、枚举
3.1、枚举类型的定义
枚举类型的定义:{}中内容是枚举类型的可能取值,叫枚举常量
#include <stdio.h>
#include <stdlib.h>
//enum默认从0开始,依次递增1
//枚举的优点:
/*
1、增加代码的可读性和可维护性
2、和#define定义的标识符比较,枚举有类型检查,更加严谨
3、防止了命名污染(封装)
4、便与调试,使用过方便,一次可以定义多个常量
*/
enum Day//星期
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜色
{
Red,
Orange,
Yellow,
Green,
Blue,
Purple
};
int main()
{
system("pause");
return 0;
}
枚举的优点:
1、增加代码的可读性和可维护性
2、和#define定义的标识符比较,枚举有类型检查,更加严谨
3、防止了命名污染(封装)
4、便与调试,使用过方便,一次可以定义多个常量
3.2、枚举的使用
#include <stdio.h>
#include <stdlib.h>
enum Color1
{
Red = 1,
Orange = 2,
Yellow = 3,
Green = 4,
Blue = 5,
Purple = 7
};
int main()
{
enum Color1 clr = Green;
printf("%d\n", clr);
system("pause");
return 0;
}
4、联合
4.1、联合的特点
联合的成员共用一块内存空间,这样一个联合变量的大小,至少是最大成员的大小。
联合的使用:
#include <stdio.h>
#include <stdlib.h>
//联合体变量声明
union Un
{
char c;
int i;
};
int main()
{
//联合体的定义
union Un un;
printf("%d\n", sizeof(un));
system("pause");
return 0;
}
联合的特点:
#include <stdio.h>
#include <stdlib.h>
union UN
{
int i;
char c;
};
int main()
{
union UN un;
printf("%d\n", sizeof(un.i));
printf("%d\n", sizeof(un.c));
un.i = 0x11223344;
un.c = 0x55;
printf("%d\n", sizeof(un.i));
system("pause");
return 0;
}
4.2、联合体大小的计算
联合体大小的计算规则:
1、联合的大小至少是最大成员的大小
2、当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
#include <stdio.h>
#include <stdlib.h>
union UN1
{
char c[5];
int i;
};
union UN2
{
short s[7];
int i;
};
int main()
{
union UN1 un1;
union UN2 un2;
printf("%d\n", sizeof(un1));//8
printf("%d\n", sizeof(un2));//16
system("pause");
return 0;
}