IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> c语言必读篇——深入理解C语言指针的应用(千字详解) -> 正文阅读

[C++知识库]c语言必读篇——深入理解C语言指针的应用(千字详解)

hello,大家好,本篇文章主要是继指针基础篇之后的进阶篇。博主初学指针时也是对指针的进阶使用深感头疼,特意码字数日力求解决指针,让大家在读了博主的文章后,能彻悟指针。同时呢我们在基础篇已经知道指针的概念,但指针真正的作用可不止于此,接下来让我们一起谈谈指针的高阶主题学习

文章大纲介绍

本文主要讲指针与数组,函数的组合使用,主要的内容有以下知识点:

  1. 指针与数组
  2. 指针数组与数组指针
  3. 数组传参和指针传参
  4. 函数指针数组搭配使用
  5. 回调函数以及qsort函数

一.数组与函数

我们来看一个实例:

1.1指向数组的指针

#include <stdio.h>

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    printf("%p\n", arr);
    printf("%p\n", &arr[0]);
    return 0; }

结果如下:
在这里插入图片描述
解读代码:
数组名表示的是数组首元素的地址。(2种情况除外,博主在数组章节有提到)
也就是说,我们可以这样写代码:

int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址

也就是说,我们可以通过指针来访问数组,那么我们要如何将指向首元素的指针后移找到数组的其他数据呢?

1.2指针访问数组

我们看下图,理解分析:
在这里插入图片描述
结论:
p+i 其实计算的是数组 arr 下标为i的地址。

1.3指针访问数组实例:

来看实例:

int main()
{
 int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, };
 int *p = arr; 
 int sz = sizeof(arr) / sizeof(arr[0]);
 int i = 0;
 for (i = 0; i<sz; i++)
 {
 printf("%d ", *(p + i));
 }
 return 0;
 }

结果如下:
在这里插入图片描述
我们可以看到利用指针我们也完成了我们的目的。

二.指针数组与数组指针

也许我们早就听过指针数组与数组指针,那么他们是什么呢?
在这里插入图片描述

2.1指针数组的定义

我们知道int arr[10]代表一个存放10个整形数据的数组,那如果我们将其改成int*arr[10]他会不会代表存放10个指针的数组呢?
答案是YES,同时我们把这种存放指针的数组称之为指针数组
类似的对比我们整形数组就很好想了。
在这里插入图片描述
那么,他的使用是什么样的呢?我们继续往下看。

2.2指针数组的使用实例

我们举个实例,来看看他的使用,代码如下:

#include <stdio.h>

int main() {
    int arr[4][4] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
    int* p2[4];    //指针数组
    printf("\n使用指针数组的方式访问二维数组arr\n");
    for (int k = 0; k < 4; ++k) {
        p2[k] = arr[k];
    }
    for (int i = 0; i < 4; ++i) 
    {
        for (int j = 0; j < 4; ++j) 
        {
            printf("arr[%d][%d]=%d\t", i, j, *(p2[i] + j));
        }
        printf("\n");
    }

    return 0;
}

看看运行结果:
在这里插入图片描述
是不是很神奇,我们利用指针完成了对二维数组的访问,当然指针数组的作用不止于此,接下来让博主带着大家进行走进数组与指针。

2.3数组指针的定义

整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。

接着我们来看c语言中数组指针的定义:

int (*p)[10];

解释:
p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个
指针,指向一个数组,叫数组指针。

这里要注意:[]的优先级要高于号的,所以必须加上()来保证p先和结合。

2.4数组指针与指向数组的指针的混淆

当我们读了他的定义后,有人就有疑问了:

数组指针不是直接可以用 指针指向数组首地址进行访问么,

其实呀这个不叫数组指针,他只是是一个普通指针,指向的是数组首元素。
而数组指针,指向的是数组所在的内存空间,譬如:

char a[4];
char (*pa)[4] = &a;

在这里,pa指向的不是a的首元素地址,而是a的整个内存空间,如果你对pa做自加移动操作,移动步长是4个char字符长度,而不是1个。
而如果是

char a[4];
char *pa = &a;

在这里,pa指向的是a的首元素地址,如果你对pa做自加移动操作,移动步长是1个char字符长度,而不是4个。

2.5数组指针的使用实例

#include <stdio.h>

void print_arr1(int arr[3][5], int row, int col) //不理解传参的读者可以跳读三,数组传参与指针传参
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
			printf("\n");
	}
}

void print_arr2(int(*arr)[5], int row, int col) 
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 0,1,2,3,4,5,6,7,8,9 };
	print_arr1(arr, 3, 5);
	print_arr2(arr, 3, 5);
	return 0;
}

我们可以看到两种方式都成功打印出了数组:
在这里插入图片描述

三.数组传参与指针传参

3.1一维数组传参(含指针数组)

一个代码读懂一维数组传参:

#include <stdio.h>
void test(int arr[10])//传递的形参为数组
{}
形参用一个数组接受。因为实参是arr,即数组首元素的地址,这里的arr[]只是便于理解。
其实本质上传递的是地址。
void test(int *arr)//用指针来接收函数传参
{}
我们知道arr是数组首元素的地址,自然可以通过指针来接收传参
void test2(int *arr[20])
{}
我们知道arr2是指针数组,自然形参可以用指针数组来接收
void test2(int **arr)
{}
我们知道arr2是指针数组,数组存放的是指针,那我们在接受传参时,必须用指向指针的指针来接收,即二级指针
int main()
{
 int arr[10] = {0};//整形数组
 int *arr2[20] = {0};//存放整形指针的数组
 test(arr);
 test2(arr2);
 return 0}

总结:
数组传参,形参可以是数组也可以是指针

3.2二维数组传参

void test(int arr[3][5])
{}
void test(int arr[][5])//二维数组传参,形参用二维数组来接收传参
{}
二维数组传参,函数形参的设计只能省略第一个[]的数字。
对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
void test(int *arr)
{}
指针接收二维数组传参的首行地址
void test(int (*arr)[5])
{}
实参是一个一维数组的地址,所以能用一个数组指针类型去接收。数组指针指的是指向数组的指针,依然为一个指针,存放的是整个数组的地址。
int main()
{
 int arr[3][5] = {0};
 test(arr);
 二维数组首元素是第一行,所以二维数组传参传的是第一行的地址,而不是首元素地址。
 return 0}

3.3指针传参

相对来说,指针传参并不难,我们直接看下列代码:
一级指针:

#include <stdio.h>

void print(int *p, int sz) 
{
    int i = 0;
   for(i=0; i<sz; i++)
   {
      printf("%d\n", *(p+i));
   }
}

int main()
{
   int arr[10] = {1,2,3,4,5,6,7,8,9};
   int *p = arr;
   int sz = sizeof(arr)/sizeof(arr[0]);
   //一级指针p,传给函数
   print(p, sz);
   return 0; 
 }

运行结果如下:
在这里插入图片描述
二级指针:

#include <stdio.h>

void test(int** ptr)
{
	printf("num = %d\n", **ptr);
}

int main()
{
	int n = 10;
	int* p = &n;
	int** pp = &p;
	test(pp);
	test(&p);
	return 0;
}

结果如下:
在这里插入图片描述

四。函数指针

我们知道,函数也有地址,既然是地址,我们也是可以通过指针访问的

4.1函数指针

函数名与&函数名都代表函数的地址,并无区别

void test()
{
 printf("hehe\n");
}
//下面pfun和有能力存放test函数的地址吗?
void (*pfun1)();

回答是:pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。
总结:
指针函数的声明类似函数,通俗来讲:返回类型 (*名称)(参数){}
请读者思考这段代码,进行思考:

void (*signal(int , void(*)(int)))(int);

4.2函数指针数组

定义:
我们把函数的地址存到一个数组中,那这个数组就叫函数指针数组.

`int (*parr1[10])();
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是 int (*)() 类型的函数指针。

4.3函数指针数组实例

我们来看看没有使用函数指针数组的简易计算器的实现:

#include <stdio.h>

int add(int a, int b)
 {
    return a + b; 
 }
int sub(int a, int b)
 {
     return a - b; 
 }
int mul(int a, int b) 
{
     return a*b; 
}
int div(int a, int b)
{
     return a / b; 
}
 
int main()
{
 int x, y;
 int input = 1;
    int ret = 0;
    do
   {
        printf( "*************************\n" );
        printf( " 1:add           2:sub \n" );
        printf( " 3:mul           4:div \n" );
        printf( "*************************\n" );
        printf( "请选择:" );
        scanf( "%d", &input);
        switch (input)
       {
        case 1:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = add(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 2:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = sub(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 3:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = mul(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 4:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = div(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 0:
                printf("退出程序\n");
 breark;
        default:
              printf( "选择错误\n" );
              break;
       }
 } while (input);
    
    return 0;
}

我们可以看的出来代码长且繁琐,接下来我们利用函数指针数组进行优化:

```c
#include <stdio.h>
int add(int a, int b) {
           return a + b; }
int sub(int a, int b) {
           return a - b; }
int mul(int a, int b) {
           return a*b; }
int div(int a, int b) {
           return a / b; }
int main()
{
     int x, y;
     int input = 1;
     int ret = 0;
     int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
     while (input)
     {
          printf( "*************************\n" );
          printf( " 1:add           2:sub \n" );
          printf( " 3:mul           4:div \n" );
          printf( "*************************\n" );
          printf( "请选择:" );
      scanf( "%d", &input);
          if ((input <= 4 && input >= 1))
         {
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = (*p[input])(x, y);
         }
          else
               printf( "输入有误\n" );
          printf( "ret = %d\n", ret);
     }
      return 0; 
     }  

程度解决了代码的繁琐程度,非常实用。

4.3指向函数指针数组的指针

解读:

指向函数指针数组的指针是一个指针,指针指向一个数组 ,那么这个数组的元素都是函数指针

//函数指针pfun
 void (*pfun)() = test;
 //函数指针的数组pfunArr
 void (*pfunArr[5])();
 pfunArr[0] = test;
 //指向函数指针数组pfunArr的指针ppfunArr
 void (*(*ppfunArr)[5])() = &pfunArr;
 return 0;

关于这个指针的话,博主认为大家了解即可,使用的相对来说较少。接下来,我们要进行压轴知识点的学习了,可以说,他也是个非常重要的知识点。接下来,我们一起学习——回调函数。

五.回调函数

定义如下:

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

5.1 qsort函数

我们看看c标准库对qsort函数参数的定义:
在这里插入图片描述
在这里插入图片描述
大家自行阅读参数以及了解参数的意义

1.qsort是一个包含在<stdlib.h>头文件下的库函数,进行快速排序
2.qsort函数可以对所有类型的数据进行排序,一个函数解决所有类型的排序问题,不需要根据不同的类型些不同的函数,提高效率
3.在使用qsort库函数之前我们需要写一个比较函数,这个需要针对各个类型写单独的比较函数,这个比较函数只需要比较两个元素之间的大小就可以,若A>B则返回1,A<B返回-1,A=B返回0,之后调用sqort函数,将比较后的返回值传入,进行排序,得到最终的排序结果结果。

5.2回调函数实例

#include <stdio.h>

//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2) {
  return (*( int *)p1 - *(int *) p2);
}

int main()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    int i = 0;
    qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
    for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
   {
       printf( "%d ", arr[i]);
   }
    printf("\n");
    return 0; 
}

6.总结

指针的进阶学习就到此结束,博主还会进行关系c语言的重难点以及分享许多比较实用的知识,如有疑问,私聊或者评论区告诉博主,博主力争帮大家解决问题,那么同时欢迎关注博主,一起与博主学习c语言
码字不易,求个三连,博主将与你们同在~

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-10-17 12:13:43  更:2022-10-17 12:17:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 6:41:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码