1.什么是指针
什么是指针?指针是内存中一个最小单元的编号,也就是内存地址。而我们口头上经常说的指针一般指的是指针变量,也就是存放地址的变量。
我们可以通过 & 取地址符号将地址存放在指针变量里面去,这个变量就被称为指针变量。
要注意的是32位机器上指针变量的大小为4个字节,而64位机器上指针变量的大小为8个字节
这是因为32位机器由32个bite存放32个 1 / 0 ,而8个bite等于一个字节,所以需要指针变量的大小就是4个字节;同理64位机器由64个bite存放64个 1 / 0,所以在64位机器上指针变量的大小就是8个字节。
2.指针类型
char *pa = NULL;
int *pb = NULL;
short *pc = NULL;
long *pd = NULL;
float *pe = NULL;
double *pf = NULL;
我们可以从这段代码中看出来,指针定义的方式是:
type *+(定义名称)
并且每一个指针变量都有不同的类型。
那么指针类型究竟有什么用呢?我们运行下列代码可以发现
如果将 指针变量+1 那么char类型和int类型所跨越的空间大小不同,一个是1个字节,另一个是4个字节。由此我们可以推出,指针类型决定了向后跨越的大小为多少。
同样的,指针类型也决定了访问的空间大小是多少
int n = 0x11223344;
char* pc = (char*)&n;
int* pi = &n;
*pc = 0;
*pi = 0;
return 0;
我们调试这段代码可以清楚的发现,char类型指针的访问空间大小为1个字节,而int类型指针的访问空间大小为4个字节,以此类推我们可以得知,指针类型也决定了访问的空间大小
3.野指针
什么是野指针呢?通俗一点说野指针就是指针变量里存放的地址是未知的,是不可控的。
那么为什么会产生野指针呢?
原因有三个,我们先讲第一个原因:没有初始化
#include <stdio.h>
int main()
{
int *p;
*p = 20;
return 0;
}
这里我们没有对p进行初始化,导致p的指针变量里面存放的地址为系统随机生成的地址,所以p在这里是野指针
第二个原因:数组越界访问
include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
*(p++) = i;
}
return 0;
}
我们这里首先只初始化了大小为10的数组,而我们使用的for循环里面,i最终的值为11,而数组的大小并不是11而是10,从而导致数组越界访问(访问到了第11个地址。
第三个原因:空间销毁
#include<stdio.h>
int* print()
{
int a = 10;
return &a;
}
int main()
{
int *p =print();
}
当我们创建一个函数以后在这个函数内部定义一个变量并返回他的地址,我们在编译器上面运行这段代码以后会发现程序会报错。原因就在于:我们在主函数里面申请调用函数时,创建了临时变量a,当出了这段函数以后,a就被销毁了,a的地址也返还给操作系统了。但是存储他的地址的指针变量p依然记得这个地址却没有权限去使用了。
但是如果我们用printf函数去打印a的值的时候,这时候出现的值依然是10。这是因为这一块地址还是被a占用了,如果有其他的变量占用了这一块地址的话我们再次打印会发现出现的值并不是10而是其他的随机数了。
那么我们要怎么样去避免野指针的出现呢?
指针初始化
小心指针越界
指针指向空间释放即使置NULL
避免返回局部变量的地址
指针使用之前检查有效性
牢记这五点,就能很大程度上避免野指针的出现了
关于怎么样将指针定义为空指针以后再赋值,代码如下:
#include <stdio.h>
int main()
{
int *p = NULL;
int a = 10;
p = &a;
if(p != NULL)
{
*p = 20;
}
return 0;
}
我们切记不能直接用赋值符号赋给空指针,因为在系统里面NULL定义为不能直接访问的对象,所以我们直接访问的话程序会报错。
4.指针运算
关于指针的运算分为三类
指针+- 整数
指针-指针指针的关系运算
指针与整数
float values[N_VALUES];
float *vp;
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
这一段代码里面所展现出来的内容就是指针与整数之间的加减
*vp++ --> ( *vp -> vp++)
*vp++的意思就是首先对vp进行解引用然后再对vp++,使得指针移动到下一个地址
我们这里还有一个容易混淆的地方
*vp++ 和 (*vp)++
这两个的区别就在于一个是对指针的移动,而另一个是对指针所指向的内容的移动
(*vp)++的意思是找到*vp所指向的内容以后对其移动,如果指向的内容是1,那输出的结果就是2
指针与指针
我们可以通过这一行代码来实现指针与指针之间的减法运算,所得的值就是9
为什么是9呢?这是因为指针与指针之间的减法所代表的意思这之间所相间隔的元素,在这个数组里面下标为9的元素与下标为0的元素之间所间隔的元素就是9,所以我们打印出来的内容就是9
但是我们要注意的是:不是所有的指针都能相减的!相减的前提是两个指针指向的是同一块空间地址
指针的关系运算
我们在这里只需要记住的是:编译器允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
5.指针与数组
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
我们在编译器上运行输出可以看到
通过这个输出结果我们可以得到结论:arr数组名所指向首元素的地址。进而我们可以推出一个结论:可以通过指针来访问数组
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{
printf("&arr[%d] = %p <---> p+%d = %p\n", i, &arr[i], i, p+i);
}
return 0;
}
我们以这一段代码为例,运行输出以后可以看到
通过 p+i 的方式,我们可以访问数组里面的任意一个元素
6.二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? 那就是存放在二级指针里面了 。二级指针不是存放地址的地址,而是存放指针变量的地址。
我们将目标对象的地址存放到指针变量里面去,这个时候如果我们要打印指针变量所打印出来的就是目标元素的地址,这个二级指针的地址是我们再占用了一块空间将一级指针的变量存放起来,我们打印出来的地址也就是一级指针变量的地址了。
通过代码实现
int b = 20;
*ppa = &b;
**ppa = 30
7.指针数组
什么是指针数组?简单的来说:指针数组就是存放指针的数组
#include<stdio.h>
int main()
{
int* arr[5];
return 0;
}
这一行代码代码所代表的意思就是能够存放5个指针变量的int类型数组
指针数组的作用也很明确,那就是不再一个一个的初始化指针,而是通过数组的方式来初始化指针,从而节省了很多工作量