网站开发包括几个部分哪里的网络推广培训好
内容提要
-
构造类型
-
结构体
-
共用体/联合体
-
枚举
-
typedef
-
构造类型
数据类型
-
基本类型/基础类型
-
整型
-
短整型:short [int] -- 2字节
-
基本整型:int -- 4字节
-
长整型:long [int] -- 32位4字节/64位8字节
-
长长整型:long long [int] -- 8字节(大多数现代机器,旧机器可能超过8字节),C99新增
注意:以上类型又都分为signed(默认)和unsigned
-
-
浮点型
-
单精度:float -- 4字节
-
双精度:double -- 8字节
-
长双精度:long double -- C99新增
-
-
字符型:char -- 1字节
-
-
指针类型
-
数据类型*:
int* char* float*
等 -- 8字节 -
void*:通用类型指针(万能指针) -- 8字节
-
-
空值类型
-
void:无返回值,无形参(不能定义变量)
-
-
构造类型(自定义类型)
-
结构体类型:struct
-
共用体/联合体类型:union
-
枚举类型:enum
-
结构体
结构体的定义【定义类型】
-
定义:自定义数据类型的一种,关键字struct,结构体类型的变量可以存储多个不同数据类型的数据。
-
语法:
struct 结构体名 // 我们定义的数据类型的名字{数据类型1 成员名称1; // 结构体中的变量叫做成员数据类型2 成员名称2; .....};
注意:结构体中定义的变量,称之为成员变量(成员属性)
-
格式说明:
-
结构体名:合法的标识符,建议首字母大写(所谓的结构体名,就是自定义类型的类型名称)
-
数据类型n:C语言支持的所有类型(包括函数,函数在这里用函数指针表示)
-
成员名称n:合法的标识符,就是变量的命名标准
-
数据类型n成员名称n:类似于定义变量,定义了结构体中的成员
-
-
注意:
-
结构体在定义的时候,成员不能赋值,举例:
struct Cat{int age = 5; // 错误,结构体定义的时候,成员不能赋值double height; // 正确void (*run)(void); // 正确};
-
-
常见的定义格式:
-
方式1:常规定义(命名结构体,只定义数据类型) -- 推荐
struct Student{int num; // 学号char name[20]; // 姓名char sex; // 性别int age; // 年龄char address[100]; // 籍贯void (*info)(void); // 信息输出(函数指针)};
-
方式2:定义匿名结构体(常用于作为其他结构体的成员使用)
struct Dog{char *name; // 姓名int age; // 年龄// 匿名结构体struct{// 匿名结构体定义时不能省略成员int year; // 年int month;// 月int day; // 日} birthday; // 生日,匿名结构体作为其他结构体成员时,需添加成员名称,否则无法访问};
注意:定义匿名结构体的同时必须定义结构体成员,否则编译报错;结构体可以作为另一个结构体的成员
总结:
-
结构体可以定义在局部位置,也可以定义在全局位置(用的比较多,因为可以实现复用)
-
全局位置的结构体名和局部位置的结构体名可以相同,遵循就近原则(和变量的定义同理)
-
-
-
结构体类型的使用:
利用结构体类型定义变量、定义数组,也可以作为函数的返回值和参数;结构体类型的使用与基本数据类型的使用类似。
结构体变量的定义【定义变量】
-
三种形式定义结构体变量
结构体变量也称之为结构体实例
-
第1种:
①先定义结构体(定义数据类型)
②然后使用(使用数据类型定义变量)
举例:
// 定义结构体(定义数据类型)struct A{int a;char b;};// 定义结构体变量struct A x; // struct A就是数据类型,x就是变量名struct A y; // struct A就是数据类型,y就是变量名
-
第2种:
①在定义结构体的同时,定义结构体变量
语法:
struct 结构体名{数据类型1 数据成员1;...} 变量列表;
举例:
struct A{int a;char b;} x,y;
此时定义了一个结构体A,x和y是这个结构体类型的变量
-
第3种:(不推荐)
在定义匿名结构体的同时,定义结构体变量
struct {int a;char b;} x,y;struct{int a;char b;} z;
此时定义了一个没有名字的结构体(匿名结构体);x,y是这个结构体类型的变量
-
-
匿名结构体
-
优点:少写一个结构体名称
-
缺点:只能使用一次,定义结构体类型的同时必须定义变量
-
应用场景:
-
当结构体的类型只需要使用一次,并且定义类型的同时定义变量
-
作为其他结构体的成员使用
-
-
定义结构体的同时,定义结构体变量初始化
struct Cat {int age;char color[20]; } cat;
-
结构体成员部分初始化时,大括号{}不能省略
-
结构体成员,没有默认值,是随机值
-
-
-
案例
/*** 先定义结构体,再定义结构体变量*/ void fun1() {// 定义结构体struct A{int a;char b;};// 定义结构体变量/实例struct A x;struct A y; }/*** 定义结构体的同时,定义结构体变量*/ void fun2() {struct A{int a;char b;} x,y;struct A z; }/*** 定义匿名结构体的同时定义变量*/ void fun3() {struct{int a; // 结构体成员char b;} x,y;struct{int a;char b;} z; }int main(int argc, char *argv[]) {fun1();fun2();fun3();return 0; }
结构体变量的使用
-
结构体变量访问结构体成员
-
语法:
结构体变量名.成员名;
①可以通过访问给成员赋值(存数据)
②可以通过访问获取成员的值(取数据)
-
结构体变量未初始化,结构体的成员是随机值(和普通的变量、数组同理)
-
-
结构体变量在定义时,可以初始化
-
建议用大括号{}标明数据的范围
-
结构体成员初始化,可以部分初始化(和数组类似),部分初始化时一定要带大括号标明数据的范围
-
-
案例:
/* 定义全局的结构体 */ struct Dog {char *name; // 名字int age; // 年龄char sex; // M:公,W:母void (*eat)(void); // 吃狗粮 };void eat() {printf("狗狗在吃狗粮!\n"); }/*** 方式1:先定义,再初始化*/ void fun1() {// 定义结构体变量struct Dog dog;// 给结构体变量成员赋值dog.name = "旺财";dog.age = 5;dog.sex = 'M';dog.eat = eat; // 函数指针,eat指针指向eat函数// 访问结构体变量成员printf("%s,%d,%c\n",dog.name, dog.age, dog.sex);// 访问成员方法(函数)dog.eat(); }/*** 方式2:定义的同时初始化*/ void fun2() {// 定义结构体变量的同时,给变量成员初始化struct Dog dog = {"旺财",5,'M',eat};// 修改成员的值dog.name = "金宝";// 访问结构体变量成员printf("%s,%d,%c\n",dog.name, dog.age, dog.sex);// 访问成员方法(函数)dog.eat(); }int main(int argc, char* argv[]) {fun1();fun2();return 0; }
结构体数组的定义
-
什么时候需要结构体数组
比如:我们需要管理一个学生对象,只需要定义一个
struct Student yixin;
假如:我们需要管理一个班的学生对象,此时就需要定义一个结构体数组
struct Student stus[33];
-
四种形式定义结构体数组
-
第1种:结构体 → 结构体变量 → 结构体数组,举例:
// 定义一个学生结构体(定义数据类型) struct Student {char* name; // 姓名int age; // 年龄float scores[3]; // 三门课程的成绩 };// 定义结构体变量/实例 struct Student zhangsan = {"张三",21,{89,99,78}}; struct Student lisi = {"李四",22,{66,78,98}};// 定义结构体数组 struct Student stus[3] = {zhangsan,lisi};
-
第2种:结构体 → 结构体数组,举例:
// 定义一个学生结构体(定义数据类型) struct Student {char* name; // 姓名int age; // 年龄float scores[3]; // 三门课程的成绩 };// 定义结构体数组并初始化 struct Student stus[3] = { {"张三",21,{89,99,78}},{"李四",22,{66,78,98}} };
-
第3种:结构体、结构体数组一体化(含初始化),举例:
// 定义一个学生结构体(定义数据类型) struct Student {char* name; // 姓名int age; // 年龄float scores[3]; // 三门课程的成绩 } stus[3] = { {"张三",21,{89,99,78}},{"李四",22,{66,78,98}} };
-
第4种:结构体、结构体数组一体化(不含初始化),举例:
// 定义一个学生结构体(定义数据类型) struct Student {char* name; // 姓名int age; // 年龄float scores[3]; // 三门课程的成绩 } stus[3];// 赋值 stus[0].name = "张三"; stus[0].age = 21; stus[0].scores[0] = 89; stus[0].scores[1] = 99; stus[0].scores[2] = 78;stus[1].name = "李四"; stus[1].age = 22; stus[1].scores[0] = 66; stus[1].scores[1] = 78; stus[1].scores[2] = 98;
-
结构体数组的访问
语法:
结构体指针 -> 成员名
举例:
// 普通指针访问 (*p).成员名 // 结构体指针访问(结构体指针访问符:->) 等价于上面写法 p -> 成员名
案例:
/* 定义全局Student结构体 */ struct Student {int id;char *name;int age;float scores[3];void (*info)(char*,int); };/*** 定义一个函数,输出信息*/ void info(char* name,int age) {printf("大家好,我是%s,今年%d岁了!\n",name,age); }void func() {// 定义结构体实例并初始化struct Student stu1 = {1,"张三",21,{78,88,98},info};struct Student stu2 = {2,"李四",22,{90,98,78},info};// stu1.info = info;// stu2.info = info;// 定义结构体数组并初始化struct Student stus[] = {stu1,stu2};// 遍历数组// 计算lenint len = sizeof(stus)/sizeof(stus[0]);// 指针遍历// 定义一个指针,获取遍历的学生对象struct Student *p = stus;for(; p < stus + len; p++){printf("%d,%s,%d,%.2f,%.2f,%.2f\n",p->id,p->name,p->age,p->scores[0],p->scores[1],p->scores[2]);// 调用函数 p-> info 等价于 (*p).info();p -> info(p -> name, p -> age);}printf("\n"); }int main(int argc, char* argv[]) {func();return 0; }
注意:
当
->
和[]
共存的时候,它们的优先级关系:[]
>->
结构体类型
结构体数组
案例
-
需求:对候选人得票的统计程序。设有3个候选人,每次输入一个得票的候选人名字,要求最后输出各人得票的结果
-
代码:
#include <string.h>/*** 定义一个候选人结构体*/ struct Person {char name[20]; // 候选人名字int count; // 候选人票数 };/*** 定义候选人数组,并初始化*/ struct Person persons[3] = {{"诸葛蛋蛋",0},{"司马苟丹",0},{"上官铁祝",0}, };int main(int argc, char *argv[]) {// 定义循环变量int i,j;// 创建一个char数组,用来接收控制台输入的候选人的名字char leader_name[20];// 使用一个for循环,模拟10次投票for(i = 0; i < 10; i++){printf("请输入您要投票的候选人姓名:\n");scanf("%s",leader_name);// 给被投票的候选人+1票for(j = 0; j < 3; j++){// 判断两个字符串的结果是否相同if(strcmp(leader_name,persons[j].name) == 0){persons[j].count++;}}}printf("\n投票结果:\n");// 方式1struct Person *p = persons; // p就是结构体指针for(; p < persons + 3; p++){// printf(" %s:%d\n",(*p).name, (*p).count);printf(" %s:%d\n",p -> name, p -> count);}printf("\n");// 方式2for(i = 0; i < 3; i++){printf(" %s:%d\n",(persons+i) -> name, (persons+i) -> count);}return 0; }
结构体指针
-
定义:结构体类型的指针变量指向结构体变量或者数组的起始地址
-
语法:
struct 结构体名 *指针变量列表;
-
举例:
struct Dog {char name[20];int age; };struct Dog dog = {"苟富贵",5};// 基于结构体的构造体指针 struct Dog *p = &dog;
结构体成员的访问
-
结构体成员访问
-
结构体数组名访问结构体成员
-
语法:
结构体数组名 -> 成员名; // 等价于如下写法 (*结构体数组名).成员名;
-
举例:
printf("%s:%d\n",persons -> name, persons -> count);
-
-
结构体成员访问符
-
.
:左侧是结构体变量,也可以叫做结构体对象访问成员符,右侧是结构体成员 -
->
:左侧是结构体指针,也可以叫做结构体指针访问成员符,右侧是结构体成员 -
举例:
struct Person *p = persons; // p 就是构造体指针 for(; p < persons + 3; p++)printf("%s:%d\n",p -> name, p -> count);
-
-
访问结构体成员有两种类型,三种方式:
-
类型1:通过结构体变量(对象|实例)访问成员
struct Stu {int id; // 结构体成员char name[20]; } stu; // 结构体变量// 访问成员 stu.name;
-
类型2:通过结构体指针访问成员
-
第1种:指针引用访问成员
struct Stu {int id; // 结构体成员char name[20]; } stu; // 结构体变量 | 对象 | 实例struct Stu *p = &stu; // 指针引用访问成员 p -> name; // 等价于(*p).name
-
第2种:指针解引用间接访问成员
struct Stu {int id; // 结构体成员char name[20]; } stu; // 结构体变量 | 对象 | 实例struct Stu *p = &stu; // 指针解引用访问成员 (*p).name; // 等价于 p -> name
-
-
结构体数组中元素的访问
struct Stu {int id;char name[20];float scores[3]; } stus[3] = {{1,"张三",{67,77,88}},// (stus+1){2,"李四",{90,99,98}},{3,"王五",{88,97,77}} }; // 取数据 -- 下标法 printf("%s,%.2f\n",stus[1].name,stus[1].scores[1]); // 李四 99// 取数据 -- 指针法(->) printf("%s,%.2f\n",stus -> name,stus -> scores[2]); // 张三 88 printf("%s,%.2f\n",(stus+1) -> name,(stus+1) -> scores[2]); // 李四 98 printf("%s,%.2f\n",(*(stus+1)).name,(*(stus+1)).scores[2]); // 李四 98
小贴士:
结构体是自定义数据类型,它是数据类型,用法类似于基本类型的int
结构体数组它是存放结构体对象的数组,类似于int数组存放int数据
基本类型数组怎么用,结构体数组就怎么用--->可以遍历,可以作为形式参数,也可以做指针等
-
-
-
结构体类型的使用案例
结构体可以作为函数的返回类型、形式参数等
举例:
#include <string.h>/*** 定义结构体*/ struct Cat {char *name; // 姓名int age; // 年龄char color[20]; // 颜色 };/*** 结构体类型作为形式参数*/ void test1(struct Cat c) {printf("test1:\n%s,%d,%s\n",c.name, c.age, c.color); }/*** 结构体类型作为形式参数,结构体类型作为返回值类型*/ struct Cat test2(struct Cat c) {return c; }/*** 结构体数组作为形式参数*/ struct Cat test3(struct Cat cats[], int len) {struct Cat *p = cats;for(; p < cats + len; p++){printf("test3:\n%s,%d,%s\n",p -> name, p -> age, p -> color);} }/** * 结构体指针作为形式参数,结构体指针作为返回类型 */ struct Cat *test4(struct Cat *cats, char *name) {struct Cat *p = cats;// 遍历for(; p < cats + 3; p++){if(strcmp(name, p -> name) == 0) return p;}return NULL;// 指针返回空 }struct Cat *test5(struct Cat (*cats)[3], char *name) {//数组指针遍历:用指针偏移for(int i = 0; i < 3; i++){if(strcmp(name, (*cats)[i].name) == 0)return &(*cats)[i]; //*cats是整个数组的指针}return NULL; }int main(int argc,char *argv[]) {// 定义结构体对象struct Cat cat = {"狗狗", 8, "yellow"};// 结构体对象作为实际参数test1(cat);// 结构体作为函数参数和返回值类型struct Cat res_cat = test2(cat);printf("test2:\n%s,%d,%s\n",res_cat.name, res_cat.age, res_cat.color);// 定义结构体数组struct Cat cats[] = {{"汤姆",16,"blue"},{"杰瑞",18,"green"},{"索菲",19,"red"}};// 结构体作为函数的实际参数test3(cats,3);// 结构体指针作为形式参数,结构体指针作为返回类型struct Cat *p = test4(cats,"汤姆");printf("test4:\n%s,%d,%s\n", p -> name, p -> age, p -> color);struct Cat *q = test5(&cats, "汤姆");printf("test5:\n%s,%d,%s\n", q -> name, q -> age, q -> color);return 0; }
结构体类型求大小
字节对齐
-
字节对齐的原因:
-
硬件要求:某些硬件平台(如ARM、x86)要求特定类型的数据必须对齐到特定地址,否则会引发性能下降或硬件异常
-
优化性能:对齐的数据访问速度更快。例如,CPU访问对齐的
int
数据只需一次内存操作,而未对齐的数据可能需要多次操作
-
-
字节对齐规则:
-
默认对齐规则
-
结构体的每个成员按其类型大小和编译器默认对齐数(通常是类型的自然对齐数)对齐
-
结构体的总大小必须是最大对齐数的整数倍
-
-
对齐细节
-
基本类型的对齐数:
char
(1字节)、short
(2字节)、int
(4字节)、double
(8字节) -
结构体成员的对齐:每个成员的起始地址必须是对齐数的整数倍
-
结构体总大小的对齐:结构体的总大小必须是其最大对齐数的整数倍
-
-
#pragma pack(n)
的影响:使用#pragma pack(n)
可以强制指定对齐数为n
(n
为 1、2、4、8、16)。此时:-
每个成员的对齐数取
n
和其类型大小的较小值 -
结构体的总大小必须是
n
和最大对齐数中的较小值的整数倍
-
-
-
对齐示例
-
默认对齐
struct S1 {char c; // 1字节(偏移0)int i; // 4字节(需对齐到4,填充3字节,偏移4-7)double d; // 8字节(需对齐到8,偏移8-15) };
struct S1 { double d; // 8字节(偏移0-7)int i; // 4字节(需对齐到8,偏移8-11)char c; // 1字节(需对齐到12,还需填充3字节) };
struct S1 {char c; // 1字节(偏移0,填充7字节)double d; // 8字节(需对齐到8)int i; // 4字节(需对齐到16,填充4字节) };
总结:结构体中,成员的顺序会影响到结构体最终的大小
-
-
使用
#pragma pack(1)
#pragma pack(1) struct S1 {char c; // 1字节(偏移0)int i; // 4字节(偏移1-4)double d; // 8字节(偏移5-12) }; #pragma pack() // S1 的大小为 13字节
#pragma pack(2) struct S1 {char c; // 1字节(偏移0,填充1字节)int i; // 4字节(偏移2-5)double d; // 8字节(偏移6-13) }; #pragma pack() // S1 的大小为 14字节
#pragma pack(4) struct S1 {char c; // 1字节(偏移0,填充3字节)int i; // 4字节(偏移4-7)double d; // 8字节(偏移8-15) }; #pragma pack() // S1 的大小为 16字节
-
在GNU标准中,可以在定义结构体时,指定对齐规则:
__attribute__((packed)); // 结构体所占内存大小是所有成员所占内存大小之和 ——attribute__((aligned(n))); // 设置结构体占n个字节,如果n比默认值小,n不起作用;n必须是2的次方
案例:
int main(int argc,char *argv[]) {struct Cat{char sex __attribute((aligned(2)));// 1 -- 2int id;// 4char name[20];// 20} __attribute__((packed)); // 结构体所占内存大小是所有成员所占内存大小之和,packed---结构体取消所有填充printf("%ld\n",sizeof(struct Cat));// 默认字节对齐(28)/ 使用packed后(25)return 0; }// 编译结果是26:存在隐藏行为 // 1. aligned(2) 覆盖结构体对齐:GCC 可能将结构体的整体对齐要求设为 max(2, 1) = 2(忽略 id 的 4 字节对齐,因 packed 冲突)。 // 2. 结构体填充规则:总大小需为 2 的倍数 → 25 → 26 字节
柔性数组
定义:柔性数组不占有结构体的大小
语法:
struct St {...char arr[0]; };
案例:
int main(int argc,char *argv[]) {struct Cat{char sex __attribute((aligned(2)));// 1 -- 2int id; // 4char arr[0]; // 0 柔性数组不占用结构体的大小char name[20]; // 20} __attribute__((packed)); // 结构体所占内存大小是所有成员所占内存大小之和printf("%ld\n",sizeof(struct Cat));// 默认字节对齐(28)/ 使用packed后(25)/ 使用aligned之后(26)return 0; }
课堂练习
计算以下结构体的大小
// 定义测试结构体 struct TEST1 {char a;int b; };struct TEST1_1 {char a;int b; }__attribute__((packed));// 取消字节对齐,取消之后,结构体数据类型大小就等于其所有成员的数据类型之和struct TEST1_2 {char a __attribute__((aligned(2)));int b; };struct TEST2 {char a;short c;int b; };struct TEST3 {int num;char name[10];char sex;int age;double score; };struct TEST3_1 {int num;char name[10];char sex;double score;int age; };struct TEST4 {int num;short name[5];char sex;int age;int scores[2]; };int main(int argc,char *argv[]) {// 创建结构体变量struct TEST1 test1;struct TEST2 test2;struct TEST3 test3;struct TEST3_1 test3_1;struct TEST4 test4;struct TEST1_1 test1_1;struct TEST1_2 test1_2;// 计算大小printf("%lu\n",sizeof(test1));// 8printf("%lu\n",sizeof(test2));// 8printf("%lu\n",sizeof(test3));// 32printf("%lu\n",sizeof(test3_1));// 32printf("%lu\n",sizeof(test4));// 28printf("%lu\n",sizeof(test1_1));// 5printf("%lu\n",sizeof(test1_2));// 8return 0; }
共用体/联合体类型
-
定义:使几个不同变量占用同一段内存的结构。共用体按定义中需要存储空间最大的成员来分配存储单元,其他成员也是使用该空间,它们的首地址是相同的
-
定义格式:
union 共用体名称 {数据类型 成员名;数据类型 成员名;... };
-
共用体的定义和结构体类似
-
可以有名字,也可以匿名
-
共用体在定义时也可以定义共用体变量
-
共用体在定义时也可以初始化成员
-
共用体也可以作为形参和返回值类型使用
-
共用体也可以定义共用体变量
-
...
也就是说,结构体的用法,共用体都支持
-
-
注意:
-
共用体弊大于利,尽量少用,一般很少用;
-
共用体变量在某一时刻只能存储一个数据,并且也只能取出一个数
-
共用体所有成员共享同一内存空间,同一时间只能存储一个值,可能导致数据覆盖
-
共用体和结构体都是自定义数据类型,用法类似于基本数据类型
-
共用体可以是共用体的成员,也可以是结构体的成员
-
结构体可以是结构体的成员,也可以是共用体的成员
-
-
-
案例:
// 定义共用体 union S {char a;float b;int c; };// S的大小是4字节// 共用体作为共用体成员 union F {char a;union S s; // 4字节 };// 大小是4字节// 共用体作为结构体的成员 struct G {int a;union S s; };// 大小是8字节// 定义一个结构体 struct H {int a;char b; };// 大小是8字节// 结构体作为结构体成员 struct I {int a;int b;struct H h; };// 大小是16字节// 共用体作为结构体成员 struct J {int a;// 4char b;// 1union S s;// 4 };// 大小是12字节void test1() {// 定义一个共用体union Stu{int num;char sex;double score;};// 定义匿名共用体union{int a;char c;} c;// 匿名共用体,一定要定义出变量名,否则报错printf("%lu,%lu\n",sizeof(union Stu),sizeof(c)); }void test2() {union C{int a;char b;};// 定义变量union C c;// 存储数据c.a = 10;c.b = 'A';printf("%d---%d\n",c.a,c.b); // 65---65c.a += 5; // c.a = 65 + 5printf("%d---%d\n",c.a,c.b); // 70---70union E{char *f;// 8 存储的是 hello world! 的首地址long a;// 8int b;// 4} e = {"hello world!"}; // hello world存储在数据段printf("%s,%ld,%d\n",e.f,e.a,e.b); } int main(int argc,char *argv[]) {test1();test2();return 0; }