C语言学习笔记----4(指针)
文章目录
- 指针深入介绍
- 指针类型
- 二级指针
- 字符指针
- 指针数组
- 数组指针
- 函数指针
- 函数指针数组
- 函数指针数组指针
- 回调函数
- 总结
指针深入介绍
众所周知,亚里士多德1撑起了古希腊科学、哲学的半壁江山。
而指针就像是C语言中的亚里士多德一样,撑起了C语言的半壁江山。
不,与其说,指针撑起了C语言的半壁江山,不如说是指针创造了C语言的一片天地。
小明:指针不就是存放了一个地址的变量而已嘛?就这?
小明,数学题你都会做了?还是你终于考上大学了?来这凑啥热闹?
指针类型
首先,要登场的是我们的二级指针
二级指针
小明同学在不经意间,已经说出了一个惊天大秘密!!!
指针就是一个变量
只不过是说 指针 这个变量和其他的变量不同,它存放的是某个数据的位置
变量,就意味着指针也是存放在内存中的某个位置,它也有自己的“门牌号”
也就是只要我有钥匙,我就可以光明正大进入指针的小家
em…当然最关键的还是需要 “&” 老大哥来帮忙配个钥匙
int main()
{int a = 10;int* pa = &a;int** ppa = &pa;//此时ppa变量中存放的内容就是pa指针的地址return 0;
}
此时,我们就通过ppa可以进入pa的小家,“光明正大”地实施改造计划。
首先,趁着 pa 出门打酱油,将 a 进行改变
int main()
{int a = 10;int* pa = &a;int** ppa = &pa;**ppa = 100;return 0;
}
小明:就这???
那来个狠的
int main()
{int a = 10;int* pa = &a;int** ppa = &pa;**ppa = 100;int b = 20;*ppa = &b;return 0;
}
新创建一个 b 变量,进行操作 **ppa = &b;
此时再使用 pa 得到的就不是 a 变量,而是 b 变量
指针pa:我媳妇呢???
通过观察,可以知道 二级指针的结构是 int* *p;
其中 int* 表示 p 的数据类型是 int* 类型的
剩下的 **“ * ”**则表示 p 是一个指针
合起来就是一个 指向一个int*类型的变量的指针
可以通过观察二级指针的结构,推导出三级指针,四级指针…
总结:
- 二级指针存放的是一级指针的地址,而一级指针存放的是数据的地址
- 二级指针可以通过两次解引用操作来访问数据
- 二级指针进行一次解引用操作就可以改变一级指针存放的内容
小明:感觉也就一般般吧
字符指针
小明(不屑):字符指针?
eg:
char ch = 'w';
char* pc = &ch;
printf("%c\n", ch);
printf("%c\n", *pc);
小明(不屑地准备滑走)
eg:
char* str1 = "Hello World";
char* str2 = "Hello World";
char strArr1[] = "Hello World";
char strArr2[] = "Hello World";
问:
1、此时 str1 和 str2 相等吗?
答:相等
2、此时 strArr1 和 strArr2 相等吗?
答:不相等
小明:为什么 str1和 str2 相等,而 strArr1 和 strArr2 不相等呢?
"Hello World"是一个字符串常量2,将字符串常量赋值给字符指针的时候是将字符串常量的首元素的地址传给了字符指针,因此 str1 和 str2 指向的都是 字符串常量的首元素的地址
而 strArr1 和 strArr2 是数组,将字符串赋值给字符数组,相当于将字符一个一个复制到了数组中。
小明:那为啥他俩不还是一模一样?为啥电脑告诉我他们不相等?电脑骗人?
注意,计算机是不会骗人的,他们只是严格的执行一条接一条的指令而已。
之所以 strArr1 和 strArr2 不相等是因为他们确实不相等。
咳咳~strArr1 和 strArr2 是两个数组
只要不是同一个数组,那么他们的首元素地址不可能相等
小明(点头):em。。。我明白了
说到数组,有两个孪生兄弟忍不住想来了
指针数组
顾名思义,指针数组就是一个存放指针的数组
网恋需谨慎,能见才靠谱
int main()
{int* arr[10];char** arr2[10];return 0;
}
而 char** arr2[10]; 表示一个有10个数据类型是 char** 的元素的数组
数组指针
顾名思义,数组指针就是一个指向数组的指针
同样,先来看看数组指针的结构
int main()
{int* p1[10];int* (p2[10]);int (*p3)[10];return 0;
}
小明:这都是数组指针???
我:你猜
小明:
所以 p1 不是数组指针
所以 p2 也不是数组指针
所以这里只有 p3 是 数组指针
因此,不难发现,数组指针和指针数组的区别
名字 | 本质 | 所占空间 |
---|---|---|
数组指针 | 指针 | 4byte / 8byte |
指针数组 | 数组 | 4 * n byte / 8 * n byte(n为数组元素个数) |
名字 | 数据类型 |
---|---|
数组指针 | int (*) [](指向的数组中的元素是int类型) |
指针数组 | int*[](数组中的元素都是int*类型) |
名字 | 作用 |
---|---|
数组指针 | 存放一个数组的首元素的地址,表示整个数组的地址 |
指针数组 | 存放n个指针,每个指针指向的元素类型相同 |
数组指针更多的是用来当二维数组3作为函数实参的形参
#include<stdio.h>
void print(int (*pa)[3], int row, int col)
{int i = 0;int j = 0;for (i = 0; i < row; i++){for(j = 0; j < col; j++){printf("%d ", pa[i][j]);}printf("\n");}
}
int main()
{int arr[2][3] = {{1, 2}, {3, 4}, {5, 6}};print(arr, 2, 3);return 0;
}
此时 arr 表示的是首元素的地址,同时也表示 第一行的地址 ,也是一个一维数组的地址
类比可得:
arr[1] 表示第二行的地址;
arr[2] 表示第三行的地址;
因此,pa相当于接收了第一行的地址(arr的每行都是 两个int类型 的数据)
而 pa[ i ] [ j ] 和 *( *( pa + i ) + j)是等价的
所以 pa[ i ] [ j ] 可以读取到第 i 行,第 j 列的元素
介绍到这里,是时候来看看指针Plus版本了
问:解释下列代码的含义
int arr1[5]; //1
int *arr2[5]; //2
int (*arr3)[5]; //3
int (*arr4[10])[5]; //4
小明:
1 是数组
2 是数组指针
3 是指针数组
4 …
你这不行啊,小明
当括号将 * 和 字符单独括起来,后面加一个[ n ],前面加一个数据类型
那这就是数组指针
小明:所以第四个是数组?
对,但也不全对
那我们不妨将arr[10]拿掉,看看它到底是何方神圣
小明:天哪?!这不是个数组指针吗?
是的,就是数组指针
由此,4到底是什么已经真相大白了。
它就是
一个有 10 个元素的指针数组
每个指针都是指向的一个有 10 个整形元素的数组
答:
1 是一个有 5 个 int 类型的元素的数组
2 是一个有 5 个 int* 类型的元素的数组
3 是一个指向一个有 5 个 int 类型的元素的数组的指针
4 是一个有 10个 int*[ 5 ] 类型的元素的数组
还有两个小伙伴也是迫不及待了
小明:还有???
函数指针
介绍函数指针之前,先看一段代码
#include<stdio.h>
void test()
{;
}
int main()
{printf("%p\n", test);printf("%p\n", &test);
}
由此可见, 函数也没有那么神秘
也是在栈4中开辟的一块地址
而且函数和数组很相似,名字都是它们的地址
要能够存储地址就必须是一个指针
判断一下,pfun1和pfun2哪个是指针
void (*pfun1)();
void *pfun2();
小明:pfun1
因为有括号,所以 pfun1 先和 * 结合
后面那个括号应该就是表示这是一个函数指针
有点东西啊,小明
不过,还是没完全对
pfun1 和 * 先结合表示这个是一个指针
后面的括号是指向函数的 参数(此时什么都没写,表示传递的参数为空)
void 表示 函数的返回类型
pfun2就是一个函数,它的返回值的类型是void*,同时传递的参数为空
又到了提问的环节
解释下列代码的含义:
(*(void (*)())0)(); //1
void (* signal(int, void(*)(int)))(int); //2
小明:
不慌,且听我慢慢道来
首先,先一层一层推进
小明:还是不明白
再推进一层
显然,这是一个函数指针类型
而这,就是将 0 进行强制类型转换为void(*)()类型
再对 0 进行解引用
末尾的 ( ) 表示函数的参数(此时也是什么都没有传递)
综上,代码1的含义是:
将 0 强制类型转换为void(*)(),再进行解引用,再传递参数
从而实现了调用在 0 地址处的函数( 0 地址一般无法这样操作)
小明:代码2呢?
同样,括号这么多,先一层一层向内部推进
小明:有点眼熟
再推进一层
小明:这表示的是两个类型,一个int, 一个void(*)(int)
没错
这就是函数signal的两个参数类型
字符串已经和括号结合,表示这是一个函数的声明
那么剩下的就是这个函数的返回类型了
所以,这个函数的返回值类型就是 void(*)(int)
综上所述,
代码2表示的含义是 函数signal 的声明
其中,这个 signal 的参数一个是 int, 另一个是 void(*)(int)
signal 的返回值的类型也是 void(*)(int)
这两个代码均在《C陷阱和缺陷》中提及
代码2的结构太复杂,可以利用 typedef 来简化
typedef void (*pfun_t)(int); pfun_t signal (int, pfun_t);
小明:如果,我需要使用多个函数,而他们的参数,返回值都是一样的,我可以用什么来进行简化呢?
接下来就是函数指针数组来大放光彩了
函数指针数组
函数指针数组和一般的指针数组一样
只不过
函数指针数组中的指针指向的都是函数
在见到函数指针数组之前,先看看正常情况下计算器的实现代码
void menu()
{printf("******************************\n");printf("0.exit\n");printf("1.Add\n");printf("2.Subtract\n");printf("3.Multiply\n");printf("4.Divide\n");printf("******************************\n");
}int Add(int x, int y)
{return x + y;
}int Subtract(int x, int y)
{return x - y;
}int Multiply(int x, int y)
{return x * y;
}int Divide(int x, int y)
{return x / y;
}int main()
{int input = 0;int x = 0;int y = 0;menu();do{scanf("%d", &input);switch (input){case 0: break;case 1: scanf("%d %d", &x, &y);printf("%d\n", Add(x, y));break;case 2:scanf("%d %d", &x, &y);printf("%d\n", Subtract(x ,y));break;case 3:scanf("%d %d", &x, &y);printf("%d\n", Multiply(x, y));break;case 4:scanf("%d %d", &x, &y);printf("%d\n", Divide(x, y));break;default:break;}} while(input);return 0;
}
而现在对其进行改造
int main()
{int input = 0;int(*arr[5])(int, int) = { 0 , Add, Subtract, Multiply, Divide };int x = 0;int y = 0;do{menu();scanf("%d", &input);if (input > 0 && input < 5){scanf("%d %d", &x, &y);printf("%d\n", (*arr[input])(x, y));}} while (input);return 0;
}
其中 int(*arr[5])(int, int) = { 0 , Add, Subtract, Multiply, Divide }; 就是一个函数指针数组
既然有函数指针数组,那么函数指针数组指针自然也是少不了的
函数指针数组指针
将函数指针去掉,就是之前介绍过的数组指针
所以,通过观察数组指针
可以得到
函数指针数组指针
是一个指向一个函数指针数组的指针,里面存放的是数组的地址
函数指针数组指针虽然名字看起来很复杂,但是他的结构也一点不简单
小明:
首先,写出数组中的元素类型
int(*)(int, int)
其次,将指针和数组添加上去
int(*(*parr)[5])(int, int) = arr;
回调函数
简介:回调函数就是一个通过函数指针调用的函数。
也就是说,将A函数的指针,作为参数传递给B函数
当A函数的指针被用来调用它所指向的A函数时,称此为回调函数
qsort就有回调函数的使用
在使用qsort的时候就需要传递一个函数指针,用来自定义如何排序
下面来简单介绍一下 qsort
qsort 的头文件是<stdlib.h>
qsort 的参数含义是数组名,元素个数,元素所占字节大小,比较方法的函数
#include<stdlib.h>
int com(void* q1, void* q2)
{return *(void*)q1 - *(void*)q2;
}
int main()
{int arr[10] = { 1,3,2,4,6,8,7,10,9,5 };qsort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(arr), com);return 0;
}
我们自己自定义的函数返回值的类型一定要是int,同时要有两个void*类型的指针作为参数
当 com 的返回值是小于等于 0 的时候,qosrt不会对数组做出改变
而当 com 的返回值是大于 0 的时候,qsort对数组进行排序
比较方法的函数可以根据需要排序的数组中的元素类型来灵活调整
总结
以上,便是C中部分的指针的介绍。
C的指针的奥妙绝不是我这三言两语能够说的清楚的,它在我看来,指针就是C中最牛逼的部分,不接受反驳
注释:
亚里士多德(Aristotle公元前384~前322),古代先哲,古希腊人,世界古代史上伟大的哲学家、科学家和教育家之一,堪称希腊哲学的集大成者。他是柏拉图的学生,亚历山大的老师。 ↩︎
字符串常量是一对双引号括起来的字符序列。 字符常量可以赋值给字符变量,如"char b=‘a’;",但不能把一个字符串常量赋给一个字符变量,同时也不能对字符串常量赋值! ↩︎
因为二维数组在内存中的使用是连续的,因此,二维数组的本质就是一维数组。只不过,二维数组相当于是多个一维数组叠加在一起 ↩︎
关于栈的内容,可以移步到这篇文章中 ↩︎
C语言学习笔记----4(指针)相关推荐
- 梓益C语言学习笔记之指针
梓益C语言学习笔记之指针 一.32位平台下,地址是32位,所以指针变量占32位,共4个字节 二.内存单元的地址即为指针,存放指针的变量称为指针变量,故:"指针"是指地址,是常量,& ...
- C语言学习笔记--数组指针和指针数组
C 语言中的数组有自己特定的类型,数组的类型由元素类型和数组大小共同决定.(如 int array[5]类型为 int[5]) 1.定义数组类型 C 语言中通过 typedef 为数组类型重命名:ty ...
- C语言学习笔记(指针篇)
1.1指针是什么 关于地址: 在程序中定义一个变量系统就会分配内存单元,根据变量类型去分配一定空间的长度.每一个字节都有一个编号,这就是"地址". 通过地址能找到变量单元,所以我们 ...
- C语言学习笔记09-数组、字符数组、字符串数组、二维数组(单字符输入输出putchar、getchar,字符串输入输出的scanf、gets、puts)
C语言数组 数组作用:可以用来保存很多记录(可以看成一种大容器).一些简单游戏也基本由数组实现,如游戏地图(二维数组)等等. 一个数组 划分 多个单元(下标区分) -存放-> 多个同类元 ...
- c语言学习笔记【结构体02】结构体指针变量与结构体变量的函数参数,C语言学习笔记结构体02结构体指针变量与结构体变量的函数参数.docx...
C 语言学习笔记[结构体02]结构体指针变量与结构体变量 的函数参数 C 语言学习笔记之结构体指针变量一提指针,那可 是 C 语言的核心了,有多少学子曾拜倒在指针的脚下.单纯的说指针,其实并不难,但是 ...
- C语言学习笔记10-指针(动态内存分配malloc/calloc、realloc、释放free,可变数组实现;Tips:返回指针的函数使用本地变量有风险!;最后:函数指针)
C语言:指针 1. 指针:保存地址的变量 *p (pointer) ,这种变量的值是内存的地址. 取地址符& 只用于获取变量(有地址的东西)的地址:scanf函数-取地址符 地址的大小 ...
- 梓益C语言学习笔记之链表&动态内存&文件
梓益C语言学习笔记之链表&动态内存&文件 一.定义: 链表是一种物理存储上非连续,通过指针链接次序,实现的一种线性存储结构. 二.特点: 链表由一系列节点(链表中每一个元素称为节点)组 ...
- 6.方法(go语言学习笔记)
6.方法(go语言学习笔记) 目录 定义 匿名字段 方法集 表达式 1. 定义 方法是与对象实例绑定的特殊函数. 方法是面向对象编程的基本概念,用于维护和展示对象的自身状态.对象是内敛的,每个实例对象 ...
- c语言中void arrout,c语言学习笔记(数组、函数
<c语言学习笔记(数组.函数>由会员分享,可在线阅读,更多相关<c语言学习笔记(数组.函数(53页珍藏版)>请在人人文库网上搜索. 1.数组2010-3-29 22:40一维数 ...
- c语言float二进制输出代码_C语言学习笔记——学前知识概述
将我大一学习C语言时做的笔记拿来与大家分享,内容比较浅显,比较适合初学者,如有错误还请见谅,提出改正,谢谢! 前言:此C语言笔记是本人在自学时记录的一些重点或初学者常犯的错误,希望我的这本笔记能够对大 ...
最新文章
- 四个月没返校,挂在宿舍的内裤变“鸟窝”,网友:画面太美,不敢面对
- 【跨域报错解决方案】Access to XMLHttpRequest at ‘http://xxx.com/xxx‘ from origin ‘null‘ has been blocked by
- 智慧停车产业链市场全透析
- 检测msmq里消息的数量
- Spring5 - Bean的初始化和销毁的4种方式
- 现代环境下的网络分割
- mysql libs 5.1.71_用python创建数据库监控平台(1)安装MySQL5.7
- cpu模拟器c语言实现_你写出来的C语言是这样调用硬件的!
- 接口测试工具--apipost脚本讲解
- mysql mysql_real_connect 内存泄露
- FireEye动态:SolarWinds Orion 新 0day用于安装SUPERNOVA
- anaconda tensorflow import PIL 报错的解决方法
- vsftpd不支持目录软链接的解决办法
- 数据结构和算法——八种常用的排序算法----选择排序
- 人人商城小程序微信支付配置
- snownlp抛出错误_9snowNLP常见用法
- 模块化机房建设指导书_模块化机房建设方案
- python批量生成姓名_python——批量生成姓名
- android清理存储空间不足,安卓手机内存空间不足该如何清理
- 【汽车】新能源汽车的分类
热门文章
- 计算机学院 网页设计大赛策划书,校园网页设计大赛策划书方案
- Linux服务器监控以及查日志教程
- 帝国cms html5 编辑器,帝国cms7.2编辑器换成百度编辑器的方法(完全正确教程)...
- 11年程序员给本科、研究生应届生以及准备从事后台开发同学的建议,学习进阶之路
- 2018深大计算机考研,深圳大学2018年硕士生拟录取名单公示(统考)
- 大数据 (三) BI报表系统 superset 源码搭建开发环境
- arm linux g 找不到,/ bin / sh:1:arm-linux-gcc:在ubuntu上找不到
- Mac端口查找和kill
- mybatis 报错There is no getter for property named 'XXX' in 'class com.xx.xx'
- MIPI 系列之 DBI