前言
指针基础概念理解,从底层出发理解指针
C语言【指针篇】(一)
- 前言
- 正文
- 1. 内存和地址
- 1.1 内存
- 1.2 究竟该如何理解编址
- 2. 指针变量和地址
- 2.1 取地址操作符(&)
- 2.2 指针变量和解引用操作符(*)
- 2.3 指针变量的大小
- 3. 指针变量类型的意义
- 3.1 指针的解引用
- 3.2 指针+-整数
- 3.3 void* 指针
- 4. 指针运算
- 4.1 指针+-整数
- 4.2 指针 - 指针
- 4.3 指针的关系运算
- 总结
正文
1. 内存和地址
1.1 内存
在讲解内存和地址前,先看一个生活案例。假设有一栋宿舍楼,楼里有100个房间但未编号,朋友来找你时,若想找到你就得挨个房间找,效率很低。但如果给每个房间按楼层和房间情况编号,如一楼为101、102、103……二楼为201、202、203……有了房间号,朋友就能快速找到房间和你。
在计算机中,CPU处理数据时,数据在内存中读取,处理后也放回内存。我们买电脑时,内存有8GB、16GB、32GB等不同规格。为高效管理这些内存空间,会把内存划分为一个个内存单元,每个内存单元大小为1个字节。
计算机中常见单位:
- 一个比特位可以存储一个二进制的位1或者0。
- 单位换算:
- 1Byte = 8bit
- 1KB = 1024Byte
- 1MB = 1024KB
- 1GB = 1024MB
- 1TB = 1024GB
- 1PB = 1024TB
每个内存单元就像一个学生宿舍,一个字节空间能放8个比特位,好比八人间,每个人是一个比特位。每个内存单元都有编号,这个编号就像宿舍房间的门牌号,有了它,CPU就能快速找到内存空间。在生活中我们把门牌号叫地址,在计算机中把内存单元的编号称为地址,C语言中给地址起了新名字叫指针。
所以可以理解为:内存单元的编号 == 地址 == 指针。
1.2 究竟该如何理解编址
计算机内有很多硬件单元,它们需协同工作,协同工作至少要能相互进行数据传递。硬件之间相互独立,通过“线”连接实现通信。CPU和内存之间有大量数据交互,也用线连接,这里我们关心一组线——地址总线。
CPU访问内存中的某个字节空间,必须知道该字节空间的位置,由于内存中字节很多,所以需要给内存编址,就像给很多宿舍编号一样。计算机中的编址并非记录每个字节的地址,而是通过硬件设计完成的。
简单理解,32位机器有32根地址总线,每根线只有两态,即表示0、1(电脉冲有无)。一根线能表示2种含义,2根线能表示4种含义,依此类推,32根地址线能表示2^32种含义,每种含义都代表一个地址。地址信息下达给内存后,就能找到对应数据,并通过数据总线传入CPU内寄存器。
2. 指针变量和地址
2.1 取地址操作符(&)
在C语言中创建变量就是向内存申请空间。例如:
#include <stdio.h>
int main()
{
int a = 10;
return 0;
}
若想得到a的地址,需学习取地址操作符(&)。示例代码如下:
#include <stdio.h>
int main()
{
int a = 10;
//&a;//取出a的地址
printf("%p\n", &a);
return 0;
}
&a取出的是a所占4个字节中地址较小的字节的地址,虽然整型变量占用4个字节,但知道第一个字节地址,就能访问到4个字节的数据。
2.2 指针变量和解引用操作符(*)
指针变量也是一种变量,专门用来存放地址,存放在指针变量中的值都会被理解为地址。
#include <stdio.h>
int main()
{
int a = 10;
int * pa = &a;//取出a的地址并存储到指针变量pa中
return 0;
}
指针变量的类型可按如下方式理解:如int *pa
,*
说明pa是指针变量,前面的int
说明pa指向的是整型(int)类型的对象。
若有char
类型变量ch
,存放ch
地址的指针变量类型应为char *
。
char ch = 'w';
char *pc = &ch;
将地址保存起来后,在C语言中可通过解引用操作符(*)使用地址。示例代码如下:
#include <stdio.h>
int main()
{
int a = 100;
int* pa = &a;
*pa = 0;
return 0;
}
在上述代码中,*pa
表示通过pa中存放的地址找到指向的空间,*pa
其实就是a变量,所以*pa = 0
就是把a改成了0。使用指针修改a的值,为修改a提供了另一种途径,使代码编写更灵活。
2.3 指针变量的大小
32位机器假设有32根地址总线,一个地址是32个bit位,需4个字节存储,所以指针变量大小是4个字节。64位机器假设有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;
}
在X86环境(32位)下输出结果均为4,在X64环境(64位)下输出结果均为8。由此可得结论:32位平台下指针变量大小是4个字节;64位平台下指针变量大小是8个字节;指针变量的大小和类型无关,只要是指针类型的变量,在相同平台下,大小都相同。
3. 指针变量类型的意义
指针变量大小和类型无关,但指针类型有特殊意义。
3.1 指针的解引用
对比以下两段代码,调试时观察内存变化:
//代码1
#include <stdio.h>
int main()
{
int n = 0x11223344;
int *pi = &n;
*pi = 0;
return 0;
}
//代码2
#include <stdio.h>
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
*pc = 0;
return 0;
}
调试发现,(如何调试的文章马上补上)代码1会将n的4个字节全部改为0,代码2只是将n的第一个字节改为0。由此可得结论:指针的类型决定了对指针解引用时的权限,即一次能操作几个字节。char*
的指针解引用只能访问一个字节,int*
的指针解引用能访问四个字节。
3.2 指针±整数
通过以下代码调试观察地址变化:
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;
char* pc = &a;
printf("%p\n", &a);
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;
}
运行结果显示,char*
类型的指针变量+1跳过1个字节,int*
类型的指针变量+1跳过了4个字节。这表明指针变量的类型差异会带来这样的变化,指针+1其实跳过1个指针指向的元素,指针也可以-1。
结论:指针的类型决定了指针向前或向后走一步的距离。
3.3 void* 指针
在指针类型中,void *
类型比较特殊,可理解为无具体类型的指针(也叫泛型指针),这种类型的指针能接受任意类型地址,但有局限性,不能直接进行指针的±整数和解引用运算。
#include<stdio.h>
int main()
{
int a = 10;
//int* pa = &a;
void* pv = &a;
//
//*pv = 20;//错误
//pv++;//错误
*(int*)pv = 20;//对
return 0;
}
例如,将一个int
类型变量的地址赋值给一个char*
类型的指针变量时,编译器会给出警告,因为类型不兼容。
而使用void*
类型则不会有此问题。示例代码如下:
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;
char* pc = &a;
return 0;
}
使用void*
类型指针接收地址:
#include <stdio.h>
int main()
{
int a = 10;
void* pa = &a;
void* pc = &a;
*pa = 10;
*pc = 0;
return 0;
}
上述代码编译时会报错,显示非法的间接寻址,
这表明void*
类型的指针虽能接收不同类型的地址,但无法直接进行指针运算。void*
类型的指针一般用在函数参数部分,接收不同类型数据的地址,实现泛型编程效果,使一个函数能处理多种类型的数据,在后续文章也有讲解
4. 指针运算
指针的基本运算有三种:指针±整数、指针-指针、指针的关系运算。
4.1 指针±整数
数组在内存中连续存放,知道第一个元素地址就能找到后面所有元素。示例代码如下:
#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]);
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));//p+i这里就是指针
}
return 0;
}
当然循环内也可以改为:
printf("%d ", *p );
p++;
上面是加的实例,那么指针-整数可以吗?当然可以
#include<stdio.h>
int main()
{
int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = &arr[sz - 1];
for (int i = sz-1; i >= 0;i--)
{
printf("%d ", *(p -i));
}
return 0;
}
4.2 指针 - 指针
(1)指向同一块空间
(2)得到两个指针的元素个数
下面代码演示自己写strlen函数
方法一:逐个计数
//指针-指针
#include <stdio.h>
size_t my_strlen(char *p)
{
//注释的也是一种小方法,如有误请指正
/*int sz = sizeof(arr) - sizeof(arr[0]);
return &arr[sz - 1] - &arr[0];*/
size_t cnt = 0;
while (*p != '\0')
{
cnt++;
p++;
}
return cnt;
}
int main()
{
char arr[] = "abcdef";
size_t len = my_strlen(arr);
printf("%zd", len);
return 0;
}
方法二:指针相减
#include <stdio.h>
size_t my_strlen(char *p)
{
char* q = p;
while (*q != '\0')
{
q++;
}
return q - p;//指针 - 指针
}
int main()
{
char arr[] = "abcdef";
size_t len = my_strlen(arr);
printf("%zd", len);
return 0;
}
4.3 指针的关系运算
使用指针打印数组内容,但通过指针关系实现
//指针的关系运算
int main()
{
int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = &arr[0];
while (p < &arr[sz])//指针比较大小
{
printf("%d ", *p);
p++;
}
return 0;
}
总结
本文是指针最基础的部分,新的概念比较多,需要仔细理解,后续将逐步进阶,达到深入理解并熟练掌握的程度,希望可以点赞关注订阅支持一下,谢谢。