复合数据类型——联合union、枚举enum、结构体、Typedef、结构体对齐、结构体数组、结构体指针结构体嵌套、结构体传参、指针函数和回调函数
1、联合union
定义:
- 联合是一个能在
同一个存储空间里
(但不同时)存储不同数据的数据类型
。
代码演示:
- 例1、了解union
#include <stdio.h>
int main()
{
union u_data
{
unsigned int a;
unsigned char b;
} data;
printf("sizeof(data) = %ld\n", sizeof(data));
data.a = 0x12345678;
printf("data.b = 0x%x\n", data.b);
data.b = 0x00;
printf("data.a = 0x%x\n", data.a);
}
执行结果:
sizeof(data) = 4
data.b = 0x78
data.a = 0x12345600
代码解析:
-
data_sizeof = 4:定义一个变量,需要分配一段内存空间,联合是一个能在同一个存储空间里(但不同时)存储不同数据的数据类型,所有的成员都在一段存储空间里面,所以这段存储空间的大小由联合里面最大的那个成员决定,此处最大是int类型,所以data_sizeof = 4。
-
data.b = 0x78[^1]
-
data.a = 0x12345600[^2]
-
例2、判断当前系统是大端还是小端字节序
方法1
#include <stdio.h>
int main()
{
union u_data
{
unsigned int a;
unsigned char b;
} data;
data.a = 0x12345678;
printf("data.b = %x\n", data.b);
if (data.b == 0x78) {
printf("LSB\n");
}else{
printf("MSB\n");
}
}
执行结果:
data.b = 78
LSB
方法2
#include <stdio.h>
int main()
{
unsigned int a = 0x12345678;
unsigned char *b = NULL;
b = (char *) &a;
printf("*b = %x\n", *b);
if (*b == 0x78) {
printf("LSB\n");
}else{
printf("MSB\n");
}
}
执行结果:
*b = 78
LSB
2、枚举enum
定义:
- 可以使用枚举类型声明表示整数常量的符号名称。
代码演示:
#include <stdio.h>
int main()
{
enum
{
water,
apple,
pig,
fish = 99,
dog,
cat,
};
printf("water = %d\n", water);
printf("apple = %d\n", apple);
printf("pig = %d\n", pig);
printf("fish = %d\n", fish);
printf("dog = %d\n", dog);
printf("cat = %d\n", cat);
}
执行结果:
water = 0
apple = 1
pig = 2
fish = 99
dog = 100
cat = 101
3、结构体
定义:
- 结构题可以由基本数据类型构造出一种新的复合类型来。
代码演示:
#include <stdio.h>
int main()
{
struct st_student
{
char name[32];
char gender[32];
int age;
float score;
} ;
/******************************
两种赋值方式
第一种按顺序存储
第二种不需按顺序存储
*******************************/
struct st_student student1 = {"lyt", "male", 20, 90.0};
struct st_student student2 = {.gender = "female", .name = "ly", .score = 100.0, .age = 18};
printf(" name: %s\n male: %s\n age: %d\n score: %f\n",student1.name, student1.gender, student1.age, student1.score);
printf(" name: %s\n male: %s\n age: %d\n score: %f\n",student2.name, student2.gender, student2.age, student2.score);
}
执行结果:
name: lyt
male: male
age: 20
score: 90.000000
name: ly
male: female
age: 18
score: 100.000000
注意:
- 定义结构体时没有从内存中分配内存,在定义变量时才分配内存。
- 结构体是按顺序存放的。
4、Typedef
定义:
- Typedef用来定义一种新的数据。
代码演示:
#include <stdio.h>
int main()
{
typedef struct _st_student
{
char name[32];
char gender[32];
int age;
float score;
} st_student;
st_student student1 = {"lyt", "male", 20, 90.0};
st_student student2 = {.gender = "female", .name = "ly", .score = 100.0, .age = 18};
printf(" name: %s\n male: %s\n age: %d\n score: %f\n",student1.name, student1.gender, student1.age, student1.score);
printf(" name: %s\n male: %s\n age: %d\n score: %f\n",student2.name, student2.gender, student2.age, student2.score);
}
执行结果:
name: lyt
male: male
age: 20
score: 90.000000
name: ly
male: female
age: 18
score: 100.000000
更多用法:
typedef unsigned char u8; u8 var1,var2; //8:char一个字节8位 //var1,var2都是unsigned char类型
typedef unsigned short uint 16; uint_16 var1,var2; //16:short两个字节16位 //var1,var2都是unsigned shortr类型
typedef char * pchar; pchar var1,var2; //var1,var2都是char *类型
typedef int * pint; pint var1,var2; //var1,var2都是int *类型
#define PCHAR char * PCHAR var1,var2; //var1是PCHAR char *类型,var2是char类型
typedef struct _st_student
{
char name[32];
int gender;
int age,
float score,
} st_student;
st_student student; struct st student student;
- 在单片机开发时,尽量不要用int或short,因为在8位的单片机,int可能是16位,在32位的单片机,int是32位。
- 单片机开发一定会定义uint16,不管是多少位的单片机,都会规定是16位。
5、结构体对齐
定义:
- 自身对齐值:自身对齐值就是结构体变量里每个成员的自身大小。
- 指定对齐值:指定对齐值是由宏#pragma pack(N)指定的值,里面的N必须是2的幂次方,如1,2,4,8,16等。如果没有通过#pragma pack宏那么在32位的Linux主机上默认指定对齐值为4,64位的Linux主机上默认指定对齐值为8,ARM CPU默认指定对齐值为8。
- 有效对齐值:结构体成员自身对齐时有效对齐值为
自身对齐值
与指定对齐值
中较小的一个。结构体圆整时,为所有成员中最大的自身对齐值
与指定对齐值
较小的一个。
代码演示:
- 求结构体的长度
#include <stdio.h>
int main()
{
typedef struct _struct_sf
{
char a;
short b;
int c;
} struct_sf;
printf("struct_sizeof = %ld\n",sizeof(struct_sf));
typedef struct _struct1_sf
{
char d;
int e;
short f;
} struct1_sf;
}
执行结果:
struct_sizeof = 8
struct1_sizeof = 12
结果解析:
- struct1_sizeof = 12
注意:
- 所有的数据都存在内存中运行,内存中的数据要给cpu,通过数据线,如果cpu是32位的,就有32根数据线连在内存(0~31),内存给cpu发数据,一下子发送32位,即4个字节。如果要保持高效性,内存给的地址是4的整数倍,即内存按4的整数倍存放数据cpu就可以一下子全部读4个字节。
6、结构体数组
定义:
- 保存和访问数组
代码演示:
#include <stdio.h>
#include <string.h>
int main()
{
typedef struct _st_student
{
char id[20];
char name[32];
char gender[32];
int age;
float score;
} st_student;
st_student studenta[2] = {{"0001", "lyt", "male", 20, 90},{"0002", "ly", "female", 20, 90}};
//定义一个名为studenta的数组,一起赋值。
st_student studentb[3]; //定义一个名为studentb的数组,分开赋值。
studentb[0].age = 20;
studentb[0].score = 60;
strcpy(studentb[0].name, "yjl");
strcpy(studentb[0].gender, "female");
strcpy(studentb[0].id, "0001");
studentb[1].age = 20;
studentb[1].score = 60;
strcpy(studentb[1].name, "yjl");
strcpy(studentb[1].gender, "female");
strcpy(studentb[1].id, "0002");
studentb[2].age = 20;
studentb[2].score = 60;
strcpy(studentb[2].name, "yjl");
strcpy(studentb[2].gender, "female");
strcpy(studentb[2].id, "0003");
for(int i=0; i<2; i++)
{
printf("a\n id: %s\n name: %s\n male: %s\n age: %d\n score: %f\n", studenta[i].id, studenta[i].name, studenta[i].gender, studenta[i].age, studenta[i].score);
}
for(int i=0; i<3; i++)
{
printf("b\n id: %s\n name: %s\n male: %s\n age: %d\n score: %f\n", studentb[i].id, studentb[i].name, studentb[i].gender, studentb[i].age, studentb[i].score);
}
}
执行结果:
a
id: 0001
name: lyt
male: male
age: 20
score: 90.000000
a
id: 0002
name: ly
male: female
age: 20
score: 90.000000
b
id: 0001
name: yjl
male: female
age: 20
score: 60.000000
b
id: 0002
name: yjl
male: female
age: 20
score: 60.000000
b
id: 0003
name: yjl
male: female
age: 20
score: 60.000000
代码解析:
- studenta[2]:一起赋值,只能赋两个
- studentb[3]:分开赋值,能赋三个
注意:
- 字符串数组赋值:
/******************************
正确的赋值:
******************************/
char str[31] = "hello"; //直接赋值
char *str;
str = "hello"; //指针str指向hello的首地址
char str[31];
strcpy(str, "hello"); //用strcpy库函数赋值,要#include <string.h>
/*****************************
错误的赋值:
******************************/
char str[31];
str = "hello"; //把hello的首地址给了str数组的首地址,把str当作指针用了
- 数组名是这个数组的首地址。
7、结构体指针
代码演示:
int main()
{
typedef struct _st_student
{
char name[32];
char id[20];
char gender[20];
int age;
float score;
} st_student;
st_student student[3];
st_student *ptr = NULL;
//给结构体成员赋值
//方式一
ptr = &student[0];
strncpy(ptr->name, "lyt", 32);
strncpy(ptr->gender, "female", 20);
strncpy(ptr->id, "0001", 20);
ptr->age = 20;
ptr->score = 90;
//方式2
ptr = &student[1];
strncpy((*ptr).name, "lyt", 32);
strncpy((*ptr).gender, "female", 20);
strncpy((*ptr).id, "0002", 20);
(*ptr).age = 20;
(*ptr).score = 90;
//方式3
ptr = &student[2];
strncpy(student[2].name, "lyt", 32);
strncpy(student[2].gender, "female", 20);
strncpy(student[2].id, "0003", 20);
student[2].age = 20;
student[2].score = 90;
for(int i=0; i<3; i++)
{
printf(" \n id: %s\n name: %s\n male: %s\n age: %d\n score: %f\n", student[i].id, student[i].name, student[i].gender, student[i].age, student[i].score);
}
}
执行结果:
id: 0001
name: lyt
male: female
age: 20
score: 90.000000
id: 0002
name: lyt
male: female
age: 20
score: 90.000000
id: 0003
name: lyt
male: female
age: 20
score: 90.000000
总结:
- 最常用的是"->"。
8、结构体嵌套
代码演示:
typedef struct _st_score
{
float math;
int english;
int chinese;
} st_score;
typedef struct _st_student
{
char name[32];
int gender;
int age;
st_score score;
st_score *pscore;
} st_student;
st_student student;
st_student *ptr = &student;
//通过student访问math的四种方式
student.score.math=90.0;
student.pscore->math =90.0;
ptr->score.math =90.0;
ptr->pscore->math =90.0;
代码解析:
- 自身是变量,用
.
,自身是指针,用->
9、结构体传参
代码演示:
1)
void increase_age(st_student *p)
{
p->age++;
}
st_student student;
increase_age(&student);
2)
st_student increase_age(st_student student)
{
student.age++;
return student; //返回一整个student
}
st_student student;
student = increase_age(student);
注意:
- 现在的C允许把一个结构赋值给另一个结构,但不能对数组这么做。
例如:
错误赋值:
数组:
char a[10];
char b[10];
a = b; //这只是把b的首地址赋给a的首地址(数组名是数组的首地址)
正确赋值:
数组:
char a[10];
char b[10];
memcpy(a,b,sizeof(b));
结构体:
st_student student1;
st_student student2;
方式1:
student2 = student1;
方式2:
memcpy(&student2, &student1, sizeof(st_student));
- 字符串结尾的
\0
:\0
=0
=0x00
;strcpy碰到0以为是最后一位就会停止。
10、函数指针和回调函数
函数指针
定义:
- 指针是用来存放地址的,而函数名就是函数的入口地址。只要有地址,就一定可以用指针指向它。
- 函数只关心参数和返回值。
例如:
void fanc (int a, char *s);
{
}
void (*p) (int a, char *s) ; //可以指向函数fanc (int a, char *s),因为返回值和参数严格一致
p = func; //指针p指向func
p(); //调用函数
void (*p) (int a, char *s); //定义一个函数指针p,要指向返回值和参数严格一致的函数
void (*p) (int, char *); //形参可以去掉
注意:
- 函数指针是指针,指针指向函数类型。
- 指针函数是函数,是个返回值是函数的函数。
- void *p (int a, char *s); //这是一个函数声明,声明了一个
函数p
,返回值是void *
类型的函数。
回调函数
代码演示:
#include <stdio.h>
typedef struct _st_score
{
float math;
float english;
float chinese;
} st_score;
//******回调函数callback1******
float calc_total_score(struct _st_score score)
{
return score.chinese + score.math + score.english;
}
//******回调函数callback2******
float calc_ch_math_score(struct _st_score score)
{
return score.chinese + score.math;
}
//******回调函数callback3******
float calc_ch_eng_score(struct _st_score score)
{
return score.chinese + score.english;
}
//******中间函数******
float printf_student_score(struct _st_score score, float(*p)(struct _st_score score))
{
printf("score: %f\n", p(score));
}
//*******************
//float(*p)(struct _st_score score):函数指针,可以指向calc_total_score、calc_ch_math_score、
// calc_ch_eng_score三个函数,需要调用哪个,就指向哪个。
//p(score):调用函数,score是调用的那个函数的形参。
//*******************
int main()
{
st_score score1 = {100, 100, 100};
st_score score2 = {100, 80, 60};
printf_student_score(score1, calc_total_score);
printf_student_score(score1, calc_ch_math_score);
printf_student_score(score1, calc_ch_eng_score);
printf_student_score(score2, calc_total_score);
printf_student_score(score2, calc_ch_math_score);
printf_student_score(score2, calc_ch_eng_score);
}
执行结果:
score: 300.000000
score: 200.000000
score: 200.000000
score: 240.000000
score: 160.000000
score: 140.000000
注意:
- 注册函数:当发生相应的事件,就调用相应的函数。都是通过函数指针实行的。把函数先放到指针那里注册好,当某个事情发生的时候就调用那个函数。