C语言---深入指针(4)(一)https://developer.aliyun.com/article/1544402
关于结构体的补充知识点
利用结构体指针来访问结构体里面的成员对象
//结构体知识点补充,利用结构体指针来找到结构体里面的数据 struct Stu//创建一个结构体 { char name[20]; int age; }; int main() { struct Stu s = { "zhangsan",20 }; //第一种写法: printf("%s %d\n", s.name, s.age); //这里的知识点就是利用结构体名字加点再加结构体对象,就可以调用出对应的数 struct Stu* ps=&s;//将变量s的地址取出存放到结构体指针内 //struct Stu*就是这个结构体指针ps的类型 //ps里面存放的是s的地址,ps就指向了s //第二种写法: //那么我们利用ps来找到这个结构体数组里面的数据怎么找呢? printf("%s %d\n", (*ps).name, (*ps).age); //ps是里面存放的是这个结构体的地址,对ps进行解引用得到的就是这个结构体s了 //第三种写法: printf("%s %d\n", ps->name, ps->age);//ps指向的对象的成员 return 0; } //结构体成员访问操作符 // . 结构体变量.成员名 //-> 结构体指针->成员名
qsort函数的总结
对于qsort函数来说
主要的就是括号内的第四个元素,也就是进行元素比较的函数的名字
对于qsort来说,这个进行比较的函数只需要返回三种值
大于0,小于0,或者等于0,只要传递回来就能直接快速排列
对于qsort函数来说:
第一个元素是要排列的数组的首元素的地址,就是数组名
第二个元素就是这个数组的元素个数sz
第三个元素就是每个元素的字节大小sizeof([0])
第四个元素就是这个比较的函数的函数名
对于这个比较函数就有说法了
这个就是固定格式
返回值是数字,就是大于0的数,0,小于0的数,所以函数前面是int
int cmp_int(const void* p1, const void* p2)
p1和p2的类型都是void*
p1和p2都指向的数组内的要进行比较的元素
如果要进行比较的话就需要对这个指针进行强制类型转换
假设:
int*强制类型转换,
(int*)p1
再对转换后的结果进行解引用就是p1指向的那个数
(int)p1
判断的是整型数组的话:
那么我们直接写:return* (int*)p1 - *(int*)p2;
两个数进行相减,返回值三种情况:大于0的数、0、小于0的数
假如说要判断结构体成员的年龄的话
return ((struct Stu)p1)->age-((struct Stu)p2)->age;
返回的就是年龄相减的值
先强制类型转换p1
(struct Stu*)p1
因为p1指向的是这个结构体数组,那么我们直接通过箭头操作符直接访问数据
(struct Stu*)p1→age
假如说要判断结构体成员的姓名的话
对于这个结构体数组来说,这个姓名就是字符串
对于字符串的比较我们要用到strcmp函数
因为我们想要的这个比较函数的返回值和这个strcmp的返回值一样的,那么我们直接返回strcm函数的值
return strcmp(((struct Stu)p1)->name, ((struct Stu)p2)->name);
strcmp(((struct Stu)p1)->name, ((struct Stu)p2)->name)
在strcmp函数中进行比较的是((struct Stu)p1)->name和((struct Stu)p2)->name
qsort实现升序和降序的原理
因为qsort默认实现的是升序
对于数组的快排,如果我们想实现数组的降序,
因为qsort是固定死的
但是qsort里面的一个元素,第四个元素,比较函数
我们只能通过这个比较函数来实现降序
对于这个代码
return ((struct Stu)p1)->age-((struct Stu)p2)->age;
如果前者大于后者就返回大于0的数,如果是升序的话,那么就要将这个较大的数和这个较小的数进行交换,因为我们要实现升序的效果,就是从小到大
如果想实现降序的话,我们将逻辑反过来:
return ((struct Stu)p2)->age-((struct Stu)p1)->age;
后面的大于前面的,返回值是小于0的数,因为要实现降序,那么我们就将较大的数与较小的数进行交换
咱们仔细想想,
对于qsort函数来讲,我们交换数组元素的条件是啥呢?
就是这个返回的值
如果我们写的是前面的值减去后面的值大于0的话就进行交换,就是前面的数大于后面的数,把大的换后面去,所以这种写法代表的排序顺序就是升序
假如后面的值大一些呢,就是前面值减去后面的值小于0,就是说后面的大,前面的小,就将这两个数进行交换,那么大的值都在前面,小的值在后面,那么我们就实现了降序了
就是对于qsort来说,我们这个快速排列需要交换满足条件的值,那么这个比较函数里面的条件就是这个满足交换条件的值交换的条件,就是return 后面的条件,如果这个返回值大于0的话就将这两个数进行对调了
p1-p2>0我们就对调这两个数----大的换后面去,---升序
p2-p1>0我们就对调这两个数----大的换前面去----降序
qsort函数的模拟实现
//我们是否能将bubble_sort函数改造成通用的算法,可以排序任意类型的数据 //模仿qsort struct stu { char name[20]; int age; }; int cmp_stu_by_name(const void* p1, const void* p2) { return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name); } int cmp_int(const void* p1, const void* p2) { return *(int*)p1 - *(int*)p2; } int cmp_stu_by_age(const void* p1, const void* p2) { return ((struct stu*)p1)->age - ((struct stu*)p2)->age; } void Swap(char* buf1, char* buf2, size_t width)//传过来的是hcar*指针,那么我们就用char*接受 { char tmp = 0; for (int i = 0; i < width; i++)//因为不知道两个元素之间有多少个字节,那么我们就把每个字节进行交换 { tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; //这里仅仅只是交换一对字节,因为char类型是1个字节 //交换完这一对字节,那我们就换下一对字节进行交换,因为buf1和buf2都是指针,那么我们利用指针的加减来实现字节间的加加 buf1++; buf2++; //这个循环总共进行width次,每次一对字节交换 } } //size_t是无符号整型 void bubble_sort(void *base,size_t sz,size_t width,int(*cmp)(const void*p1, const void* p2))//将函数的形参改成void*,因为我们不知道传过来的是什么类型的数据 //而恰好void*可以接收任意类型的地址,那么bubble_sort这个函数就能处理任意类型的数据了 //这里的base指向的是数组的首元素的地址,sz是数组元素个数 //仅仅知道这两个消息是仅仅不够的,因为我们不知道每个元素的字节大小是多少,因为不同类型的元素字节大小不同 //解释一下这个函数,第一个接收的是数组首元素的地址,第二个是数组元素个数,第三个是元素之间的宽度,就是字节大小 //第四个就是比较函数 { //趟数 for (int i = 0; i < sz-1; i++) { //一趟内部的两两比较 for (int j = 0; j < sz - 1 - i; j++) { //唯一要改变的就是下面的比较的代码了 //1.比较两个元素 if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)//如果比较函数的返回值大于0,就进行交换 //如果传过去的元素满足条件大于0满足交换的条件,那么就进入条件语句进行交换 //那么我们需要将arr[j]的地址和arr[j+1]的地址传到比较函数进行大小比较 //元素之间的字符大小根据j的变化来表示,j*width //将base强制转换成char*指针,因为对于char*来说,加几就是跳过几个字节 //(char)base+width { //2.交换两个元素 Swap((char*)base + j * width, (char*)base + (j + 1) * width,width); //将起始地址传过去,但是并不知道交换多宽的数据,我们就一定得将宽度传过去 //将两个交换的元素传过去,还有宽度也传过去 //交换的是(char*)base + j * width和 (char*)base + (j + 1) * width这两个地址指向的元素 } } } } print(int arr[], int sz) { for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } } void test1() { int arr[] = { 3,1,7,9,4,2,6,8,0 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);//cmp_int就是最前面的比较函数 print(arr, sz); } void test2() { struct stu arr[] = { {"zhangsan", 20},{"lisi",35},{"wangwu", 18} }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name); } void test3() { struct stu arr[] = { {"zhangsan", 20},{"lisi",35},{"wangwu", 18} }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age); } int main() { test1();//对于整型数组 //test2();//对于结构体--姓名 //test3();//对于结构体--年龄 return 0; } //p1和p2指向的是我要比较的两个元素 //因为排序我们得知道他们的大小才能进行,那么这个bubble_sort的第四个形参的那个比较函数的返回值就体现了他们的大小 // // // // // 对于不同的类型的数据间的比较,我们要将内循环里面的比较条件语句进行改变 // // // 整理一下: // 拿这个整数数组排列来说 // // 首先我们先创建了一个test函数,test函数里面有数组的创建和数组长度的计算,还有一个冒泡函数 // // 对于冒泡函数, // 我们传过去了一个首元素的地址--就是数组名 // 还传过去数组的长度 // 还有数组元素之间的宽度,因为我们不知道不同元素间的宽度 // 最后还传了一个比较数组函数的函数名字 // // // // 在经典的冒泡函数中,我们利用两层循环对数组进行排序 // // 而面对不同的元素的时候,这个比较的条件一定是要进行更换的,但是两层循环可以不用动 // // // 在我们创建冒泡函数接受传来的实参的时候,有这么几个元素 // // 我们用void*base来接受传来的数组名,因为我们并不知道传来的是什么类型的数据,恰好void*类型就可以接待任何类型的数据了 // // 在后面,我们创建了size_t sz来接受数组长度,size_t就是无符号整型的意思 // // 在后面我们又创建了一个size_t width,用来接收传来的数组中元素之间的字节宽度 // // 最后,因为下面传来的实参是比较数组中两个相邻元素的函数,那么我们就用一个函数指针进行接收,一定是比较函数哦 // 在这个函数指针创建的时候,可是有说法哦 // int(*cmp)(const void *p1,const void *p2) //返回类型是整数的函数指针,返回类型有三种,大于0,等于0,小于0的整数 // 利用返回的值进行判断,判断是否交换这两个相邻的元素的位置 // // 而里面的const void *p1就是来接受任何类型的地址 // // 说完冒泡排序的外面,再说里面吧 // // 出了基本的两层循环,改变的就是里面的条件语句 // if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) // 利用传来的比较函数,我们比较了数组中相邻的两个元素的大小,如果返回值>0,我们就让这两个相邻的元素进行交换 // 将base的指针类型强制转换成char*类型的指针 // (char*)base + j * width这个就是这个比较函数的第一个元素,也是起始点 // (char*)base + (j + 1) * width这个是第二个元素,结束点 // // 将两个元素传到比较函数里面,返回的值就是return *(int*)p1-*(int*)p2 // // 可能你们又忘了为什么这么写: // p1的类型在上面是void*类型,我们就需要将其强制转换到整型元素,再对其进行解引用操作,返回的值就是这两个元素之间的差值,反应两个元素的大小 // // // 为什么后面还有j*width呢? // 随着j的变化,从0到sz-1-i,因为char类型的数据是1个字节的,如果我们有了width了,我们就知道我们该从哪里比较,到哪里停止了 // // // // 那么就进入这个if条件语句里面的Swap交换函数了 // // 这个交换函数可有意思 // 传过来的元素地址:(char*)base + j * width, (char*)base + (j + 1) * width,width // // 因为传过来的是地址,所以我们就用指针进行接收,我们还接受了元素的宽度 // 在这个函数里面,我们创建了一个临时变量tmp // 还创建了一个循环 ,i从0开始,到width结束,就是循环width次,有多少个字节就交换几次,下面的就是很常见的交换的步骤了 // 但是是指针之间的交换,这些指针都是1个字节,因为传过来的是char*类型的指针 // // 再后面,就进行指针++,换另一个字节,直到两个元素之间的字节都交换完成,那么这两个元素就交换完成了 // 交换四对字节 // //这些代码没有具体类型的要求,你只要传一个数据,我们都能接受 // 因为我们使用void*类型的指针进行存储的