1. 内存和地址
讲到指针就不得不提到内存。计算上CPU在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中。电脑上内存有8GB/16GB/32GB等,为了提高内存中数据的管理效率,把内存划分为一个个的内存单元,每个内存单元为
1个字节
,每个内存单元都有一个编号,有了这个内存单元的编
号,CPU就可以快速找到⼀个内存空间。
这个内存单元的编号叫做地址,在C语言里,地址又称为指针。
2.指针变量
指针变量顾名思义就是个变量,是一个用来存放指针(地址)的变量,它和整型变量类似(int a=10),这里就是有个整形变量a存放着一个整形10。只不过指针变量存放的是指针(地址)而整型变量存放着是整形数据。
在上述(int a=10)中我们知道a中存放着一个10,那这个值到底是存放在哪里,我们又如何拿出来使用呢?其实在C语⾔中创建变量就是向内存申请了一块空间,这块空间就是用来存放整数10的。以为是存放整形数据的所以这块空间有
4个字节
,
(补充:char类型占1个字节,short类型占2个字节,int类型和float类型占4个字节,double类型和long long类型占8个字节)
。那么用来存放整数10的这块空间就有4个内存单元,也就是有4个内存单元编号,也就是
指针(地址)
。
所以只要知道a的指针(地址),我们就可以找到里面存放的整数10,然后使用它。那我们该如何找到a的地址?这里就要用到下面方法
3.取地址操作符(&)
用法如下:
#include<stdio.h>
int main()
{
int a = 10;
printf(" %p \n", &a ); //&a,这里就是取出a的地址, %p是用来打印地址的 ,和%d类似
return 0;
}
int main()
{
int a = 10;
printf(" %p \n", &a ); //&a,这里就是取出a的地址, %p是用来打印地址的 ,和%d类似
return 0;
}
打印出来a的地址如下图:
&a取出的是a所占4个字节中地址
较⼩
的字节的地址。
虽然整型变量占⽤4个字节,我们只要知道了第⼀个字节地址,顺藤摸⽠访问到4个字节的数据。
我们取出来的地址就可以存在指针变量中,用法如下:
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a; //这句代码的意思就是取出 a的地址 存放在变量 p 中, * 表示变量p是指针变量⽽前面的 //int 是在说明pa指向的是整型(int) 类型的对象。
printf("a的地址:%p\n", &a);
printf("p存放的地址:%p\n", p);
return 0;
}
int main()
{
int a = 10;
int* p = &a; //这句代码的意思就是取出 a的地址 存放在变量 p 中, * 表示变量p是指针变量⽽前面的 //int 是在说明pa指向的是整型(int) 类型的对象。
printf("a的地址:%p\n", &a);
printf("p存放的地址:%p\n", p);
return 0;
}
打印结果:
从打印的结果可以看出,p里面存放的的确是a的地址。
就类似上图,a的内存为地址为00FDF7CC,在p里面就存放着00FDF7CC。类似的我们就可以用char* p1='w';来定义一个字符指针变量。
那我们将a的地址存放在指针变量pa里面有什么用呢?其实,我们可以使用变量pa里面存放的a的地址,从而找到a里面存放的内容并使用它。这里就要用到下面的方法
4.解引用操作符(*)
用法如下:
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a; //这里和上个例题一样,将a的地址取出来存放在指针变量pa里面, *pa 的意思就是通 // 过pa中存放的地址,找到指向的空间 *pa其实就是a变量了
printf("*p=%d\n", *p);// *p 这里的
return 0;
}
int main()
{
int a = 10;
int* p = &a; //这里和上个例题一样,将a的地址取出来存放在指针变量pa里面, *pa 的意思就是通 // 过pa中存放的地址,找到指向的空间 *pa其实就是a变量了
printf("*p=%d\n", *p);// *p 这里的
return 0;
}
打印结果:
从打印的结果可以看出,*p其实就是变量a。当然我们也可以通*p去修改a的值。
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;
*p = 20; //这里就相当于(a=20),把20赋给a
printf("a=%d\n", a);
printf("*p=%d\n", *p);
return 0;
}
int main()
{
int a = 10;
int* p = &a;
*p = 20; //这里就相当于(a=20),把20赋给a
printf("a=%d\n", a);
printf("*p=%d\n", *p);
return 0;
}
打印结果:
从打印的结果可以看出,我们可以通*p去修改a的值。
5.指针的运算
先看下面一个代码:
int main()
{
int arr[3] = { 1,2,3 };
printf("&arr[0]=%p\n", &arr[0]); // 打印数组第一个元素的地址
printf("&arr[1]=%p\n", &arr[1]); // 打印数组第二个元素的地址
printf("&arr[2]=%p\n", &arr[2]); // 打印数组第三个元素的地址
{
int arr[3] = { 1,2,3 };
printf("&arr[0]=%p\n", &arr[0]); // 打印数组第一个元素的地址
printf("&arr[1]=%p\n", &arr[1]); // 打印数组第二个元素的地址
printf("&arr[2]=%p\n", &arr[2]); // 打印数组第三个元素的地址
return 0;
}
}
从打印的结果和图片可以看出在
数组中的地址是连续的
,由数组是
int类型
,数组里面存放的数据是整型,所以第一个元素与第二个元素的地址相差了
4个字节
。如果数组是
char类型
,那
第一个元素与第二个元素的地址相差了
1个字节
。
当我们知道了数组中的地址是连续的后,来看下一段代码
#include<stdio.h>
int main()
{
int arr[3] = { 1,2,3 };
int* p = &arr[0]; //将数组下标为0的元素的地址取出来存放在p;
printf("&arr[0]=%p\n", p); //打印数组下标为0的元素的地址
printf("p+1=%p\n", p + 1 ); //这里就用到了指针运算的 指针+整数
printf("p+2=%p\n", p + 2) ;
return 0;
}
int main()
{
int arr[3] = { 1,2,3 };
int* p = &arr[0]; //将数组下标为0的元素的地址取出来存放在p;
printf("&arr[0]=%p\n", p); //打印数组下标为0的元素的地址
printf("p+1=%p\n", p + 1 ); //这里就用到了指针运算的 指针+整数
printf("p+2=%p\n", p + 2) ;
return 0;
}
从打印的结果可以看出配
p+1的是数组第二个元素
的地址,也就是说
p+1跳过了4个字节
,那为什么p+1后会跳过4个字节而不是1个呢?这里就要说明一下
指针变量类型的意义
:
指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。比如:上述的指针是一个int *的指针,因为
int占4字节,所以指针+1前跳过4个字节,如果是char* 类型的指针则跳过1个字节。
指针-整数
道理一样,就是变成向后跳。
指针除了可以加减整数外还可以进行指针之间的相减,那
指针-指针
后得到的是什么呢?
看下面代码来解释:
int main()
{
int arr[5] = { 1,2,3,4,5 };
int* p1 = &arr[0]; //取出arr[0]的地址存放在p1
int* p2 = &arr[4]; //取出arr[4]的地址存放在p1
printf("p2-p1=%d\n", p2 - p1 ); //打印出p2-p1的结果
return 0;
}
{
int arr[5] = { 1,2,3,4,5 };
int* p1 = &arr[0]; //取出arr[0]的地址存放在p1
int* p2 = &arr[4]; //取出arr[4]的地址存放在p1
printf("p2-p1=%d\n", p2 - p1 ); //打印出p2-p1的结果
return 0;
}
打印结果:
由打印结果可以看出,
指针-指针得到的是一个整数,这个整数就表示p2与p1之间相差4个元素
。(要是p1-p2的话得到的是-4)准确来说,
指针-指针得到的是相减指针之间元素的绝对值
。
6.野指针
野指针指的是指针指向的地址是未知的,指针指向了一个随机的未知的,错误的地址。
那野指针是怎么形成的呢?
1.指针变量未初始化(int* p)像括号的例子,定义一个指针变量p,但是没有给指针变量附一个地址,这样指针p就指向了一块未知的,随机的空间,这个指针p就是个野指针。
所以我们在定义一个指针变量的时候就应该给该指针附上我们知道的地址,如果不知道附什么地址可以将指针变量置未空指针(int* p=NULL)
2.指针的越界访问
这个代码是想在arr数组存入0 1 2 3 4这5个数,但是
有错误
int main()
{
int arr[5] = { 0 };
int* p = &arr[0];
for (int i = 0; i < 6 ; i++) //由于i<6,所有当i=5时,指针p指到了arr数组外面去了,就变成野指针
{
*p = i;
p++;
printf("%d ", arr[i]);
}
return 0;
}
{
int arr[5] = { 0 };
int* p = &arr[0];
for (int i = 0; i < 6 ; i++) //由于i<6,所有当i=5时,指针p指到了arr数组外面去了,就变成野指针
{
*p = i;
p++;
printf("%d ", arr[i]);
}
return 0;
}
所以我们在使用指针时要小心指针越界,⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。
3.指针指向的那个空间释放了
假设有一个指针变量p里存放着一个函数的地址,当函数调用完后,函数的空间就会释放,那p里面也就存放了一个已经释放空间的地址,当我们利用p去访问它时就会出错,这时的p就是野指针。
所以指针变量不使用时要及时置位空指针,使用指针前要检查一下其地址是否有效。