C语言---深入指针(4)(二)

简介: C语言---深入指针(4)

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*类型的指针进行存储的
相关文章
|
3月前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
63 0
|
1月前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
84 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
1月前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
54 9
|
1月前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
45 7
|
2月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
153 13
|
2月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
2月前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
129 3
|
2月前
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
62 11
|
2月前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
2月前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
44 1