目录
《小菜狗 C 语言入门 + 进阶笔记》目录:《小菜狗 C 语言入门 + 进阶笔记》(0)简介
1、指针变量的运算
指针变量保存的是地址,而地址本质上是一个整数,所以指针变量可以进行部分运算,例如加法、减法、比较等。
指针的基本运算有三种,分别是:
指针的基本运算有三种,分别是:
- 指针 ± 整数;
- 指针 - 指针;
- 指针的关系运算;
1.1、指针 ± 整数
请看下面的代码:
#include <stdio.h>
int main(){
int a = 10, *pa = &a, *paa = &a;
double b = 99.9, *pb = &b;
char c = '@', *pc = &c;
//最初的值
printf("&a=%#X, &b=%#X, &c=%#X\n", &a, &b, &c);
printf("pa=%#X, pb=%#X, pc=%#X\n", pa, pb, pc);
//加法运算
pa++; pb++; pc++;
printf("pa=%#X, pb=%#X, pc=%#X\n", pa, pb, pc);
//减法运算
pa -= 2; pb -= 2; pc -= 2;
printf("pa=%#X, pb=%#X, pc=%#X\n", pa, pb, pc);
//比较运算
if (pa == paa) {
printf("%d\n", *paa);
} else {
printf("%d\n", *pa);
}
return 0;
}
运行结果:
&a=0X28FF44, &b=0X28FF30, &c=0X28FF2B
pa=0X28FF44, pb=0X28FF30, pc=0X28FF2B
pa=0X28FF48, pb=0X28FF38, pc=0X28FF2C
pa=0X28FF40, pb=0X28FF28, pc=0X28FF2A
2686784
从运算结果可以看出:
pa、pb、pc 每次加 1 时,它们的地址分别增加 4、8、1,正好是 int、double、char 类型的长度;
pa、pb、pc 每次减 2 时,它们的地址分别减少 8、16、2,正好是 int、double、char 类型长度的 2 倍。
可以看出来:指针变量加减运算的结果跟数据类型的长度有关,而不是简单地加 1 或减 1。
(1)
以 a 和 pa 为例,a 的类型为 int,占用 4 个字节,pa 是指向 a 的指针。
如下图所示:
刚开始的时候,pa 指向 a 的开头,通过 *pa 读取数据时,从 pa 指向的位置向后移动 4 个字节,把这 4 个字节的内容作为要获取的数据,这 4 个字节也正好是变量 a 占用的内存。
(2)
如果pa++;
使得地址加 1 的话,就会变成如下图所示的指向关系:
这个时候 pa 指向整数 a 的中间,*pa 使用的是红色虚线画出的 4 个字节,其中前 3 个是变量 a 的,后面 1 个是其它数据的,把它们“搅和”在一起显然没有实际的意义,取得的数据也会非常怪异。
(3)
如果pa++;
使得地址加 4 的话,正好能够完全跳过整数 a,指向它后面的内存,如下图所示:
(扩展)
我们知道,数组中的所有元素在内存中是连续排列的,如果一个指针指向了数组中的某个元素,那么加 1 就表示指向下一个元素,减 1 就表示指向上一个元素。
但是对于指向普通变量的指针,我们往往不进行加减运算,虽然编译器并不会报错,但这样做没有意义,因为不知道它后面指向的是什么数据。
1.2、指针 - 指针
就像日期 - 日期得到天数⼀样,指针和指针可以相减,指针 - 指针的绝对值是指针和指针之间元素的个数。
指针-指针的前提是两个指针指向同⼀块空间(比如同⼀个数组)。
应用:
写⼀个函数求字符串长度。(本质是模拟实现 strlen 函数)
//指针-指针
#include <stdio.h>
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;
}
int main()
{
printf("%d\n", my_strlen("abc"));
return 0;
}
1.3、指针的关系运算
代码如下:
//指针的关系运算
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
while(p<arr+sz) //指针的⼤⼩比较
{
printf("%d ", *p);
p++;
}
return 0;
}
结果输出:
>
>=
<
<=
==
!=
2、指针变量的大小
我们了解到,32 位机器
有 32 根地址总线,每根地址线出来的电信号转换成数字信号后是 1 或者 0,那我们把 32 根地址线产生的二进制序列当做⼀个地址,那么⼀个地址就是 32 个 bit 位,需要 4 个字节才能存储。
指针变量是用来存放地址的
,那么指针变量的大小就是 4 个字节
。
同理 64 位机器
,假设有 64 根地址线,存储起来就需要 8 个字节的空间,指针变量的大小就是 8 个字节。
#include <stdio.h>
//指针变量的大小取决于地址的大小
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{
printf("%zd\n", sizeof(char *));
printf("%zd\n", sizeof(short *));
printf("%zd\n", sizeof(int *));
printf("%zd\n", sizeof(double *));
return 0;
}
输出结果:
//32位平台
4
4
4
4
//64位平台
8
8
8
8
结论:
- 32 位平台下地址是 32 个 bit 位,指针变量大小是 4 个字节;
- 64 位平台下地址是 64 个 bit 位,指针变量大小是 8 个字节;
- 注意
指针变量的大小和类型是无关的
,只要是指针类型的变量,在相同的平台下,大小都是相同的
。
3、void* 指针
在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为无具体类型的指针(或者叫泛型指针),这种类型的指针可以用来接受任意类型地址
。
但是 void* 类型的指针不能直接进行指针的运算(加法、减法和比较运算)和解引用的运算。
举例:
(1) 在下面的代码中,将⼀个 int 类型的变量的地址赋值给⼀个 char* 类型的指针变量。编译器会给出⼀个警告,是因为 int 类型和 cahr 类型不兼容。
#include <stdio.h>
int main()
{
int a = 10;
int *pa = &a;
char *pc = &a; //编译时出现警告
return 0;
}
(2) 而使用 void* 类型就不会有这样的警告问题。
#include <stdio.h>
int main()
{
int a = 10;
void *pa = &a;
void *pc = &a; //编译时没有警告
return 0;
}
(3) 底下所示 void* 类型的指针可以接收不同类型的地址,但是无法直接进行指针运算。
#include <stdio.h>
int main()
{
int a = 10;
void *pa = &a;
*pa = 10; //编译时程序报错
return 0;
}
总结使用:
⼀般 void* 类型的指针是使用在函数参数的部分,用来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果,使得⼀个函数来处理多种类型的数据。
4、NULL 指针
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。
NULL 指针是一个定义在标准库中的值为零的常量。请看下面的程序:
#include <stdio.h>
int main ()
{
int *ptr = NULL;
printf("ptr 的地址是 %p\n", ptr );
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
ptr 的地址是 0x0
在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。
如需检查一个空指针,您可以使用 if 语句,如下所示:
if(ptr) /* 如果 p 非空,则完成 */
if(!ptr) /* 如果 p 为空,则完成 */
5、关于 * 和 & 的谜题
假设有一个 int 类型的变量 a,pa 是指向它的指针,那么 *&a
和 &*pa
分别是什么意思呢? *&a
可以理解为 *(&a)
,&a
表示取变量 a 的地址(等价于 pa),*(&a)
表示取这个地址上的数据(等价于 *pa),绕来绕去,又回到了原点,*&a
仍然等价于 a。 &*pa
可以理解为 &(*pa)
,*pa
表示取得 pa 指向的数据(等价于 a),&(*pa)
表示数据的地址(等价于 &a),所以 &*pa
等价于 pa。
6、对星号 * 的总结
在我们目前所学到的语法中,星号*
主要有三种用途:
- 表示乘法,例如
int a = 3, b = 5, c; c = a * b;
,这是最容易理解的。 - 表示定义一个指针变量,以和普通变量区分开,例如
int a = 100; int *p = &a;
。 - 表示获取指针指向的数据,是一种间接操作,例如
int a, b, *p = &a; *p = 100; b = *p;
。
《小菜狗 C 语言入门 + 进阶笔记》目录:《小菜狗 C 语言入门 + 进阶笔记》(0)简介
每日一更!
公众号、优快云等博客:小菜狗编程笔记
谢谢点赞关注哈!目前在飞书持续优化更新~
日更较慢有需要完整笔记请私我,C/C++/数据结构-算法/单片机51-STM32-GD32-ESP32/嵌入式/Linux操作系统/uboot/Linux内核-驱动-应用/硬件入门-PCB-layout/Python/后期小程序和机器学习!