目录
一.指针是什么:
理解指针的两个要点:
1.我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中。
为了高效管理内存空间,我们会把内存划分成一个一个的内存单元,每个内存单元的大小为1字节,每个内存单元都有一个编号。指针就是内存中最小单元的编号,也就是地址。
2.平时所说的指针,通常指的是指针变量,是用来存储地址的变量类型。
总的来说:指针就是地址,口语中的指针通常指指针变量,其值是另一个变量的地址,也就是内存的单元编号。
如图所示:
如上图所示,一个格子是内存最小单元,格子右边的数字是16进制形式的地址编号,即指针。
二.指针和指针类型:
2.1指针类型的声明和变量的取地址:
我们可以用类型名 + *来声明指针变量,使用取地址操作符(&)取变量地址赋给指针变量
示例:
#include<stdio.h>
int main()
{
int a = 0;
int* pa = &a;
printf("%p",pa);
return 0;
}
如上,pa中储存的就是储存整型变量a所用的地址编号 .
运行结果如下所示:
此时的编号就是变量a在内存中的位置。
注:*p = &a 是错误的, int* p = &a、p = &a正确。
2.2指针的解引用
我们可以用解引用操作符(*)来解引用指针,通过指针访问对应的数据。
示例:
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;
printf("%d", *pa);
return 0;
}
运行结果如下:
通过解引用整型指针pa,来访问变量 a 中存储的数据。
我们也可以通过解引用指针来修改变量的数据。
示例:
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;
*pa = 20;
printf("%d", *pa);
return 0;
}
运行结果如下:
2.3指针类型:
指针类型是C语言中数据类型之一,与数组,整型(int),字符型(插入)等类型同级别。
指针类型可以是整型指针(int*),单精度浮点型指针(float*),双精度浮点型指针(double*),甚至是函数指针,自定义数据类型指针等。
指针所指向的类型决定了编译器将指针所指向的内存区域里的内容当成什么来看待。
示例:
#include<stdio.h>
int main()
{
int a;
int*pa = &a;
printf("%p\n", pa);
printf("%p\n", pa + 1);
printf("%p\n", (float*)pa + 1);
printf("%p\n", (double*)pa + 1);
printf("%p\n", (long long*)pa + 1);
return 0;
}
运行结果如下图所示:
由此可知,整型变量a的存储地址是00S3FD98,变量a的地址加一是00D3FD9C,切换成单精度浮点型地址后加一也是00D3FD9C,两种类型的指针变量增加的量刚好是数据类型所占有的空间大小——4字节,而切换成双精度浮点类型指针后,增加的量是其对应的空间大小——8字节。指针的增加量就是我们常说的指针的步长。
总的来说,指针类型决定这编译器对该地址或该地址的数据的类型而有着不同的计算方式。
指针类型的作用还包括解引用时,访问空间的权限有多大。
2.4 NULL指针
NULL指针,就是我们口语中的空指针,程序不允许访问地址为0
的内存,因为该内存是操作系统保留的。常规情况下,内存地址 0
有特殊的意义,它表明该指针不指向一个可访问的内存位置,如果指针包含空值(零值),则假定它不指向任何东西。
2.5指针的大小
我们首先要知道一些基础的知识:
一个单元有多大?(1个字节)
地址是如何编制的?
对于三十二位机器,有三十二根地址线,每根地址线有高低电平两种信号(高电压,低电压),用1来表示高电平,用0来表示低电平。那么三十二位机器的地址编号,也就是指针就可以用四个字节储存,一共有二的三十二次方减一种不同的编号。
示例:
#include<stdio.h>
int main()
{
printf("%d\n", sizeof(int*));
printf("%d\n", sizeof(float*));
printf("%d\n", sizeof(short*));
printf("%d\n", sizeof(char*));
return 0;
}
我们求出个类型指针所占内存的大小,并通过printf函数打印出来
结果如下图:
我们可以知道,各类型的指针所占用的内存空间大小相同。
当我们使用64位机器时,地址线拥有64条,就表示者地址编号更多,所需要的内存空间就更大。
结果如图所示:
三.野指针:
3.1野指针的定义:
概念:指针指向的位置是不可知的,即指针变量存储的值是随机的,不确定的。
3.2野指针的危害:
野指针指向的位置是随机的, 危害也是随机的,不一定会产生错误。当程序产生错误,一般为内存泄露导致程序中断。
若野指针指向的位置存放的是一个病毒等东西,对其解引用后就会导致电脑中毒。
3.3野指针成因:
1).指针未初始化
示例:
#include<stdio.h>
int main()
{
int* pa;
*pa = 20;
return 0;
}
这里我们声明了一个整型指针变量pa,但是指针变量未经过初始化,则指针变量的值是随机的,指向的位置也是随机的,即该指针是一个野指针。
2). 指针越界访问
示例:
#include<stdio.h>
int main()
{
int a[] = { 1,2,3,4,5 };
for (int i = 0; i <= 5; i++)
{
*(a + i) = 0;
}
return 0;
}
这里通过指针a来访问内存,但是访问的不是数组a所使用的内存,此时的(a + i)就是野指针,这里发生的错误就是所谓的越界访问。
3).指针指向的空间释放:
示例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* a = (int*)malloc(sizeof(int));
free(a);
*a = 10;
return 0;
}
这里我们将申请的空间a释放掉后,a指针所指向的空间归还操作系统,这时候a就变成了野指针,再去解引用整型指针a,就可能会发生难以预料的情况。
4).在超越变量作用域下使用指针:
示例:
#include<stdio.h>
#include<stdlib.h>
int* fun()
{
int a = 10;
int* pa = &a;
return pa;
}
int main()
{
int* b = fun();
return 0;
}
很明显,函数fun中申请的变量生命周期在函数内部,出函数后变量销毁,此时的b便是野指针。
3.4如何规避野指针
1.指针初始化。
注:当指针无值可以赋时,我们将其赋值为NULL,避免指针是随机变量。
2.注意指针越界访问。
3.指针指向空间释放后即使赋为NULL,避免形成野指针。
4.避免返回局部变量地址。
5.指针使用之前检查其有效性。
四.指针的三种运算
4.1 指针 +- 整数:
示例:
#include<stdio.h>
int main()
{
int a;
int* pa = &a;
double b;
double* pb = &b;
char c;
char* pc = &c;
printf("%p\n", pa);
printf("%p\n", pa + 1);
printf("%p\n", pb);
printf("%p\n", pb + 1);
printf("%p\n", pc);
printf("%p\n", pc + 1);
return 0;
}
运行结果如图所示:
我们可以知道,无论指针类型时char、int或者是double,其类型指针加一,得到的结果时其指向的地址加上数据类型所占内存的大小(字节)。
指针减整数同理,示例:
#include<stdio.h>
int main()
{
int a;
int* pa = &a;
double b;
double* pb = &b;
char c;
char* pc = &c;
printf("%p\n", pa);
printf("%p\n", pa - 1);
printf("%p\n", pb);
printf("%p\n", pb - 1);
printf("%p\n", pc);
printf("%p\n", pc - 1);
return 0;
}
运行结果如下:
4.2指针减指针
(1)指针和指针可以做减法操作,但不适合做加法运算。
(2)两个指针都指向同一个数组。
(3) 相减结果为两个指针之间的元素数目,而不是两个指针之间相差的字节数。
示例:
#include<stdio.h>
int main()
{
int a[] = { 1,2,3,4,5,6 };
printf("%d\n", &a[5] - a);
printf("%d\n", a - &a[5]);
return 0;
}
运行结果如下
我们不知能看出两个指针相减得到的值是两指针之间的元素数目,还能看出来相减之后的结果,可以为正也可以为负,但所得结果的绝对值都是一样的。
4.3指针的关系运算
1).两指针指向同一个数组。
2).通过比较,我们可以知道同一数组的两个指针哪个更靠后。
3).标准未定义两个任意的指针相比较会发生什么。
示例:
#include<stdio.h>
int main()
{
int a[] = { 1,2,3,4,5 };
if (a < &a[4])
printf("a < &a[4]", *a, a[4]);
return 0;
}
结果如下所示:
由此,我们可以得到 a[4] 比 a 更加靠后。
五.指针数组和数组指针
5.1指针数组:
故名思意,就是存放指针的数组。
示例:
#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = 30;
int* d[3] = { &a,&b,&c };
printf("%p\n%p\n%p", *d, d[1], d[2]);
return 0;
}
运行结果如下所示:
这里数组小标操作符( [] )的优先级高于解引用操作符(*),d 会先与数组小标操作符相结合再与解引用操作符结合,即形成存放三个元素类型为 int* 的,首元素地址为d的数组。
5.2数组指针
顾名思义,就是指向数组的指针。
示例:
#include<stdio.h>
int main()
{
int a[][3] = { 1, 2, 3, 4, 5, 6 };
int(*pa)[3] = &(a[0]);
for (int i = 0; i < 2; i++)
{
printf("%d: %p, %p\n", i, *(a + i), pa + i);
}
return 0;
}
运行结果如下所示:
易知,pa中储存的是含有三个元素的数组的地址。
5.3指针数组和数组指针的区别
1).数组指针是一个指针,指向一个数组;指针数组是一个数组,里面的元素都是指针。
2).指针数组的写法:*p[3];
数组指针的写法:(*p)[3];
3)数组指针是一个变量,占用一个指针存储空间;而指针数组是多个变量,占用两个或以上的指 针存储空间。
六.二级指针
二级指针的定义:二级指针是指向一级指针的指针。说白了就是指针的指针。
示例:
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
printf("%p\n", &pa);
printf("%p\n", ppa);
printf("%p\n", *ppa);
printf("%d", **ppa);
return 0;
}
运行结果如下所示:
我们可以看出,解引用ppa得到的是a的地址,即pa的值,而对ppa两次解引用,得到a的值,ppa中储存的是pa的地址,此时的ppa就是所谓的二级指针,他指向pa。