本篇博客详细介绍C语言最后的三种自定义类型,它们分别有着各自的特点和应用场景,重点在于理解这三种自定义类型的声明方式和使用,以及各自的特点,最后重点掌握该章节常考的考点,如:结构体内存对齐问题,使用联合判断字节序的存储问题。学完本篇博客达到理解会运用!
一、结构体(struct)
1.1 结构体类型的声明和结构体的嵌套以及结构体指针(是指针)
//.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 * ps=&s;
//.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 * ps=&s;
return 0;
}
//结构体声明结合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* Ps
int main()
{
//定义结构体变量
Student s={"张三","李四","王五",111,98.5,{"王麻子","陕西"}};
//定义结构体指针
Ps=&s;
1.2 结构体变量的定义及初始化
Student s={"张三","李四","王五",111,98.5,{"王麻子","陕西"}};
结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间;结构体变量才包含了实实在在的数据,需要内存空间来存储。
1.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 为什么存在内存对齐?
1.5.2 结构体大小的计算(重点)
//练习1
#include<stdio.h>
struct S1
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d\n", sizeof(struct S1));
return 0;
}
//练习2
#include<stdio.h>
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S2));
return 0;
}
//练习3
#include<stdio.h>
struct S3
{
double d;
char c;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S3));
return 0;
}
//练习4-结构体嵌套问题
#include<stdio.h>
struct 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 结构体传参
1.7 位段(了解)
1.7.1 什么是位段
struct bs
{
unsigned m;
unsigned n: 4;
unsigned char ch: 6;
};
1.7.2 位段的内存分配
1.7.3 位段的跨平台问题
总结:跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
二、枚举(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 7
int 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 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;
}
2.2 枚举类型的使用
enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
clr = 5; //ok??错误!!!
2.3 枚举类型的特点
三、联合(共用体)(union)
在C语言中,变量的定义是分配存储空间的过程。一般的,每个变量都具有其独有的 存储空间,那么可不可以在同一个内存空间中存储不同的数据类型(不是同时存储)呢? 答案是可以的,使用联合体就可以达到这样的目的。联合体也叫共用体,在C语言中 定义联合体的关键字是union。
3.1 联合类型的定义
union data{
int n;
char ch;
double f;
};
union data a, b, c;
3.2 联合类型的特点及利用联合判断判断字节序的存储方式
3.2.1 特点
#include<stdio.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;
}
以上便是自定义类型全部内容,认真理解消化,一定会有极大的收获,可以留下你们点赞、关注、评论,您的支持是对我极大的鼓励,下期再见!