指针

指针作为C语言中极具代表性的特征之一,也是C语言学习中的一大难点。
简单来说,指针我们需了解的最基础的即:

  1. 指针是一个用来存放地址的变量,地址唯一标识一块内存空间。
  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
  3. 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。

指针与数组传参

了解指针与数组的传参,首先了解传值调用和传址调用的问题
传值调用:除了数组,其他数据实参均以直接拷贝,以传值形式调用
传址调用:数组传参时不可直接拷贝,需降维为指针;但数据也可以进行传址调用,只要在它前面加上取地址操作符即可

在讲述这个问题之前,我们先看以下这段代码:

int main()
{char str1[] = "hello.";char str2[] = "hello.";char *str3 = "hello.";char *str4 = "hello.";if(str1 ==str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");     if(str3 ==str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

以上一段代码的输出结果为"str1 and str2 are not same" ,"str3 and str4 are same"
因为str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当多个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

指针和数组作为参数传入函数时,传入的是地址,即指向变量的地址和数组的首地址,可以在函数中改变指针或数组的值,但本质上还是值的传递(区别于变量的值传递的是:变量值传递不会改变实参原来的值。),我们无法对指针和数组的地址进行操作(如:地址赋值,分配内存等),要进行地址操作需要使用指针引用或二级指针。

一维数组传参

数组名作为函数参数传递时,传递数组首元素的地址,函数接收的实际为原参数的一份拷贝,使得函数操纵时不影响实际值
对于一维数组传参,有以下几种表示方法:

void test(int arr[])
void test1(int arr[10])
void test2(int *arr)
void test3(int *arr[20])
void test4(int **arr)

以上五种写法都是正确的
用数组接收传的是首元素地址,可以指定数组大小,也可以不指定,因为传到函数内的是地址,故可以用指针接收,第四种情况中,将一个指针数组的数组名传入,第五种则是传入指针数组数组名,代表首元素地址,首元素是一个指向数组的指针,再取地址,表示二级指针

调用函数时实际传递的是一个指针,即函数形参为指针,但数组名依然可被表示为数组首元素的地址被调用。两种调用明显用指针较为准确,因为实参实际是指针,而函数中一维数组可以不标明数组长度也是这个原因:函数没有给数组参数分配内存空间,形参指向已经开辟好的空间。指定数组长度,则数组作为一个显式参数传给数组

二维数组传参

二维数组的表示方法如下:

void test(int arr[3][5])
//void test(int arr[][])
void test(int arr[][5])
//二维数组传参,函数形参的设计只能省略第一个[]的数字

以上表示方法第二种有错误

从实参传递来的是数组的起始地址,在内存中按数组排列规则存放(按行存放),而并不区分行和列,如果在形参中不说明列数,则系统无法决定应为多少行多少列,不能只指定一维而不指定第二维,即多维数组传参要指定第二维或者更高维的大小,可以省略第一维的大小

一级、二级指针传参

一级指针传参和二级指针传参较为简单,传参后,对指针参数执行间接访问操作使函数修改原本变量的值,值得一提的是:
函数参数部分为一级指针时,函数可接收的参数有以下几种

  1. 一个整形指针
  2. 整型变量地址
  3. 一维整型数组数组名

函数参数部分为二级指针时,函数可接收的参数有以下几种

  1. 二级指针变量
  2. 一级指针变量地址
  3. 一维指针数组的数组名
  4. 二维数组的数组名

数组指针与指针数组

数组指针

定义

int  (*p)[10];

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

数组指针的运用

void print_arr1(int arr[3][5], int row, int col)
{int i = 0;int j;for (i = 0; i<row; i++){for (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;int j;for (i = 0; i<row; i++){for (j = 0; j<col; j++){printf("%d ", arr[i][j]);}printf("\n");}
}
int main()
{int arr[3][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };print_arr1(arr, 3, 5);//数组名arr,表示首元素的地址//但是二维数组的首元素是二维数组的第一行//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址//可以用数组指针来接收print_arr2(arr, 3, 5);return 0;
}

如上述代码所示,两个函数作用都是遍历二维数组的各个元素,区别不大

指针数组

int  *p[10];

//[]的优先级要高于*,说明p是一个数组,结合*,即存储指针的数组,叫指针数组

函数指针数组

函数指针

  void *pfun();

pfun先和*结合,说明pfun是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。

函数指针数组

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

int (*parr[10])();

//[]的优先级要高于*,说明paar是一个数组,数组的内容为int (*)()类型的函数指针
函数指针数组一般用于:转移表
举个例子:实现计算器操作的代码
普通写法

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;while (input){scanf("%d", &input);switch (input){case 1:scanf("%d %d", &x, &y);ret = add(x, y);break;case 2:scanf("%d %d", &x, &y);ret = sub(x, y);break;case 3:scanf("%d %d", &x, &y);ret = mul(x, y);break;case 4:scanf("%d %d", &x, &y);ret = div(x, y);break;default:printf("选择错误\n");break;}printf("ret = %d\n", ret);}return 0;
}

利用函数指针数组

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){scanf("%d", &input);if ((input <= 4 && input >= 1)){scanf("%d %d", &x, &y);ret = (*p[input])(x, y);}elseprintf("输入有误\n");printf("ret = %d\n", ret);}return 0;
}

相较之下,简便了很多

回调函数

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

void bubble(void *base, int count , int size, int(*cmp )(void *, void *))

看到两道很有意思的题和大家分享一下,可以验证是否掌握了指针的大部分知识:

int main()
{char *str[] = { "welcome", "to", "fortemedia", "nanjing" };char **p = str + 1;str[0] = (*p++) + 2;str[1] = *(p+1);str[2] =p[1] + 3;str[3] = p[0]+(str[2]-str[1]);printf("%s\n", str[0]);  // printf("%s\n", str[1]);  //nanjingprintf("%s\n", str[2]);  //jingprintf("%s\n", str[3]);  //greturn 0;
}

进化版…

int main()
{char *c[] = { "ENTER", "NEW", "POINT", "FIRST" };char**cp[] = { c + 3, c + 2, c + 1, c };char***cpp = cp;printf("%s\n", **++cpp);          //POINTprintf("%s\n", *--*++cpp + 3);    //ERprintf("%s\n", *cpp[-2] + 3);     //STprintf("%s\n", cpp[-1][-1] + 1);  //EWreturn 0;
}

还有这两段代码的名称,可以尝试读一下(出自《C陷阱与缺陷》)

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

指针进阶(指针与数组传参、数组指针与指针数组、函数指针数组、回调函数的辨析)相关推荐

  1. 什么是二维数组以及二维数组传参

    什么是二维数组 当刚看见二维数组时肯定会对二维数组有一些疑惑二维数组是什么? int arr[4] = { 1,2,3,4 };//这个便是一维数组 以 int 类型为例子当 int 类型的元素集合在 ...

  2. 【C语言进阶】⑤关于数组传参和指针传参辨析

    文章目录 一.数组 1.1.一维数组 1.2.二维数组 1.3.指针和数组 二.数组传参 2.1一维数组传参 2.2二维数组传参 2.3小结 三.指针传参 3.1一维指针传参 3.3二级指针传参 四. ...

  3. 指针-数组传参,指针传参

    目录 数组传参 一维数组 二维数组 指针传参 一级指针 二级指针 一个特殊的二级指针 数组传参 一维数组 我们把传入一维数组时输入的是数组名,数组名是一个指向首元素的指针 那么在函数接收时,只需要保证 ...

  4. 【让你从0到1学会C语言】指针/数组传参以及static关键字

    作者:喜欢猫咪的的程序员 专栏:<C语言> 喜欢的话:世间因为少年的挺身而出,而更加瑰丽.                                  --<人民日报> ...

  5. 一篇让你弄明白C语言指针传参和数组传参~

    文章目录 前言 ⭐️指针传参和数组传参 ⭐️一维数组传参 ⭐️一级指针传参 ⭐️一道经典的指针传参相关题目 ⭐️二维数组传参 ⭐️函数指针!!! ⭐️一个有趣的代码!(*(void( * )())0) ...

  6. [C] 数组指针、指针数组及数组传参

    指针 字符指针 数组指针 指针数组 一维数组传参 1. 整型数组 2. 指针数组 二维数组传参 一级指针传参 二级指针传参 小引 在指针的学习阶段,有不少同学都十分畏惧这个物什,甚至"谈指针 ...

  7. C语言补漏:字符串指针与字符数组传参

    字符串指针与字符数组传参 深信服的笔试上被吊打,其中对一道用指针做形参的题目印象十分深刻,借此恶补了一晚上指针,今天总结,以作警示. ​ 试想有如下情形,将一个字符串指针做形参赋值函数修改其字符串,函 ...

  8. 【C语言】指针第二弹(指针数组、数组指针、数组传参)

    一. 指针数组 指针数组就是存放指针变量的数组,指针数组的本质是数组,而非指针. 1.1 定义和初始化 定义:int* arr[3]  //arr是存放整型指针的数组,包含3个元素 初始化:int* ...

  9. 数组传参中形参的秘密,以及数组名当作函数实际参数的特点,以及二维数组,以及外部变量和全局变量

    1.数组传参中形参的秘密 第三行:形参中不存在数组的概念,即便是中括号约定了数组的大小,也无效. 第二十行:传递的是一个地址,是数组的首地址. 数组名代表了整个数组的首地址. 第二十一行:第一个元素的 ...

最新文章

  1. android炫酷的自定义view,Android自定义View实现炫酷进度条
  2. html class 位置,HTML class 属性 | 菜鸟教程
  3. android bootloader阶段GPIO的控制
  4. 关于prefrenceactivity和preferencefragment的作用
  5. php 5.5 xhprof for windows
  6. python通信编程_python 通信编程
  7. 卸载驱动出现:rmmod: can't change directory to '/lib/modules': No such file or directory
  8. PHP的单引号和双引号
  9. svn的使用--解决commit冲突问题
  10. 190728-flink官方文档阅读和实战记录
  11. Atitit 物化视图与触发器性能测试方法 attilax总结 1.1. 触发器主要影响更新性能。。。 1 1.2. 临时打开关闭触发器,如果db不支持可以更改条件使其不触发 1 1.3. 打开定时
  12. 图解Python List数据结构
  13. VMware ESXi 6.7 安装LEDE
  14. 企业业务逻辑常见风险
  15. 实对称矩阵对角化为什么要做正交化单位化操作呢?
  16. 字节跳动上班有多累?
  17. Linux 音频系统简析
  18. python风变编程和扇贝编程_想学习phython ,纠结是扇贝编程还是风变编程?
  19. 什么是VBA,他有什么作用
  20. (21)恢复数据(recovery)时间和撤销数据(removal)时间

热门文章

  1. 放大电路反馈类型的判断方法
  2. 分布式架构设计之基础软件系统架构
  3. 企业如何正确挑选源代码加密软件
  4. Jmeter性能测试案例(一)
  5. 雪落时刻,天地寂静,静静地落
  6. 如何从sim卡中读取手机号码?
  7. 基于PHP+MySQL的学生成绩管理系统——计算机毕业设计
  8. 基于Fabric+IPFS大规模数据上链方案
  9. 未来的城市:智慧城市定义、特征、应用、场景
  10. Final Cut Pro下载附免费资源