自定义类型

结构体

1.定义:结构体是一些值的集合,这些值称为成员变量,每个成员可以是不同类型的变量。(是聚合类型)

2.结构体的声明:

struct tag
{
    member-list;
}variable-list;

c中结构体不能为空(即成员变量不能省略)

c中结构体关键字不能省略

tag(标签)的注意:

  1. 见名知义;
  2. 可以省略;(匿名结构体)
  3. 不建议省略。

此处的variavle-list(变量名)建议省略,在使用时再定义变量名

struct Stu
{
   char name[20];
   int age;
   char sex[5];
};
struct Stu obj;//定义全局变量int main(){ struct Stu obj1;//定义局部变量}

结构体之间是不能相互赋值的,例如下面的代码是非法的:

struct A
{
 int a;
 char b;
}x;
struct B
{
 int a;
 char b;
}*p;
p=&x;

3.结构体的成员:

结构体的成员可以是标量,数组,指针,结构体等任意类型

结构体的地址和第一个成员的地址在数值上是相等的

结构体成员的访问:

  • 通过点操作符(.)
struct Stu
{
 int age;
};
int main()
{
  struct Stu A;
  A.age=20;
}
  • 当结构体指针访问成员变量时通过指向操作符(->
struct A
{
   int a;
   char b;
};
int main()
{
  struct A *p;
  p->a=10;   //p->a等同于(*p).a
  p->b='B';
}
  

4.结构体的自引用:

struct test
{
   int a;
   struct test b;
};

这是不允许的,因为定义变量需要开辟空间,在这里大小是未知的

正确的做法如下:

struct test
{
   int a;
   struct test* b;
};
 typedef struct node
{
   int data;
   struct node* next;
}node_t;
int main()
{
node_t n;
}

typedef对当前结构体类型重命名

在上一段代码中,如果不加typedef是定义了一个结构体变量,而加上是定义了一个结构体类型

5.结构体的不完整声明:

struct A
{
  int a;
  struct B *p;
};
struct B
{
 int b;
 struct A *q;
};

在A里使用还未定义的B是不可以的,需对B先进行不完整声明(理论上是这样的,但对某些编译器来说还是可以编译通过的)

struct B;

6.结构体变量的定义和初始化

能够定义变量的地方基本都可以定义结构体变量

  • 结构体的初始化和数组初始化是一样的(花括号套花括号)
  • 结构体和数组一样不能被整体赋值
struct Node
{
  int data;
  struct Node *next;
}n1={10,NULL};


struct Node n2={20,NULL};
  

7.结构体内存对齐(计算结构体的大小)

  • 为什么存在结构体对齐?

            平台原因(移植原因)

        性能原因(内存未对齐,处理器需做两次内存访问,对齐后仅需一次访问)

  • 对齐规则:
  1.  第一个成员在与结构体变量偏移量为0的地址处(即第一个成员变量默认对齐)
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍地址处对齐数=编译器默认的一个对齐数与该成员大小的较小值,  vs 中的默认值为8,Linux中的默认值为4)
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数包括第一个)的整数倍
  4. 如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

总的原则就是自己的偏移量要整除自己的对齐数

总结:结构体的内存对齐是拿空间来换取时间的做法

例如:

struct S2{ //4
	char a;//1+3
	int b;//4->1+3+4=8
};
struct S4{
	char a; //1
	struct S3{ //8
		double d;//8
		char b[3];//1->3->8+3,b的偏移量是8,自身对齐数是1,8可以整除1,所以b是对齐的,成员变量是数组时,数组先看一个是否对齐,然后再放其他的
		int i;//4->8+3+1+4=16
	};
	//1+7+16=24
	char h[3];//1->3->24+3=27+1
	char* c[2];//4->8->27+1+8=36+4
	double e;//8->36+4+8=48
	struct S2 f[2];//4->16->48+16=64
	char g;//1->64+1=65
};
//s4的最大对齐数是8,65不是8的倍数,所以S4的总体大小是72

8.结构体传参

  • 结构体传参不发生降维
  • 结构体传参尽量不要传结构体,而是传结构体指针
struct S{
  int data[1000];
  int num;
};
struct S s={{1,2,3,4},1000};
void  print(struct S* ps)
{
  printf("%d\n",ps->num);
}

位段

1.位段的声明:

位段的声明和结构体是类似的,有两个不同

  • 位段的成员必须是int,unsigned  int,signed  int ,unsigned char,signed  char;
  • 位段的成员名后边有一个冒号和一个数字 (数字为允许使用的比特位数)
struct A
{
   int _a:2;
   int _b:5;
   int _c:10;
   int -d:30;
}

2.位段的存储与内存分配:

在上一段代码中,位段是这样存储的


先开辟一个整形字节的大小,a占2个bit位,b占5个bit,c占10个bit,剩下15个bit不够放d,在开辟4个字节放入d的30bit,其中放d的时候可能有两种情况:

  • 将d的30bit先拿出15bit放入第一个开辟的空间,再将剩下的15bit放入第二个整形字节中
  • 浪费掉第一个整形字节中剩余的15bit,直接将d的30bit放入第二个整形字节中

这是不确定的,要看编译器

在内存分配上:

  • 位段的空间是按照需要以4个字节或者1个字节的方式来开辟的
  • 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段

3.位段的应用:

在网络协议中经常使用,充当网络报头

枚举

枚举的顾名思义就是一一列举

1.枚举类型的定义

enum Color
{
   RED,
   GREEN,
   BLACK
};

enum Color叫枚举类型

{ }中的内容叫枚举常量,枚举常量是有值得,默认从0开始,一次递增1,在定义的时候也可以赋值

枚举类型与结构体不同的是成员之间是以逗号分隔的

2.枚举的使用

enum Color
{
   RED,
   GREEN,
   BLACK
};
enum Color clr=GREEN;

用枚举常量给枚举变量赋值,不建议给枚举变量赋其他的值(clr=5;)

枚举和宏相似,且宏用的会更多一点

联合

1.联合类型的声明

union  Un
{
   char c;
   int i;
};

2.联合的特点:

  • 联合的成员是共用同一块内存空间的
  • 所有的联合成员的地址是一样的

3.联合大小的计算

  • 联合的大小至少是最大成员的大小
  • 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍(对齐数规则和结构体是一样的)

4.用联合体判断当前计算机的大小端存储:

union Un
{
	char c;
	int i;
}x;
int main()
{
	x.i = 1;
	printf("%d\n", x.i);
	printf("%d\n", x.c);
	system("pause");
	return 0;
}

在这段代码中,在联合体开辟的4个字节中,char占的是最低字节的地址

给i赋值1,如果输出的的结果是c为1,则说明计算机为小端,否则为大端

5.联合体和结构体的使用:

将整形的ip地址转为点分十进制的表示形式

union ip_addr
{
	unsigned long addr;
	struct
	{
		unsigned char c1;
		unsigned char c2;
		unsigned char c3;
		unsigned char c4;
	}ip;
};
int main()
{
	union ip_addr my_ip;
	my_ip.addr = 176238749;
	printf("%d.%d.%d.%d\n", my_ip.ip.c4, my_ip.ip.c3, my_ip.ip.c2, my_ip.ip.c1);
	system("pause");
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值