欢迎光临
我们一直在努力

C语言基础复盘:指针的深入理解(1)

目录

  • 引言
  • 一、内存和地址
    • 内存
    • 如何理解编址
  • 二、指针变量和地址
    • 取地址操作符(&)
    • 指针变量和解引用操作符(*)
      • 指针变量
      • 拆解指针类型
      • 解引用操作符
    • 指针变量的大小
  • 三、指针类型的意义
    • 指针的解引用
    • 指针+ -整数
    • void*指针
  • 四、指针运算
    • 指针 + – 整数
    • 指针 – 指针
    • 指针的关系运算
  • 总结

引言

距离上次写博客已经好久了,中间遇到许多挫折,几度想要放弃,最后想通了,与其每天生活在焦虑中,不如真正的静下心来学习知识,不求做到完美,但求每天都有进步,敬不完美的明天。

一、内存和地址

内存

说到内存,大家第一时间想到的会是什么呢?想必有很多人会认为在电脑中500GB、1TB这样的会是内存,要是这样说就比较外行了。 在搞清楚内存是什么之前,我们先看看电脑中有什么。 先简单介绍一下,电脑中有CPU、内存、硬盘、还有一些外部设备等等。像我们刚才提到的500G、1T这种都是硬盘,而4GB、8GB、16GB、32GB这种才是内存。 程序运行起来后,程序要加载到内存中,数据的存储也是在内存中进行。 我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,我们买电脑时,电脑上内存是8GB/16GB/32GB等,那这些内存空间 如何高效管理 ? 把内存划分为一个个的内存单元,每个内存单元的大小取1个字节。其中一个比特位可以存储一个2进制的位1或者0(具体如下图)。 在这里插入图片描述 每个内存单元,相当于一个学生宿舍,一个字节空间里面能放 8 个比特位,就好比同学们住的八人间,每个人是一个比特位。 每个内存单元也都有一个编号(这个编号就相当于宿舍房间的门牌号),有了这个内存单元的编号,CPU 就可以快速找到一个内存空间。 生活中我们把门牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C 语言中给地址起了新的名字叫:指针。 所以我们可以理解为: 内存单元的编号 = 地址 = 指针 在这里插入图片描述

如何理解编址

计算机内有很多的硬件单元,而硬件单元是要互相协同工作的。所谓协同,就是至少相互之间要能够进行数据传递。 但硬件与硬件之间是互相独立的,想要相互通讯就要用 “ 线 ” 将他们连起来。而CPU和内存之间也是有大量的数据交互的,所以,两者必须也用线连起来。 我们现在只需要理解一组线———地址总线。

CPU想要访问内存中的某个字节空间,就必须知道这个字节空间在内存的什么位置,所以需要给内存进行编址(就像想要找到一个宿舍,需要给宿舍编号一样)。 计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的。

我们可以简单理解,32位机器有32根地址总线,每根线只有两态,表示0,1(电脉冲有无),那么一根线,就能表示2种含义,2根线就能表示4种含义,依次类推。32根地址线,就能表示2^32种含义,每一种含义都代表一个地址。

地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传入CPU内寄存器。 在这里插入图片描述

二、指针变量和地址

取地址操作符(&)

我们要先理解,变量创建的本质是什么? 变量创建的本质是在内存中申请一块空间,用来存放数据。 举个例子:

int main()
{
int a = 2;
return 0;
}

向内存申请4个字节的空间,用来存储2;空间的名字叫a,这个a名字不是给编译器、计算机看的,而是给程序员自己看的。因为计算机知道地址是多少,但程序员很难记住地址。

那我们如何得到 a 的地址呢?这里就得学习一个操作符 (&)——取地址操作符

1 #include <stdio.h>
2 int main()
3 {
4 int a = 10;
5 &a; //取出a的地址
6 printf("%p\\n", &a);
7 return 0;
8 }

我们得到结果,并将其绘图,直观得到: 在这里插入图片描述

按照我画图的例子,会打印处理:006FFD70;因此 &a 取出的是 a 所占 4 个字节中地址较小的字节的地址。虽然整型变量占用 4 个字节,但我们只要知道了第一个字节地址,就能顺藤摸瓜地访问到 4 个字节的数据。

指针变量和解引用操作符(*)

指针变量

我们通过取地址操作符 (&) 拿到一个地址,如:0x006FFD70,这个数值有时候也是需要存储起来,方便后期再使用,我们要把这样的地址存放在指针变量中。 举个例子:

1 #include <stdio.h>
2 int main()
3 {
4 int a = 10;
5 int * pa = &a; //取出a的地址并存储到指针变量pa中
6
7 return 0;
8 }

p被称为指针变量,理解为:存放指针的变量。 指针变量也是一种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址。

拆解指针类型

p的类型是 int * ,我们能够通过 p(地址)找到 a(数据)。这里的 int *应该分开理解。

  • int说明p指向的数据(a)的类型是int
  • *说明p是指针变量

同理,字符数据的指针变量就是char *

解引用操作符

“ * ” – 解引用操作符(也叫间接访问操作符) 举个例子:

#include <stdio.h>

int main()
{
int a = 0;
int *p = &a;
*p = 10;
printf("%d",a);

return 0;
}

运行结果如下: 在这里插入图片描述 由此我们知道,解引用操作符的作用是知道地址后,通过地址来找到地址所指向的对象。 那么为什么不直接修改 a 的值呢? 我们要拥有大哥思维,即所有的事不要亲历亲为,要让手下的“小弟”去干活。 这里就是把a的修改交给了pa来操作,这样对a的修改,就多了一种的途径,慢慢积累就会发现这样做的好处了。

指针变量的大小

pa 是指针变量,指针变量也是变量,也是需要向内存申请空间的。 那么pa 这个指针变量的大小是多少?向内存申请了多大空间? 其实,地址的存储需要多大空间,指针变量的大小就是多大。

我们分析一下:

32位的机器上,地址是通过32根地址线传递的 那么一个地址就是32个0/1的二进制,需要32个bit位存储 = 4个字节 指针变量的大小也就是4个字节 64位的机器上,地址是通过64根地址线传递的 那么一个地址就是64个0/1的二进制,需要64个bit位存储 = 8个字节 指针变量的大小也就是8个字节

总结一下:

  • 32位平台下地址是32个bit位,指针变量大小是4个字节
  • 64位平台下地址是64个bit位,指针变量大小是8个字节
  • 注意指针变量的大小和类型是无关的,只要是指针类型的变量,在相同的平台下,大小都是相同的。

三、指针类型的意义

指针变量的大小和类型无关,指针类型是有特殊意义的。

指针的解引用

#include <stdio.h>

int main()
{
int a = 0x11223344;
int *pa = &a;
//char *pa = &a;
*pa = 0;

return 0;
}

我们进行调试: 请添加图片描述 我们取出a的地址,然后分别将它存在int*和char*类型的指针变量pa中,然后进行解引用操作,将pa的值改为0,分别得到以下结果: int* 请添加图片描述 那么我们就得到,当我们是整数类型指针时,我们的视野是4个字节;当我们是字符类型指针时,我们的视野是1个字节。简单来说它的意义就是视野范围(操作范围)不同,整型类型指针认为它是指向的是整型,所以它能访问4个字节;字符类型指针认为它是指向的是字符,所以它只能访问1个字节。

因此,我们得出结论: 指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字节)。 比如:char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

指针+ -整数

#include <stdio.h>

int main()
{
int a = 10;
int* pa = &a;
char* pc = &a; // int*
printf("pa = %p\\n", pa);
printf("pa+1 = %p\\n", pa + 1);

printf("pc = %p\\n", pc);
printf("pc+1 = %p\\n", pc + 1);

return 0;
}

运行结果如下:

请添加图片描述 由此我们可以看出,int*类型的指针变量加1,跳过了4个字节;char*类型的指针变量加1,跳过了1个字节。

因此,我们得出结论: 指针的类型决定了指针向前或者向后走一步有多大(距离)。

void*指针

在指针类型中有一种特殊的类型是 void * 类型,可以理解为无具体类型的指针(或者叫泛型指针),这种类型的指针可以用来接受任意类型地址。但是也有局限性,void* 类型的指针不能直接进行指针的+ -整数和解引用的运算。

当我们有一个指针变量,但是不知道要接收什么类型的指针时,直接创建一个void*类型的指针变量就行。

但是物极必反,当我们对void*类型的指针变量进行解引用操作或进行+ -运算时,会报错。 请添加图片描述

四、指针运算

指针运算有三种,分别是:

  • 指针 + – 整数运算
  • 指针 – 指针
  • 指针的关系运算

指针 + – 整数

我们先以数组为例,因为数组在内存中是连续存放的,只要知道第一个元素的地址,顺藤摸瓜就能找到后面的元素。

#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
//拿到第一个元素的地址
int* p = &arr[0];
for (i = 0; i < sz; i++)
{
printf("%d ", *p);
p++;//p = p+1;
}

return 0;
}

运行结果如下:

在这里插入图片描述

同样的问题,我们再换一个思路:

在这里插入图片描述 因为 p+i 就是数组中下标为 i 元素的地址,所以 *(p+i) 就是下标为 i 的这个元素。

#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
//拿到第一个元素的地址
int* p = &arr[0];
for (i = 0; i < sz; i++)
{
printf("%d ", *(p+i));
}

return 0;
}

运行结果如下:

请添加图片描述 我们再举一个例子,当我们想逐字符打印一串字符串时,

#include <stdio.h>
int main()
{
char arr[] = "hello world";
char* pa = &arr[0];//&arr[0]也可以写成arr
while(*pa)//*pa也可以写成*pa != 0
{
printf("%c",*pa);
pa++;
}

return 0;
}

运行结果如下:

在这里插入图片描述

指针 – 指针

指针 – 指针的前提是两个指针指向了同一块空间,否则不能相减。 | 指针 – 指针 |:得到的是两个指针之间的元素个数。 举个例子:

#include <stdio.h>

int main()
{
int arr[10] = { 0 };
printf("%lld\\n", &arr[9] &arr[0]);
printf("%lld\\n", &arr[0] &arr[9]);

return 0;
}

运行结果如下:

在这里插入图片描述

数组随着下标的增长,地址是由低到高变化的

用数学中的移项,并类比日期与天数的问题: &arr[0] + 9 == &arr[9] —> 日期 – 日期 = 天数 &arr[0] – &arr[9] == -9 —> 日期 + 天数 = 日期 9 == &arr[9] – &arr[0] —> 日期 – 天数 = 日期

再举个例子:问题:想一个函数,求字符串个数。

①用指针 + – 整数来写

#include <stdio.h>

size_t my_strlen(char* p)
{
size_t count=0;//计数器
while (*p)
{
count++;
p++;
}
return count;
}

int main()
{
char arr[] = "abcdef";
//数组名其实也是数组首元素的地址
//arr == &arr[0]
char* p = arr;
printf("%zu", my_strlen(arr));
/* size_t n = my_strlen(p);
printf("%zu", n);*/

return 0;
}

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

②如果我们想用指针 – 指针来写

#include <stdio.h>

size_t my_strlen(char* p)
{
char* start = p;
while (*p)//使用p来找到字符串中的\\0
{
p++;
}
return p start;
}

int main()
{
char arr[] = "abcdef";
//数组名其实也是数组首元素的地址
//arr == &arr[0]
char* p = arr;
printf("%zu", my_strlen(arr));
/* size_t n = my_strlen(p);
printf("%zu", n);*/

return 0;
}

运行结果如下:

在这里插入图片描述

指针的关系运算

指针也是可以比较大小的。 我们之前想要打印一个数组的内容时需要用到数组下标,for循环打印,现在我们可以用指针的关系运算来打印。

#include <stdio.h>

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

运行结果如下:

在这里插入图片描述

总结

再学一遍指针,现在真正将它学透了,收获颇丰,继续坚持,一切皆有可能。 最后送大家一句话:

世界上只有一种真正的英雄主义,那就是在认清生活的真相后依然热爱生活。——罗曼·罗兰

赞(0)
未经允许不得转载:171主机测评 » C语言基础复盘:指针的深入理解(1)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址