课堂代码(6_19)

#ifndef _TEST_H_
#define _TEST_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Windows.h>
#include <math.h>
#include <time.h>
#include <assert.h>
#include <errno.h>










#endif // !_TEST_H所有变量声明

//#include "58.h"
//
//
////int main()
////{
////
////	return EXIT_SUCCESS;
////}
//
//
//
//
//int main()
//{
//
//	int a[100];
//	printf("请输入有多少个数据:-->");
//	int num = 0;
//	scanf("%d", &num);
//
//	printf("请输入数据列表:");
//	for (int i = 0; i < num; i++)
//	{
//		scanf("%d", &a[i]);
//	}
//
//	int max = a[0];
//	for (int i = 0; i < num; i++)
//	{
//		if (max < a[i])
//		{
//			max = a[i];
//		}
//	}
//
//	printf("最大值:%d\n", max);
//	return EXIT_SUCCESS;
//}
//
//
//
//
//
//////联合体本身大小不是把所有类型加起来的大小
//////一般是有联合体内部最大元素的大小决定
//////所有元素共享空间
////union _un
////{
////	int a;
////	char b;
////	char c;
////	char d;
////	int e;
////};
////union _UN
////{
////	int a;
////	char b;
////};
////int main()
////{
////	union _un obj;
////	obj.a = 0x11223344;
////	printf("0x%p\n", &obj.a);
////	printf("0x%p\n", &obj.b);
////	printf("0x%p\n", &obj.c);
////	printf("0x%p\n", &obj.d);
////	printf("0x%p\n\n", &obj.e);
////
////	printf("%x\n", obj.a);
////	printf("%x\n", obj.b);
////	printf("%x\n", obj.c);
////	printf("%x\n", obj.d);
////	printf("%x\n\n\n", obj.e);
////
////	union _UN OBJ;
////	OBJ.a = 1;
////	printf("%d\n", OBJ.b);
////
////	return EXIT_SUCCESS;
////}
//
//////union _u {
//////	int a;
//////	int b;
//////	char c;
//////};
//////int f1(int a)
//////{
//////	return 2 * a;
//////}
//////int f2(int b)
//////{
//////	return 3 * b;
//////}
//////char f3(char c)
//////{
//////	return 4 * c;
//////}
//////int main()
//////{
//////	union _u obj;
//////	obj.a = 10;
//////	fi(obj.a);
//////
//////	obj.b = 10;
//////	fi(obj.b);
//////
//////	obj.c = 10;
//////	fi(obj.c);
//////	return EXIT_SUCCESS;
//////}
//
//
//
//
////枚举
////枚举是一种类型,编译时会进行类型检查
////enum color
////{
////	RED,
////	BLACK,
////	GREEN,
////	YELLOW,
////	BLUE
////};
////typedef enum color color_c;
////int main()
////{
////	enum color c = RED;
////	printf("%d\n", c);
////	printf("%d\n", RED);
////	printf("%d\n", BLACK);
////	printf("%d\n", GREEN);
////	printf("%d\n", YELLOW);
////	printf("%d\n", BLUE);
//////	RED = 100;  //错误,枚举类型本身是整型常量,不可以被赋值
////
////	color_c a = BLUE;
////	printf("\n%d\n", a);
////	return EXIT_SUCCESS;
////}

引言

结构体(Structure)[在C标准中有时也称为聚合体(Aggregate)]是统一在同一个名字之下的一组相关变量的集合,它可以包含不同类型的变量
结构体通常用来定义储存在文件中的记录
将指针和结构体联合使用,可以实现更复杂的数据结构,如链表、队列、堆栈和数

2 结构体的定义

关键字 struct 用来引出一个结构体定义
关键字 struct 之后的标识符称为结构体标记,它是用来给这个结构体定义命名的。结构体标记与关键字 struct 一起用来声明具有这个结构体类型的变量
在结构体定义的花括号内声明的变量是结构体的成员
同一个结构体类型中的成员不能重名
每一个结构体定义都必须用一个分号来结束
结构体的成员可以是具有原始数据类型的变量,也可以是聚合体聚合体数据类型的变量
一个结构体不能包含它自身的实例,但可以包含一个指向相同类型的另外一个对象的指针
包含一个指向自身结构体类型的指针为成员的结构体称为自引用结构体。自引用结构体被用来构建链式数据类型
结构体定义创建一种新的可用来定义变量的数据类型
struct card {
char* face;
char* suit;
}aCard, deck[52], * cardPtr;
将aCard声明为一个类型为struct card 的变量
将deck声明为一个包含了52个具有struct card类型的元素数组
将cardPtr声明为一个指向struct card类型的指针
上述语句完成后,系统为一个类型为struct card的变量aCard、数组feck中的52个struct card类型的元素以及一个未初始化的指向struct card类型的指针申请空间
结构体定义中的结构体标记名是可以省略的。若结构体定义中没有标记名,则该结构体类型的变量就只能在结构体定义的同时进行声明
运用于结构体上的合法操作只有:
将结构体变量赋值给其他具有相同类型的结构体变量
用&运算符取得结构体变量的地址
访问结构体变量中的成员
用sizeof运算符确定结构体变量的大小

3 结构体的初始化

可以采用初始值列表来对结构体进行初始化
若列表中初始值个数少于结构体成员的个数,则剩余成员自动地被初始化为0(成员是指针时,被初始化为NULL)
在函数之外定义的结构体变量,若没有显式地在外部进行初始化,将被自动地初始化为0或NULL
通过将一个结构体变量赋值给另外一个与其类型相同的结构体变量,或逐个对结构体变量成员进行赋值来实现结构体变量的初始化

4 结构体访问

可以用结构体成员运算符(.)和结构体指针运算符(–>)来访问结构体的成员
结构体成员运算符是通过结构体变量名来访问结构体成员的
结构体指针运算符通过指向结构体的指针来访问结构体成员

5 在函数中使用结构体

将结构体传递给函数有三种方式:
传递结构体的个别成员
传递整个结构体
传递一个指向结构体的指针
结构体变量在默认的情况下以传值的方式被传递给一个函数的
若采用(模拟)按引用方式来传递一个结构体,传递给被调函数的是结构体变量的地址。结构体数组(与其他数组一样)都是自动以(模拟)按引用方式传递的
要想以传值的方式来传递一个数组,可创建一个以该数组为成员的结构体,然后以传值的方式来传递这个结构体。这样数组就以传值的方式被传递过去了

6 typedef的使用

关键字typedef提供了一种为已定义好的数据类型创建同义词的机制
为了创建更简短的类型名称,通常使用typedef 来定义结构体类型的名字
typedef 还常常被用来为基本数据类型创建一个别名。例如,一个处理4字节长整数的程序运行于某个系统时,会被要求用类型 int 定义变量;而运行于另外一个系统时,则会被要求用类型long定义变量。为了可移植,程序就用typedef 为4字节长的整数创建一个别名,如Integer。这样,只需对别名Integer的定义做一次修改,就可使程序能够运行于两个不同的系统之上

7 共用体

共用体是以与声明一个结构体相同的格式,通过关键词union来声明的。它的成员共享同一个存储空间
共用体的成员可以是任意数据类型,但是存储一个共用体所用的字节总数,必须保证至少足以能够容纳其最大的成员
每次只允许访问共用体的一个成员,确保以正确的数据类型来访问共用体中的数据
可对共用体执行的操作有:两个具有相同类型的共用体之间的赋值,用&运算符取得一个共用体变量的地址,用结构体成员运算符或结构体指针运算符来访问共用体的成员
可以在声明语句中,用与其第一个成员数据类型相同的数值来对共用体进行初始化

8 位运算符

在计算机内部,,所有数据都是以二进制数序列的形式来表示的,每个数位取值0或1
在大多数计算机系统中,8个二进制数位的序列组成一个字节——存储一个字符型变量的标准存储单元,其他数据类型的变量则语言更多的字节来存储
位运算符用来处理整型操作数(如字符型 char 、短整型 short 、整型 int 以及长整型 long );有符号整型和无符号整型的各个数位。通常将操作数当成无符号整型数据来处理
位运算符有:按位与(&),按位或(||),按位异或(^),左移(>>),右移(<<)和按位取反(~)
按位与、按位或和按位异或都是逐位地对两个操作数进行比较。当两个操作数相应的二进制数位都是1时,按位与运算符才会将运算结果的二进制数位设置为1。只要两个操作数相应数位有一个是1(或者两个都是1),按位或运算符就会将运算结果的相应的二进制数位设置为1。仅当两个操作数相应的二进制数位只有一个是1时,按位异或运算符才将运算结果相应的二进制数位设置为1
左移运算符是将其左边的操作数按位向左移动,移动的位数由其右边的操作数指定。右边腾空的数位补0,左边移出的数位丢弃
右移运算符是将其左边的操作数按数位向右移动,移动的数位由其右边的操作数指定。对无符号整数进行右移运算时,左边腾空的数位补上0,右边移出的数位丢弃
按位取反运算符是将其操作数中所有的0设置成1,所有的1设置成0,然后得到运算结果
通常,按位与运算符要与一个被称为掩码的操作数一起使用,掩码是某些特定位被置成1的一个整数。掩码用来在选取某些数位的同时屏蔽掉其他数位
符号常量CHAR_BIT(在头文件 < limits.h >中定义)表示一个字节中的二进制位数(标准情况下是8)。可以用它来增强一个位操作程序的可移植性和可扩展性
每个二进制位运算符都有一个相应的位运算后赋值运算符

9 位域

当结构体或共用体中有无符号整型或有符号整型成员时,C语言允许用户指定这些成员所占用的存储位数,这被称为位域。通过将数据存储在它们所需的最小数目的存储位内,位域能够提高存储空间的利用率。
声明一个位域的方法是:在无符号整型或有符号整型的成员的名字后面加上一个冒号 ( : )和一个表示位域宽度的整型常数。这个常数值必须是一个整数,其范围取值在0到系统中存储一个整形所需二进制位数之间的闭区间内
对结构体中位域成员,是按照与其他成员相同的方式来访问的
可以指定一个无名位域作为结构体中的补位
宽度为0的无名位域将使下一个位域对齐在一个新的存储单元的边界上

10 枚举常量

通过关键字enum引入的枚举常量,是一个用标识符表示的整形常量的稽核。除非专门定义,枚举常量中枚举的值都是从0开始并且递增1
在定义枚举类型时,可以通过给标识符赋值来显式地给枚举常量定值

11 自定义类型

结构体
枚举
联合

12 结构体内存对齐

结构体的第一个成员一定放在结构体起始位置的 0 偏移处,截止字节由其本身类型大小决定
从第二个成员开始,每个成员都要放在某个对齐数的整数倍的偏移处(这个对齐数:成员自身的大小和默认对齐数的较小值 ——8(VS)——4(linux))
结构体的总大小必须是所有成员的对齐数中最大对齐数的整数倍
如果有嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的总大小是所有对齐数(包含嵌套结构体的对齐数)中最大对齐数的整数倍

13 为什么结构体内存对齐

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

总体来说:
1.结构体的内存对齐是拿空间来换取时间的做法
2.所以在设计结构体的时候,为了满足对齐,又要节省空间可以让占用空间小的成员尽量集中在一起

14 修改默认对齐数

#pragma pack (1)//默认对齐数修改为4,改为 1 即为没对齐,一般修改为2,4,6……
struct S3 {
double d;
char c;
};
#pragma pack()//取消默认对齐数的修改

15 结构体传参

函数传参时,参数是需要压栈的。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销较大,就会导致性能的下降
结构体 传参的时候,要传结构体的地址

16 位段

位段的声明和结构体是类似的,但是有两个不同:
1.位段的成员必须是 int、unsigned int或signed int 、char。
2.位段的成员名后边有一个冒号和一个数字。
比如:struct A {
int _a : 2;
int _b : 3;
int _c : 10;
int _d : 30;
};
A就是一个位段类型
冒号后边的数字表示要存储 _a 需要两个字节, _b 需要3个字节

位段的成员可以是 int、unsigned int或signed int 、char(属于整形家族)类
位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的
位段涉及很多不确定因素,位段是不跨平台的,可移植的程序应避免使用位段
VS中多余位段不会被舍弃

17 枚举的优点

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章