C语言基础12构造类型

数据类型分类

1.基本类型

  • 整数型        
    • 短整型:short(2个字节)
    • 整型(默认):int(4个字节)
    • 长整型:long(8个字节)
    • 长长整型:long long
  • 浮点型
    • 单精度:float(4个字节)
    • 双精度:double(8个字节)
  • 字符型:char(1个字节)

注意:整数型和字符型分有符号signed和无符号unsigned,默认是有符号,有符号可以省略关键字signed

2.指针类型

  • 数据类型*:int*,char*,float*等
  • void*:任意数据类型的指针

3.空类型

  • void:没有返回值或没有形参(不能定义变量)

4.自定义类型/构造类型

  • 结构体类型:struct
  • 共用体类型(联合体):union
  • 枚举类型:enum

结构体

  • 定义:自定义数据类型的一种,关键字struct,结构体类型的变量可以存储多个不同类型的数据。
  • 定义格式:
struct 结构体名
{
    数据类型1 成员名称1;
    数据类型2 成员名称2;
    ...
}

注意:结构体中定义的变量,我们称之为成员变量。

  • 格式说明:
    • 结构体名:合法的标识符,建议单词的首字母大写
    • 数据类型:C语言支持的所有类型
    • 成员名称:合法的标识符,就是变量的命名标准
    • 数据类型n 成员名称n:类似于定义变量,定义了结构体中的成员
  • 注意:
    • 结构体在定义时,成员不能赋值
    • 举例:
struct Cat
{
    int age = 5;//错误,结构体定义时,成员不能赋值
    double heught;//正确
}
  • 常见的定义格式:
    • 方式一:常规定义(只定义类型)——推荐
struct Student
{
    int num;//学号
    char name[20];//姓名
    char sex;//性别
    int age;//年龄
}
  • 方式2:定义匿名结构体(常用于作为其他结构体的成员使用)
struct Dog 
{ 
char *name;// 姓名 
int age;// 年龄 
struct // 此时这个结构体就是匿名 
{ 
int year;// 年 
int month;// 月 
int day;// 日 
} birthday; 
} 
  • 注意:定义匿名结构体的同时必须定义结构体变量,否则编译报错,结构体可以作为另一个结 构体的成员。
  • 总结:
        1. 结构体可以定义在局部位置,也可以定义在全局位置;
        2. 全局位置的结构体名和局部位置的结构体名可以相同,就近原则(和普通变量的定义
同理)
  • 结构体类型的使用:
        利用结构体类型定义变量,定义数组;结构体类型的使用与基本数据类型的使用类似。

结构体变量的定义

  • 三种形式定义结构体变量:
        结构体变量也称为结构体的实力。
  •         第一种
                ① 先定义结构体
                ② 然后使用 struct 结构体名 变量名
// 先定义结构体(先定义结构体这个数据类型) 
struct A 
{ 
int a; 
char b; 
}
// 定义结构体变量 
struct A x; 
struct A y; 
  • 第二种
        在定义结构体的同时,定义结构体变量;
// 定义结构体的同时定义结构变量 
struct A 
{ 
int a; 
char b; 
} x,y; 
struct A z; 
此时定义了一个结构体 A x y 是这个结构体类型的变量。
  • 第三种:不推荐
        在定义匿名结构体的同时,定义结构体变量;
struct 
{ 
int a; 
char b; 
} x,y; 
struct 
{ 
int a; 
char b; 
} z; 
此时定义了一个没有名字的结构体(称为匿名结构体) ;y,x 是这个匿名结构体类型的变量;
  • 匿名结构体:---弊大于利(尽量少用)
    • 优点:少写一个结构体名称
    • 缺点:只能使用一次;定义的结构体类型的同时就必须定义变量
  • 应用场景:
    • 当结构体的类型只需要使用一次,并且定义类型的同时定义变量。
    • 作为其他结构体的成员使用。
  • 定义结构体的同时,定义结构体变量初始化
struct Cat 
{ 
int age; 
char color[20]; 
} cat; 
  • 结构体成员部分初始化是,大括号不能省略。
  • 结构体的成员,没有默认值,是不确定的数

案例:

/**
* 结构体变量的定义 
*/ 
#include <stdio.h> 
// 先定义结构体,再定义结构体变量 
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() 
{ 
fun1(); 
fun2(); 
fun3(); 
return 0; 
} 

结构体变量的使用

  • 结构体变量访问结构体成员
    • 格式
结构体变量名.成员名;
可以通过访问给成员赋值(存数据)
可以通过访问获取成员的值(取数据)
  • 结构体变量未初始化,结构体的成员值随机(不确定)
  • 结构体变量在定义时,可以初始化
    • 建议用大括号标明数据的范围
    • 结构体成员初始化时,可以部分初始化,部分初始化时一定要带大括号标明数据的范围
  • 案例:
/**
* 结构体变量的初始化 
*/ 
#include <stdio.h> 
/* 全局的结构体(数据类型) */ 
struct Dog 
{ 
char *name;// 姓名 
int age;// 年龄 
char sex;// M:公,W:母 
};
/* 先定义,再初始化 */ 
void fun1() 
{ 
// 定义一个结构体 
// struct Dog 
// { 
// char *name;// 姓名 
// int age;// 年龄 
// char sex;// M:公,W:母 
// }; 
// 定义结构体变量 
struct Dog dog; 
// 给结构体变量的成员赋值 
dog.name = "旺财"; 
dog.age = 5; 
// 访问结构体变量的成员 
printf("%s,%d,%c\n",dog.name,dog.age,dog.sex); 
}
/* 定义的同时初始化 */ 
void fun2() 
{ 
// 定义结构体变量并初始化 
struct Dog dog = {"招财",23,'M'}; 
// 修改成员的值 
dog.name = "进宝"; 
// 访问结构体变量的成员 
printf("%s,%d,%c\n",dog.name,dog.age,dog.sex); 
}
int main() 
{ 
fun1(); 
fun2(); 
return 0; 
} 

结构体数组的定义

  • 什么时候需要结构体数组?
        比如:我们需要管理一个学生对象,只需要定义一个 struct Student majie;
        假如:我们需要管理多个学生对象,此时就需要一个结构体的数组 struct Student students[64];
  • 三种形式定义结构体数组
    • 第一种:先定义结构体类型,然后定义结构体变量,再将变量存储到结构体数组中
// 定义一个学生类型的结构体 
struct Student 
{ 
char *name; 
int age; 
float scores[3];// 三门课程的成绩 
}; 
// 定义结构体对象 
struct Student zhangsan = {"张三",23,{67.5,89.0,90.0}}; 
struct Student lisi = {"李四",21,{77.0,80.0,85.0}}; 
// 定义结构化数组 
struct Student students[3] = {zhangsan,lisi};
  •          第二种:定义结构体类型,然后定义结构体数组并初始化
// 定义一个学生类型的结构体 
struct Student 
{ 
int id; 
char *name; 
int age; 
float scores[3];// 三门课程的成绩 
};
// 定义结构体数组并初始化 
struct Student students[3] = { 
{1,"张三",23,{67.5,89.0,90.0}},// 注意:这里赋值的顺序需要跟成员在结构体中的顺序一致 
{2,"李四",21,{77.0,80.0,85.0}} 
}; 
  • 第三种:定义结构体类型同时定义结构体数组并初始化
// 定义一个学生类型的结构体 
struct Student 
{ 
int id; 
char *name; 
int age; 
float scores[3];// 三门课程的成绩 
} students[3] = { 
{1,"张三",23,{67.5,89.0,90.0}},// 注意:这里赋值的顺序需要跟成员在结构体中的顺序一致 
{2,"李四",21,{77.0,80.0,85.0}} 
}; 
 
  • 第四种:定义结构体类型同时定义结构体数组,然后通过索引给结构体成员赋值
// 定义一个学生类型的结构体 
struct Student 
{ 
int id; 
char *name; 
int age; 
float scores[3];// 三门课程的成绩 
} sts[3]; 
sts[0].id = 1; 
sts[0].name = "张三"; 
sts[0].age = 12; 
sts[0].scores[0] = 98; 

小贴士:

结构体数组名访问结构体成员:
格式:结构体数组名 -> 成员名

 结构体指针

定义:结构体类型的指针变量指向结构体变量或者数组的起始地址。

语法:struct 结构体名 *指针变量列表

举例:

struct Dog
{
    char name[20];
    int age;
}
struct Dog dog;
struct Dog *p = &dog;
  • 结构体成员访问

    • 结构体数组名访问结构体成员
      • 格式:结构体数组名 -> 成员名
    • 结构体成员访问符:

:左侧是结构体变量(结构体对象、实例);也可以叫做结构体对象访问成员符;右侧是结构体成员

  • ->:左侧是一个指针,也可以叫结构体指针访问成员符;右侧是结构体成员
    • 访问结构体成员有两种类型,三种方式:

类型一:通过结构体对象访问成员

struct Stu
{
    int id;
    char name[20];
}stu;

//访问成员
stu.name;

类型二:通过结构体指针访问成员

1.指针引用访问成员

struct Stu 
{ 
int id; 
char name[20]; 
} stu; 
struct Stu *p = &stu; 
// 指针引用访问成员 
p -> name; 

2.指针解引用间接访问成员

struct Stu 
{ 
int id; 
char name[20]; 
} stu; 
struct Stu *p = &stu; 
// 指针解引用间接访问成员 
(*p).name; 

  •  结构体数组中元素的访问
struct Stu 
{ 
int id; 
char name[20]; 
float scores[3]; 
} stus[3] = { 
{1,"张三",{86,88,56}}, 
{2,"李四",{75,66,78}}, 
{3,"王五",{70,99,90}} 
};
// 取数据 --- 下标法 
printf("%s,%2f\n",stus[1].name,stus[1].scores[2]);// 李四,78 
// 结构体成员引用符号:-> 指针法 
printf("%s,%2f\n",stus -> name,stus -> scores[2]);// 张三,56 
printf("%s,%2f\n",(stus + 1)-> name,(stus + 1)-> scores[2]);// 李四,78 
printf("%s,%2f\n",(*(stus + 1)).name,(*(stus + 1)).scores[2]);// 李四,78 

小贴士

结构体是自定义数据类型,它是数据类型,用法类似于基本类型的 int
结构体数组它是存放结构体对象的数组,类似于 int 数组存放 int 数据;
基本类型数组怎么用,结构体数组就怎么用 ---> 可以遍历,可以作为形式参数,也
可以做指针等;

 结构体类型的使用案例

#include <stdio.h> 
// 定义结构体 
struct Cat 
{ 
char *name;// 姓名 
int age;// 年龄 
char color[20];// 颜色 
}
// 1.结构体类型作为形式参数 
void test1(struct Cat c); 
// 2.结构体类型作为形式参数,结构体类型作为返回值类型 
struct Cat test2(struct Cat c); 
// 3.结构体数组作为形式参数 
void test3(struct Cat cats[],int len); 
// 4.结构体数组作为形式参数,结构体指针作为返回值数据类型 
struct Cat *test4(struct Cat cats[],int len); 

测试

int main() 
{ 
// 定义结构体对象 
struct Cat cat = {"小黑",8,"baise"}; 
// 结构体对象作为实际参数 
test1(cat); 
// 定义结构体类型对象 
struct Cat cat1 = {"小白",8,"heise"}; 
// 调用函数并接收返回值 
struct Cat c1 = test2(cat1); 
// 通过返回值访问结构体对象的成员 
printf("%s==%d==%s\n",c1.name,c1.age,c1.color); 
// 定义结构体数组 
struct Cat cats[3] = { 
{"汤姆",16,"蓝色"}, 
{"杰瑞",18,"褐色"}, 
{"唐老鸭",19,"白色"} 
};
// 结构体数组名作为实际参数 
test3(cats,3); 
// 定义结构体数组并初始化 
struct Cat cats1[3] = { 
{"汤姆",16,"蓝色"}, 
{"杰瑞",18,"褐色"}, 
{"唐老鸭",19,"白色"} 
};
// 调用函数 
struct Cat *p = test4(cats1,3); 
struct Cat *w; 
// 通过指针运算遍历数组 
for(w = p; w < p + 3; w ++) 
{ 
// p[i][j] = *(p[i]+j) = *(*(p+i)+j) 三者等价 
// 通过结构体指针访问符访问结构体的成员 
printf("%s----%d----%s\n",w -> name,w -> age,w -> color); 
} 
} 

结构体类型求大小

  • 规则:字节对齐(数据在内存中存储在其类型大小的整数倍上)
        1. 首先保证结构体中的成员存储在自身的对齐边界(类型大小的整数倍);
        2. 在满足 1 的条件下,最终大小要满足 最大成员 所占存储单元的整数倍
  • 为什么要使用字节对齐:
        节省内存,提高访问效率。
  • GNU 标准中,可以在定义结构体时,指定对齐规则:
​​​​​​​__attribute__((packed)); 结构体所占内存大小是所有成员所占内存大小之和
 __attribute__((aligned(n))); 设置结构体占n个字节,如果n比默认值小,n不起作用;n必须是2的次方
  •  柔性数组:
struct st
 { 
    ...
 char a[0]; 
}

 柔性数组不占有结构体的大小。

案例:

/**
* 求结构体数据类型的大小 
*/ 
#include <stdio.h> 
// 定义测试结构体 
struct TEST1 
{ 
char a;// 1 
int b; // 4 
};
struct TEST1_1 
{ 
char a;// 1 
int b;// 4 
}__attribute__((packed));// 取消字节对齐,取消之后,结构体数据类型大小就等于其所有成员的数据类型之和 
struct TEST1_2 
{ 
char a __attribute__((aligned(2))); 
int b; 
}; 
struct TEST2 
{ 
char a;// 1 
short c; // 2 
int b; // 4 
};
struct TEST3 
{ 
int num;// 4 
char name[10];// 10 
char sex;// 1 
int age;// 4 
double score;// 8 
};
struct TEST4 
{ 
int num;// 4 
short name[5];// 10 
char sex;// 1 
int age;// 4 
int scores[2];// 8 
};
int main() 
{ 
// 创建结构体变量 
struct TEST1 test1; 
struct TEST2 test2; 
struct TEST3 test3; 
struct TEST4 test4; 
struct TEST1_1 test1_1; 
struct TEST1_2 test1_2; 
// 计算大小 
printf("%lu\n",sizeof(test1)); 
printf("%lu\n",sizeof(test2)); 
printf("%lu\n",sizeof(test3)); 
printf("%lu\n",sizeof(test4)); 
printf("%lu\n",sizeof(test1_1)); 
printf("%lu\n",sizeof(test1_2)); 
} 
  •  
  •  快速计算结构体大小:
https://blog.csdn.net/weixin_72357342/article/details/131135555
https://blog.csdn.net/x2528238270/article/details/120798606

 共用体/联合体类型

定义:
        使几个不同的变量占用同一段内存的结构。共用体按定义中需要存储空间最大的成员来分配存储单元,其他成员也是用该空间,他们的首地址是相同。
定义格式:
union 共用体名称 
{ 
数据类型 变量名; 
数据类型 变量名; 
... 
}; 
共用体的定义和结构体类型类似:
        1. 可以有名字,也可以匿名;
        2. 共用体在定义时也可以定义共用体变量;
        3. 共用体在定义时也可以初始化成员;
        4. 共用体也可以作为形参和返回值类型使用;
        5. 共用体也可以定义共用体数组
        ...
        也就是说,结构体的语法,共用体都支持。
注意:
  • 共用体弊大于利,尽量少用,一般很少用;
  • 共用体变量在某一时刻只能存一个数据,并且也只能取出一个数。
  • 共用体和结构体都是自定义数据类型,用法类似于基本数据类型
    • 共用体可以是共用体的成员,也可以是结构体的成员。
    • 结构体可以是共用体的成员,也可以是共用体的成员。
案例:
/**
* 共用体 
*/ 
#include <stdio.h> 
// 定义共用体 
union S 
{ 
char a; 
float b; 
int c; 
};
// 共用体作为共用体的成员 
union F 
{ 
char a; 
union S s; 
};
// 共用体作为结构体的成员 
struct G 
{ 
int a; 
union S s; 
};
// 定义一个结构体 
struct H 
{ 
int a; 
char b; 
};
// 结构体作为结构体成员 
struct I 
{ 
int a; 
int b; 
struct H h; 
};
// 共用体作为结构体成员 
struct J 
{ 
int a; 
char b; 
union S s; 
}; 
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);// 取数据 
c.a += 5; 
printf("%d---%d\n",c.a,c.b);// 取数据 
union E 
{ 
char *f; 
long a; 
int b; 
} e = {"hello world!"}; 
printf("%s,%p---%ld,%p---%d\n",e.f,&(e.f),e.a,&(e.a),e.b); 
}
int main() 
{ 
test1(); 
test2(); 
} 

枚举类型

定义:
        我们一般情况下,定义常量使用宏定义(#define 宏名称 值),宏定义非常适合没有关联关系的常量;但是有时候我们可能需要对一组拥有关联关系的量进行定义,比如 周一 ~ 周日 1 ~12 等,那么使用宏定义,就不是很清晰在,这个时候就需要使用到枚举。
枚举的存在就是将多个拥有关联关系的常量组合到一起,提高代码的可读性。
说明:
        枚举类型定义了一组常量,我们在开发中直接使用这些常量。(常用)
        当然枚举类型也可以类似于结构体一样定义变量等操作。(不常用)
        枚举常量有默认值,从0 开始依次加 1 ;我们可以在定义时指定它的值,如果个别没有赋值,可以根据赋值依次加1 推导。
特点:
        定义了一组常量,类似于定义了多个自定义常量(宏定义)
        提供了代码的可读性(避免了魔术数字)
定义语法:
     1.  定义枚举类型名以后就可以定义该枚举类型的变量
        enum 枚举类型名 变量表;

     

  2.在定义枚举类型的同时定义该枚举类型的变量。

enum 枚举类型名{ 枚举元素列表 }变量;

3.直接定义枚举类型变量。
enum { 枚举元素列表 } 变量表;
案例:
/**
* 枚举类型 
*/ 
#include <stdio.h> 
// 常量-宏定义 
// 常量的命名:大写英文字母+下滑下,举例:MAX_VALUE 
#define PI 3.1415926 
void test1() 
{ 
// 定义枚举类型 
enum Week 
{ 
SUN=10,MON,TUE,WED,THU,FRI,SAT 
};
printf("%d,%d,%d\n",SUN,WED,SAT); 
// 定义枚举类型的变量(先定义变量,后赋值) 
enum Week w; 
// 初始化 
w = MON; 
printf("%d\n",w); 
// 定义枚举类型的变量同时赋值(定义变量的同时赋值) 
enum Week w1 = THU; 
printf("%d\n",w1); 
enum H 
{ 
A,B,C 
} x,y; 
x = B; 
y = C; 
printf("x=%d,y=%d\n",x,y);// 1,2 
}
void test2() 
{ 
// 定义枚举 
enum CaiQuan 
{ 
SHI_TOU,JIAN_DAO,BU 
};
printf("请输入0~2之间的整数:\n[0-石头,1-剪刀,2-布]\n"); 
int number; 
scanf("%d",&number); 
switch(number) // switch和enum是天生的搭档 
{ 
case SHI_TOU: 
printf("石头\n"); 
break; 
case JIAN_DAO: 
printf("剪刀\n"); 
break; 
case BU: 
printf("布\n"); 
break; 
} 
}
int main() 
{ 
test1(); 
test2(); 
} 

typedef

  • 说明:给类型重命名,不会影响到类型本身
  • 作用:给已有的类型起别名
  • 格式:
    • ​​​​​​​​​​​​​​typedef 已有类型名 新别名;
  • 使用:
// 定义结构体 
struct Student 
{ 
int id; 
char *name; 
char sex; 
int age; 
};
// 类型重命名 
typedef struct Student Stu; 
// 定义变量 
struct Stu stu = {1,"张甜",'M',21}; 
// 定义结构体的同时类型重命名 
typedef struct PersonInfo 
{ 
int a; 
double b; 
} Per; 
// 定义变量 
struct Per per = {2,5}
应用场景
1. 数据类型复杂(结构体,共用体,枚举,结构体指针)时使用
2. 为了跨平台兼容性,例如:
        1. size_t:类型重命名后的数据类型, typedef unsigned long size_t;
        2. unit_16:类型重命名后数据类型
案例:
//类型重命名 
#include <stdio.h> 
struct Student 
{ 
int age; 
char* name; 
double score; 
int arr[3]; 
};
typedef struct Student Stu_t; 
typedef Stu_t* pStu_t; 
void test1() 
{ 
Stu_t s1 = {23, "zhangsan", 23.33, {11, 22, 33}}; 
printf("%d, %s, %f, %d\n", s1.age, s1.name, s1.score, s1.arr[0]); 
//Stu_t *p = &s1; 
Stu_t* p; 
p = &s1; 
pStu_t p2; 
p2 = p; 
printf("%d, %s, %f, %d\n", p2->age, p2->name, p2->score, p2->arr[0]); 
}
int main() 
{ 
test1(); 
return 0; 
}

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值