• 详解C语言指针(二)


    1. 字符指针

    字符指针是指针类型的变量,其存储的是字符(字符数组或字符串)的地址。
    例如:pc里面存储的就是字符ch的地址。

    int main()
    {
        char ch = 'y';
        char *pc = &ch;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    字符指针也可以初始化为指向字符数组或字符串的首字符的地址。

    const char *str = "Hello_CSD"; // 初始化字符指针,指向字符串常量
    
    • 1

    上面代码的意思是把一个常量字符串的首字符 H 的地址存放到指针变量 str中。
    在这里插入图片描述
    紧接着,我们来看下面一道面试题目,并分析运行结果:

    #include 
    int main()
    {
        char str1[] = "hello_word!";
        char str2[] = "hello_word!";
        const char *str3 = "hello_word!";
        const char *str4 = "hello_word!";
        if(str1 ==str2)
        {
        	printf("str1 and str2 are same\n");
    	}
        else
        {
        	printf("str1 and str2 are not same\n");
        }
        
        if(str3 ==str4)
        {
        	printf("str3 and str4 are same\n");
    	}
        else
        {
        	printf("str3 and str4 are not same\n");
    	}
    	      
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    运行结果如下:
    在这里插入图片描述
    分析如下:

    • str1 和 str2 是字符数组,它们创建的时候是在栈上分配内存,每个都有自己的内存地址。上面只是用相同的常量字符串去初始化它们俩。
      str1和str2是数组名,在这里表示的是首元素的地址,str1和str2是两块独立空间的起始地址,因此,str1 == str2 的比较结果为 不相同,因为它们指向不同的内存位置。
      在这里插入图片描述
    • 然而,str3 和 str4 是指向常量字符串的指针,而C/C++会将这个常量字符串存储在程序的数据段中,只有一份拷贝。因此,它们实际上是指向了同一个常量字符串。
      然而把一个常量字符串赋给指针变量的时候,是把一个常量字符串的首字符的地址存放到了指针变量中,所以str3 == str4 的比较结果为 “相同”,因为它们指向相同的内存位置。
      在这里插入图片描述

    2. 指针数组

    详解C语言指针(一)在这篇文章中,已经讲到了指针数组的概念,需要的话,可以点击链接跳转去查看,这里就不在赘述。
    指针数组可以用于模拟实现二维数组。这样我们就可以创建出具有不规则大小的二维数组,每行的长度可以不同。在这里,我们用指针数组,来模拟实现一个二维数组。

    #include 
    #include 
    
    int main() 
    {
        int* arr[3]; // 指针数组,每个元素指向一行的整数数组
    
        // 分配内存并初始化每行的长度
        for (int i = 0; i < 3; ++i)
        {
        	arr[i] = (int*)malloc(sizeof(int) * 3);//每行3个元素
        }
    
        // 初始化二维数组的值
        for (int i = 0; i < 3; i++) 
        {
            for (int j = 0; j < 3; j++) 
            {
                *(*(arr + i) + j) = 0; // *(*(arr + i) + j) 给每个元素赋值
            }
        }
    
        // 访问和打印二维数组的值
        for (int i = 0; i < 3; i++) 
        {
            for (int j = 0; j < 3; j++) 
            {
                printf("%d ", *(*(arr + i) + j));//arr[i][j] == *(*(arr + i) + j)
            }
            printf("\n");
        }
    
        // 释放内存
        for (int i = 0; i < 3; i++) 
        {
            free(arr[i]);
            arr[i] = NULL;
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    在这里插入图片描述

    3. 数组指针

    3.1 什么是数组指针?

    数组指针是指针类型的变量,其存储的是整个数组的地址。
    数组指针的定义方式:数组的数据类型 + (* 指针变量名) + [数组的元素个数] = &数组名
    例如:p就是数组指针变量

    int arr[10];
    int (*p)[10] = &arr;//[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
    
    • 1
    • 2

    在这里插入图片描述

    3.2 &数组名 VS 数组名

    上篇文章说道:数组名在大多数情况下,表示的是数组首元素的地址。但是有两个例外:

    • sizeof(数组名),计算的是整个数组的大小,单位是字节。
    • &数组名,取出的是整个数组的地址,其类型是数组指针。

    数组在内存中是一块连续的空间,&数组名是指向整个数组的指针,而数组名是指向数组首元素的指针,它们都指向了整个数组的起始位置,因此它们的地址是相同的

    #include 
    
    int main()
    {
    	int arr[] = { 1, 2, 3, 4, 5 };
    	printf("arr  == %p\n", arr);//数组首元素地址,类型是int*
    	printf("&arr == %p\n", &arr);//数组的地址,类型是int (*)[5];
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    运行结果:
    在这里插入图片描述
    &arr和arr,虽然值是一样的,但其意义是完全不同的。

    • arr是数组名,表示的是数组首元素的地址,类型是int*。
    • &arr表示的数组的地址,类型是int (*)[5]。
    • arr+1,跳过一个整型(4个字节),而&arr+1跳过整个数组(元素个数*元素大小个字节:20个字节)。
    #include 
    
    int main()
    {
    	int arr[] = { 1, 2, 3, 4, 5 };
    	printf("arr = %p\n", arr);      
    	printf("&arr= %p\n", &arr);      
    	printf("arr+1 = %p\n", arr + 1);  
    	printf("&arr+1= %p\n", &arr + 1);  
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    运行结果:
    在这里插入图片描述

    4. 数组参数

    在写代码的时候难免要把 数组 或者 指针 传给函数,下面将详细介绍数组、指针传参时,函数的参数是如何设计的。

    4.1 一维数组传参

    1. 函数参数用数组来接收数组传参。

    函数可以接受一个一维数组作为参数,这样函数可以访问整个数组。函数声明中使用数组名,这里可以不指定数组的大小。

    void processArray(int arr[], int size) 
    {
        // 在函数中使用 arr 数组,size 表示数组的大小
        for (int i = 0; i < size; i++) 
        {
            // 处理数组元素
        }
    }
    
    int main() {
        int arr[] = {1, 2, 3, 4, 5};
        int size = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
        processArray(arr, size); // 将数组 arr 和 数组大小传递给函数
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    1. 函数参数用指针来接收数组传参。

    数组名是首元素的地址,当数组作为实参传递给函数参数(形参)时,其实是将数组的首元素的地址传递给了形参,因此,可以使用指针来接收数组传参。

    void processArray(int* arr, int size) 
    {
        //在函数中使用 arr 指针,size 表示数组的大小
        for (int i = 0; i < size; i++) 
        {
            // 处理数组元素
        }
    }
    
    int main() {
        int arr[] = {1, 2, 3, 4, 5};
        int size = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
        processArray(arr, size); // 将数组 arr 和 数组大小传递给函数
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    1. 即使上面数组传参,用数组来接收时,其实并没有创建一个新的数组来接收,本质上也是将数组首元素的地址传递给了形参。
      在这里插入图片描述

    4.2 二维数组传参

    二维数组作为实参传递给函数形参时,和一维数组传参类似,也可以使用数组或者指针来接收。

    1. 函数参数用数组来接收数组传参。

    二维数组作为实参传递给函数形参时,可以使用一个二维数组来接收。需要注意的是,函数形参部分的二维数组的列数是不可以省略的,行数可以省略。但为了代码的可读性,通常建议明确指定二维数组的行数和列数。

    #include 
    
    // 定义函数,接受二维数组和行列数作为参数
    void process2DArray(int arr[][3], int row, int col) 
    {
        for (int i = 0; i < rows; i++) 
        {
            for (int j = 0; j < cols; j++) 
            {
                //处理数组元素
            }
        }
    }
    
    int main() 
    {
        int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
        
        // 调用函数并传递二维数组以及行列数
        process2DArray(arr, 3, 3);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    1. 函数参数用指针来接收数组传参。

    我们知道,在大多数情况下,数组名是数组首元素地址,那么二维数组的的数组名是什么呢?其实,二维数组也没例外,其数组名表示的也是首元素的地址,其首元素是一个一维数组,所以首元素的地址也就是一维数组的地址,其类型为数组指针。
    在这里插入图片描述
    二维数组作为实参,传递给函数形参时,和一维数组一样,也是将数组首元素地址传递给了形参,因此参数可以用指针来接收。

    #include 
    
    // 定义函数,使用指针来接收
    void process2DArray(int (*arr)[3], int row, int col) 
    {
        for (int i = 0; i < rows; i++) 
        {
            for (int j = 0; j < cols; j++) 
            {
                //处理数组元素
            }
        }
    }
    
    int main() 
    {
        int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
        
        // 调用函数并传递二维数组以及行列数
        process2DArray(arr, 3, 3);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    5. 函数指针

    函数指针是指向函数的指针,其存放的是函数的地址。
    函数指针的定义方式:函数返回类型 + (* 指针变量名)+ (函数参数1, 函数参数2, …)= &函数名(或者函数名)。

    #include 
    
    int Add(int a, int b)
    {
    	return a + b;
    }
    int main()
    {
    	int (*pf)(int, int) = &Add;//()的优先级要高于*号的,所以必须加上()来保证pf先和*结合。
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    了解上述以后,来分析如下两段代码:

    //代码1
    (*(void (*)())0)();
    //代码2
    void (*signal(int , void(*)(int)))(int);
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述
    在这里插入图片描述

    6. 函数指针数组

    函数指针数组是一个数组,数组的每个元素都是一个指向函数的指针。
    函数指针数组定义方式:函数返回值类型 + (* 数组名[数组元素个数])+(函数参数1, 函数参数2, …);
    例如:parr就是一个函数指针数组。

    int (*parr[10])();
    
    • 1

    在这里插入图片描述
    函数指针数组其中一个重要用途是实现转移表。它包含了一组函数指针,用于根据某个输入条件或标识来选择执行不同的操作或函数。
    如下代码就是使用函数指针数组来实现函数的选择和调用。

    #include 
    
    void menu()
    {
    	printf("**************************\n");
    	printf("****  1. Add  2. Sub  ****\n");
    	printf("****  3. Mul  4. Div  ****\n");
    	printf("****      0. exit     ****\n");
    	printf("**************************\n");
    }
    
    int Add(int x, int y)
    {
    	return x + y;
    }
    
    int Sub(int x, int y)
    {
    	return x - y;
    }
    
    int Mul(int x, int y)
    {
    	return x * y;
    }
    
    int Div(int x, int y)
    {
    	return x / y;
    }
    
    int main()
    {
    	int input = 0;
    	int x = 0;
    	int y = 0;
    	int(*cal[5])(int, int) = { NULL, Add, Sub, Mul, Div };
    
    	do
    	{
    		menu();//打印菜单
    		printf("请输入你的选择:");
    		scanf("%d", &input);
    		if (input >= 1 && input <= 4)
    		{
    			printf("请输入两个操作数:");
    			scanf("%d%d", &x, &y);
    			int ret = cal[input](x, y);
    			printf("ret == %d\n", ret);
    		}
    		else
    		{
    			printf("选择错误,请重新输入\n");
    		}
    		
    	} while (input);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 在上面的代码中,我们首先定义了四个不同的函数(加法、减法、乘法、除法),它们的地址都与 int(*)(int, int)类型相匹配。

    • 接下来,我们声明了一个函数指针数组 cal,其中的每个元素都是一个指向函数的指针,这些函数的指针都具有相同类型。我们用这些函数的地址来初始化这个函数指针数组。

    • 最后,我们通过选择 input 变量来决定要执行的操作,然后使用函数指针数组来调用相应的函数,并输出结果。

    7. 指向函数指针数组的指针

    指向函数指针数组的指针,其存放的是函数指针数组的的地址。

    void test(const char* str)
    {
     printf("%s\n", str);
    }
    int main()
    {
     	//函数指针pfun
     	void (*pfun)(const char*) = test;
     	//函数指针的数组pfunArr
     	void (*pfunArr[5])(const char* str);
     	pfunArr[0] = test;
    	 //指向函数指针数组pfunArr的指针ppfunArr
     	void (*(*ppfunArr)[5])(const char*) = &pfunArr;
     	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述
    这里了解一下就好,不是很重要。

    8. 回调函数

    回调函数是一个通过函数指针调用的函数。它是指函数作为参数传递给另外一个函数,在函数内部,当通过这个函数指针来调用其所指向的函数时,我们说这就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

    例如,排序函数可以接受用户定义的比较函数作为回调来自定义排序规则。(冒泡排序排任意类型的数据)。

    #include 
    
    //用户自定义的比较函数
    int cmp_int(const void* e1, const void* e2)
    {
    	return *(int*)e1 - *(int*)e2;
    }
    //打印数组内容
    void Print(int* arr, int sz)
    {
    	for (int i = 0; i < sz; ++i)
    	{
    		printf("%d ", arr[i]);
    	}
    
    	printf("\n");
    }
    //交换两个数据
    void swap(char* e1, char* e2, size_t size)
    {
    	for (size_t i = 0; i < size; ++i)
    	{
    		char tmp = *e1;
    		*e1 = *e2;
    		*e2 = tmp;
    		e1++;
    		e2++;
    	}
    }
    
    //冒泡排序排任意类型的数据
    void bubble_sort(void* base, size_t num, size_t size, int(*cmp)(const void*, const void*))
    {
    	//排序趟数
    	for (size_t i = 0; i < num - 1; ++i)
    	{
    		//每一趟排序比较的趟数
    		for (size_t 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 main()
    {
    	int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
    	size_t sz = sizeof(arr) / sizeof(int);
    
    	printf("排序前:");
    	Print(arr, sz);
    
    	bubble_sort(arr, sz, sizeof(int), cmp_int);
    
    	printf("排序后:");
    	Print(arr, sz);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    代码的主要部分和执行流程:

    • cmp_int 函数是一个比较函数,用于比较两个整数的大小。将它传递给 bubble_sort 函数以进行元素比较。

    • Print 函数用于打印整数数组的内容。

    • swap 函数用于交换两个元素的内容,它是通用的,可以处理不同数据类型的元素

    • bubble_sort 函数实现了冒泡排序算法。它接收一个指向待排序数组的指针 base,数组的元素个数 num,每个元素的大小 size,以及一个进行元素比较的函数 cmp。在排序过程中,它使用传递的比较函数来决定元素的顺序,并使用 swap 函数来交换元素的位置。

    • 在 main 函数中,创建了一个整数数组 arr,然后调用 bubble_sort 函数对数组进行排序,并使用 cmp_int 函数进行比较。最后,打印排序前和排序后的数组内容。

    运行结果:
    在这里插入图片描述

    上面的代码就展示了如何使用函数指针以及通用的交换函数来实现冒泡排序。

    至此,本片文章就结束了,若本篇内容对您有所帮助,请三连点赞,关注,收藏支持下。

    创作不易,白嫖不好,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!

    如果本篇博客有任何错误,请批评指教,不胜感激 !!!
    在这里插入图片描述

  • 相关阅读:
    第9章 IO流、第 10章 多线程
    VS2022+opencv4.6.0安装和配置过程
    C# 获取Json对象中指定属性的值
    YOLOv5实现车辆检测(含车辆检测数据集+训练代码)
    高频故障-桌面图标变成白纸图标的恢复方案
    【踩坑】Android 编译线程爆了, gradle 内存 OOM 解决之路
    基于Matlab实现多个图像增强案例(附上源码+数据集)
    05 SpringBoot注册Web组件
    实验二:数据类型、运算符和表达式——桂林航天工业学院
    uni-app的三种操作数据库方法
  • 原文地址:https://blog.csdn.net/m0_50655389/article/details/133578915