指针
- 1. 前言
- 1.1 什么是指针?
- 1.2 为什么人们常说,指针是C语言的灵魂?
- 2. 指针变量和地址
- 2.1 取地址操作符(&)
- 2.2 指针变量和解引用操作符(*)
- 2.2.1 指针变量
- 2.2.2 如何拆解指针类型
- 2.2.3 解引用操作符
- 2.3 指针变量的大小
- 3. 指针变量类型的意义
- 3.1 指针的解引用
- 3.2 指针+-整数
- 3.3 void* 指针
- 4. 指针运算
- 4.1 指针+-整数
- 4.2 指针-指针
关于指针的这篇博客我会把它拆分成5篇博客来细讲,请耐心看完。
1. 前言
在学习C语言的过程中,几乎所有的人都会遇到一个困难且无法避免的知识点,但同样它也是每一位学习C语言的人所需跨越的点——指针。
1.1 什么是指针?
在C语言中有一句最本质的话:指针,就是内存地址。
内存里的每一个字节,都有一个唯一的编号,这个编号就是地址。
而用来存放这个地址的变量,我们将它称为指针变量,简称指针。
可以用一个生活中的例子来解释上面的话:
- 内存就像是一家酒店。
- 每个房间都是一块内存空间。
- 门牌号就是内存地址。
- 记录门牌号的纸张就是指针。
当每个房间都有了房间号,就可以快速的找到房间。
如果把上面的例子对照到计算机中,又是怎么样呢?
我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放在内存中,那我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,那这些内存空间如何高效的管理呢?
其实也是把内存划分为一个个的内存单元,每个内存单元的大小取一个字节。
一个比特位可以储存一个2进制位的1或0
bit-比特位1Byte=8bit Byte-字节1KB=1024Byte KB1MB=1024KB MB1GB=1024MB GB1TB=1024GB TB1PB=1024TB PB其中,每个内存单元,相当于一个学生宿舍,一个字节空间里面能放8个bit位,就好比同学们住的8人间,每个人是一个比特位。
每个内存单元也都有一个编号(这个编号就相当于·宿舍房间的门牌号),有了这个内存单元的编号,CPU就可以快速找到一个内存空间。
生活中我们把门牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C语言中给地址起了新的名字叫:指针。
所以我们可以理解为:
内存单元的编号 == 地址 == 指针
1.2 为什么人们常说,指针是C语言的灵魂?
很多人都说指针是C语言的灵魂,这究竟是为什么呢?
核心原因在于:指针让C语言拥有了直接操作内存的能力,同时赋予了语言极高的灵活性、效率和底层控制力,这是C语言区别于很多高级语言的关键特性。
主要有以下几个好处:
- 运行效率高,传参、处理数据开销小
- 能实现动态内存分配,灵活管理空间
- 是链表、树等复杂数据结构的基础
- 可以做底层开发(系统、驱动)
- 让C语言语法更灵活、功能更强大
2. 指针变量和地址
2.1 取地址操作符(&)
理解了指针和地址的关系,我们再回到C语言,在C语言中创建变量其实就是向内存申请空间,比如:
#include<stdio.h>intmain(){inta=10;return0;}比如,上述的代码就是创建了整型变量a,内存中申请4个字节,用于存放整数10,其中每个字节都有地址,上图中4个字节的地址分别是:
0x00F9FA80 0x00F9FA81 0x00F9FA82 0x00F9FA83那我们如何取出a的地址呢?
这里就得学习一个操作符(&)-取地址操作符
#include<stdio.h>intmain(){inta=10;&a;//取出a的地址printf("%p\n",&a);return0;}&a取出的是a所占4个字节中地址较小的地址。
虽然整形变量占4个字节,我们只要知道了第一个字节地址,顺藤摸瓜访问到4个字节的数据也是可行的。
2.2 指针变量和解引用操作符(*)
2.2.1 指针变量
那我们通过取地址操作符(&)拿到的地址是一个数值,比如0x006FFD60,这个数值有时候也是需要存储起来,方便后期再使用的,那我们把这样的地址值储存在哪里呢?答案是:指针变量中。
比如:
#include<stdio.h>intmain(){inta=10;int*p=&a;//取出a的地址并存储在指针变量p中return0;}指针变量也是一种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址。
2.2.2 如何拆解指针类型
我们看到p的类型是int*,我们该如何理解指针的类型呢?
inta=10;int*pa=&a;这里的pa左边写的是int*,(*)是在说明pa是指针变量,而前面的int是说明pa指向的是整型(int)类型的对象
那如果有一个char类型的变量ch,ch的地址,要放在什么类型的指针变量呢?
charch='c';char*pc=&ch;2.2.3 解引用操作符
我们将地址保存起来,未来是要使用的,那怎么使用呢?
在现实生活中,我们使用地址要找到一个房间,在房间里可以拿去或者存放物品。
C语言中其实也是一样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象,这里我们必须学习一个操作符叫:解引用操作符(*)。
#include<stdio.h>intmain(){inta=10;int*pa=&a;printf("%d\n",*pa);*pa=100;printf("%d\n",*pa);return0;}上面的代码就运用到了解引用操作符,*pa的意思就是通过pa存放的地址,找到指向的空间,*pa其实就是a变量了;所以*pa = 100这个操作其实就等价于a = 100。
有同学肯定在想,这里如果目的只是想让a变成100的话,写成a = 100不就完了吗?为什么非要使用指针呢?
其实这里是把a的修改交给了pa来操作,这样对a的修改,就多了一种的途径,写代码就会更加灵活,后期慢慢就能理解了。
2.3 指针变量的大小
**在不同的机器中指针的大小是不同的。**比如:
在32位机器中,一个地址的大小是4个字节。但是在64位机器中,一个地址的大小是8个字节。
X86环境输出结果 X64环境输出结果结论:
- 32位平台下地址是32个bit位,指针变量大小是4个字节
- 64位平台下地址是64个bit位,指针变量大小是8个字节
- 注意指针变量的大小和类型是无关的,只要是指针类型的变量,在相同平台下,大小都是相同的。
3. 指针变量类型的意义
指针变量的大小和类型无关,只要是指针变量,在同一个平台下,大小都是一样的,为什么还要有各种各样的指针类型呢?
其实指针类型是有特殊意义的,我们接下来继续学习。
3.1 指针的解引用
对比下面两段代码,主要在调试时观察内存的变化。
//代码1#include<stdio.h>intmain(){intn=0x11223344;int*pi=&n;*pi=0;return0;}//代码2#include<stdio.h>intmain(){intn=0x11223344;char*pc=(char*)&n;*pc=0;return0;}调试我们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将n的第一个字节改为0。
结论:指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字节)。
比如:char*的指针解引用就只能访问一个字节,而int*的指针的解引用就能访问4个字节。
3.2 指针±整数
先看一段代码,调试观察地址的变化。
#include<stdio.h>intmain(){intn=10;char*pc=(char*)&n;int*pi=&n;printf("&n = %p\n",&n);printf("pc = %p\n",pc);printf("pc+1 = %p\n",pc+1);printf("pi = %p\n",pi);printf("pi+1 = %p\n",pi+1);return0;}代码运行的结果如下:
我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。这就是指针变量的类型差异带来的变化。指针+1,其实跳过1个指针指向的元素。指针可以+1,那也可以-1。
结论:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。
3.3 void* 指针
在指针类型中有一种特殊的类型是 void* 类型的指针变量,可以理解为无具体类型的指针(或者叫泛型指针),这种类型的指针可以用来接受任意类型地址。但是也有局限性,void*类型的指针不能直接进行±整数和解引用的运算。
举例:
#include<stdio.h>intmain(){inta=10;int*pa=&a;char*pc=&a;return0;}在上面的代码中,将一个int类型的变量的地址赋值给一个char*类型的指针变量。编译器给出了一个警告(如下图),是因为类型不兼容。而使用void*类型就不会有这样的问题。
使用void*类型的指针接收地址:
#include<stdio.h>intmain(){inta=10;void*pa=&a;void*pc=&a;*pa=10;*pc=0;return0;}VS编译代码的结果:
那么我们可以看到,void*类型的指针可以接收不同类型的地址,但是无法直接进行指针运算。
那么void*类型的指针到底有什么用呢?
一般void*类型的指针是使用在函数参数的部分,用来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果。使得一个函数来处理多种类型的数据。
4. 指针运算
指针的基本运算有三种,分别是:
- 指针±整数
- 指针-指针
- 指针的运算关系
4.1 指针±整数
因为数组在内存中是连续存放的,只要知道第一个元素的地址,顺藤摸瓜就能找到后面的所有元素。
intarr[10]={1,2,3,4,5,6,7,8,9,10};数组元素和下标#include<stdio.h>intmain(){intarr[10]={1,2,3,4,5,6,7,8,9,10};int*p=&arr[0];inti=0;intsz=sizeof(arr)/sizeof(arr[0]);for(i=0;i<sz;i++){printf("%d ",*(p+i));}return0;}4.2 指针-指针
指针-指针得出的结果会是什么呢?是之间的字节数吗?
不是,是两个指针之间相差的元素个数,而不是字节数。
#include<stdio.h>My_strlen(char*p){char*s=p;while(*s!='\0'){s++;}returns-p;}intmain(){printf("%d\n",My_strlen("abcdef"));return0;}