目录
一、指针是什么
要了解指针,那我们先说说什么是内存?
内存就是计算机中的存储数据的区域
而指针是内存中的一个最小单元的编号,也就是地址
我们平时口头语中所说的指针,通常指的是指针变量,是用来存放内存地址的变量
那么内存在计算机中是如何编号的呢?
对于32位的计算机,我们假设有32根地址线,那么把每根地址线通电,就会产生高低压和低电压,把电信号转换成数字信息,用二进制表示,就是0和1。
那么32根地址线产生的地址就会是:
假设计算机中的内存条一个长方形表示一块内存单元,0,1,2,3…表示一个编号(在计算机中编号(地址)用16进制表示):
在计算机中一个地址是一个字节,那么我们就可以计算32位的计算机可编址的大小:
下面我们举个例子加深对指针变量的理解:
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;
*pa = 20;
printf("%d", a); // 20
return 0;
}
上述这个例子当中,pa是用来存放地址的,也就是指针变量
*pa就是通过pa里边的地址,找到a,然后把a的值改为20
二、指针和指针类型
我们都知道,变量有不同的类型,整型、浮点型等。指针也是有类型的。
我们给指针相应的类型:
char* pc = NULL;
int* pi = NULL;
short* ps = NULL;
long* pl = NULL;
float* pf = NULL;
double* pd = NULL;
那么指针类型有什么意义?
2.1、指针不同类型的解引用
首先我们举个例子:
#include <stdio.h>
int main()
{
int a = 0x11223344;
int* pa = &a;
*pa = 0; // 内存中的字节全改为0,即00 00 00 00
//char* pc = &a;
//*pc = 0; //内存中的字节改为00 33 22 11,只改了第一个字节
return 0;
}
pa在内存中的结果:
pc在内存中的结果:
由上述例子可知:
指针类型决定了解引用操作的时候,访问了几个字节 char* 访问一个字节,int* 访问4个字节
2.2、指针±整数
下面我们来看看指针±整数会出现什么结果:
由上图可知int * 指针+1,则跳过一个整型,向后走了4个字节。
char * 指针+1,则跳过一个字符,向后走了1个字节。
指针类型决定了指针的步长,也就是向前或向后走一步要多大距离
三、野指针
野指针就是指针指向的位置是未知的、随机的、不正确的,没有明确限制的
3.1、造成的原因:
1、指针未初识化
#include <stdio.h>
int main()
{
int* p; // p就是野指针
*p = 20;
return 0;
}
2、指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
for (i = 0; i <= sz; i++) // 越界访问,造成野指针
{
*p = i;
p++;
}
return 0;
}
3、指针指向的空间已释放
#include <stdio.h>
int* test()
{
int num = 100;
return # // 出函数,num空间就不存在了
}
int main()
{
int* p = test();
*p = 200; // 找不到原空间,此时p是野指针
return 0;
}
3.2、如何规避野指针:
1、指针初识化
int* p = NULL;
2、小心指针越界访问
3、指针指向空间释放,及时置NULL
//开辟动态空间
int* p = malloc();
//使用
//释放
free(p);
p = NULL;
4、避免返回局部变量的地址
5、指针使用之前检查有效性
int* p = NULL;
if(p != NULL)
{
printf("%d\n", *p);
}
四、指针的运算
4.1、指针±整数
举个例子:
#include <stdio.h>
#define N 5
int main()
{
int arr[N];
int* vp = arr;
int i = 0;
//指针+-整数;指针的关系运算
for (i = 0; i < 5;i++)
{
*vp++ = 0;
}
return 0;
}
由于++优先级比 * 高,所以++只作用于vp,即 *(vp++),所以数组内的元素都为0
4.2、指针的关系运算
for (vp = &arr[5]; vp > &arr[0];)
{
*--vp = 0; // 先--vp,再*vp,不会越界
}
把代码改造为:
int main()
{
for (vp = &arr[N - 1]; vp >= &arr[0]; vp--)
{
*vp = 0;
}
}
第二个代码在绝大部分的编译器上可以顺利完成,但是还应该避免这样写,因为标准并不保证它可行。
标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但不允许与指向指针第一个之前的那个内存位置的指针进行比较。
4.3、指针-指针
首先我们举个例子:
由上述可知,指针-指针绝对值的结果为指针之间的元素个数。
五、指针和数组
数组和指针不是一个东西
数组能够存放一组连续空间的数,数组的大小取决于元素大小
指针是一个变量,是存放地址的,32位下大小为4个字节,64位下大小为8个字节。
联系就是:数组名是地址(指针),数组把首元素的地址交给一个指针变量后,可以通过指针来访问数组。
下面我们来举个例子:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *p);
p++;
}
printf("\n");
return 0;
}
运行结果:
我们也可以通过数组下标访问每个元素
for (i = 0; i < sz; i++)
{
printf("%d ", *(p+i));
}
六、二级指针
指针变量也是变量,那么指针变量的地址又是存在哪里的呢?
这就是我们所说的二级指针。
int a = 10;
int* p = &a; // p是一级指针变量
int* pp = &p; // pp就是二级指针变量
int * 是pp所指向p的类型,p的int是指向a的类型
对于二级指针的运算有:
*p = 200;
// 等价于**pp = 200;
// 等价于a = 200;
举个例子:
#include <stdio.h>
int main()
{
int a = 10;
int* p = &a;
printf("%d\n", *p);
int** pp = &p;
printf("%d\n", **pp);
int* ps = *pp;
*ps = 20;
printf("%d\n", *ps);
return 0;
}
运行结果:
**pp:第一次解引用找到p,再次解引用找到a
七、指针数组
首先问一问指针数组是指针还是数组呢?
当然,是数组,是存放指针的数组。
数组我们已知有:
int arr1[10]; // 整型数组,存放整型的数组
char arr2[10]; // 字符数组,存放字符的数组
那么指针数组又是什么呢?
int* arr3[5]; // 指针数组,存放整型指针的数组
char* arr4[5]; // 存放字符指针的数组
下面我们举个例子:
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = 30;
int* arr[3] = { &a,&b,&c };
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", *(arr[i]));
}
return 0;
}
运行结果:
指针数组的应用:
用一维数组模拟一个二维数组:
#include <stdio.h>
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int arr4[] = { 4,5,6,7,8 };
int* arr[4] = { arr1,arr2,arr3,arr4};
int i = 0;
for (i = 0; i < 4; i++)
{
int j = 0;
for (j = 0; i < 5; j++)
{
printf("%d ", arr[i][j]); // 等价于*(*(arr+i)+j)
}
printf("\n");
}
return 0;
}
运行结果: