深入理解指针

我们首先从思维导图切入,大致理解指针这一章的思维与框架:

d254aff7a5ce4b70935f632745c051eb.png

基本概念的理解 

内存和地址 

 我们都知道电脑在运行时,需要将内存加载到cpu中,等待运行完毕后,又返回到cpu当中,那么,cpu是如何快速找到地址的呢?这就涉及到了地址的概念,我们把内存分为一个一个内存单元,每个内存单元的大小取一个字节,再把一个字节分为8个byte。(每个内存单元都有一个编号)这样就可以通过地址寻找到内存。c语言中,我们给地址起了新的名字,叫作指针,但是,指针是如何进行编址的呢?

计算机中的编址,并不是把每个字节的地址记录下来,而是由硬件设备完成的。计算机有很多硬件单元,而硬件单元是要相互协同工作的(数据之间要能够进行数据传递),那么数据之间如何通信呢?答案是用“线”连接起来,CPU和内存也有大量数据交互,也需要用线连起来。不过,我们今天关注一组线,叫作地址总线,32位机器有32位地址线,每根地址线能代表两种含义0或者1,总共就能表示2^32种含义,每种含义都代表一个地址,地址信息被下达给内存,就可以找到数据,再通过地址总线传入CPU内寄存器。

ceb650c96ec04d1d98cd5687a7e7c023.jpeg

指针变量和解引用操作符

我们了解到一个事实:那就是,当创建变量时,其实就是在向内存申请空间。此时我们用取地址操作符取出的就是地址较小字节的地址。

指针变量在x32环境下为4个字节,再x64环境下为8个字节。那么,指针变量有什么意义呢?

指针的类型决定了对指针解引用时有多大权限。

我们将地址存在指针变量当中。解引用操作符就相当于把值从地址里拿出来。我们把对a的修改,转移成了对a的修改。

指针的运算

指针的类型决定了指针向前一步或者向后一步有多大。

指针加减整数

指针加减整数就跳到对应的数值上。

指针减去指针

结果为两个整数之间元素的个数

指针的关系运算

体现两个指针的大小关系

void*

void*类型的指针可以接收不同类型的地址,但无法进行指针运算。

const修饰指针

当const修饰变量时,原有的值不能被修改。但是,当我们绕过变量本身,修改地址,就可以改变原有的值。

#include<stdio.h>
int main()
{
	const int n = 0;
	int* p = &n;
	*p = 20;
	return 0;
}

如图的情况,就可以通过改变地址来改变const修饰的变量的值。

当const放在*左边,不能通过指针改变所指向的内容,但是能够改变指针变量本身。

当const放在*右边,能通过指针改变所指向的内容,但是不能够改变指针变量本身。

野指针

何为野指针:野指针即为指针指向位置不可知,不正确的,没有限制的。

造成野指针的原因:

第一个原因是指针越界访问(当指针范围超过数组arr的范围),第二原因是指针指向的空间已经被释放,第三个原因是指针未初始化(默认为随机值)。

如何规避野指针:

1初始化指针。若明确知道指针指向哪里就赋值地址,如果不知道,则置为NULL。

2避免越界访问。一个程序向内存中申请了哪些空间,就只能指向哪些空间。

3当指针不再使用时,及时置空NULL。只要是NULL指针就不会访问,同时使用指针之前可以判断指针是否为NULL。

4避免返回局部变量的地址。

assert断言

assert用于确保程序执行时的判定条件,如果不符合,就报错终止程序运行,这个宏被称为断言。assert()报错后,在标准错误流stderr中写入一条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名以及行号。

assert能自动标识文件和出问题的行号,还有一种无需更改代码就能开启和关闭assert的机制。如果文件没有问题,不需要断言,就在#include<stdio.h>的前面加上NDEBUG宏定义。

assert的缺点就是加入了检查,增加了程序的运行时间。

数组指针

arr在大多情况下表示的是首元素的地址,只有在两种情况下表示的是整个数组的地址,第一种情况是:sizeof(arr),第二种情况是&arr。

使用指针访问数组

数组名arr是首元素的地址,可以赋值给p,数组名arr和p是等价的,我们可以用arr[i]或者p[i]访问数组。

一维数组传参的本质

在数组传参的时候,传递的是数组名,也就是说本质上数组传参本质上传递的是数组⾸元素的地址。所以在传参的时候sizeof(arr)传递的是首元素的地址的大小,而不是数组的大小。所以在函数内部,无法求出数组元素的个数。

一维数组传参,可以写成数组的形式,也可以写成指针的形式。

# define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
void test(int arr[])//写成数组形式,本质上还是指针
{

}
void test(int* arr)//参数写成指针形式
{
	printf("%d\n", sizeof(arr));
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	test(arr);
	return 0;
}


指针数组模拟二维数组

25b9b0954991437294e38b747cb08766.jpeg

指针变量

字符指针变量

int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}

代码 const char* pstr = "hello bit."; 特别容易让同学以为是把字符串 hello bit 放
到字符指针 pstr ⾥了,但是本质是把字符串 hello bit. ⾸字符的地址放到了pstr中。 

数组指针变量

整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。
浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。

那么,数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。

int (*p)[10];[]的优先级要⾼于*号的,所以必须加上()来保证p先和*结合。指向的是大小为10个整型的数组。p是一个指针,指向一个数组,叫作指针数组。

转移表

函数指针的实现:int(*p[5])(int x, int y) = { 0, add, sub, mul, div };

ret = (*p[input])(x, y);//定义了一个函数指针数组。

qsort函数

回调函数就是⼀个通过函数指针调⽤的函数。

qsort函数的实现

void calc(int(*pf)(int, int))
{
int ret = 0;
int x, y;
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("ret = %d\n", ret);
}


sizeof和strlen

这一部分的内容 我们就通过一些题目来学习吧~

一维数组:

876ebd4bf9c64dd3a56f92e8a8582d45.png

2.16 --sizeof(数组名)的场景

3.这里的a并没有单独放在sizeof内部,所以这里的a代表首元素地址,a+0还是首元素地址,大小是4或者8个字节。

4.a是首元素的地址,*a就是首元素,大小就是首元素,大小为4

*a=a[0]=*(a+0)

5.a是首元素地址,a+1是第二个元素地址,类型为int*。大小为4或8个字节。

6.a[1]就是第二个元素,大小为4个字节。

7.&a是数组的地址,数组的地址也是地址,大小是4/8个字节

8.有两种理解方式①*和&抵消,

②将整个数组的地址取出来再解引用,得到的就是整个数组。int(*)[4]

9.&a+1是跳过整个数组得到的地址,大小是4/8个字节。

10.首元素地址大小,4或8字节。类型int*

11.数组第二个元素地址。4/8


字符数组:

2ef2a83b4948451799a5bf6dca2c42fa.png

2.6个元素,6个字节

3.首元素地址,就是4或8个字节。

4.首元素,大小为1个字节。

5.第二个元素,1字节

6.4/8 char(*)[4]

7.4/8

8.第二个元素

07ec5cb8f46848f7a0ee4eabcfd093f3.png

1.求到\0。数组中没有\0,就会导致越界访问。结果随机。

2.数组首元素地址。随机

3.参数 const char*。*arr是首元素,就是'a',97。97地址不允许访问。strlen得到的就是野指针。err

4.error。

5.是数组的地址。起始位置是数组第一个元素,随机值x

6.随机值x–6

7.随机x-1

f522097844254b708ef54e6e62862c7b.png

2.7

3.arr+0就是首元素地址。

4.arr是首元素地址,*arr就是首元素,大小是1字节。

5.第二个元素,大小一个字节。

6.数组的地址。4/8

7.跳过整个数组。4/8

8.第二个元素地址。4/8
d0b6d94e220e4d1b9cf07d0acfa66b78.png

1.6整个数组地址

2.首元素地址,\0之前有6个字符

3.首元素,97,出错

4.b 98出错

5.数组的地址 6

6.指针指向\0后面

7.第二个元素后,5

430fe34f38f049e8881715b2ff9c54cc.png
 1.算出的是p指针的大小,4/8个字节

2.char*跳过一个字符。是b的地址。4/8

3.p的类型是char*,*p的类型是char类型,一个字节

4.①p[0]等价于*(p+0)。p加0还是a的地址,解引用得到a,一个字节

②当作数组理解,把常量字符串想象成数组,p可以理解成数组名。p[0]就是首元素。

5.&p是4/8个字节,类型为char**

6.q+1,跳过char*,跳过p指针大小指向p的尾部。是地址就是4/8个字节。

7.4/8

d518810eaaac4d4a84e5bbb7d2345892.png

2.6

3.5,指向第二个元素

4.就是a字符,97,err

5.等价*(p+0)

6.&p是指针变量p的地址,和字符串abcdef没有关系,答案是随机值,p指针存放的是什么,不知道

7.随机值,和上面的随机值没有关系

8.5

二维数组:

1ead8bea6aec4ee798071f1fc8c8c09b.png 1.3*4*4

2.4

3.16

4.a[0]并没有单独放在sizeof内部。a[0]就是数组首元素的地址==arr[0][0],加1后是arr[0][1]的地址。大小是4/8

5.解引用,表示第一行第二个元素,大小为4

6.第二行的地址,数组指针的地址是4/8个字节。

7.方法一:第二行16 方法二:a[1]是第二行的数组名,相当于把a[1]单独放在sizeof内部

8.a[0]是第一行的数组名,&a[0]取出的就是数组的地址,就是第一行的地址。 +1就是第二行的地址。4/8个字节

9.访问第二行,大小是16个字节。

10.a作为数组名既没有单独放在sizeof内部,a表示数组首元素的地址,也就是第一行的地址,*a就是第一行,计算的就是第一行的大小,16个字节

*a==*(a+0)==a[0]

11.没有访问元素,a[3]无需真实存在,只需要通过类型判断就能算出长度。

a[3]是第四行的数组名,单独放在sizeof内部,计算的是第四行的大小,16个字节

指针运算笔试题目 

44c1fc34a8ac446fa7164b39e6646b42.png

 bd2664e5b1014d779ee9f5f6bd328bb8.jpeg

 &a Int(*)[5]

所以答案为5  2 

57f592308e75405fb17434fbef9251ca.png

c53b3aeb5f554ceb95299ae3b768846a.jpeg 指针加减整数。结构体指针加一跳过一个结构体。0x100000+1-->0x1000020十六进制:0x1000014。

强制转化为unsigned long就不是指针了。整型值+1,就是加上真实的1。

强制转化为unsigned int*,本质上是跳过一个整型+4。

cbb8a062f1084adfaaca8458c0d173b4.png

 9511244568524c62895277f615b77430.jpeg

a[0]==&a[0][0]。 a[0]是数组名,数组名又表示首元素地址。其实就是a[0][0]的地址。

df850f70a004497597ab307756446667.png

 %p:

%d(打印有符号的整数):1000 0000 0000 0000 0000 0000 0000 0100

                                              1111 1111  1111  1111  1111  1111 1111  1011

                                              1111  1111  1111 1111   1111 1111  11111 1100补码

四个二进制位换算一个十六进制位 fffffffc(x86环境)

如上图所示,打印的值分别为10 5。

#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}

char*p="abcdef";是把首元素的地址赋值给p  。

 我们来逐个解读各个语句的含义:**++cpp,原先cpp指向c+3,加1后其指向c+2,解引用后打印POINT。

**--**++cpp,cpp指向c+2,++后指向c+1,--后将c+1改为c,指向c,打印TER。

*cpp[-2]+3,翻译过来就是*(*(cpp-2))+3,-2的时候拿的是c+3,再+3,得到的是ST。

cpp[-1][-1]+1翻译过来就是*(*(cpp-1)-1)+1,cpp-1后指向c+2,只有++cpp或者--cpp的时候指针才会动。-1后得到了c+1。打印EW。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值