1. 指针是什么?
指针是什么要理解两个要点:
- 指针是内存中一个最小单元的编号,也就是地址。
- 平时所说的指针,通常是指指针变量,是用来存放内存地址的变量。
总结:指针就是地址,平常说的指针通常指的是指针变量。
平时所说的内存8个g16个g,那么这些内存是怎么来管理的?
一个小格子就是一个内存单元,再把每个内存单元编号,这个编号就是本内存单元的地址,地址也就是指针。
指针就是地址,地址就是编号,指针就是内存单元的编号。
变量地址存储、指针变量等等关于指针和指针类型的基本介绍都放在这里:指针等基本介绍
这里不再过多次阐述。
2. 指针和指针类型
在相同环境下,不同的类型的指针变量大小都是四个或八个字节,那么区别在哪?
//指针类型的关系
int main()
{
int a = 0x11223344;
//十六进制数11代表八个比特位也就是一个字节
//11223344正好是整形四个字节
int* pa = &a;
*pa = 0;
return 0;
}
调试观察:
此时指针变量pa就等同于a,这时执行最后一步,解引用找到a把值改掉:
对上面代码做一些小小的修改:
//指针类型的关系
int main()
{
int a = 0x11223344;
char* pa = (int)&a;
//本应该是int*的指针,强转成char*会有什么不同
*pa = 0;
return 0;
}
char*的指针一样可以存的下整形a的地址,但是当pa解引用修改a的时候会怎样?
这里解引用只修改了第一个字节的地址,这是因为char类型指针,解引用只能访问一个字节,int类型的指针一次解引用可以访问四个字节,所以指针类型是有意义的。
结论:指针类型决定了指针解引用时可以访问几个字节,如果是int类型指针可以访问四个字节,而char类型则是1个,推而广之,每个类型的指针解引用都可以访问对应的字节数。
指针类型还有其他作用:
//指针类型的关系
int main()
{
int a = 0x11223344;
int* pa= &a;
char* pb = &a;
printf("%p\n", pa);
printf("%p\n", pb);
return 0;
}
都是取出a的地址并打印:
如果分别给不同指针指针类型地址加一,结果会变成什么:
结论二:指针类型决定了指针±1操作的时候,跳过几个字节,也可以理解为指针的步长。
根据不同的情况来选择不同的指针类型,使得指针的使用方便有效,这就是指针类型的意义。
3. 野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
有下面几个原因:
- 指针未初始化
int main()
{
int* p;
//p没有初始化就意味着没有明确的指向
//局部变量未初始化放的是随机值
*p = 10;
//非法访问内存了 ,这里p就是野指针
return 0;
- 指针越界访问
int main()
{
int arr[10] = { 0 };
int* p = arr;//&arr[0]
for (int i = 0; i <= 10; ++i)
{
*p = i;
//把i的值依次赋给数组当前的元素
p++;
//每次p++访问下一个元素
//当i=10的时候,其实数组每个元素已经赋值了
//但是还是要执行并且解引用
//这时越界了,指向的位置就是未知
//也就造成了野指针
}
return 0;
}
- 指针指向的空间释放
int* test()
{
int a = 0;
//进入函数,局部变量创建a
return &a;
//返回a的地址
//但是局部变量的作用域是在本函数内部
//所以出了函数作用域就销毁了
//此变量也不能在被使用
}
int main()
{
int* p = test();
//但是p依然保存着a的地址
//所以p可以找到它的空间,但是不能访问
printf("%d", *p);
return 0;
}
以上原因都会导致程序出现问题。
3.1 如何避免野指针的出现
- 明确地给指针初始化
int main()
{
int a = 0;
int* p = &a;
//先给指针初始化一个明确的值
return 0;
}
- 赋值为空指针
int main()
{
int* p = NULL;//NULL -> 0
//当不知道该给指针初始化什么
//可以赋NULL空指针
return 0;
}
- 指针初始化
- 小心指针越界
- 指针指向空间释放即使置NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性
4. 指针运算
- 指针± 整数
#define VALUES 5
int main()
{
int arr[VALUES];
int* vp;
for (vp = &arr[0]; vp < &arr[VALUES];)
{
*vp++ = 0;
//*vp = 0;
//vp++;
}
return 0;
}
数组的地址是由低到高,最后vp所指向的虽然不在本数组内部,但是没有操作就不算越界。(就看看)
指针赋值:
int main()
{
int arr[10] = { 0 };
int* p = arr;
for (int i = 0; i < 10; ++i)
{
*(p+i) = 1;
//*p++ = 1;
}
return 0;
}
- 指针-指针
int main()
{
int arr[10] = { 0 };
printf("%d\n", arr[9] - arr[0]);//9
return 0;
}
|指针 - 指针|得到指针之间的元素个数。必须是指向同一块空间的指针才能相减。
模拟实现strlen的第三种方法:
int slen(char* s)
{
char* ret = s;
while (*s != '\0')
{
s++;
}
return s - ret;
}
int main()
{
char s[] = "abcde";
printf("%d\n", slen(s));
return 0;
}
- 指针的关系运算
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
代码简化, 这将代码修改如下:
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
5. 指针和数组
数组:一组相同类型元素的集合。
指针:是变量,存放的是地址。
int main()
{
int arr[10] = { 0 };
//arr是首元素地址
int* p = arr;
//可以通过指针来访问数组
//这就是他们的联系
return 0;
}
6. 二级指针
前面所接触到的都是一级指针变量:
int main()
{
int a = 10;
int* pa = &a;
*pa = 20;
//这里存放a的地址,解引用一次就找到a了
//所以是一级指针
return 0;
}
既然指针变量pa和a一样都是整形变量,那么pa在内存中一定也占用空间,那是否可以取地址&pa再存放到另一个指针变量里?
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
//ppa是二级指针变量
printf("%d\n", **ppa = 20);
//需要解引用两次才能找到a
return 0;
}
这就是二级指针了。
二级指针的含义:
二级指针变量是用来存放一级指针变量的地址。
7. 指针数组
存放指针的数组就是指针数组。
int main()
{
int a = 1;
int b = 2;
int c = 3;
int* par[3] = { &a,&b,&c };
//存放一组元素地址的数组
//就是指针数组
return 0;
}
可以用指针数组来实现二维数组:
int main()
{
int arr[] = { 1,2,3 };
int ar[] = { 2,3,4 };
int a[] = { 3,4,5 };
//数组名是首元素地址,放到指针数组
int* parr[] = { arr, ar, a };
//打印每一个数组
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; ++j)
{
//打印数组的每个元素
printf("%d ", parr[i][j]);
//printf("%d ", *(*(parr+i)+j));
}
printf("\n");
}
return 0;
}
以上就是初阶指针的介绍。
下期:结构体