一维数组的数组名
除了两种情况(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
内存没有被释放
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);
}
内存对齐
结构体计算内存对齐原则
- 第一个属性开始 从0开始偏移
- 第二个属性 要放在 min(该类型大小, 对齐模数) 的整数倍
- 所有属性计算之后,整体做二次偏移
- 计算结果 放在 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));
}