惠州专门做网站,wordpress外观编辑,上海网安网站建设,wordpress关闭缓存本篇博客详细介绍C语言最后的三种自定义类型#xff0c;它们分别有着各自的特点和应用场景#xff0c;重点在于理解这三种自定义类型的声明方式和使用#xff0c;以及各自的特点#xff0c;最后重点掌握该章节常考的考点#xff0c;如#xff1a;结构体内存对齐问题… 本篇博客详细介绍C语言最后的三种自定义类型它们分别有着各自的特点和应用场景重点在于理解这三种自定义类型的声明方式和使用以及各自的特点最后重点掌握该章节常考的考点如结构体内存对齐问题使用联合判断字节序的存储问题。学完本篇博客达到理解会运用
一、结构体struct 数组可理解为一种自定义数据类型它存放的是一组相同数据类型数据的集合每个数据可看作是一个变量但是现实生活只有这些内置类型还是不够的假设我想描述学生描述一本书这时单一的内置类型是不行的。描述一个学生需要名字、年龄、学号、身高、体重等;描述一本书需要作者、出版社、定价等。需要不同的数据类型因此C语言为了解决这个问题增加了结构体这种自定义的数据类型让程序员可以自己创造适合的类型。 结构体是一种存放不同数据类型的数据集合同样也是一种自定义数据类型。结构体是C语言由面向过程向面向对象的一种转变。 1.1 结构体类型的声明和结构体的嵌套以及结构体指针(是指针) 结构体类型的声明通常包括结构体的名称和其成员变量的定义结构体的成员变量可以包括基本数据类型变量、数组、结构体类型结构体的嵌套、指针类型。 结构体指针是指向结构体类型变量内存地址的指针。通过使用结构体指针可以访问或者修改结构体成员变量 下面使用一个具体的例子说明如何进行结构体类型的声明和结构体的嵌套以及定义结构体指针。 结构体的声明必须放在函数的外部它告知编译器这个结构体包括哪些成员按照如下格式进行声明 struct 结构体名 { 成员列表结构体所包含的变量或数组或结构体或指针 }; 此时结构体名就是类型名。 //.c文件中(C语言语法)普通的声明结构体的方式和定义结构体变量
struct Address
{char name[20];char area[20];
}; //分号不能少struct Student
{char * name1; //字符串指针const char * name2; //字符串指针用const修饰只可通过指针解引用访问不可进行修改char name3[20]; //字符数组int id;float score;Address address; //结构体嵌套
}; int main()
{//定义结构体变量struct Student s{张三,李四,王五,111,98.5,{王麻子,陕西}};//定义结构体指针struct Student * pss;//.cpp文件中(C语法)语普通的声明结构体的方式和定义结构体变量
struct Address
{char name[20];char area[20];
}; //分号不能少struct Student
{char * name1; //字符串指针const char * name2; //字符串指针用const修饰只可通过指针解引用访问不可进行修改char name3[20]; //字符数组int id;float score;Address address; //结构体嵌套
}; int main()
{//定义结构体变量Student s{张三,李四,王五,111,98.5,{王麻子,陕西}};//定义结构体指针Student * pss;return 0;
} 从上面可以看出.c和.cpp语法的区别在于c语法可以省略关键字struct而c语言语法不能省略struct为了实际开发统一使用可以结合使用typedef进行类型重命名便可在两个文件同时使用省略关键字struct。 //结构体声明结合typedef使用typedef struct Address
{char name[20];char area[20];
}Address; //分号不能少typedef struct Student
{char * name1; //字符串指针const char * name2; //字符串指针用const修饰只可通过指针解引用访问不可进行修改char name3[20]; //字符数组int id;float score;Address address; //结构体嵌套
}Student,*Ps; //利用typedef对结构体和结构体指针进行重命名
//相当于typedef struct Student Student
//相当于typedef struct Student* Psint main()
{//定义结构体变量Student s{张三,李四,王五,111,98.5,{王麻子,陕西}};//定义结构体指针Pss; 注意事项 在对结构体成员设计时要考虑两个方面 考虑数据的长度用合适的数据类型变量保存对于较大的数据可以用字符串进行保存 如字符数组 char str[20]; 内存开辟在栈区保存的是实际的数据 字符串指针 const char * str; 字符串存储在常量区指针开辟在栈区保存的是这个数据的地址 整型数据int存放不下时可以用字符串的方式存储也可使用上述方法。 1.2 结构体变量的定义及初始化 结构体和数组类似因此初始化方式和数组一样采用花括号赋值结构体嵌套时里面的结构体也是需要花括号进行初始化的。 注意对结构体中的字符数组或者字符串进行初始化必须使用字符串拷贝函数切不可直接赋值如s.name张三因为左边是字符数组名为字符首元素的地址地址是编号是一个常量常量不可以做左值左值必须是可以修改的变量右边是一个字符串常量存储在数据区且不可以进行修改代表首元素的地址。改正strcpy(s.name,张三) 注意事项 数组与结构体类似但是存在以下两点不同之处 结构体变量名和数组名不同数组名在表达式中会被转换成指针/地址而结构体变量名并不会无论在任何表达式中它表示的都是整个集合本身要想取得结构体变量的地址必须要加取地址符数组在内存中存放的并且随着下标的增大数组元素的地址也随着增大数组所占的字节数等于数组元素个数乘以单个元素所占字节数而结构体所占内存空间的大小需要结合内存对齐问题进行计算 Student s{张三,李四,王五,111,98.5,{王麻子,陕西}}; 结构体是一种自定义的数据类型是创建变量的模板不占用内存空间结构体变量才包含了实实在在的数据需要内存空间来存储。
1.3 结构体成员的三种访问方式 结构体成员的访问一共有3种方式但实际为了开发方便常常使用2种 1.通过结构体变量访问结构体成员语法形式结构体变量.结构体成员名 2.通过结构体指针变量先解引用找到该结构体变量再 . 访问 语法形式(*结构体指针变量).结构体成员名 3.为了简化第二种引入指向符: - 语法形式结构体指针变量-结构体成员名 实际开发常常用的是第1种和第3种第3种更加实用。 #include stdio.h
typedef struct Address
{char name[20];char area[20];
}Address,* Pa;typedef struct Student
{const char* name1; //字符串指针用const修饰只可通过指针解引用访问不可进行修改int id;float score;Address address; //结构体嵌套
}Student, * Ps;int main()
{Student s { 张三,22203,99.5,{王恒,西安市} };Ps p s;//使用代码实现访问s中的成员address中的area成员嵌套访问//第一种访问方式通过结构体变量访问结构体成员:结构体变量.结构体成员名printf(%s %d %f %s %s \n,s.name1,s.id,s.score,s.address.name,s.address.area);//第二种访问方式通过结构体指针变量先解引用找到该结构体变量再 . 访问printf(%s %d %f %s %s \n, (*p).name1, (*p).id, (*p).score, (*p).address.name, (*p).address.area);//第三种访问方式为了简化第二种引入指向符: -访问printf(%s %d %f %s %s , p-name1, p-id, p-score, p-address.name, p-address.area);return 0;
}
1.4 结构体数组(是数组) 结构体数组是包含结构体类型元素的数组。通过使用结构体数组可以创建多个结构体类型的实例并以数组的形式组织这些实例。 #include stdio.h// 结构体声明
typedef struct Person
{char name[50];int age;float height;
}Person;int main()
{// 创建结构体数组并初始化Person people[3] {{Alice, 30, 160.5},{Bob, 25, 175.0},{Charlie, 35, 180.2}};// 访问结构体数组元素for (int i 0; i 3; i) {printf(Person %d\n, i 1);printf(Name: %s\n, people[i].name);printf(Age: %d\n, people[i].age);printf(Height: %.2f\n, people[i].height);printf(\n);}return 0;
}1.5 结构体的内存对齐及结构体占用内存的计算(重点)
1.5.1 为什么存在内存对齐 计算机内存是以字节为单位划分的理论上CPU可以访问任意编号的字节但实际情况并非如此CPU通过地址总线来访问内存一次能处理几个字节的数据就命令地址总线读取几个字节的数据32位CPU一次可以处理四个字节的数据那么就从内存中读取4个字节的数据少了浪费主频多了没有用64位处理器同样每次读取8个字节。 以32位CPU为例实际寻址步长为4个字节也就是只对地址编号为4的倍数的内存寻址例如4812....这样做可以以最快的速度寻址不遗漏一个字节也不重复对一个字节寻址对于程序来说一个变量的最好位于一个寻址步长的范围内这样一次就可以读取变量的值如果跨步长存储就需要读取两次然后拼接数据效率显然降低 将一个数据尽量放在一个步长之内避免跨步长存储这就要求各种数据类型按照一定的规则在内存空间上排列这就是内存对齐提高了读取效率代价就是浪费内存空间 总体来说 结构体的内存对齐是拿空间来换取时间的做法。 那在设计结构体的时候我们既要满足对齐又要节省空间如何做到 让占用空间小的成员尽量集中在一起 1.5.2 结构体大小的计算重点 1结构体变量的首地址从偶数地址开始用0表示 2结构体每个成员相对于结构体首地址的偏移量都是min{该成员数据类型字节数指定对齐方式}大小的整数倍即每个成员在内存上开辟的起始地址必须是这个数否则存在空间浪费。 3结构体的大小为min{结构体最大基本数据类型成员所占字节数将嵌套结构体里的基本数据类型也算上得到最大的基本数据类型所占字节数指定对齐方式}大小的整数倍。 预处理指令#pragma pack(n) 可以改变默认对齐数。n 取值是 1, 2, 4, 8 , 16。 vs中默认值 8gcc中默认值 4 //练习1
#includestdio.h
struct S1
{char c1;int i;char c2;
};int main()
{printf(%d\n, sizeof(struct S1));return 0;
} //练习2
#includestdio.h
struct S2
{char c1;char c2;int i;
};int main()
{printf(%d\n, sizeof(struct S2));return 0;
} //练习3
#includestdio.h
struct S3
{double d;char c;int i;
};
int main()
{printf(%d\n, sizeof(struct S3));return 0;
} //练习4-结构体嵌套问题
#includestdio.hstruct S3
{double d;char c;int i;
};
struct S4
{char c1;struct S3 s3;double d;
};int main()
{printf(%d\n, sizeof(struct S4));return 0;
} 结构在对齐方式不合适的时候我们可以自己更改默认对齐数。 #include stdio.h
#pragma pack(8)//设置默认对齐数为8
struct S1
{char c1;int i;char c2;
};
#pragma pack()//取消设置的默认对齐数还原为默认#pragma pack(1)//设置默认对齐数为1
struct S2
{char c1;int i;char c2;
};
#pragma pack()//取消设置的默认对齐数还原为默认
1.6 结构体传参 结构体变量名代表的是整个集合本身作为函数参数时传递的是整个集合也就是说是原来结构体成员的一份临时拷贝而不是像数组名一样被编译器转换成一个指针如果结构体成员较多尤其是成员为数组时函数传参的时候参数是需要压栈会有时间和空间上的系统开销。 如果传递一个结构体对象的时候结构体过大参数压栈的的系统开销比较大所以会导致性能的下降。传送的时间和空间开销会很大影响程序的执行效率此外这种值传递不可以通过函数内部来修改外部数据最好的办法是使用结构体指针这时由实参传向形参只是传递一个地址即结构体变量的地址占用4个字节非常迅速并且可以在函数内部通过解引用结构体指针从而修改外部数据另外如果不想改变外部的数据可以给形参加上const进行修饰 结论 结构体传参的时候要传结构体的地址。 1.7 位段(了解)
1.7.1 什么是位段 有些数据在存储时并不需要占用一个完整的字节只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态用 0 和 1 表示足以也就是用一个二进位。正是基于这种考虑C语言又提供了一种叫做位段的数据结构。在结构体定义时我们可以指定某个成员变量所占用的二进制位数Bit这就是位段。 位段的成员必须是 int、unsigned int 或signed int 或者char。 位段的成员名后边有一个冒号和一个数字。 struct bs
{unsigned m;unsigned n: 4;unsigned char ch: 6;
}; :后面的数字用来限定成员变量占用的位数。成员 m 没有限制根据数据类型即可推算出它占用 4 个字节Byte的内存。成员 n、ch 被:后面的数字限制不能再根据数据类型计算长度它们分别占用 4、6 位Bit的内存。C语言标准规定位域的宽度不能超过它所依附的数据类型的长度。通俗地讲成员变量都是有类型的这个类型限制了成员变量的最大长度:后面的数字不能超过这个长度。 1.7.2 位段的内存分配 位段的成员可以是 int unsigned int signed int 或者是 char 属于整形家族类型位段的空间上是按照需要以4个字节 int 或者1个字节 char 的方式来开辟的。位段涉及很多不确定因素位段是不跨平台的注重可移植的程序应该避免使用位段。 1.7.3 位段的跨平台问题 int 位段被当成有符号数还是无符号数是不确定的。位段中最大位的数目不能确定。16位机器最大1632位机器最大32写成27在16位机 器会出问题。位段中的成员在内存中从左向右分配还是从右向左分配标准尚未定义。 当一个结构包含两个位段第二个位段成员比较大无法容纳于第一个位段剩余的位时是 舍弃剩余的位还是利用这是不确定的。 总结跟结构相比位段可以达到同样的效果并且可以很好的节省空间但是有跨平台的问题存在。
二、枚举(enum)
2.1 枚举类型的定义 在实际编程中有些数据的取值往往是有限的只能是非常少量的整数并且最好为每个值都取一个名字以方便在后续代码中使用比如一个星期只有七天一年只有十二个月一个班每周有六门课程等。#define命令虽然能解决问题但也带来了不小的副作用导致宏名过多代码松散看起来总有点不舒服。C语言提供了一种枚举Enum类型能够列出所有可能的取值并给它们取一个名字。
#include stdio.h#define Mon 1
#define Tues 2
#define Wed 3
#define Thurs 4
#define Fri 5
#define Sat 6
#define Sun 7int main(){int day;scanf(%d, day);switch(day){case Mon: puts(Monday); break;case Tues: puts(Tuesday); break;case Wed: puts(Wednesday); break;case Thurs: puts(Thursday); break;case Fri: puts(Friday); break;case Sat: puts(Saturday); break;case Sun: puts(Sunday); break;default: puts(Error!);}return 0;
} 枚举类型定义方式如下 enum 枚举类型名字 {枚举值列表} enum是一个新的关键字专门用来定义枚举类型这也是它在C语言中的唯一用途注意最后的;不能少。并且枚举值必须是有符号整型类型。枚举类型变量需要存放的是一个整数它的字节长度和 int 相同。 例如列出一个星期有几天
enum week{ Mon, Tues, Wed, Thurs, Fri, Sat, Sun };
可以看到我们仅仅给出了名字却没有给出名字对应的值这是因为枚举值默认从 0 开始往后逐个加 1递增也就是说week 中的 Mon、Tues ...... Sun 对应的值分别为 0、1 ...... 6。我们也可以给每个名字都指定一个值
enum week{ Mon 1, Tues 2, Wed 3, Thurs 4, Fri 5, Sat 6, Sun 7 };更为简单的方法是只给第一个名字指定值
enum week{ Mon 1, Tues, Wed, Thurs, Fri, Sat, Sun };
这样枚举值就从 1 开始递增跟上面的写法是等效的。 枚举是一种类型通过它可以定义枚举变量 有了枚举变量就可以把列表中的值赋给它 enum week{ Mon 1, Tues, Wed, Thurs, Fri, Sat, Sun };
enum week a, b, c;
enum week a Mon, b Wed, c Sat;
判断用户输入的是星期几。
#include stdio.h
int main(){enum week{ Mon 1, Tues, Wed, Thurs, Fri, Sat, Sun } day;scanf(%d, day);switch(day){case Mon: puts(Monday); break;case Tues: puts(Tuesday); break;case Wed: puts(Wednesday); break;case Thurs: puts(Thursday); break;case Fri: puts(Friday); break;case Sat: puts(Saturday); break;case Sun: puts(Sunday); break;default: puts(Error!);}return 0;
} 需要注意的两点是 1) 枚举列表中的 Mon、Tues、Wed 这些标识符的作用范围是全局的严格来说是 main() 函数内部不能再定义与它们名字相同的变量。 2) Mon、Tues、Wed 等都是常量不能对它们赋值只能将它们的值赋给其他的变量。 枚举和宏其实非常类似宏在预处理阶段将名字替换成对应的值枚举在编译阶段将名字替换成对应的值。我们可以将枚举理解为编译阶段的宏。 2.2 枚举类型的使用
enum Color//颜色
{RED1,GREEN2,BLUE4
};
enum Color clr GREEN;//只能拿枚举常量给枚举变量赋值才不会出现类型的差异。
clr 5; //ok??错误
2.3 枚举类型的特点 为什么使用枚举 我们可以使用 #define 定义常量为什么非要使用枚举 枚举的优点 增加代码的可读性和可维护性和#define定义的标识符比较枚举有类型检查更加严谨。便于调试使用方便一次可以定义多个常量 三、联合(共用体)(union) 在C语言中变量的定义是分配存储空间的过程。一般的每个变量都具有其独有的 存储空间那么可不可以在同一个内存空间中存储不同的数据类型不是同时存储呢 答案是可以的使用联合体就可以达到这样的目的。联合体也叫共用体在C语言中 定义联合体的关键字是union。
3.1 联合类型的定义 它的定义格式为 union 共用体名 { 成员列表 }; 共用体也是一种自定义类型可以通过它来创建变量 union data{int n;char ch;double f;
};
union data a, b, c;
3.2 联合类型的特点及利用联合判断判断字节序的存储方式
3.2.1 特点 联合的成员是共用同一块内存空间的这样一个联合变量的大小至少是最大成员的大小因为联 合至少得有能力保存最大的那个成员。 #includestdio.h
union Un
{int i;char c;
};
union Un un;int main()
{// 下面输出的结果是一样的吗printf(%d\n, (un.i));printf(%d\n, (un.c));
在联合体中所有成员共享同一块内存因此这两行输出的地址应该是相同的。//下面输出的结果是什么un.i 0x11223344;un.c 0x55;printf(%x\n, un.i);;
//由于联合体的特性此时整型成员i的值将被覆盖为字符型成员c的值因此输出的结果将是55。return 0;
}
3.2.2 利用联合判断判断字节序的存储方式
#include stdio.h
union union_test
{int s; char t;
}test;int main(){printf(联合体 union_test 所占的字节数%d\n, sizeof(union union_test));test.s 0x12345678;if (0x78 test.t) {printf(小端模式\n);}else {printf(大端模式\n);}return 0;
} 以上便是自定义类型全部内容认真理解消化一定会有极大的收获可以留下你们点赞、关注、评论您的支持是对我极大的鼓励下期再见