目录
前言
1. 内存、地址、指针
2. 指针变量和地址
2.1 取地址操作符(&)
2.2 指针变量和解引用操作符(*)
3. 指针变量的大小
4. 指针变量类型的意义
4.1 指针的解引用操作
4.2 指针 +- 整数
5. 指针的算术运算
5.1 指针 +- 整数
5.2 指针 - 指针
5.3 指针的关系运算
结语
在C语言中,有很多操作都是直接针对内存操作的。指针亦是如此,今天就让我们就来讲解C语言这一大特点,也是难点 —— 指针和指针操作
要想理解内存、地址、指针之间的关系,我们得先理解电脑内存是怎么一回事儿?
在我们购买电脑时,会有各种电脑内存规格:8GB/16GB/32GB……,在电脑处理数据的时候,都是要在内存中读取,数据在处理好后也会放回到内存中。我们都知道,电脑需要处理的数据量是非常庞大的,那么这些内存空间要怎么设计才能更高效管理呢?
其实计算机会把内存划分为一个个的内存单元,每个内存单元的大小为 1 个字节,每个字节又对应 8 个比特位。每个内存单位元也都有一个编号,有了编号,CPU(中央处理器)就能更快速地找到一个内存空间。
在计算机中,我们把内存单元的编号也成为地址。C语言中也给地址起了一个新称号:指针
所以我们可以认为:内存单元的编号 == 地址 == 指针
在C语言中,我们创建变量时其实都会向内存申请空间。例如,我们创建一个整型变量 a,它在内存中会占据 4 个字节,用于存放整数 1,而每个字节都有地址,我们可以来看看 a 的地址:
我们可以用取地址操作符 & 来获取变量的地址,它需要用 %p 来打印
#include
int main()
{
int a = 1;
printf("%p", &a);
return 0;
}
打印结果如下:
这时候就会有小伙伴想问,不是说一个整型变量 a 在内存中会占据 4 个字节吗,问什么这里才显示一个地址?哎,问得好!实际上,我们在用 &a 取出地址时,它取出的是 a 所占 4 个字节中地址较小字节的地址。
眼尖的小伙伴也发现了,变量 a 的地址问什么会变化。这是因为在VS2022环境下,变量的地址不是永恒不变的,它是内存随机分配,因此每次打印所得到的地址也会不一样,但这也不影响我们理解内存的本质
在我们使用取地址操作符(&)拿到一个地址时,我们可以把这个数值存放在指针变量当中,比如:
#include
int main()
{
int a = 1;
int* p = &a;
return 0;
}
int* p = &a ————> 取出 a 的地址存放到指针变量 pa 当中
而指针变量也是一种变量,它是专门用来存放地址的,存放在指针变量当中的值我们都理解为地址
我们来解析一下指针变量
int a = 1;
int* p = &a;
p 是指针变量名,在 p 的左边是 int * , * 号表示 p 是指针变量, int 则表示该指针变量指向的是整型( int )类型的对象,如图:
同理可得,当我们有一个字符类型 char 的变量 b,那么就需要 char * 类型的指针变量来存放它的地址,即 char * p = &b。
对于一个指针来说,我们把指针变量名去掉后,得到的就是指针变量类型,如 char *, int * ……
我们使用指针变量存放某一变量的地址后,为的是方便后面使用,当我们想要使用它时,就得靠解引用操作符(*)来帮忙,我们先来看一个例子
#include
int main()
{
int a = 1;
int* p = &a;
*p = 2;
printf("%d\n", a);
return 0;
}
运行结果如图:
在上面代码当中,我们就使用了解引用操作符, * p 的意思就是通过 p 中存放的地址,来找到它所指向的空间,即 a ,用 * p = 2 ,来让 a 的值变成 2,因此当我们再打印 a 的值时,结果就为 2
指针变量,归根结底也是一种变量。而是变量就会有大小,即占有多少个字节
指针变量相比其他类型的变量,略显特殊,它是专门用来存放地址的,它的大小与所在的环境有关。在 32 位机器下,每个地址都有 32 个比特位,就需要 4 个字节才能存储,因此指针变量的大小就得是 4 个字节。同理,在 64 位机器中,指针变量的大小就为 8 个字节。我们可以在 VS2022 的 X86 和 X64 的环境下,使用 sizeof 关键字来验证一下
X86(32位) X64(64位)
我们可以看到,无论是哪种指针变量类型,得到的大小都是一样的
总结一下:
指针变量大小与类型无关,那为什么还需要各种各样的指针类型呢,哎,这么想不无道理,但创建这么多不同的指针类型还是很有必要的,我们接着往下看
我们写两段代码,a 的值为 16 进制的 11223344,指针变量 p 我们分别设为 int * 和 char * ,并分别进行解引用操作,监视内存我们可以发现, int * 在解引用后将 a 的四个字节全部改成 0;而 char * 在解引用后则是将 a 的较小的字节改成 0,剩下的三个字节还是不变
结论一:指针类型决定了对指针解引用时能有多大的权限,即一次能操作多少个字节。对于char * 类型来说,解引用就只能访问 1 个字节,而对 int * 来说,解引用可以访问 4 个字节
当指针加减整数时,也与指针类型有关,我们可以来看一段代码
#include
int main()
{
int a = 10;
int* p1 = &a;
char* p2 = (char*)&a;
printf("%p\n", &a);
printf("%p ——> %p\n", p1, p1 + 1);
printf("%p ——> %p\n", p2, p2 + 1);
return 0;
}
运行结果如下:
通过观察我们可以看出, char * 类型的指针 +1 跳过了 1 个字节,而 int * 类型的指针 +1 则跳过了 4 个字节
结论二:指针类型可以决定指针向前或向后走多少个字节(而我们可以利用这个特性来实现数组的创建和打印)
小练习:使用指针创建一个数组并打印出来
#include
int main()
{
int arr[10];
int i = 0;
int* p = arr; //数组名即数组首元素的地址
for (i = 0; i < 10; i++)
{
scanf("%d", (p + i));
}
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
运行结果如下:
在这里所使用的就是结论二,又因数组在内存中是连续存放的,所以只要知道第一个元素的地址,就能顺藤摸瓜地找到后面的元素
还是那个例子,用指针打印数组
#include
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
int* p = arr; //数组名即数组首元素的地址
size_t sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i)); //这里就是指针+-整数
}
return 0;
}
运行结果如下:
例子一:求数组中的元素个数
#include
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int num = (p + 10) - p;//这里就是指针-指针
printf("%d\n", num);
return 0;
}
运行结果如下:
在同一数组中,指针 - 指针得到就是两元素相差的个数,因此得到的就是 10
例子二:我们可以写一个函数 my_strlen 来模拟 strlen ,作用是求取字符串的长度
#include
int my_strlen(char* s)
{
char* p = s;
while (*p != '\0')
{
p++;
}
return p - s;//这里就是指针-指针
}
int main()
{
int len = my_strlen("abcd");
printf("%d\n", len);
return 0;
}
运行结果如下:
字符串的最后都会有 '\0' ,我们可以用这个特性来得到字符串的长度。使用 while 循环,当 *p != '\0' 时,p 就 ++;而最后当 *p == '\0' 时,就会跳出循环,返回 p - s 的值,即返回字符串长度,这样我们就能得到字符串的长度了
#include
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;//arr是数组首元素的地址
int i = 0;
size_t sz = sizeof(arr) / sizeof(arr[0]);
while (p < arr + sz)//指针的大小比较
{
printf("%d ", *p);
p++;
}
return 0;
}
运行结果如下:
今天我们一起学习了指针的一些基础知识,包括指针变量,指针类型等等;如有总结不到位的地方还请多多谅解,若有出现纰漏,希望大佬们看到错误之后能够在私信或评论区指正,博主会及时改正,共同进步!
欢迎各位在评论区友好讨论。如果觉得不错的话,麻烦您点个赞吧,十分感谢!