《小菜狗 C 语言入门 + 进阶笔记》(33)指针入门 3 -- 指针运算i++,i--,以及特殊指针void*,NULL


《小菜狗 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/后期小程序和机器学习!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小菜狗编程笔记

你的鼓励将是我最大的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值