编程入门——自定义类型函数的探究

一 分类

①结构体 位段(位段算是一种特殊的结构体)

②枚举

③联合体(共用体)

二 结构体的探究

为什么要有结构体呢?结构体是对一个复杂对象的描述。如果有结构体的话,数据之间就相当于建立了联系。

1结构体的声明

一般在主函数前单独声明,这样就可以看作是一个全局变量,在函数中直接。如果在函数中使用的话,出了这个函数的作用域结构体变量的生命周期也就结束了。使用基本格式:

struct TAG
{
	M_list;
}V_list;

注意:

① 声明结构体的时候,末尾的;不能省略

②一般结构体名是要使用大写字母的

③结构体类型包括两部分: struct (指定该类型为结构体)+结构体变量名。因此结构体类型是用户自定义的。

④结构体声明的时候系统并不会为他开辟空间。只有当结构体初始化的时候才会为他开辟空间吉安。因此在声明结构体的时候不可以对他进行初始化。

⑤结构体也可以嵌套。只不过接下来初始化的时候要在大括号内再加上大括号。

⑥可以使用typedef重命名。若使用的话,初始化可以直接使用s1进行初始化。

特殊:

2结构体的定义

结构体可以在声明结构体的时候创建变量,也可以在使用结构体的时候再创建变量,但是此时一个是全局变量,一个是局部变量

struct STUDENT
{
	char name;
	int age;
	float score;
}s1;//全局变量

int main()
{
	struct STUDENT s1;//局部变量

	return 0;
}

3 结构体的初始化。

创建了结构体变量之后可以同时赋值,也可以先创建再赋值。

接下俩展示两种方式:

struct STUDENT
{
	char name;
	int age;
	float score;
}s1;
int main()
{
	//struct STUDENT s1= { 'zs',18,85.5 };//创建的同时赋值

	struct STUDENT s1;
	s1.name = 'zs';
	s1.age = 18;
	s1.score = 85.5;//先创建后赋值

	return 0;
}

4 结构体操作符

①. 访问结构体的成员,但是有缺陷,不能对指针进行直接访问。如果非要用的话,可以写成(*p),name的形式。(p为指向结构体的指针)

②-> 适用范围比较广。可以先将结构体的地址保存到一个指针变量中。(节省空间)

struct STUDENT
{
	char name;
	int age;
	float score;
};
int main()
{
	struct STUDENT s1= { 'zs',18,85.5 };//创建的同时赋值
	struct STUDENT* p = &s1;
	printf("%d\n", p->age);
	return 0;
}

5 匿名结构体

 结构体使用的时候有时候为了避免重名,会使用。但是这种结构体没有结构体名称,因此在创建的时候应该在后跟一个变量列表。

需要注意的是,可能两个匿名结构体的成员完全一致,但是编译器却会把它当作里啷个完全不同的结构体。跟设计的初衷——避免重名其实也是吻合的。

匿名结构体只能使用一次。

6 结构体的自引用

相当于一个链式访问。

7 结构体内存对齐

① 如何进行结构体内存对齐操作

a.对于第一个结构体成员来说,偏移量为0。直接进行存储。

b.对于中间的各个变量来说,要将变量的大小和默认对其数进行对比,取较小的作为对齐数。此时变量存放的位置的对齐数的整数倍。(vs中默认为8)

c.对于结构体变量整体大小的确认,是最大对齐数的整数倍

d.如果有嵌套结构体,作为结构体成员的结构体对齐数是这个结构体中的最大对齐数。

我们来举例子体会一下

#define _CRT_SECURE_NO_WARNINGS 1 
#include
struct STUDENT
{
	char name;
	int age;
	float score;
};
int main()
{
	printf("%d\n", (int)sizeof(struct STUDENT));
	return 0;
}

编程入门——自定义类型函数的探究_第1张图片

②结构体成员的设置

 由此可以看出来,结构体成员的不同顺序可能会开辟不同的空间。

那么怎么做才能减小空间的浪费?

将占用空间小的结构体成员排放在一起。

③为什么会存在内存对齐?

1. 平台原因 ( 移植原因 ) :
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
定类型的数据,否则抛出硬件异常。
2. 性能原因 :
数据结构 ( 尤其是栈 ) 应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
问。
总体来说:
结构体的内存对齐是拿 空间 来换取 时间 的做法。

④如何修改默认对齐数?

#pragma pack();

在括号内加入自己所需的对齐数就好。

三 位段

1 什么是位段?

段位其实是一种特殊的结构体。但是他的成员只能是int,unsigned int,signed int,char类型。并且他所占空间是按照比特位进行计算的。

2 为什么会有位段?

刚才了解了结构体之后,发现结构体为了提升效率,有时候会牺牲空间。而段位的设计恰好就可以节省空间。

3位段的存储方式

struct S
{
	char a : 3;//所占比特位
	char b : 4;
	char c : 5;
	char d : 4;
};

对于这个位段来说,需要占用三个字节大小。

我们来分析一下:一个char类型共占用1个字节(8bit),因此先存放了a,b,剩下的一个空间不够存放c,因此再开辟一个字节,对于开辟的第二个空间来说,剩下3bit显然不足以放下d,因此需要再次开辟一个字节,这样一来一共开辟了3字节。

注意:位段中的对齐数是根据其中成员最大类型来定的。如果同时开辟int char两个类型的变量,如果char不够存放,那么此时开辟32个bit位(8个字节),因为int是相较于char成员类型大小更大。

4 位段在内存中的分配

我们先对上面的位段进行初始化。

struct S
{
	char a : 3;//所占比特位
	char b : 4;
	char c : 5;
	char d : 4;
};
int main()
{
	struct S s = { 0 };
	s.a = 10;//2^3+2^1 (1)010->截断 
	s.b = 12;//2^3+2^2 1100
	s.c = 3;//2^1+2^0  00011
	s.d = 4;//2^2      0100
	return 0;
}

编程入门——自定义类型函数的探究_第2张图片

 我么通过一张图来了解一下。由于vs中是小端字节存储模式,因此如图所示开辟了3字节的空间。

对于a来说,由于存储的数字超过了空间,因此要发生截断。截断高位数字。

对于b来说,接着a进行存放

对于c来说,由于第一个字节的空间已经放不下了,因此开辟了一个字节存放,没有用到的位置存放0.

对于d来说,同c。

5 位段的跨平台问题

根据上面的描述,其实可以知道,位段是不跨平台的。

为什么?

①位段被当成有符号的还是无符号数是不确定的。
②位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出现问题)
③位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。上述例子假设是从右到左在内存中分配。
④是否利用剩余的空间无法确定。上述的例子是舍弃。

四 枚举

1 声明与定义

#define _CRT_SECURE_NO_WARNINGS 1 
#include
enum SEX
{
	MALE,//0
    FEMALE,//1
	SECRET//2
};//上面是可能取值
int main()
{
	enum SEX s = MALE;//直接用上述的变量进行初始化
	return 0;
}


注意:第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。

也可以对MALE进行赋值,如果赋值1,那么FEMALE,SECRET变成2,3.

2 使用

用来定义常量

3 优点

①增强代码可读性。比如上述例子中MALE,FEMALE,SECRET就是性别中的三种取值。相当于进行了封装 

②传递参数错误。不能直接进行常数的传递,避免使用的过程中混淆了常数代表的含义。

③去除equals两者判断。由于常量值地址唯一,使用枚举可以直接通过“==”进行两个值之间的对比,性能会有所提高。

④编译优势

五 联合体(共用体)

1 定义和初始化。

其实自定义类型函数的定义和初始化差不多,可以参照结构体

#define _CRT_SECURE_NO_WARNINGS 1 
#include
union STUDENT
{
	char name;
	int age;
	float score;
}s;
int main()
{
	printf("%d\n", (int)sizeof(s));//4
	return 0;
}

2 特点

共用体中成员共用一块内存空间。

3 内存计算规则

①至少是最大成员的大小

②当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

4 联合体使用

#define _CRT_SECURE_NO_WARNINGS 1 
#include
int check_system()
{
	union C
	{
		char n;
		int m;
	}c;
	c.m = 1;//共用一块内存空间,对m进行修改也是对n进行修改
	return c.n;
};
int main()
{
	if (check_system() == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}

	return 0;
}

以判断大小端存储为例子,这样一来相当于进行了封装。代码风格更加优秀。

你可能感兴趣的:(知识总结,c#,c++)