03-数组和结构体

一维数组的数组名

除了两种情况(sizeof和取地址步长)以外,一维数组名指向数组第一个元素的指针

// 一维数组名称
void test01()
{
    // 一维数组的名 是不是指向第一个元素的指针?
    int arr[5] = {1, 2, 3, 4, 5};
    printf("sizeof arr = %d\n", sizeof(arr));

    // 对数组名取地址
    printf("%d\n%d\n", &arr, &arr+1);

    // 第一种 对数组名称sizeof
    // 第二种 对数组名称取地址,步长是整个数组长度
    // 除了这两种情况以外,一维数组名 指向数组第一个元素指针
    int *p = arr;

    // 数组名 指针常量 int * const p
    // arr = NULL;  // 指针的指向是不可修改的
    // arr[0] = 100;   // 指针指向的值是可以修改的

    // 数组访问试试,下标可以为负数,如果指针向后偏移,负数为向前走
    p = p + 3;
    // p[-1] = *(p - 1);
    printf("arr[-1] %d\n", p[-1]);
}

一维数组名做函数参数

// 数组作形参最好用这种写法,提高了可读性,虽然用int *arr也行
void printArr(int arr[], int length)
{
    for (int i = 0; i < length; i++) {
        printf("%d ", arr[i]);
    }
    for (int i = 0; i < length; i++) {
        printf("%d ", *(arr + i));
    }
}
void test02()
{
    int arr[5] = {1, 2, 3, 4, 5};
    printArr(arr, sizeof(arr) / sizeof(arr[0]));
}

数组指针定义

// 这种最好理解,最常用
// 3.直接定义数组指针
void test03()
{
    int arr[5] = {1, 2, 3, 4, 5};
    
    // 语法 数组元素类型 (*数组指针变量名称) [元素个数]
    int (*p)[5] = &arr;
    
    // *p = arr
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d\n", i, (*p)[i]);
    }
}

// 1.先定义出数组的类型,在通过类型定义出数组指针
void test01()
{
    int arr[5] = {1, 2, 3, 4, 5};

    // 这行代码使用 typedef 关键字创建了一个类型别名 ARRAY_TYPE,该
    // 别名表示了一个包含5个整数的数组类型
    typedef int(ARRAY_TYPE)[5];

    // ARRAY_TYPE* arrP就是一个指针了, 指针指向包含4个int元素的数组 与 int(*p)[4] 等价
    ARRAY_TYPE *arrP = &arr;
    // *arrP ==== arr 相当于左右两侧都加了个解引用 *arrP = *(&arr) = arr

    // 使用 arrP 访问数组元素
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d\n", i, (*arrP)[i]);
    }

    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d\n", i,  *((*arrP)+i));
    }
}

// 2.先定义出数组指针的类型,通过类型创建数组指针变量
void test02()
{
    int arr[5] = {1, 2, 3, 4, 5};

    typedef int(*ARRAY_TYPE)[5];

    ARRAY_TYPE arrP;
    arrP = &arr;

    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d\n", i, (*arrP)[i]);
    }
}

二维数组数组名

void test01()
{
    // 这种定义方式,可读性强
    int arr[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };

    // 一般情况下,指向第一行数组指针
    int (*p)[3] = arr;
    // *(p+1)指向第二行第一个,然后再+2跑到6的位置
    printf("%d\n", (*(p+1))[2]);
    printf("%d\n", p[1][2]);
    // for (int i = 0; i < 3; i++) {
    //     printf("");
    // }
}

// 二维数组做函数参数
void printArr(int arrp[][3], int row, int col)
// void printArr(int (*arrp)[3], int row, int col)
{
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
            printf("%d ", arrp[i][j]);

            // 可读性不高,但是需要理解
            // printf("%d ", *(*(arrp + i) + j));
        }
        printf("\n");
    }
}
void test02()
{
    int arr[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    printArr(arr, 3, 3);   

    printf("sizeof(arr) = %d\n", sizeof(arr));
    printf("arr = %d \narr + 1 = %d\n", &arr, &arr + 1);

}

指针数组排序

// 交换两个整数的值
void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 选择排序函数
void selectionSort(int arr[], int n)
{
    int i, j, min_index;

    // 遍历整个数组
    for (i = 0; i < n - 1; i++)
    {
        // 假设当前未排序部分的第一个元素是最小的
        min_index = i;

        // 在未排序部分找到最小元素的索引
        for (j = i + 1; j < n; j++)
        {
            if (arr[j] < arr[min_index])
            {
                min_index = j;
            }
        }

        // 将最小元素与当前未排序部分的第一个元素交换位置
        swap(&arr[i], &arr[min_index]);
    }
}

// 字符串选择排序函数
void charselectionSort(const char *arr[], int n)
{
    int i, j, min_index;

    // 遍历整个数组
    for (i = 0; i < n - 1; i++)
    {
        // 假设当前未排序部分的第一个元素是最小的
        min_index = i;

        // 在未排序部分找到最小元素的索引
        for (j = i + 1; j < n; j++)
        {
            if (strcmp(arr[j], arr[min_index]) > 0)
            {
                min_index = j;
            }
        }

        // 将最小元素与当前未排序部分的第一个元素交换位置
        if (i != min_index)
        {
            const char *temp = arr[i];
            arr[i] = arr[min_index];
            arr[min_index] = temp;
        }
    }

    for (int i = 0; i < n; i++)
    {
        printf("%s ", arr[i]);
    }
    printf("\n");
}
void test01()
{
    int arr[] = {64, 25, 12, 22, 11};
    int n = sizeof(arr) / sizeof(arr[0]);
    selectionSort(arr, n);

    printf("\n排序后的数组:\n");
    for (int i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
    const char *parr[] = {"ccc", "bbb", "aaa", "ddd", "fff", "eee"};
    charselectionSort(parr, sizeof(parr) / sizeof(parr[0]));

    const char **arrp = parr;
    for (int i = 0; i < sizeof(parr) / sizeof(parr[0]); i++)
    {
        printf("%s ", arrp[i]);
    }
}

结构体赋值问题以及解决

问题:挡结构体有属性创建在堆区,赋值之后,释放会导致堆区属性重复释放,并且有内存泄漏
系统提供复制->浅拷贝
解决:利用手动赋值行为,解决问题,解决方式:利用深拷贝,重新在堆区创建内存

name 定义在栈上,这样赋值没有问题

struct Person
{
    char name[64];
    int age;
};

// 一维数组名称
void test01()
{
    struct Person p1 = {"John", 10};
    struct Person p2 = {"TOM", 11};

    p1 = p2;
    printf("%s %d\n", p1.name, p1.age);
    printf("%s %d\n", p2.name, p2.age);
}

name定义在堆上,当执行p1 = p2;时,浅拷贝(逐字节拷贝)出现问题

在释放内存之前,将 p1 的内容设置为 p2,这意味着 p1.name 指向 p2.name 指向的内存,当释放 p1.name 时,也释放了 p2.name,导致重复释放内存。而且之前的p1.name内存没有被释放

![[Pasted image 20230921202614.png]]


struct Person2
{
    char *name;
    int age;
};
// 一维数组名称
void test02()
{
    struct Person2 p1, p2;
    p1.name = (char *)malloc(sizeof(char) * 64);
    strcpy(p1.name, "John");
    p1.age = 10;

    p2.name = (char*)malloc(sizeof(char) * 128);
    strcpy(p2.name, "Tom");

    if (p1.name == NULL || p2.name == NULL) {
    // 处理内存分配失败的情况
    printf("Memory allocation failed.\n");
    exit(1);
    }

    p2.age = 11;

    p1 = p2;

    printf("%s %d\n", p1.name, p1.age);
    printf("%s %d\n", p2.name, p2.age);

    if (p1.name != NULL) {
        free(p1.name);
        p1.name = NULL;
    }

    if (p2.name != NULL) {
        free(p2.name);
        p2.name = NULL;
    }
}

结构体嵌套一级指针

typedef struct Person
{
    char *name;
    int age;
}Person;

// 一维数组名称
void test01()
{
    // 二级指针,指向三个一级指针
    Person **person_ptr = (Person**)malloc(sizeof(Person*) * 3);

    for (int i = 0; i < 3; i++) {
        person_ptr[i] = (Person*)malloc(sizeof(Person));
        person_ptr[i]->name = (char*)malloc(sizeof(char)*64);
        sprintf(person_ptr[i]->name, "name_%d", i+1);
        person_ptr[i]->age = 18 + i;
    }

    for (int i = 0; i < 3; i++) {
        printf("person%d, name:%s, age:%d\n", i, person_ptr[i]->name, person_ptr[i]->age);
    }

    // 释放
    for (int i = 0; i < 3; i++) {
        printf("person%d is freed!\n", i);        
        
        if (person_ptr[i] != NULL) {
            if (person_ptr[i]->name != NULL) {
                free(person_ptr[i]->name);
                person_ptr[i]->name = NULL;
            }
            free(person_ptr[i]);
            person_ptr[i] = NULL;
        }
    }

    // 释放 person_ptr 数组本身的内存
    free(person_ptr);
}

结构体嵌套二级指针

在堆区分配3个老师i的指针
将3个老师分配到堆区
将老师姓名分配到堆区
老师带的学生数组分配到堆区
学生姓名分配到堆去
打印所有数据
释放

typedef struct Teacher
{
	char * name;
	char ** Students;
}Teacher;


void allocateSpace(Teacher*** teacherAyy)
{
    // 堆区分配内存
    // ts是个指针数组,指向三个指针
    Teacher **ts = (Teacher**)malloc(sizeof(Teacher*)*3);

    for (int i = 0; i < 3; i++) {
        // 给老师分配内存
        ts[i] = (Teacher*)malloc(sizeof(Teacher));

        // 给老师姓名属性分配内存和赋值
        ts[i]->name = (char*)malloc(sizeof(char) * 64);
        sprintf(ts[i]->name, "teacher_%d", i + 1);

        // 给老师带领的学生分配内存
        ts[i]->Students = (char**)malloc(sizeof(char*)*3);
        for (int j = 0; j < 3; j++) {
            ts[i]->Students[j] = (char*)malloc(sizeof(char));
            sprintf(ts[i]->Students[j], "%sstudent_%d", ts[i]->name, j + 1);
        } 
    }


    // 对三级指针解引用变为二级指针,然后赋值
    // 建立关系
    *teacherAyy = ts;

}

void printTeacher(Teacher** teacherArr)
{
    for (int i = 0; i < 3; i++)
    {
        printf("teacher_name:%s\n",teacherArr[i]->name);
        // 打印带的学生
        for (int j = 0; j < 3; j++) {
            printf("student_info->%s\n",teacherArr[i]->Students[j]);
        }
    }
    

}

void freeSpace(Teacher** teacherArr)
{
    // 释放要从下往上释放
    if (!teacherArr) {
        printf("teacherArr is none\n");
        return;
    }

    // 从下到上释放
    for (int i = 0; i < 3; i++) {
        if (teacherArr[i]->name != NULL) {
            free(teacherArr[i]->name);
            teacherArr[i]->name = NULL;
        }

        for (int j = 0; j < 3; j++) {
            if (teacherArr[i]->Students[j] != NULL) {
                free(teacherArr[i]->Students[j]);
                teacherArr[i]->Students[j] = NULL;
            }
        }
        // 释放学生数组
        free(teacherArr[i]->Students);
        teacherArr[i]->Students = NULL;

        // 释放老师
        free(teacherArr[i]);
        teacherArr[i] = NULL;
    }
}
void test01()
{
    // 教师数组创建
    Teacher ** teacherArr = NULL;

    // 分配内存
    allocateSpace(&teacherArr);

    // 打印内容
    printTeacher(teacherArr);

    // 释放,把teacherArr指向的空间释放
    freeSpace(teacherArr);
    teacherArr = NULL;
}

结构体偏移量

typedef struct Student
{
    char a;
    int b;
}Student;

// 计算偏移量
void test01()
{
    Student s1;
    Student *p;
    p = &s1;
    // 想算的属性的地址,减去首地址
    printf("b的偏移量:%d\n", (int)&(p->b) - (int)p);

    printf("b的偏移量:%lld\n", (long long)offsetof(Student, b));

}

void test02()
{
    Student s1;
    Student *p;
    p = &s1;
    p->b = 10;
    // 打印值
    long long offset = (long long)offsetof(Student, b);
    // char*的步长是1
    printf("b:%d\n", *(int*)((char*)p+offset));
    printf("b:%d\n", *(int*)((int*)p+1));
}

typedef struct Student2
{   
    char a;
    int b;
    Student s;
}Student2;
void test03()
{
    // 结构体嵌套结构体初始化
    Student2 s = {'a', 10, {'b', 2000}};
    Student2 *sp = &s;
    // 访问到2000

    // 先找到结构体s的偏移量
    int offset1 = (int)offsetof(Student2, s);
    // 再找到2000的偏移量
    int offset2 = (int)offsetof(Student, b);
    printf("属性s中的b的值为->%d\n", *(int*)((char*)sp+offset1+offset2));

    printf("属性s中的b的值为->%d\n", ((Student*)((char*)sp+offset1))->b);

}

内存对齐

![[Pasted image 20230923204931.png]]

结构体计算内存对齐原则

  1. 第一个属性开始 从0开始偏移
  2. 第二个属性 要放在 min(该类型大小, 对齐模数) 的整数倍
  3. 所有属性计算之后,整体做二次偏移
  4. 计算结果 放在 min(该结构体中最大数据类型,对齐模数)的整数倍上
#pragma pack(show) //查看对齐模数
//#pragma pack(1) //默认对齐模数为 8  对齐模数可以改为 2的n次方


//对于自定义数据类型  内存对齐规则 如下:
//1、从第一个属性开始  偏移为0 
//2、第二个属性开始,地址要放在  该类型整数倍   与 对齐模数比  取小的值 的整数倍上
//3、所有的属性都计算结束后,整体再做二次对齐,整体需要放在属性中最大类型 与 对齐模数比 取小的值的整数倍上
typedef struct _STUDENT{
	//       对齐模数8      对齐模数1
	int a;   // 0 ~ 3        0  ~ 3
	char b;  // 4 ~ 7        4 
	double c;// 8  ~ 15      5  ~ 12
	float d; // 16 ~ 19      13 ~ 16
}Student;


void test01()
{
	Student s;
	printf("size of =  %d\n", sizeof(Student));

	// printf("对齐模数:%zu\n", alignof(s.a));  // 打印int类型的对齐模数
    // printf("对齐模数:%zu\n", alignof(s.b)); // 打印double类型的对齐模数
    // printf("对齐模数:%zu\n", alignof(s.c));   // 打印char类型的对齐模数
}


//当结构体嵌套结构体时候,只需要看子结构体中最大数据类型就可以了
typedef struct _STUDENT2{
	char a; //  0 ~  7
	Student b;  // 8 ~ 31
	double c;   // 32 ~ 39
}Student2;


void test02()
{
	printf("size of =  %d\n", sizeof(Student2));

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值