本次重点:
1、结构体 :
在我们日常写代码中,可能会运用到生活场景,而面对一些比较复杂的问题时。比如:人们姓名和年龄、住址、号码、颜色等等,我们不可能一个一个创造变量来代替,不然那个代码量是相当多,一个人还好,如果是100、1000个人呢,那代码量就非常膨大,而这个时候我们的结构体的作用就显而易见了。
struct tem
{
};
这就是结构体的定义,tem是随意写的,而struct是不能省的,以及注意程序结尾,那里是有一个分号的。这样正常的结构体定义就完成了。
肯定是第二个正确,有我们前面学的指针可知,要想在一个函数里引用其它函数,哪怕引用的是自身也需要用指针来接收
接下来我就举一个用人的名字和号码、年龄等信息,组成一个结构体:
struct Proper
{
char name[20];
int age;
char number[20];
char address[20];
};
左边的就是结构体的特殊形式,它比平常我们定义的结构体多了两部分。
接下来我们来看看两种方式定义的结构体:
下面一行就是对应特殊形式定义的结构体,上面一行就是平常我们定义的结构体。特殊形式相较于普通形式,在代码的书写上,比较简洁,当前我们可能看不出两者有很大的区别,当我们定义的结构体名字较长时,此时特殊形式的定义的优势就显现出来了。
struct Proper
{
char name[20];
int age;
};struct net
{
int math;
struct Proper p;
};int main()
{
struct net m = { 20, {"yang", 20} };
return 0;
}
简单的嵌套使用就是这样。当然嵌套的次数不限,嵌套的次数越多程序可读性也就越差。
关于结构体对齐大家可能有点陌生,我们先来看看怎么计算结构体的大小,目前我们学过的也就是利用操作符sizeof来计算,strlen()函数是计算字符的所以在这里就不适用。
struct stu1
{
int age;
};struct stu2
{
char net;
};int main()
{
int num1 = sizeof(struct stu1);int num2 = sizeof(struct stu2);
printf("num1 = %d\n", num1);
printf("num2 = %d\n", num2);
return 0;
}
struct stu1
{
int age;
int num;
};struct stu2
{
char net;
char num;
};int main()
{
int num1 = sizeof(struct stu1);int num2 = sizeof(struct stu2);
printf("num1 = %d\n", num1);
printf("num2 = %d\n", num2);
return 0;
}
不管是一个还是两个,最终的结果都与类型本身所占的字节数有关。当然3个以及更多,结果也是这样。
那接下来我们来试试不同类型的组合:
struct stu1
{
int age;
char num;
};struct stu2
{
char net;
int num;
};int main()
{
int num1 = sizeof(struct stu1);int num2 = sizeof(struct stu2);
printf("num1 = %d\n", num1);
printf("num2 = %d\n", num2);
return 0;
}
此时的变化还是没有多大,我猜测可能会与char类型本身只占一个字节有关 所以接下来我们利用float和int类型试试:
struct stu1
{
int age;
float num;
};struct stu2
{
float net;
int num;
};int main()
{
int num1 = sizeof(struct stu1);int num2 = sizeof(struct stu2);
printf("num1 = %d\n", num1);
printf("num2 = %d\n", num2);
return 0;
}
此时变化依旧没有多大,所以我们接下来增加三个来试试:
struct stu1
{
char net;
int age;
float num;
};struct stu2
{
int mar;
float net;
char num;
};struct stu3
{
float mar;
char net;
int num;
};int main()
{
int num1 = sizeof(struct stu1);int num2 = sizeof(struct stu2);
int num3 = sizeof(struct stu3);
printf("num1 = %d\n", num1);
printf("num2 = %d\n", num2);
printf("num3 = %d\n", num3);
return 0;
}
大家现在就可以观察到发生了变化,我们在上面不是观察到与类型自身所占字节数有关,那为什么这里就不是9而是8呢,大家心里肯定产生了巨大疑问。首先我们观察,不管这三个类型的位置如何变化,在这里位置不是产生变化的原因,接下来我们就要引出结构体内存对齐的概念了。
官方的解答:结构体内存对齐是指当我们创建一个结构体变量时,会向内存申请所需的空间,用来存储结构体成员的内容。我们可以将其理解为结构体成员会按照特定的规则来存储数据内容。
当然这段话有点难理解,我们先来了解下结构体对齐的规则:
1. 第一个成员在与结构体变量偏移量为 0 的地址处。2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 = 编译器默认的一个对齐数 与 该成员大小的 较小值 。 (VS中默认的值为 8)3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
接下来我就来给你们画图了解,我就拿struct stu1来给你们解释:
首先我们看第一条规则:第一个成员不管什么类型总是从偏移量为0开始,所以看图,第一个char就从0位置哪里开始。
我们再看规则第二条除了第一个成员,其它成员要对齐到对齐数的整数倍,所以第二个成员也就是int 类型要对齐到4的整数倍,所以它只能从4开始而不能从1开始,那么有的人就会问中间空的位置怎么办,我想说的是空的位置也就是浪费了而已,我们不用管它。
最后我们看第三条规则,结构体总大小为最大对齐数,此时char的对齐数是1,int对齐数是4,float对齐数也是4,所以最大的对齐数就是4。
接下来大家看图位置站到最后也只是到了11,而我们规则是要求对齐到最大对齐数的整数倍也就是4的整数倍,所以最后结构体的总大小就是12。
第四条规则大家可以看前面我们算单个类型排位置时的规律就能理解了。
大家可以多花时间练练,练熟了就懂了。
还有一个知识点就是,当前我们vs编译器默认对齐数是8也就是这样:
我们在选类型自身对齐数时,我们是选较小的对齐数。在这里可能与第三条规则选取对齐数是相反的,但大家要牢牢记住,别记混了。
在这里给大家介绍一个预处理命令,这个预处理命名可以改变对齐数也可以自己设定,也就是#pragma。
实际应用:
#include
#pragma pack(2)struct stu1
{
char net;
int age;
float num;
};struct stu2
{
int mar;
float net;
char num;
};struct stu3
{
float mar;
char net;
int num;
};int main()
{
int num1 = sizeof(struct stu1);int num2 = sizeof(struct stu2);
int num3 = sizeof(struct stu3);
printf("num1 = %d\n", num1);
printf("num2 = %d\n", num2);
printf("num3 = %d\n", num3);
return 0;
}
大家也可以明显的观察上面这幅图,这里也就比上面多了一条预处理命名,但结果却发生了明显变化。
当然在这里就有同学问了,如果按照先前的要求这里不应该是6吗,怎么会是10呢。那么在这这里就是还有一个坑,那就是位置对齐的数目不能小于自身的数据类型所占的空间,也就是虽然此时的系统默认对齐数是2,但是int和float型,由于他们自身所占的数据类型大小就是4,4大于默认默认对齐数2了,所以它们的对齐数还是4,也就是说上面的图画的是错的,正确的图应该是下面这图:
结构体和数组传参不一样,数组传参只需要把首地址传过去,也就是数组名,而不需要取地址符号,而我们结构体就需要传地址符号,当然有一种写法不需要取地址符号如下图:
#include
struct Proper
{
char name[100];
int age;
};void print1(struct Proper ps)
{
printf("%d ", ps.age);
}void print2(struct Proper* ps)
{
printf("%d ", ps->age);
}int main()
{
struct Proper s = { "yang", 20 };
print1(s);
print2(&s);
return 0;
}
当然这两种有分别的名字,第一个不需要取地址符号的是传结构体,而第二种就是传地址。
在我们实际运用过程中,还是第二种好用点,虽然第一种更能理解,但实际上当遇上复杂代码,开辟的空间就比较多,会导致程序比较冗杂。所以我更比较推荐第二种写法。
“枚举的意思就是一一列举,将所有的情况都列举出来,那么取值的时候只能是这几种情况的一种,不能是别的。”
枚举简单的说也是一种数据类型,只不过是这种数据类型只包含自定义的特定数据,它是一组有共同特性的数据的集合。举个例子,颜色也可以定义成枚举类型,它可以包含你定义的任何颜色,当需要的时候,只需要通过枚举调用即可,另外比如说季节(春夏秋冬)、星期(星期一到星期日)等等这些具有共同特征的数据都可以定义成枚举。
enum day
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};enum gender
{
male,
female
};
根据我们平成定义的语句,两个语句之间都是用分号相隔,而在枚举中都是用“ ,”来间隔,而且最后一个语句不需要用什么符号来结束,这就是枚举与我们平常定义语句的不同之处。
2.3 枚举的用法
我们先来看看枚举默认的大小是多少:
#include
enum day
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
int main()
{
enum day a = Mon;
enum day b = Tues;
enum day c = Wed;
enum day d= Thur;printf("%d ", a);
printf("%d ", b);
printf("%d ", c);
printf("%d ", d);
return 0;
}
在枚举中都是默认第一个数据为0,因为Mon位于第一个位置,所以自动被赋值为0,然后Mon下面的其它数据依次递增。当然你可以自己赋值,在开头或者中间都可以,赋值完后它依然满足上面的规律。
如下图:
#include
enum day
{
Mon,
Tues = 100,
Wed,
Thur,
Fri,
Sat,
Sun
};
int main()
{
enum day a = Mon;
enum day b = Tues;
enum day c = Wed;
enum day d= Thur;printf("%d ", a);
printf("%d ", b);
printf("%d ", c);
printf("%d ", d);
return 0;
}
在C语言中,枚举(enum)是一种用户定义的数据类型,它可以为一组相关的整数常量定义一个名称,使得它们可以在程序中更方便地使用。枚举的优点包括:
提高代码可读性:使用枚举可以使得代码更易于阅读和理解。例如,如果有一个表示星期的枚举类型,可以将星期几表示为1到7的整数,而不是使用不明确的数字。
减少出错概率:使用枚举可以减少在程序中输入错误数字的可能性。因为枚举类型是有限的,只能使用已定义的枚举值,而不能使用其他值。
节省内存:枚举类型实际上是整数类型的子集,因此它们通常比字符串或其他数据类型占用更少的内存。
扩展性:枚举类型可以很容易地扩展,只需要在定义枚举类型的代码中添加新的枚举值即可。这使得枚举类型非常灵活,可以根据需要进行定制。
安全性:枚举类型可以提高代码的安全性,因为它们只能使用已定义的枚举值。这有助于防止输入无效值或未定义值导致程序崩溃或产生错误结果的情况。
联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
// 联合类型的声明union Un{char c ;int i ;};// 联合变量的定义union Un un ;// 计算连个变量的大小printf ( "%d\n" , sizeof ( un ));
联合在目前看来用的可能比较少。
union Un{int i ;char c ;};union Un un ;// 下面输出的结果是一样的吗?printf ( "%d\n" , & ( un . i ));printf ( "%d\n" , & ( un . c ));// 下面输出的结果是什么?un . i = 0x11223344 ;un . c = 0x55 ;printf ( "%x\n" , un . i );
这个输入至少证明了一个联合体的成员就是共用一块内存空间。
union Un1{char c [ 5 ];int i ;};union Un2{short c [ 7 ];int i ;};// 下面输出的结果是什么?printf ( "%d\n" , sizeof ( union Un1 ));printf ( "%d\n" , sizeof ( union Un2 ));