欢迎光临
我们一直在努力

C语言09-构造数据类型(自定义数据类型)

一、结构体

1.1 结构体介绍 

① 结构体:将多个数据类型(包含基本数据类型和构造数据类型)组合在一起,描述一个相对复杂的事物(个体)。

② 结构体是一种自定义数据类型。

③ 结构体在声明时不开辟内存空间,在定义时才开辟内存空间。

④ 用法:

   1)首先声明结构体类型

   2)然后定义结构体变量,并进行数据访问

1.2 定义结构体的语法

①  声明结构体格式:    // 只是声明, 不开辟内存空间。
struct  结构体名
 {
    具体的属性值1;
    具体的属性值2;
    具体的属性值3;

           ……
 }; // 有个分号,不可以省略

struct Date // 声明日期 结构体 不开辟开辟内存空间
{
    int year;
    int mon;
    int day;
};
struct Person // 声明人员信息 结构体 不开辟开辟内存空间
{
    char name[50];
    int age;
    int heigh;
    char phone[20];
    struct Date  date; // 构造类型(嵌套结构体)
};

1.3 定义结构体的变量及初始化

① 格式:struct  结构体名  变量名;

② 定义结构体变量会开辟内存空间。
③   struct Person per2; 

  • 定义结构体变量,编译器会为 per2 分配内存空间,空间大小 = 结构体所有成员大小之和(考虑内存对齐)。
  • Person是结构体名,per2是变量名。

④ struct Person *point= &per2;

  •  定义结构体指针并指向 per2,point 本身是一个指针变量,占 8 字节(64位系统)或 4 字节(32位系统)。
  •  struct Person* 是类型:指向 Person 结构体的指针,&per2 是取地址操作,获取 per2 的首地址,将 per2 的地址赋值给 point。

//定义结构体变量 ,会开辟内存空间
struct Person per;
struct Person per1={0}; // 全部清0
// 全部初始化
struct Person per2 = {"张三", 20, 1.80, "110"};//按照声明里面顺序填写

// gcc 支持的语法
struct Person per3 = {
.name = "李四",
.phone = "120",
};

1.4 结构体成员的访问

① 成员访问运算符 
         .       双目运算符 ,从左到右   1级优先级
         ->     双目运算符 ,从左到右   1级优先级

②  .  或 ->运算符的使用场景:取决于运算符的的左操作数

  • 当左操作数为变量时,使用   . 运算符
  • 当左操作数为指针时,使用  -> 运算符

即:

  • 通过变量访问成员时使用 .运算符 
  • 通过指针访问成员时使用 ->运算符 

③ per2 是变量的情况:
per2.name  ; 表达式的类型是 name 的数据类型 。 当使用访问运算符访问后,表达式的类型 由右操作数决定
④ per2 是指针的情况:
per2->name  ; 表达式的类型是 name 的数据类型 。 当使用访问运算符访问后,表达式的类型 由右操作数决定

#include <stdio.h>
#include <string.h>
// 声明日期 结构体 不开辟开辟内存空间
struct Date
{
int year;
int mon;
int day;
};
// 声明人员信息 结构体 不开辟开辟内存空间
struct Person
{
char name[50];
int age;
float heigh;
char phone[20];
// struct Date date;
};

int main(int argc, char **argv)
{
//定义结构体变量 ,会开辟内存空间
struct Person per;
struct Person per1={0}; // 全部清0
// 全部初始化
struct Person per2 = {"张三", 20, 1.80, "110"};

// gcc 支持的语法
struct Person per3 = {
.name = "李四",
.phone = "120",
};

//访问
printf("name:%s heigh:%.2f phone:%s\\n", per2.name, per2.heigh, per2.phone);
// per3 -> struct Person 自定义类型
strcpy(per3.name, "王麻子"); // per3.name -> char [50]
per3.age = 20; // per3.age – > int
per3.heigh = 1.34; // per3.heigh -> float
strcpy(per3.phone, "130"); // per3.phone-> char [20]

printf("name:%s heigh:%.2f phone:%s\\n", per3.name, per3.heigh, per3.phone);

struct Person *point = NULL; //定义了一个名为 point 的结构体指针变量,初始化为NULL
point = &per2; //让指针指向 per2
printf("name:%s heigh:%.2f phone:%s\\n", point->name, point->heigh,
point->phone);

struct Person per = {0};
printf("input name:");
scanf("%s", per.name);

printf("input age:");
scanf("%d", &(per.age));

printf("input heigh:");
scanf("%f", &per.heigh);

printf("input phone:");
scanf("%s",per.phone);
struct Person *point = &per;

printf("name:%s heigh:%.2f phone:%s\\n", point->name, point->heigh,
point->phone);

printf("sizeof per is %lu\\n",sizeof(struct Person ));
return 0;
}

1.5 结构体的大小

① 结构体的大小不是简单地将所有成员大小相加,而是要考虑内存字节对齐

② 内存对齐规则

  • 每个成员变量的起始地址必须是其类型大小的整数倍
  • 结构体的总大小必须是其最大成员对齐要求的整数倍。

struct Person 
{   
    char name[50]; // 50
    int age;   //4
    float heigh; //4
    char phone[20]; //20
 };  
printf("sizeof per is %lu\\n",sizeof(struct Person ));  // 80byte,要考虑内存对齐

③  结构体声明后,系统会对其内存空间布局进行优化调整,以提高CPU读写内存的效率。

  • 系统会根据变量的地址值及其类型大小进行求余运算。若余数为0,则该变量可存储在当前地址;否则地址递增1后重新尝试。

  • 找出结构体中占用空间最大的基本数据类型成员(4字节或8字节)。最终结构体的大小将被调整为4或8的整数倍。

1.6 结构体传参

1.6.1 值传递

void  fun(struct Person per) 
{

     //对结构体的数据成员操作, 只能读取操作。
}

① 值传递(传整个结构体)使用频率较低,
② 存在劣势:由于采用值传递方式,被调函数中需要对实参进行完整复制。函数调用结束后形参空间将被释放。若结构体占用较大内存空间,则会导致效率显著降低。

1.6.2 地址传递

void func(struct Person *p)

{   

     p->age = 30;    // 修改的是原变量

}

① 直接传递地址可提高内存使用效率,因为所有指针均为8字节大小。
② 在被调函数中,如果只需要读取数据的话,加const 修饰指针就可以了。 如果需要读写的话,去掉const修饰。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Date
{
int year;
int mon;
int day;
};
struct Person
{
char name[50]; // 这个地方是结构体的声明,不能给变量初值
int id; // 原因是没有内存空间
struct Date date; //嵌套结构体,date 是结构体变量,不是指针!
};

void fill_per(struct Person* p) // 对结构体进行修改
{
printf("input name:");
scanf("%s", p->name); // 字符串不需要 &

printf("input id:");
scanf("%d", &p->id); // int 需要 &

printf("input birth year:");
scanf("%d", &p->date.year); // 嵌套逐层访问

printf("input birth mon:");
scanf("%d", &p->date.mon);

printf("input birth day:");
scanf("%d", &p->date.day);
}

void show_per(const struct Person* p) // 对结构体进行读取操作,
{
printf("name:%s id:%d y:%d m:%d d:%d\\n", p->name, p->id, p->date.year,
p->date.mon, p->date.day);
}

int main(int argc, char** argv)
{
struct Person per = {0}; // 定义时初始化,用0填充结构体 ,清0
// memset(&per, 0, sizeof(per)); //用 memset 清零,(已存在的变量重新清零)
fill_per(&per);
show_per(&per);
return 0;
}

二、结构体数组

2.1 结构体数组定义

        先定义结构体类型,再定义数组

struct 结构体名 数组名[整形常量];

定义: strcut Person array[10];  // 定义包含10个Person元素的数组

2.2 结构体数组初始化及访问

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Date
{
int year;
int mon;
int day;
};
struct Person
{
char name[50];
int id;
struct Date date;
};
void show_per(const struct Person* p)
{ printf("name:%s id:%d y:%d m:%d d:%d\\n", p->name, p->id, p->date.year,
p->date.mon, p->date.day);
}
//void show_pers(const struct Person *array, int size) 数组退化为指针
void show_pers(const struct Person array[], int size)
{
int i = 0;
for (i = 0; i < size; i++)
{
show_per(&array[i]); //下标方式访问 array[i] 等价于 *(array + i)
}
}
int main(int argc, char** argv)
{
struct Person array[5] = {0}; //全部初始化为0,所有元素所有成员都为0
// 全部初始化
struct Person array2[3] = {
{"zhangsan", 10, {2026, 05, 10}},
{"lisi", 11, {2025, 03, 12}},
{"wangmaizi", 12, {2024, 01, 22}},
};
// gcc 支持的初始化方式
struct Person array3[3] = {
[1] = {"zhangsan", 20, {2021, 10, 1}},
}; //其他元素(下标0和2)自动初始化为0

printf("array3 name:%s y:%d m:%d d:%d\\n", array3[1].name,
array3[1].date.year, array3[1].date.mon, array3[1].date.day);
show_pers(array2, 3);

return 0;
}

三、typedef 和struct

#include <stdio.h>
#include <stdlib.h>

struct Person //定义一个名为 Person 的结构体类型
{
char name[50];
int id;
};

typedef unsigned char u8; //将 unsigned char 重命名为 u8
//定义了一个函数指针类型 PFUN,指向的函数:接受两个 int 参数,返回 int* 类型
typedef int* (*PFUN)(int, int);
//定义结构体 Person1 并同时创建别名 PER,
typedef struct Person1
{
char name[50];
int id;
} PER;

// PER == struct Person1

int main(int argc, char** argv)
{
struct Person per = {0}; // 初始化per的所有成员为0
PER per2 = {"zhangsan", 20}; // 使用PER别名创建结构体变量
struct Person1 per3 = {"lisi", 10}; // 使用原始结构体名创建变量
printf("name:%s id:%d\\n", per2.name, per2.id);
return 0;
}

四、共用体(联合体)

4.1 共用体介绍

① 共用体(Union) 是一种特殊的数据类型,允许在同一内存位置存储不同的数据类型,

但同一时刻只能存储一个成员的值。

② 其定义、初始化和成员访问方式与结构体完全相同,但在内存分配上存在关键差异:

    1)内存分配差异:

  • 结构体:为每个成员独立分配内存空间,涉及字节对齐问题
  • 共用体:仅分配最大成员所需的内存空间,所有成员共享同一块内存

    2)主要应用场景:

  • 需要节省内存空间时(在结构体和共用体均可用的场合)
  • 作为函数参数传递时,当参数存在多个互斥选项的情况(多选一)
  • 可用于判断系统的字节序(大小端模式)

③ 结构体 VS 共用体

// 结构体:每个成员独立存储

struct Student {

     int id;                // 4字节

     float score;      // 4字节

     char grade;     // 1字节

}; // 总大小 ≈ 12字节(考虑对齐)

// 共用体:所有成员共享内存

union Data {

      int id;               // 4字节

      float score;      // 4字节

      char grade;     // 1字节

 };  // 总大小 = 4字节(最大成员的大小)

4.2 共用体定义

union  共用体类型名 
{
    数据成员1;
    数据成员2;
    数据成员3;
        ……
}; // 分号不可省略

4.3 共用体初始化及访问

#include <stdio.h>
union TestAAA //共用体直接定义
{
int a; // 4字节
char b; // 1字节
long d; // 8字节
};

intmain(int argc, char **argv)
{
union TestAAA test={0}; //初始化清零
test.a =300; // 设置整数值300(十进制) = 0x12C(十六进制
printf("a is %d\\n",test.a); // a is 300
printf("b is %d\\n",test.b); // b is 44
printf("d is %ld\\n",test.d); // b is 300
test.d =123132300; // 会覆盖原来 test.a 中值 ,内存只有一份。
return 0;
}

4.4 大小端判断

#include <stdio.h>
//大小端判断
union End
{
int ia; // 4字节
char cc; // 1字节
};
int main(int argc, char **argv)
{
union End end = {0}; // 初始化为0
end.ia = 1; // 设置int值为1
printf("union size is %lu\\n",sizeof(union End )); //union size is 4
if (1 == end.cc) // 检查低字节的值,读取char成员(只读1个字节)
{
printf("小端\\n"); //小端
}
else
{
printf("大端\\n");
}
return 0;
}

五、枚举类型(enum)

① 枚举:定义一个自定义的变量,把变量可以取到所有的值一一列举出来(或者说一个取值的范围)。

② 枚举值在C语言中被视为整型(int)常量数据。

③ 枚举类型的目的是为了提高程序的可读性和可维护性。

④  声明枚举类型:

     1)格式:enum 枚举类型名 {枚举值1,枚举值2,枚举值3,枚举值4…..}

     2)枚举值 默认从0 开始,后面的值都是依次+1

      enum WEEK {MON,TUE,WED,THU,FRI,SAT,SUN};
       下标值分别为     0      1       2       3      4     5     6

     3)枚举值 ,用户可以指定初值。

          enum WEEK1 {MON1=3,TUE1,WED1,THU1,FRI1,SAT1,SUN1};
         下标值分别为     3       4        5         6        7       8        9

⑤ 定义变量

#include <stdio.h>
//枚举的定义
// MON = 0 1 2 3 4 5 6
enum WEEK {MON,TUE,WED,THU,FRI,SAT,SUN}; //默认值从0开始
//3 4 5 6 7 8 9
enum WEEK1 {MON1=3,TUE1,WED1,THU1,FRI1,SAT1,SUN1}; //显式指定起始值,后续递增
intmain(int argc, char **argv)
{

enum WEEK week= THU; // week = 3
// week =1000; 枚举这个类型,c语言中 当整形变量对待。不建议这么写。
switch(week)
{
case MON: // 0
printf("go to school\\n");
break;
case FRI: // 4
printf("to play…\\n");
break;
case SUN: // 6
printf("go to sleep\\n");
break;
default:
printf("发呆\\n"); //发呆

}
int a=20;
a += FRI; // a = 20 + 4 = 24
printf("a is %d\\n",a); //a is 24
return 0;
}

赞(0)
未经允许不得转载:171主机测评 » C语言09-构造数据类型(自定义数据类型)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址