1、指针的概念
(1)指针要点
- 指针是内存中的最小单元的编号,也就是地址
- 一般说的指针多指指针变量,指针变量是用来存放内存地址的变量
(2)图形理解
2、指针的类型
(1)指针变量
对一个非指针变量例如int a,可以用&(取地址符)来取出a的地址,将这个地址存入一个指针变量中
int a = 10;
int* pa = &a;
(2)指针类型
这个指针变量,同理,整型存储整数,而指针类型存储指针(地址)
int a = 0;
int *pa = &a;
char b = 'A';
char *pb = &b;
float c = 0.3;
float *pc = &c;
总之就是指向什么数据类型(例如指向int)就用什么样的指针类型(指针类型为int*),即格式为:
指针指向的变量类型* 指针名称
- “”前面的类型决定C语言如何使用这个地址,即:决定应该访问多少个字节,正常情况下指向变量的类型和指针类型是一一对应的(例如int对应int)
- 如果没有对应指向变量的类型和指针类型(例如int变量对应char*指针)通过指针访问变量时,操作变量就有可能出现问题
(3)“地址的编址问题”和“指针类型大小”
- 一般来说一个最小的存储单元是一个字节
- 如何来编址呢?
- 通过权衡最终决定一个字节对应一个地址比较合适。对于32位机器(32位电脑)就有32根地址线,假设32根地址线都能产生高电平(1)和低电平(0),则对于的可能产生的二进制有2的32次方种情况,即需要4G的空间进行编址(同理64位也是类似的)
- 指针类型的大小?
- 因此32位机器上,一个地址就是32个位构成(每个位都是1或者0)的二进制序列,那么地址就用4个字节来存储,所以一个指针变量的大小是4个字节
- 因此64位机器上,那一个指针变量的大小就是8个字节,才能存放一个地址
- 因此总结:指针的大小在32位平台是4个字节,64位平台是8个字节
3、指针的解引用
#include <stdio.h>
int main()
{
int n = 0x11223344;//十六进制数字
char* pc = (char*)&n;
int *pi = &n;
*pc = 0; //观察内存的变化
*pi = 0; //观察内存的变化
return 0;
}
- 因此指针类型决定了对指针解引用的权限,能够从起始地址开始操作几个字节
4、指针的运算操作
- 指针+/-整数==指针
#include <stdio.h>
//演示实例
int main()
{
int n = 10;
//0000 0000|0000 0000|0000 0000|0000 1010
char *pc = (char*)&n;//将int*类型的指针强制转化为char*类型
int *pi = &n;//指向类型和指针类型一一对应
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}
可以看到指针类型决定了指针向前和向后的步长有多大,注意,系统提供的地址在不同机器有可能不一样
- 指针-指针==整数
#include <stdio.h>
int main()
{
char arr[] = "abcdef";
int a = arr[5] - arr[0];
printf("%d", a);//得到两个指针之间元素的个数
return 0;
}
- 指针的关系运算
#include <stdio.h>
int main()
{
int arr[10] = { 1,1,2,4,3 };
if (&arr[3] > &arr[0])
{
printf("(&arr[3])是高地址\n");
}
return 0;
}
指针间的关系比较,比较的是高地址和低地址
另外标准规定,允许指向数组元素的指针与与指向数组最后一个元素后面的那个内存的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较
5、野指针的问题
野指针就是指针指向的位置是不明确的不可知的(随机的、不正确的、没有明确限制的)
(1)野指针的成因
- 指针未初始化
#include<stdio.h>
int main()
{
char *a;//这里只是声明了指针变量,没有明确指针指向的数据具体是什么,只知道该数据是int类型
scanf("%s", a);//scanf访问了一个野指针
printf("%s", a);
return 0;
}
- 指针越界访问
#include<stdio.h>
int main()
{
int arr[4] = {1, 2, 4, 0};
for(int i = 0; i < 10; i++)//这里的10不应该超过4,要改成比4小的数字
{
*(arr++) = i;//数组越界访问了,其结果是未知的,程序也有可能异常中止
)
return 0;
}
- 指针指向的空间释放
指针指向的空间释放主要在动态内存开辟的时候才会出现,有关动态内存的知识之后再说
(2)如何规避野指针
- 指针初始化
- 小心指针越界
- 指针指向的空间释放后,要及时置空NULL(动态内存分布的部分)
- 避免返回局部变量的地址
- 指针在使用之前检查有效性(动态内存的时候会大量使用)
6、指针和数组
实际上指针和数组的关系十分密切
#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]);
printf("%p\n", arr + 1);
printf("%p\n", &arr[1]);
return 0;
}//打印的结果是一样的,因此我们可以得出一个结论:数组名就是首元素地址
还可以写出如下的代码
#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 (int i = 0; i < sz; i++)
{
printf("\"&arr[%d] = %p\" 和 \"p+%d = %p\"\n", i, &arr[i], i, p + i);
}
printf("\n");
for (int i = 0; i < sz; i++)
{
printf("\"&arr[%d] = %d\" 和 \"p+%d = %d\"\n", i, arr[i], i, *(p + i));
}
return 0;
}
7、二级指针的概念
是变量就需要开拓一块内存来使用,每块内存都需要一个地址来维护,因此,只要是变量就会有地址,而指针变量也是一种变量,所以指针变量也有地址,那么一个一级指针变量的地址应该存放在一个什么样的指针类型变量里呢?答案就是二级指针
int a = 10;
int *pa = &a;
int* *ppa = &pa;
理顺一下逻辑:ppa存放的是“变量pa的地址”,pa存放的是“变量a的地址”
8、“指针数组”和“数组指针”的区别
(1)指针数组
顾名思义,是一个数组,每个数组元素是“一个指针变量”
#include <stdio.h>
int main()
{
int a = 1;
int b = 9;
int c = 4;
int d = 6;
int* arr[4] = { &a, &b, &c, &d };
for (int i = 0; i < 4; i++)
{
printf("%p\n", arr[i]);
}
return 0;
}
(2)数组指针
顾名思义,是一个指针,这个指针存放的是“整个数组的地址”
#include <stdio.h>
int main()
{
int arr[3] = { 6, 2, 3 };//实际上,任何一个变量,只要删去变量名,剩下的就是变量的类型,因此这个数组的类型就是int[3]
int(*p1)[3] = &arr;//&arr是取出整个数组的地址
printf("%p\n%p\n", p1, p1 + 1);
int(*p2)[3] = (p1 + 1);
printf("%zd", (p2) - (p1));//得到int()[3]元素有1个
return 0;
}