系列文章目录

第一回  三十二个关键字  心性修持大道生
没看过第一回? 点这里
第二回  悟彻指针真妙理  归本数组合元神


前言

大家好!我们又见面了,今天更新了第二回的内容,一起来看看叭。

文章目录

  • 系列文章目录
  • 前言
  • 一、指针
    • 1.1 指针的内存布局
    • 1.2 小兔子要开几扇门?
    • 1.3 int *p = NULL 和 *p = NULL 的区别
    • 1.4 如何将数值存储到指定的内存地址
  • 二、数组
    • 2.1 数组的内存布局
    • 2.2 &array[0]和&array的区别
    • 2.3 数组名a作为左值和右值的区别
  • 三、金箍棒和九齿钉耙---指针和数组的区别
    • 3.1 以指针的形式访问和以下标的形式访问指针
    • 3.2 a和&a的区别
    • 3.3 指针和数组的定义和声明
    • 3.4 指针和数组的对比
  • 四、指针数组和数组指针
    • 4.1 指针数组和数组指针的内存布局
    • 4.2 数组指针的步长
    • 4.3 地址的强制转换
  • 五、多维数组和多级指针
    • 5.1 二维数组布局是棋盘吗?
    • 5.2 经典例子
  • 六、函数指针
    • 6.1 函数指针的定义
    • 6.2 函数指针的使用
    • 6.3 函数指针数组
  • 总结

提示:以下是本篇文章正文内容

一、指针

1.1 指针的内存布局

先看下面的例子:

int a = 5;
int *p = &a;

这里定义了一个指针p,但是指针到底是什么呢?
我们前面已经学习了int、char等数据类型,他们大小不一。
我么可以把它们看做是不同型号的模具压出来的,如下图:

int类型的模具压一下是4个字节,char类型压一下是1个字节…
我们定义一个整型变量a,就好比用int类型的模具在内存上压了一下,发现压了四个字节。

指针也是一种数据类型,那我们就用int*作为模具在内存上压一下, (在32位系统下)发现指针变量也占4个字节,于是这四个字节的空间被命名为p,p里面只能存放某个内存地址。并且这个某个内存的地址开始的连续4个字节只能存放int类型的数据。
图解:

思考:为什么p只能存放某个内存地址呢??

在锤子眼里,什么东西都是钉子

在指针眼里,什么东西都是地址

也就是说,不论我们在指针变量里放什么,都会被视作地址。

Tips:指针变量的大小与类型无关,32位系统下均为4.

1.2 小兔子要开几扇门?

* 是解引用操作符,* 通过对指针进行解引用操作,可以修改被指向地址存放的值。
举个例子:

#include<stdio.h>int main()
{int a = 0;int* p = &a;*p = 10;printf("%d", a);
}

打印结果为10,这说明*pa找到了内存中的某个区域,将其改为10.
这时细心的小伙伴就发现了,你只举了int * 类型的例子,可是指针变量有这么多类型,大小也都是4个字节,有什么区别吗?
我们看以下例子:

int main()
{int a = 64;char* p = &a;*p = 10;    //只有一个字节的访问权限,只能修改a中的一个字节
}

我们首先定义整型变量a,其值为64,十六进制存储为0x00000400
p是一个char*类型的指针,*p = 10; 这条语句只能改变了a的一个字节里面的数据。
因而修改后a的值为0x0000040a

指针变量类型实际上决定了在解引用时能访问几个字节。

1.3 int *p = NULL 和 *p = NULL 的区别

先看下面的代码:

int *p = NULL;

这段代码的意思是:定义一个指针变量p,它指向的内存里存放一个int类型的数据;定义p的同时把p的值设为0x00000000,这个过程叫做初始化。

int *p ;
*p = NULL;

这段代码的意思是:定义一个指针变量p,它指向的内存里存放一个int类型的数据;定义p的同时,我们不知道p里面存的是哪个地址,这个地址可能是非法的;接着我们把*p的值改为0x00000000,也就是说,我们在内存里随便找了个地址,对它进行了一些操作,这种做法明显是错误的。

对于这种指针,我们称为野指针,又称为野狗

野狗的特点: 1.没人要,可能出现在任何地方。
       2.和野狗玩耍,可能出现严重的后果。

为了避免出现野狗伤人,我们在定义指针变量时一定要初始化。

1.4 如何将数值存储到指定的内存地址

如果我们想往一个指定的地址写入数据,比方说想在0x004ffd80中写入0x100,可用以下代码:

 int *p = (int*)0x004ffd80;*p = 0X100;

这玩意必须得强制类型转换,这样0x001ffd80才不会被看做是一个整型数据。
为什么是0x004ffd80这个地址呢?
因为并不是所有内存中的地址我们都有权限访问,

这个地址是通过定义整型变量 i 从监视窗口得到的,所以我们可以偷偷使用。

Tips:VS2019这样做是不行的,因为每次编译器为整型变量 i 分配的地址是不同的。
第一次分配的可用地址第二次使用不一定合法。
经过尝试,编译器会报如下的错误。

二、数组

2.1 数组的内存布局

先看下面的例子:

int array[5];

定义一个整型数组array,数组有五个元素,每个元素的类型是int。

当我们定义一个数组array时,编译器根据其元素类型和元素个数为其分配内存空间,并将其命名为array
名字array一旦和这块内存匹配就不能被改变,array[0]是数组的元素,但不是元素的名字,数组的每个元素都是没有名字的。

2.2 &array[0]和&array的区别

请看以下代码:

#include<stdio.h>int main()
{int array[5] = { 0 };printf("%p\n", &array);printf("%p\n", &array[0]);
}

执行后我们发现结果是一样的,但是意义是不一样的
&array[0]是取数组首元素的地址,&array是取整个数组的地址,整个数组的地址在数值上和首元素地址是一样的,因而打印结果一样。
执行以下代码,

#include<stdio.h>int main()
{int array[5] = { 0 };printf("%p\n", &array[0]);printf("%p\n", &array[0]+1);printf("%p\n", &array);printf("%p\n", &array + 1);
}

我们发现他们的步长是不同的。
&array[0]+1步长为4,跳过了一个数组元素大小的内存空间
&array+1步长为20,跳过了一整个数组大小的内存空间

2.3 数组名a作为左值和右值的区别

一般来说,赋值运算符左边的是左值,右边的是右值。
左值和右值有什么区别呢?
我们执行以下代码:

#include<stdio.h>int main()
{const int a = 5;a = 10;
}

编译器报错了,内容是 表达式必须是可修改的左值
这说明左值是可修改的,而要修改一个变量的值,我们就需要它的地址,通过地址去找到它并修改。

假定有一条语句a = b;

左值:编译器认为a的含义是a代表的地址。

右值:编译器认为b的含义是b所在地址存储的内容。
(图隔开)
既然明白了左值和右值的区别,我们来看数组作为左值、右值的情况。
左值:

#include<stdio.h>int main()
{int array[5] = { 0 };array[0] = 10;
}

我们知道数组的每个元素是没有名字的,但是可以通过它的地址找到它,这样就把10赋进去了。这说明array[0]的含义是array[0]代表的地址。

#include<stdio.h>int main()
{int array[5] = { 0,1,2,3,4 };array[0] = array[1];
}

array[0] = array[1];这条语句,执行胡array[0]的内容变为1,说明数组元素作为右值时,array[1]的含义是这个地址存储的内容。

Tips:数组名不能作为左值。

三、金箍棒和九齿钉耙—指针和数组的区别

指针和数组看似有许多共同点,实际二者完全不同。
指针就是指针,数组就是数组。

你可以认为他们是两个串通好的坏女人,经常穿着对方的衣服来哄骗你

3.1 以指针的形式访问和以下标的形式访问指针

请看以下代码:

 char *p = "abcdef";

这里我们定义了一个指针变量p,它本身在栈上占四个字节,他存储了一块内存空间的首地址,这块内存空间位于静态区,大小为7个字节。(字符串末尾带’\0’)如果我们现在想访问字符‘e’,有两种方式:
1.以指针形式访问:*(p+4)

找到p里面存储的地址,加上4个字符的偏移量,得到新的地址,然后解引用得到‘e’

2.以下标形式访问: p[4]

找到p里面存储的地址,加上4个元素的偏移量,得到新的地址,然后解引用得到‘e’

经过这么一折腾,我们发现:这不是一样吗?
没错,以指针形式访问和以下标形式访问没有本质区别
但这似乎是指针和数组唯一的联系了。

3.2 a和&a的区别

我们先看一个例子:

int main()
{int a[5] = {5, 4, 3, 2, 1};int *ptr = (int *)(&a + 1);printf( "%d,%d", *(a + 1), *(ptr - 1));return 0;
}

程序的输出是什么呢?
&a+1,取数组的首地址,向后走一整个数组的步长,变成了a[5],显然已经越界,但是有问题吗?没有问题。
为什么呢?
打个比方,我没有访问银行柜台的权限,但是我总有看一眼柜台的权限吧?
我确实指向了a[5],但并不代表我要去做什么非法的事情。

int *ptr = (int *)(&a + 1);将刚刚得到的地址强制类型转换赋值给ptr

*(a + 1), a是数组首元素的地址,+1变为第二个元素地址,解引用后输出4

*(ptr - 1), ptr指向a[5],-1指向a[4],解引用得到1

3.3 指针和数组的定义和声明

先思考定义是什么? 声明又是什么

int i;
extern int x ;

谁是定义?谁是声明?
话不多说,上图。

声明的经典例子:函数的声明

void Print(int x,int y)

我们已经知道声明是不分配空间的
那么下面的式子完全等价:

extern int a[];
extern int a[100];

编译器完全不需要知道这个数组有几个元素,他只要知道a数组的定义在别的地方,还知道这个数组a的起始地址。
这样数组内的每个元素的地址都可以通过起始地址计算出来。
(配图分割)
如果我们定义了一个数组,在另外一个文件中使用,其声明也必须是数组。
举例:

如果定义为数组,声明为指针,会是什么效果呢
编译器认为a是一个指针变量占四个字节,它直接取了数组前四个字节作为存储的地址,= =
这个地址的有效性不得而知。
一样的,如果定义指针,声明为数组,也是错的!!!
(配图)

3.4 指针和数组的对比

四、指针数组和数组指针

指针数组?函数指针?绕晕了吗,没关系,博主带你一探究竟。

4.1 指针数组和数组指针的内存布局

指针数组: 指针数组是一个数组,数组的每个元素是指针,数组占多少字节由元素个数决定即N*sizeof(a[0])。

数组指针: 数组指针是一个指针,指针指向一个数组,32位系统下,指针占4个字节,至于其指向的数组大小是未知的。

猜猜看,下面谁是指针数组,谁是数组指针?
Tips:注意操作符的优先级。

 int* a[10];//式子1int(*a)[10];//式子2

式子1:由于[ ]的优先级高于 * ,式子1中a先和[ ]结合,说明a是一个数组,接着与 * 结合,说明数组里的每个元素是 int * 类型,即数组p里面存放了10个指针,最后和int结合,说明指针指向的内容的数据类型为int,所以p是一个由指向整型数据的指针组成的数组。

式子2:由于*p有小括号,p先和 * 结合,说明p是一个指针,接着与[ ]结合,说明p指向的内容是数组,接着与int 结合,说明数组的元素类型为int,所以p是一个指向整型数组的指针。

4.2 数组指针的步长


通过这个报错,我们可以知道:
把变量名去掉,剩下的部分就是数组指针的类型
再看一个例子:

#include<stdio.h>int main()
{char a[5] = { 'A','B','C','D' };char(*p3)[5] = &a;//char(*p4)[5] = a;printf("%p\n", p3);printf("%p\n", p3+1);return 0;
}

输出后我们发现p3+1的地址比p3的地址大5,
这说明数组指针的步长就是所指向的数组的大小

4.3 地址的强制转换

请看这个例子:

struct Test
{int num;char* pc_Name;short Date;char a[2];short b[4];
}*p;

假设p的值为0x00000000,试求下列式子:

p+0x1=0x_________;

(unsigned int*)p+0x1=_________;

(unsigned long)p+0x1=0x______;

一个指针变量和整数相加减,结果是什么呢?
前面我们已经多次聊到步长
p+0x1,这个0x1可不是普通的整数,实际上它是sizeof(Test) * 0x1,
由于结构体Test的大小为20个字节,因而答案为0x00000014.
不会判断结构体大小?点我!!!

(unsigned long)p+0x1,()是强制类型转换,指针变量p被转换成一个无符号长整型数,这样就变成了普通的加减法,加上+0x1就是直接加上1

(unsigned int*)p+0x1,()是强制类型转换,指针变量p被转换成一个指向无符号整型变量的指针,0x1实际上是0x1 * sizeof(unsigned int),因而答案为0x00000004

五、多维数组和多级指针

5.1 二维数组布局是棋盘吗?


初学时,我们把二维数组认为是几行几列的棋盘,这样方便理解。
实际上并不是这样,因为内存是线性的。
实际上的布局是这样:

没错,就是把他们拼起来2333…
即学即用,来看这题:

#include<stdio.h>int main()
{int a[3][2] = { (0,1),(2,3),(4,5) };int* p;p = a[0];printf("%d", p[0]);
}

我想你已经想出了答案,是0吗?NONONO,答案是1;
为什么呢?
因为这题是有坑的!!!

我们注意到初始化时使用了( ),括号表达式的结果是最右边那个表达式,因此实际上的初始化为:

 int a[3][2] = { 1,3,5 };

p是数组第一行的地址,第一行的地址是第一个元素的首地址,所以p[0]作为右值时候是1.

5.2 经典例子

还没过瘾?再来一题。

 int a[5][5];int(*p)[4];p = a;

求 &p[4][2] - &a[4][2] 的值。
直接运用图解:

六、函数指针

6.1 函数指针的定义

经过前面的学习,我们应该能猜到:函数指针是一个指向函数的指针
举例:

 int* (*p)(int n);

这是一个函数指针,函数的参数是int,返回值类型为int*

6.2 函数指针的使用

例1:用(*p)代替函数名

#include<stdio.h>
void Print(int x)
{printf("%d", x);
}
int main()
{void (*pf)(int x);pf = &Print;int a = 5;(*pf)(a);
}

例2:回调函数

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

请看以下代码:

#include<stdio.h>void menu()
{printf("***************\n");printf("***  1.Add  ***\n");printf("***  2.Sub  ***\n");printf("***  3.Mul  ***\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;
}void Calc(int (*pf)(int x, int y))
{int x = 0;int y = 0;int ret = 0;printf("please input x and y:");scanf("%d%d", &x, &y);ret = pf(x, y);printf("%d", ret);
}int main()
{menu();int input = 0;scanf("%d", &input);switch (input){case 1:Calc(Add);break;case 2:Calc(Sub);break;case 3:Calc(Mul);}
}

我们通过使用回调函数,代码整体非常简洁,原因是Calc以函数指针为参数,只需要接受函数。
试想,如果没有Calc这个函数,我们需要在每个单独的函数里都要插入“请输入两个数x和y”,还有一系列相关的重复语句
如果不用回调函数,我们就会遇到下面的代码:

int main()
{menu();int input = 0;scanf("%d", &input);int x = 0;int y = 0;int a = 0;int b = 0;int c = 0;int d = 0;switch (input){case 1:printf("please input x and y:");scanf("%d%d", &x, &y);Add(x, y);break;case 2:printf("please input a and b:");scanf("%d%d", &a, &b);Sub(a, b);break;case 3:printf("please input c and d:");scanf("%d%d", &c, &d);Mul(c,d);break;}
}

这太繁杂了,光是重复的语句就是一大堆,如果有十来个这样的函数,代码惨不忍睹。而回调函数就解决了重复语句过多的问题。

6.3 函数指针数组

没错,就是在p后面加一个[10],

int (*p[10])(char * pf)

你可以把一大群函数指针放进去。


总结

第二回至此结束,博主是小白一枚,讲解自有缺漏甚至错误,劳请斧正。

一网打尽:指针和数组相关推荐

  1. char *a 和char a[] 的区别(指针和数组的区别)

    2019独角兽企业重金招聘Python工程师标准>>> 在C/C++中,指针和数组在很多地方可以互换使用,这使得我们产生一种错觉,感觉数组和指针两者是完全等价的,事实上数组和指针是有 ...

  2. 用指针查找数组中温度值为32的天数

    <程序设计基础实训指导教程-c语言> ISBN 978-7-03-032846-5 p92 5.1.2 上级实训内容 [实训内容4]假设一个数组用于存储一周7天,每天24小时温度的度数,数 ...

  3. 浅谈C中的指针和数组(一)

    本文转载地址:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242138.html 在原文的基础上加入自己的想法作为修改. 指针是C/C ...

  4. IOS笔记-C语言中的指针与数组

    1.指针与数组 1)指针与一维数组 i.数组指针(指向数组元素的指针) 类型  *指针变量名: 指针定义完成后要初始化(不想让指向任何内容,=0,=NULL) int a[10]; int *p =  ...

  5. C语言杂谈:指针与数组 (上) (转)

    转自:http://blog.jobbole.com/86400/ 介绍 1> 指针定义:指针是保存变量地址的变量. 2> 本文重点 >> 指针与数组之间的关系 >> ...

  6. 国2c语言中指针与数组的赋值运算,C语言到汇编-指针与数组2

    本章剩下的内容主要有: 1.指针与数组 & 地址算术运算 2.字符指针与函数 3.多维数组 & 指针与多维数组 4.指向函数的指针 每个内容举一个例子,第一个例子: int a[10] ...

  7. 【C 语言】数组与指针操作 ( 数组符号 [] 与 指针 * 符号 的 联系 与 区别 | 数组符号 [] 与 指针 * 符号 使用效果 基本等价 | 数组首地址 与 指针 本质区别 )

    文章目录 前言 一.数组符号 [] 与 指针 * 符号 使用效果 基本等价 二.数组首地址 与 指针 本质区别 前言 参考 [C 语言]指针 与 数组 ( 指针 | 数组 | 指针运算 | 数组访问方 ...

  8. 【C 语言】指针 与 数组 ( 指针 | 数组 | 指针运算 | 数组访问方式 | 字符串 | 指针数组 | 数组指针 | 多维数组 | 多维指针 | 数组参数 | 函数指针 | 复杂指针解读)

    相关文章链接 : 1.[嵌入式开发]C语言 指针数组 多维数组 2.[嵌入式开发]C语言 命令行参数 函数指针 gdb调试 3.[嵌入式开发]C语言 结构体相关 的 函数 指针 数组 4.[嵌入式开发 ...

  9. 指针和数组的区别是什么?

    指针和数组的区别是什么? 其实数组是一个地址,指针则是指向地址的地址. 举个例子: char array[10]; char *pt; pt=array; char array[10];的含义是,在内 ...

  10. 指针数组、数组指针、数组的区别与联系

    指针数组.数组指针.数组的区别与联系! 一:基本定义 1.指针数组 char *arr[4] = {"hello", "world", "shannx ...

最新文章

  1. linux通过串口读取文件,Linux 串口读写(二)
  2. BZOJ3239 Discrete Logging
  3. .net框架读书笔记---虚方法
  4. Nodejs之view中的视图模板之——EJS模板语言,快速入门
  5. 利达主机联网接线端子_拾遗●接线端子或连接器(1)
  6. 法拉科机器人编程软件_发那科机器人编写简单的程序教程
  7. HFSS - GSM 900 和 DCS 1800 双频PIFA天线的设计与仿真
  8. [转载]使用 Abbot 框架自动化测试 Eclipse 插件的用户界面,第 2 部分
  9. ps太卡怎么办?几步帮您解决问题
  10. 华为p9总显示切换服务器中,怎么更改华为p9的多任务切换 | 手游网游页游攻略大全...
  11. uygurqa输入法android,uygurqa维语输入法
  12. 【C语言编程练习】20050. 计算存款利息
  13. 【可穿戴技术】相关资料
  14. Python进行Excel数据统计
  15. 【XSY2515】管道(pipe)(最小生成树+倍增lca)
  16. 端到端的图像压缩----《Variational Image Compression With A Scale Hyperprior》论文笔记
  17. gitlab编译安装史----虽败犹荣
  18. i5 11320h和r5 5600u参数对比选哪个好
  19. jQuery重定向如何跳转到另一个网页
  20. 日冕物质抛射检测matlab,中国科学技术大学 日冕物质抛射研究取得重要进展

热门文章

  1. 2020安洵杯部分WP —— 没人比我更懂签到题和问卷题队伍
  2. 720度全景图有什么优势?
  3. 【自动控制原理】系统带宽
  4. 网页超链接无法访问本地文件
  5. Python获取高德POI(关键词搜索法)
  6. Error: L6218E: Undefined symbol
  7. centos6.6_vsftpd 虚拟账户FTP服务搭建
  8. 阿塞拜疆对加密货币收入及利润征税
  9. 「津津乐道播客」#301 这是一期价值3000元的当代社畜科学点餐指南
  10. 服务器带宽上行与下行是什么意思,有什么区别