C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C语言 自定义类型

C语言自定义类型全解析

作者:诚挚的乔治

在C语言中自定义类型主要有结构体类型、位段、枚举类型、联合体类型,自定义类型是面试常会碰到的内容,今天我们来详细了解一下它

前言

初学C语言

我们先接触的都是内置的类型

比如说int char short float double long等等

这一期就来聊一聊自定义类型的知识

结构体类型

首先我们要知道什么是结构体

结构体就是各种值集合

这些值被称作结构体成员,这些成员可包括各种不同的类型

struct tag         //这里的struct是结构体的关键字,tag是结构体标签,也就是结构体的名称
{
	number - list; //结构体成员列表
}veriable-list;    //结构体的变量 

结构体的声明

如果结构体的标签是student,我拿student来举例子

结构体的完整声明

struct Student
{
    char name[20];//姓名
    char sex;//性别
    int age;//年龄
    int num;//学号
};   //这里的分号不能丢

结构体的不完全声明(匿名结构体类型)

struct
{
	int a;
	char b;
	double c;
}s;   //这s不能省略

匿名结构体的特点就是没有结构体标签

但这样写用户使用时只能使用一次,也就是说只能在结构体声明时就定义变量

因为你找不到结构体标签,就相当于找不到门牌号一样,无法再对其定义一个变量

结构体变量的定义与初始化

结构体的定义大致分为三种情况

<1>结构体声明的同时定义

struct Student 
{
    char name[20];//姓名
    char sex[20];//性别
    int age;//年龄
}s={"zhangsan","nan",20};

<2>结构体先声明,再定义

#include<stdio.h>
struct Student
{
    char name[20];//姓名
    char sex;//性别
    int age;//年龄
    int num;//学号
};
 
 
int main()
{
    struct Student s = { "zhangsan",'w',20,111 };
	return 0;
}

<3>匿名结构体定义

struct 
{
    char name[20];//姓名
    char sex[20];//性别
    int age;//年龄
} s = { "zhangsan","nan",20};

注意:结构体初始化与数组相同,都必须整体进行赋值。

结构体的自引用

struct Node //初始话链表
{
	int a;
	struct Node next;
};

结构体的自引用就是结构体再套用自己

学过数据结构的朋友应该知道这是初始化链表

不过这一个代码有问题的

问题在于无法求出这个结构体的大小,不清楚这个结构体有多大,因为无法求出自引用的结构体有多大

所有自引用的结构体要用指针来访问

struct Node //初始话链表
{
	int a;
	struct Node* next;
};

故就可以通过指针来访问每一个结点

结构体的访问

当结构体定义且变量初始化完成后,就可以通过操作符来访问变量中的成员

当然,这里给出了两个操作符

分别是  .操作符和 -> 操作符

当使用结构体变量时,就用点操作符,当访问结构体指针变量就用箭头操作符

(1)通过结构体变量进行访问:

printf("%s\n",s.name);

(2)通过结构体指针进行访问:

printf("%s\n",ps->name);

结构体的传参

函数的调用有时候需要传一些参数

参数的类型可以是不同的类型,可以是数组,也可以是指针

同样结构体的传参也可通过传结构体或者传指针

传结构体

#include<stdio.h>  
struct tag
{
	int a;
	char b[20];
}s1 = { 100,"abcdef" };
void print()
{
	printf("%d", s1.a);
}
int main()
{
	print(s1);
	return 0;
}

传地址

#include<stdio.h>
struct tag
{
	int a;
	char b[20];
}s2 = { 100,"abcdef" };
void print(struct tag*s2)
{
	printf("%d", s2->a);
}
int main()
{
	print(&s2);
	return 0;
}

我们要知道函数传参是形参就是实参的临时拷贝

参数是要压栈的(向系统申请空间),既然是临时拷贝,就会再次再栈上开辟空间,当实参足够大时,显然会浪费一定的空间和时间

相比较与传结构体,传指针会更好 

结构体的内存对齐(强烈建议观看)

在另外一篇文章详细讲过——C语言结构体中内存对齐的问题理解

位段

可能有人没有听过什么是位段

位段的结构类型跟结构体有些类似可以类似结构体去学习

也可以说

位段是结构体特殊的实现

位段的声明

相较于结构体,位段的声明有两点不同

<1>规定位段的成员的必须是int,unsigned int ,signed int (但是写成char类型也没什么大的问题)

<2>位段的成员后面有一个冒号和一个数字

struct A  //位段的声明
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

位段的内存管理

#include<stdio.h>
struct A
{
	int a : 2;
	int b : 5;
	int c : 10;
	int d : 30;
};
int main()
{
	
	printf("大小是%d字节", sizeof(struct A));
	return 0;
}

为什么位段的大小是八个字节呢?

成员内包含的数字代表的是这个成员需要利用的内存,单位是bit。

位段成员申请内存时都是以四个字节或者一个字节单位(当成员是char类型时)

int a : 2; //申请4个字节,也就是32个bit位,利用两个还剩30个
int b : 5; //利用5个,还剩25个
int c : 10; //利用10个,还剩15个
int d : 30; //这里的十五不够,所以再申请了4个字节

最终的结果就是八字节

但问题是,变量d利用的空间是留下的15个bit加上重新申请的空间呢

这个结果在不同的环境的结果是不同的,所以位段的跨平台性比较差

位段使用的前提是你知道存储的内存大概有多大

就比如说年龄

十个bit位0111111111,最大值就可以达到1023

就不需要再申请一次利用一个int类型的空间大小

这就达到了节省内存的作用,存在即合理

位段的应用 

struct A
{
	char a : 3;
	char b : 4;
	char c : 5;
};
main()
{
	struct A s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	return 0;
}

位段的跨平台性

1.int位段被当成有符号数还是无符号数是不确定的,有时候系统会自动转化为无符号整形。

2.位段中最大位的数目不能确定。(因为在早期的16位机器int最大16,而32位机器int最大32)

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

意思当内存一个字节00000000,存入01010,可能会出现00001010或者01010000

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

 枚举类型

枚举类型适用于可以一一列举的类型

比如说星期、性别

枚举类型的定义

enum Day   //星期
{
	//枚举的可能取值
	Mon,
	Tues,
	Wed,
	Thir,
	Fri,
	Sta,
	Sun
};
enum Sex  //性别
{
	MALE,//0
	FEMALE,//1
	SECRET//2
};

枚举类型是有初始值的

如果你没赋予它初始值,就会默认从零开始,依次加一

枚举类型赋予初始值

#include<stdio.h>
enum Sex  //性别
{
	MALE = 4,
	FEMALE=10,
	SECRET//
};
main()
{
	printf("%d %d %d", MALE,FEMALE,SECRET);
	return 0;
}

可以看到,其实默认的值是可以改的

当某个成员被赋予某个值的时候,后续的成员就在此基础上加一

枚举类型的优点

1.相较于数字,枚举增加代码的可读性和可维护性

2.和#define定义的标识符比较枚举有类型检查,更加严谨。

3.防止了命名污染(封装)

4.便于调试

5.使用方便,一次可以定义多个常量

联合体类型

联合体类型也叫共用体

联合体的定义

union Un  
{
	int a;
	char b;
};

union是联合体关键字,Un是联合体的标签

联合体的特点

共用体,顾名思义,这里的共用就是公用内存

内存的也可被折叠

#include<stdio.h>
union Un
{
	char c;
	int i;
};
int main()
{
	union Un u = {0};
	printf("%d\n", sizeof(u));
	printf("%p\n", &u);
	printf("%p\n", &(u.c));
	printf("%p\n", &(u.i));
	return 0;
}

他们有相同的地址

c和i存放在同一块内存空间上,修改c或者i都会影响到另一个成员。

 联合体内存大小的计算

<1>联合体内存大小是最大成员的大小

<2>最大成员的大小如果不是最大对齐数的整数倍,就会对齐到最大对齐数的整数倍

(联合体也存在内存对齐)

#include<stdio.h>
union Un1
{
	char c[5];  
	int i;
};          
//Un1成员最大成员大小5,最大对齐数是4,所以Un1的大小是8;
union Un2
{
	char c[7];
		int i;
};
//Un2成员最大成员大小7,最大对齐数是4,所以Un2的大小是8;
int main()
{
	printf("%d\n", sizeof(union Un1));
	printf("%d\n", sizeof(union Un2));
	return 0;
}

到此这篇关于C语言自定义类型全解析的文章就介绍到这了,更多相关C语言 自定义类型内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文