指针是什么
计算机内存空间如何进行管理呢?
计算机的没存空间被分成一个一个小的内存单元,每个单元大小为1byte(字节),每个内存单元都安排一个编号,通过编号可以访问内存空间,这个编号就叫作地址,地址也叫做指针,指针就是内存单元的编号
指针:就是地址,口语中说的指针通常指的是指针变量。
指针变量:
我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个变量就是指针变量
在初识C语言中我们已经知道的指针知识
- 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
- 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
- 总结:
指针变量是用来存放地址的,地址是唯一标示一块地址空间的。
指针的大小在32位平台是4个字节,在64位平台是8个字节。- 32根地址线能够管理4G的空闲时间
指针和指针类型
我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢?
指针也是有类型的,指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
所有指针类型的大小都是相同的,是一个地址长度的大小,32位机器上是四个字节的大小,64位机器上是8个字节的大小
指针定义:int a = NULL;*代表该变量是指针变量,int 表示该指针变量存储的是int 型变量的地址。
char 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。那指针类型的意义是什么?
指针±整数
#include <stdio.h>
int main()
{
int n = 10;
char* pc = (char*)&n;
int* pi = &n;
printf("&n = %p\n", &n);
printf("pc = %p\n", pc);
printf("pc + 1 = %p\n\n", pc + 1);
printf("pi = %p\n", pi);
printf("pi + 1 = %p\n\n", pi + 1);
return 0;
}
说明了char指针+1就跳过一个字节,int指针+1就跳过4个字节都是该变量所占内存空间的大小。
指针的类型决定了指针±1操作的时候,跳过几个字节。决定了指针的步长
总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。
指针的解引用
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5};
short *p = (short*)arr;
int i = 0;
for(i=0; i<4; i++)
{
*(p+i) = 0;
}
for(i=0; i<5; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
最终输出结果为0 0 3 4 5
这个结果说明了只改变了前两个整形,也是前16个字节是4个短整形的大小。
结论:
指针类型决定了指针在被解引用的时候访问几个字节
如果是int的指针,解引用访问4个字节
如果是short的指针,解引用访问1个字节
总结:指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
但是同等操作空间的指针不能混用,int型变量放到float*指针变量中去,*指针就不是访问int型了
#include<stdio.h>
int main()
{
int num = 10;
float* pf = (float*)#
printf("%d", *pf);
return 0;
}
最终程序的输出结果为0
出现了这两条警告说明了把*pf当作了float类型的变量。
野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针成因
1.指针未初始化
int main()
{
int* p;
*p = 10;
return 0;
}
p没有初始化,就意味着没有明确的指向
一个局部变量不初始化的化,放的是随机值:0xcccccccc(vs编译器中)
*p非法访问内存了,这里的p就是野指针
2.指针越界访问
int main()
{
int arr[10] = { 0 };
int* pa = arr;
int i = 0;
for (i = 0; i <= 10; i++)
{
*pa = i + 1;
pa++;
}
return 0;
}
指针pa最后指向的是数组arr第十个元素后面的元素,指针指向的范围超出数组arr的范围,pa就是野指针
指针指向的空间释放
#include<stdio.h>
int* test()
{
int a = 10;
return (&a);
}
int main()
{
int* pa = test();
printf("%d\n", *pa);
return 0;
}
a的作用域和生命周期是在test函数内出了这个函数变量a就被销毁了,这时候pa所接收的地址就不是该程序内的地址了,pa就属于野指针了。
但是*pa也可以访问那片空间但是非法的,如果*pa之前有其他操作,*pa中的值就可能会改变
比如:
#include<stdio.h>
int* test()
{
int a = 10;
return (&a);
}
int main()
{
int* pa = test();
printf("hello word\n");
printf("%d\n", *pa);
return 0;
}
这里放在动态内存开辟的时候讲解,这里就简单提示一下。
如何规避野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放即使置NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性
但这样仅仅是规避野指针,也无法完全避免野指针
例子:
#include<stdio.h>
int* test()
{
int a = 10;
return (&a);
}
int main()
{
int* pa = test();
printf("hello word\n");
if (pa != NULL)
{
printf("%d\n", *pa);
}
return 0;
}
指针运算
- 指针± 整数
- 指针-指针
- 指针的关系运算
指针±整数
指针±整数表示的是该指针移动该数据类型的小的整数被的位置。
用 指针+整数来模拟数组的遍历
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int* pa = arr;
for (i = 0; i < sz; i++)
{
printf("%d ", *(pa + i));
}
return 0;
}
指针-指针
指针-指针得到的是指针和指针之间元素的个数,不是所有指针都能相减,指向同一块空间的指针才能相减,
例子:
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%d\n", &arr[9] - &arr[0]);
return 0;
}
第十个元素和第1个元素之间相差9个元素。
应用:用指针相减模拟strlen函数
#include<stdio.h>
int My_strlen(char* str)
{
char* start = str;
while (*str != '\0')
{
str++;
}
return (str - start);
}
int main()
{
int len = My_strlen("abcdef");
printf("%d\n", len);
return 0;
}
只有指针相减没有指针相加,就类比于日期,日期减去日期是中间相隔的天数,日期+日期就没有了意义。
指针的关系运算
指针的关系运算就是指针间的大小比较
例子:
int main()
{
int arr[10] = { 0 };
int* pa = &arr[10];
for (pa = &arr[10]; pa >= arr;)
{
*--pa = 1;
}
return 0;
}
这种是遍历指针赋值数组,其中pa = &arr[10]虽然存放的是数组外的地址但没有对它的内部元素进行访问,并不属于越界
有人会想到一种改进的方法,把pa–放到循环语句调整部分。
int main()
{
int arr[10] = { 0 };
int* pa = &arr[10];
for (pa = &arr[9]; pa >= arr;pa--)
{
*pa = 1;
}
return 0;
}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
- 允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
指针和数组
指针变量是存放变量地址的,数组是存放一系列相同类型元素的集合,他们看似没什么关系,但是就是因为数组名代表数组首元素的地址使指针变量和数组产生了联系
既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个数组就成为可能。
验证数组元素地址与指针+下标地址相同
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* pa = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("&arr[%d] = %p <====> pa+%d = %p\n", i, &arr[i], i, pa + i);
}
return 0;
}
直接通过指针来访问数组。
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* pa = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(pa + i));
}
return 0;
}
二级指针
格式:int ** ppa = &pa;
别被它的格式望文生义说二级指针使指针的指针,并不是,二级指针变量是存放一级指针变量的地址。
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
return 0;
}
- *ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa .
- **ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .
指针数组
中文一般形容词在前名词在后,指针数组中指针是修饰指针的,所以指针数组是一个数组。
整形数组是存放整型数据的,字符型数组是存放字符型数据的,指针数组顾名思义也就是存放指针型数据的
我们可以用用指针数组模仿二维数组
二维数组的遍历输出
#include<stdio.h>
int main()
{
int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
用指针数组模拟二维数组
#include<stdio.h>
int main()
{
int arr1[4] = { 1, 2, 3, 4 };
int arr2[4] = { 2, 3, 4, 5 };
int arr3[4] = { 3, 4, 5, 6 };
int* arr[3] = { arr1,arr2,arr3 };
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
虽然可以用指针数组模拟二维数组,但是它们两个在内存中存储形式不同,二维数组是所有元素相邻,指针数组是把三个存储位置不同的三个一维数组名存在里面