一个存储区的地址应该是它自身大小的整数倍(双精度浮点类型存储区的地址只需要是4的整数倍),这个规则叫数据对齐,结构体内部的存储区通常也需要遵守数据对齐的规则,数据对齐有可能导致结构体相邻子存储区之间有空隙

/*
 * 数据对齐和补齐演示
 * */
#include <stdio.h>
typedef struct {
    char buf[2];
    int num;
} tmp;
typedef struct {
    char ch1;
    int num;
    char ch2;
} tmp1;
int main() {
    printf("sizeof(tmp)是%d\n", sizeof(tmp));
    printf("sizeof(tmp1)是%d\n", sizeof(tmp1));
    return 0;
}

结构体存储区的大小必须是它所包含的占地最大,基本类型子存储区大小的整数倍(如果这个基本类型子存储区是doule类型的则结构体存储区的大小只需要是4的整数倍),这个规则叫数据补齐,数据补齐可能造成结构体最后会多占用一些无效的字节

枚举也可以用来创建新的数据类型,枚举类型存储区就是整数类型存储区,枚举类型存储区在使用的时候只能存放有限的几个整数,枚举类型也需要先声明然后才能使用,声明枚举类型的时候需要使用enum关键字,声明枚举类型的时候还需要提供一组名称,计算机为每个名称分配一个对应的整数,只有这些整数才能记录到枚举类型的存储区里.不同枚举类型存储区能记录的整数范围不同,计算机把从0开始的非负数按顺序分配给枚举类型里的所有名称,可以在声明枚举类型的时候指定把某个整数分配给某个名称,这个名称后面的所有名称被分配的整数也会随着改变

/*
 * 枚举类型演示
 * */
#include <stdio.h>
int main() {
    enum /*season*/ {CHUN, XIA = 5, QIU, DONG};
    printf("QIU是%d\n", QIU);
    return 0;
}

联合也可以用来创建数据类型,联合也需要先声明然后才能使用,声明联合的时候需要使用union关键字,联合的所有子存储区所占的内存互相重叠,联合所有成员变量存储区的开始地址都一样,联合存储区的大小就是最大子存储区的大小,联合类型的存储区可以当作多种不同类型的存储区使用,每个成员变量代表了一种可选的类型

/*
 * 联合演示
 * */
#include <stdio.h>
typedef union {
    int num;
    float fnum;
} tmp;
int main() {
    tmp utmp = {0};
    printf("&(utmp.num)是%p\n", &(utmp.num));
    printf("&(utmp.fnum)是%p\n", &(utmp.fnum));
    printf("sizeof(tmp)是%d\n", sizeof(tmp));
    return 0;
}

记录普通类型存储区地址的指针叫做一级指针,记录一级指针存储区地址的指针叫做二级指针,二级指针也需要先声明然后才能使用,声明二级指针的时候需要写两个*

/*
 * 二级指针演示
 * */
#include <stdio.h>
int main() {
    int num = 0;
    int *p_num = #
    int **pp_num = &p_num;   //二级指针声明语句
    **pp_num = 10;
    printf("num是%d\n", num);
    *pp_num = NULL;
    printf("p_num是%p\n", p_num);
    return 0;
}

二级指针名称前使用**可以表示捆绑的普通类型存储区,二级指针名称前使用*可以表示捆绑的一级指针存储区

如果把指针数组里第一个指针的地址记录到一个二级指针里就可以通过这个二级指针找到指针数组里的每个指针,这个时候可以把二级指针看作指针数组的代表,二级指针不可以代表二维数组

/*
 * 二级指针形式参数演示
 * */
#include <stdio.h>
typedef struct {
    int row;
    int col;
} pt;
typedef struct {
    pt center;
    int radius;
} circle;
void larger(const circle *p_cl1, const circle *p_cl2, circle **pp_cl) {
    *pp_cl = (circle *)(p_cl1->radius >= p_cl2->radius ? p_cl1 : p_cl2);
}
int main() {
    circle cl1 = {0}, cl2 = {0}, *p_cl = NULL;
    printf("请输入一个圆的位置:");
    scanf("%d%d%d", &(cl1.center.row), &(cl1.center.col), &(cl1.radius));
    printf("请再输入一个圆的位置:");
    scanf("%d%d%d", &(cl2.center.row), &(cl2.center.col), &(cl2.radius));
    //p_cl = larger(&cl1, &cl2);
    larger(&cl1, &cl2, &p_cl);
    printf("比较大的圆是((%d, %d), %d)\n", p_cl->center.row, p_cl->center.col, p_cl->radius);
    return 0;
}

无类型指针有可能实际记录的是一级指针的地址,这个时候就必须首先把它强制类型转换成二级指针然后才能使用

二级指针通常作为函数的形式参数使用,被调用函数可以通过二级指针形式参数向调用函数传递一个地址数据


C语言里函数也有地址,函数的名称可以表示函数的地址,函数指针可以用来记录函数的地址,函数指针也需要先声明然后才能使用,函数指针的声明可以根据函数声明变化得到

/*
 * 函数指针演示
 * */
#include <stdio.h>
int add(int num, int num1) {
    return num + num1;
}
int main() {
    int (*p_func)(int, int) = add;    //函数指针声明
    printf("add是%p\n", add);
    printf("结果是%d\n", p_func(3, 8));
    return 0;
}

函数指针也分类型,不同类型的函数指针适合与不同格式的函数捆绑

函数指针可以用来调用函数