文章目录
C语言基础6:指针基础
1. 指针是什么
指针是什么:在计算机科学中,指针(Pointer )是编程语言中的一个对象,利用地址,它的值直接指向( points to )存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此.将地址形象化的称为"指针"。意思是通过它能找到以它为地址的内存单元。
指针是个变量,存放内存单元的地址(编号),一个内存单元的大小是 1 个 字节。
#include <stdio.h>
int main()
{
int a = 10;//在内存中开辟一块空间
int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个之指针变量。
return 0;
}
关于指针的详细介绍见:
C语言基础1:初识C语言(转义、注释;数组、操作符、反码、补码、static、define、指针、结构体常变量;局部变量;作用域、生命周期)
指针的大小在32位平台是4个字节,在64位平台是8个字节。
2. 指针和指针类型
指针和指针类型用 以下源代码举例:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 0x11223344;
int* pa = &a;
*pa = 0;
return 0;
}
按F10(Fn)打开调试,然后在【窗口】→【内存】→【内存(1)】,调出调试栏
在地址栏输入 &a ,我们看到结果存放的是 44 33 22 11
接着将光标继续运行,运行*pa = 0;
后,&a 内存中存放的数据就是 00 00 00 00存放
修改代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 0x11223344;
char* pa = &a;
*pa = 0;
return 0;
}
如果可以运行,那么运行后的 char* pa 结果是 00 33 22 11,这个结果说明:指针类型决定了指针进行解引用操作的时候,能够访问空间的大小。
int* p;
*p 能够访问4个字节
char* p;
*p 能够访问1个字节
double* p;
*p 能够访问8个字节
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 0x11223344;
char b = 'b';
int* pa = &a;
char* pc = &b;
printf("%p\n", pa);
printf("%p\n", pa + 1);
printf("%p\n", pc);
printf("%p\n", pc + 1);
return 0;
}
总结:指针的类型决定了指针向前或向后走一步的距离有多大。
下面以数组举例,整型数组中存放的数字都是0,定义整型指针p然后依次给数组的首地址+1并赋值为1,程序运行完成后,数组内的数字都会为1。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int i = 0;
int arr[10] = { 0 };
int* p = arr; //数组名 - 首元素的地址
//将arr数组中的0替换为1
for (i = 0; i < 10; i++)
{
*(p + i) = 1;
}
return 0;
}
arr[10] 中存放的数据都是1。我们可以看到内存中数据存储的方式
将代码改成如下,定义字符型指针p
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int i = 0;
int arr[10] = { 0 };
//int* p = arr; //数组名 - 首元素的地址
char* p = arr;
//将arr数组中的0替换为1
for (i = 0; i < 10; i++)
{
*(p + i) = 1;
}
return 0;
}
代码改成这样后,4个字节存放的数据就会由原来的01 00 00 00 变成 01 01 01 01 。因为int* p;
*p 能够访问4个字节,而char* p;
*p 能够访问1个字节。
3. 野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
3.1 野指针成因
3.1.1 指针未初始化
int *p;
局部变量指针未初始化,默认为随机值。
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
3.1.2 指针越界访问
指针指向的范围超出数组arr的范围。
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
3.1.3 指针指向的空间释放
#include <stdio.h>
int* test()
{
int a = 10;
return &a;
}
int main()
{
int *p = test();
*p = 20;//原test函数已被销毁
return 0;
}
3.2 如何规避野指针
(1)指针初始化
(2)小心指针越界
(3)若指针指向空间释放,及时将指针置NULL
(4)避免返回局部变量的地址
(5)指针使用之前检查有效性
将NULL转到定义,发现本质 NULL 就是 0;
#include <stdio.h>
int main()
{
int *p = NULL;
//....
int a = 10;
p = &a;
if(p != NULL)
{
*p = 20;
}
return 0;
}
4. 指针运算
4.1 指针 ± 整数
对数组的首地址不断+1,再进行解引用,输出数值。
修改限制条件,同时对地址+2,再进行解引用,输出数值。
或者对指针进行减运算。
#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
4.2 指针 - 指针
指针减去指针的结果是指针之间元素个数的绝对值,而不是字节大小。
4.3 指针的关系运算
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[5];
int* p = NULL;
for (p = &arr[5]; p > &arr[0];)
{
*--p = 1;
}
return 0;
}
指针p 开始在&arr[5]的地址,p先减 减为&arr[4],然后对 arr[4] 中的数据赋值为1,之后对其中的数据依次赋值,到 p = &arr[0] 时,for 循环 条件&arr[0]>&arr[0]不满足,循环终止
若修改代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[5];
int* p = NULL;
for (p = &arr[4]; p >= &arr[0]; p--)
{
*p = 1;
}
return 0;
}
p 开始在&arr[4]的地址,p先用,然后arr[4]赋值为1,之后地址为&arr[3],然后对 arr[3] 中的数据赋值为1,之后对其中的数据依次赋值,到 p = &arr[0] 时,for 循环 条件满足,进行运算,之后循环结束,但是 p - - 了,对于 p 的地址我们不清楚它在哪里了。这就是数组在使用指针的时候注意的地方。
综上,实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。标准规定
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较,也就是说 P2 可以 和 P3 比较,但是不能和 P1 比较 。
5. 指针和数组
指针在数组中使用的时候需要注意以下几点:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
printf("%p\n", arr); //数组名arr,表示首元素的地址
printf("%p\n", &arr[0]); //取出数组arr的首元素地址
printf("%p\n", &arr); //取出整个数组的地址
return 0;
}
一般情况下,数组名表示的是数组首元素的地址,数组是可以通过指针来访问的。
(1)&数组名(例如:&arr),此时的数组名 arr 表示整个数组,而不是首元素的地址,它取出的是整个数组的地址
(2)sizeof(arr),也就是sizeof(数组名),此时的数组名 arr 表示的整个数组,而 sizeof(数组名)计算的是整个数组的大小。
我们可以通过打印指针和数组的地址,来比对一下他们的地址有什么不同。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int i = 0;
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(" %p === %p\n", p + i, &arr[i]);
}
return 0;
}
6. 二级指针
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a; //pa 就是一级指针
int** ppa = &pa; //ppa 就是二级指针
int*** pppa = &ppa; //pppa 就是三级指针
}
上述程序说明:a 的地址存放在 pa 中,pa 的地址存放在ppa中,pa 是一级指针,而ppa 是二级指针。
那么当寻找a的时候,三级指针中存放了二级指针的地址,找到了二级指针的地址,然后发现里面还存放了一级指针,找到了一级指针的地址,发现里面存放的是10,也就是a。
7. 指针数组
指针数组,本质上是数组,该数组的作用是用于存放指针,指针数组中的每个元素都是一个指针