自学内容网 自学内容网

深入理解C语言指针(下)

 一、字符指针变量

        在指针的类型中我们知道有一种指针类型为字符指针 char * ,一般使用方法如下:

        但是还有一种使用方式如下:

        对于const char* pstr = "hello world!";我们会自然而然地觉得这行代码是把hello world!这个字符串放到了pstr这个字符指针中了,类似与数组,但是其实本质上是把字符串hello world!的首地址(即首字符h的地址)放在了pstr这个字符指针中了,记住指针就是地址,放在指变量中的一定是地址。

        char* pstr = "hello world!"是常量字符串,它和数组是非常相似的,也是在一个连续空间中存放了多个字符,但是不同于数组的是,常量字符串中的内容不能修改

二、数组指针变量

        请问:数组指针变量是指针变量还是数组呢?答:数组指针变量是指针变量

        数组指针变量存放的是数组的地址,是能够指向数组的指针变量。

        int ( * p )[ 10 ];

        这行代码的意思是:p 先和 * 结合,说明 p 是一个指针变量,然后指针指向的是一个大小为 10 个整型的数组。所以 p 是一个指针,指向一个数组,叫数组指针。

        这里要注意的是:[ ]的优先级是高于 * 的,所以必须加上()来保证p先和*结合。如果不加()  int  * p [ 10 ];这行代码表示的就是一个指针数组,该数组的类型是int * [10],数组中存放的元素类型是int *。

        如果要存放整个数组的地址,就得存放在数组指针变量中,想要获得数组的地址,就得用到&数组名:

        int ( * p ) [ 10 ] = &arr;

        int 说明p指向的数组的元素类型是整型;p是数组指针的变量名;*说明这是一个指针变量;[10]说明p指向的数组的元素个数是10。该数组指针变量的类型是 int ( *  ) [ 10 ]

三、二维数组传参的本质

        过去当一个函数的其中一个参数是二维数组是,我们是这样写的 :

        void test ( int arr[ 3 ] [ 5 ] , int r , int c );

        当我们学习了指针后,我们是否能写出其他形式呢?首先让我们再次来理解一下二维数组:二维数组可以看做是每个元素是一维数组的数组。该二维数组的每个元素是一个一维数组。

        所以,根据数组名是数组首元素的地址这个规则,不难得到二维数组的数组名表示的就是第一行的地址,是一维数组的地址。根据上面的例子,第一行的一维数组的类型就是 int [5],所以第一行的地址的类型就是数组指针类型 int (*)[5]。那就意味着二维数组传参本质上是传递了地址,传递的是第一行这个一维数组的地址,那么形参也是可以写成指针形式的。如下:

        void test ( int (*p) [ 5 ] ,  int r , int c )

        总结:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。

四、函数指针变量

        1、函数指针变量的创建和使用

        由前面学习整型指针,数组指针铺垫,我们不难得出结论:函数指针变量应该是用来存放函数地址的,未来我们是可以通过地址来调用函数的。

        函数是有地址的,函数名就是函数的地址,当然也可以通过 & 函数名 的方式获得函数的地址。如果我们要将函数的地址存放起来,就得创建函数指针变量,函数指针变量的写法其实和数组指针变量非常类似。如下:

        int ( * p ) ( int x , int y );

        int 说明p指向的函数的返回类型是整型;p是函数指针变量名;*说明这是一个指针变量;int x , int y 说明p指向的函数的参数类型和个数。该函数指针变量的类型是int ( * ) ( int x , int y )。 

        综上可知,指针可以指向任何内存中的对象。   

        2、typedef关键字

        typedef是用来对类型重命名的,它可以将复杂的类型简单化。

        (1)typedef int* ptr_t;   将int* 重命名为ptr_t;

        (2)typedef int (*parr_t) [5];将数组指针类型int (*) [5] 重命名为parr_t;(新的类型名必须在*的右边

        (3)typedef void(*pf_t)(int);将函数指针类型void(*)(int)重命名为pf_t。(新的类型名必须在*的右边)                  

五、函数指针数组

        把多个函数的地址存到一个数组中,这个数组就叫函数指针数组

        函数指针数组的定义方法:int ( * p [ 3 ] ) ( ) ; p先和[]结合,说明p是数组,该数组内的元素的类型是int ( * ) ( );该函数指针数组的类型是int ( *  [ 3 ] ) ( )

        函数指针数组的用途是转移表

        转移表的实例:通过函数指针数组实现简易计算器:

六、回调函数       

        回调函数就是一个通过函数指针调用的函数。

        如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

七、qsort使用举例及模拟实现

        qsort(quick sort)是一个库函数,它是用来对数据进行排序的。不管是什么类型的数据,它都能对其进行排序。qsort函数的使用需要包含头文件<stdlib.h>

        void qsort ( void * base , size_t num , size_t size , int(* cmp)(const void*,const void*) );

        base指向了待排序数组中的第一个元素的地址;(使用void*是因为待排序的数据的类型是不确定的)num待排序的数据的元素个数size待排序数组中一个元素的大小,单位是字节int(* cmp)(const void*,const void*)这是一个函数指针变量,指针指向的函数可以用来比较base指向的数组中的任意两个元素的大小

        1、使用qsort函数排序整型数据                

#include <stdio.h>
#include<stdlib.h>
int cmp(const void* x, const void* y);
int main()
{
    int arr[10] = { 1,3,4,5,8,9,2,10,7,6 };
    size_t sz = sizeof(arr) / sizeof(arr[0]);
    qsort(arr, sz, sizeof(arr[0]), cmp);
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}
int cmp(const void* x, const void* y)
{
    return (*(int *)x) - (*(int *)y);
}

        2、使用qsort排序结构数据

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int cmp_by_age(const void* x, const void* y); //按照年龄排序
int cmp_by_name(const void* x, const void* y);    //按照名字排序
struct stu 
{
    char name[20];
    int age;
};
int main()
{
    struct stu s[3] = { {"zhangsan",18},{"wangwu",35},{"lisi",15} };
    size_t sz = sizeof(s) / sizeof(s[0]);
    qsort(s, sz, sizeof(s[0]), cmp_by_age);
    for (int i = 0; i < 3; i++)
    {
        printf("%s %d \n", s[i].name,s[i].age);
    }
    qsort(s, sz, sizeof(s[0]), cmp_by_name);
    for (int i = 0; i < 3; i++)
    {
        printf("%s %d \n", s[i].name, s[i].age);
    }
    return 0;
}
int cmp_by_age(const void* x, const void* y)
{
    return ((struct stu*)x)->age - ((struct stu*)y)->age;
}
int cmp_by_name(const void* x, const void* y)
{
    return strcmp(((struct stu*)x)->name, ((struct stu*)y)->name);
}

        3、qsort函数的模拟实现

        使用回调函数,模拟实现qsort(采用冒泡的方式)。

 #include<stdio.h>
void bubble(void* base, size_t num, size_t size, int (*cmp)(const void*, const void*));
int cmp(const void* x, const void* y);
void swap(void* p1, void* p2, size_t size);
int main()
{
    int arr[] = { 1,3,4,6,8,2,7,10,9,5 };
    size_t sz = sizeof(arr) / sizeof(arr[0]);
    bubble(arr, sz, sizeof(arr[0]), cmp);
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}
void bubble(void* base, size_t num, size_t size, int (*cmp)(const void*, const void*))
{
    for (int i = 0; i < num - 1; i++)
    {
        for (int j = 0; j < num - 1 - i; j++)
        {
            if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
            {
                swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
            }
        }
    }
}
int cmp(const void* x, const void* y)
{
    return *(int*)x - *(int*)y;
}
void swap(void* p1, void* p2, size_t size)
{
    for (int i = 0; i < size; i++)
    {
        char tmp = *((char*)p1 + i);
        *((char*)p1 + i) = *((char*)p2 + i);
        *((char*)p2 + i) = tmp;
    }
}

八、 sizeof 和 strlen 的对比

        

sizeofstrlen

1、sizeof是操作符;

2、sizeof计算操作数所占内存的大小,单位是字节;

3、不关注内存中存放什么数据。

1、strlen是库函数,使用需要包含头文件string.h;

2、strlen是求字符串长度的,统计的是\0之前字符的个数;

3、关注内存中是否有\0,如果没有\0,就会持续往后找,可能会越界


原文地址:https://blog.csdn.net/2401_86861045/article/details/143880404

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!