自定义类型:结构体,枚举,联合

结构体

结构体的基本声明

基础知识:结构是一些值的集合,这些值称为成员变量,结构的每个成员可以是不同类型的变量。

//学生
struct Stu
{
	//学生的相关信息
	char name[20];
	int age;
}s1,s2;
//s1,s2是直接创建的结构体变量(全局变量)在{}内创建的则是局部变量

 特殊声明:

匿名结构体类型(没有写结构体名称的结构体),只能在初始化时给定创建的结构体(即只能使用一次)。

结构体的自引用:

在结构中包含一个类型为该结构本身的成员

struct Stu
{
	int data;
	struct Stu* next;
};

常用于链表中,通过指针来连接数据。

结构体内存对齐

可以看到同样成员变量的结构体在计算大小时却占用不同大小的内存空间 ,而且占用空间大小与各部分变量占用之和也不相等。这是因为结构体在储存的时候有着专属的对齐规则

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

那么为什么需要存在内存对齐现象呢?

1.平台原因:

        不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2.性能原因:

        数据结构(尤其是栈)应尽可能地在自然边界上对齐,因为访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访问。

        总的来说就是就是拿空间来换取时间。

  结构体传参:

#include<stdio.h>
struct S
{
	int data[1000];
	int num;
};
void print1(struct S ss)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", ss.data[i]);
	}
	printf("%d\n", ss.num);
}

void print2(struct S* ps)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", ps->data[i]);
	}
	printf("%d\n", ps->num);
}

int main()
{
	struct S s = { {1,2,3},100 };
	print1(s);//传值调用
	print2(&s);//传址调用
	return 0;
}

理论上两种传参都可以,但是更建议使用第二种传参方式即传址调用,因为函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销。

位段:

声明和结构体相似,但是成员名后有一个冒号和一个数字。冒号后的数字表示需要为数据分配的比特位, 例如用来表示真假的 int flag;实际flag只需一个比特位

struct A
{
	int a : 2;
	int b : 3;
};

此处A就是一个位段

 位段的内存分配:

1.位段的成员可以是int ,unsigned int,signed int或者是char(属于整形家族)类型

2.位段的空间上是按照需要以4个字节(int)或者1个字节(char )的方式来开辟的

3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

位段的跨平台问题:

1.int位段被当成有符号数还是无符号数是不确定的。

2.位段中最大位的数目不能确定,(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题)

3.位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

4.当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余位还是继续利用,也是不确定的。

总结:跟结构体相比,位段可以达到同样的效果,,还可以节约内存空间,但是有跨平台的问题存在。

C语言实现一个简单的通讯录

contact.c(局部模块函数的设计)

#include "contact.h"
//各个函数模块的实现

void InitContact(Contact* pc)
{
	assert(pc);
	pc->count = 0;
	memset(pc->data, 0, sizeof(pc->data));
}

void AddContact(Contact* pc)
{
	assert(pc);
	if (pc->count == 100)
	{
		printf("通讯录已满,无法添加\n");
		return;
	}
	printf("请输入名字:>");
	scanf("%s", pc->data[pc->count].name);
	printf("请输入年龄:>");
	scanf("%d", &(pc->data[pc->count].age));
	printf("请输入性别:>");
	scanf("%s", pc->data[pc->count].sex);
	printf("请输入电话:>");
	scanf("%s", pc->data[pc->count].tele);
	printf("请输入地址:>");
	scanf("%s", pc->data[pc->count].addr);
	
	pc->count++;
	printf("增加成功\n");

}

void ShowContact(const Contact* pc)
{
	assert(pc);
	int i = 0;
	printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
	for (i = 0; i < pc->count; i++)
	{
		printf("%-20s\t%-3d\t%-5s\t%-12s\t%-30s\n", 
			pc->data[i].name,
			pc->data[i].age,
			pc->data[i].sex,
			pc->data[i].tele,
			pc->data[i].addr);
	}
}

static int  FindByName(Contact* pc, char name[])
{
	assert(pc);
	int i = 0;
	for (i = 0; i < pc->count; i++)
	{
		if (0 == strcmp(pc->data[i].name, name))
		{
			return i;
		}
	}
	return -1;
}

void DelContact(Contact* pc)
{
	char name[MAX_NAME] = { 0 };
		assert(pc);
		int i = 0;
		if (pc->count == 0)
		{
			printf("通讯录为空,没有信息可以删除\n");
			return;
		}
		printf("请输入要删除人的名字:>");
		scanf("%s", name);
		//删除
		//先查找
		int pos = FindByName(pc, name);
		if (pos == -1)
		{
			printf("要删除的人不存在\n");
			return;
		}
		//再删除
		for (i = pos; i < pc->count-1; i++)
		{
			pc->data[i] = pc->data[i + 1];
		}
		pc->count--;
		printf("删除成功\n");
}

void SeaContact(Contact* pc)
{
	assert(pc);
	char name[MAX_NAME] = { 0 };
	printf("请输入要查找的人的名字:>\n");
	scanf("%s", name);
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要查找的人不存在\n");
		return;
	}
	printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
	printf("%-20s\t%-3d\t%-5s\t%-12s\t%-30s\n",
		pc->data[pos].name,
		pc->data[pos].age,
		pc->data[pos].sex,
		pc->data[pos].tele,
		pc->data[pos].addr);
}

void ModifyContact(Contact* pc)
{
	assert(pc);
	char name[MAX_NAME] = { 0 };
	printf("请输入要修改的人的名字:>\n");
	scanf("%s", name);
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要修改的人不存在\n");
		return;
	}
	printf("要修改的人的信息已经查找到,请开始修改\n");
	printf("请输入名字:>");
	scanf("%s", pc->data[pos].name);
	printf("请输入年龄:>");
	scanf("%d", &(pc->data[pos].age));
	printf("请输入性别:>");
	scanf("%s", pc->data[pos].sex);
	printf("请输入电话:>");
	scanf("%s", pc->data[pos].tele);
	printf("请输入地址:>");
	scanf("%s", pc->data[pos].addr);

	printf("修改成功\n");
}

int cmp_peo_by_name(const void* e1, const void* e2)
{
	return strcmp(((People*)e1)->name, ((People*)e2)->name);
}
//按照名字来排序
void SortContact(Contact* pc)
{
	assert(pc);
	qsort(pc->data, pc->count, sizeof(People), cmp_peo_by_name);
	printf("排序成功\n");
}

test.c(通讯录主逻辑)

#include "contact.h"
void menu()
{
	printf("*******      通讯录     ******\n");
	printf("***** 1.add     2.del   *****\n");
	printf("***** 3.search  4.modify*****\n");
	printf("******5.show    6.sort  *****\n");
	printf("*****       0.exit      *****\n");
}
int main()
{
	int input = 0;
	Contact con;//通讯录
	//初始化通讯录
	InitContact(&con);
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		
		case 1:
			AddContact(&con);
			break;
		case 2:
			DelContact(&con);
			break;
		case 3:
			SeaContact(&con);
			break;
		case 4:
			ModifyContact(&con);
			break;
		case 5:
			ShowContact(&con);
			break;
		case 6:
			SortContact(&con);
			break;
		case 0:
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;

}

 contact.h(头文件和函数的声明)

#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TELE 12
#define MAX_ADDR 30
//人的信息
typedef struct People
{
	int age;
	char name[20];
	char sex[10];
	char tele[12];
	char addr[30];
}People;

//通讯录
typedef struct Contact
{
	People data[100];//存放人的信息
	int count;//记录当前通讯录中实际的个数
}Contact;

//初始化通讯录
void InitContact(Contact* pc);

//增加联系人
void AddContact(Contact* pc);

//打印通讯录
void ShowContact(const Contact* pc);

//删除联系人
void DelContact(Contact* pc);

//查找联系人
int  FindByName(Contact* pc, char name[]);

void SeaContact(Contact* pc);

//修改联系人
void ModifyContact(Contact* pc);

//排序通讯录中的内容
void SortContact(Contact* pc);

枚举:

顾名思义,枚举就是一一列举,把可能的取值一一列举出来,“枚举”(是一种数据类型,它由一组预定义的常量组成,这些常量通常被称为“枚举值”。枚举在编程中被广泛使用,主要用于限制变量的取值范围,使代码更加清晰、易于维护,并减少错误。

 枚举类型的定义

 以上是一个简单的星期的枚举,枚举的定义与结构体类似,需要注意的是成员间使用逗号分隔,而结构体是使用分号,{}中的内容就是枚举常量,这些成员都是有值的,默认从0开始,每次加一,当然也可以在定义的时候赋初值。

枚举的优点:

我们可以使用#define来定义常量,为什么还要用枚举?

  • 增加代码的可读性和可维护性
  • 比#define定义的标识符更严谨,因为枚举有类型检查
  • 防止了命名污染(封装)
  • 便于调试
  • 使用方便,一次可以定义多个常量

 联合(供用体)

联合类型的定义

联合是一种特殊的自定义类型,定义的变量包含一系列成员,特征是这类成员共用同一块空间,但是每次只有一个成员在使用这片空间,类似于共享单车,其占用空间大小为单个成员占用的最大空间大小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值