大家好,今天我们来学习指针。
指针
指针是什么呢?
- 指针是内存中一个最小单元的编号,也就是地址
- 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
指针也可以说成是地址,但我们口语中的指针通常是指针变量。
让我们看到这张内存图,我们就应该知道指针变量是我们通过&符号将地址存放在一个变量中,这个变量就称为指针变量。
#include <stdio.h>
int main()
{
int a = 10;//在内存中开辟一块空间
int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
中,p就是一个之指针变量。
return 0;
}
我们这就创建了一个指针变量。那我们要打印a该怎么做呢?
这里指针变量p里存放的是a的地址,我们对p进行解引用那么*p指向的对象就是a=10,所以我们打印 *p那么就可以打印出结果了。
总结:
指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
那么一个小的单元到底是多大呢,是一个字节,我们给一个字节一个地址进行编排对于32根地址线的机器,所有指针类型的值都是一个32位的整数,那么就会有2的32次方个地址,也就是2的32次方个字节,那么我们对它进行运算就可以得出是4GB的内存。这就是32位机器所能编排的内存。
总结:
指针是用来存放地址的,地址是唯一标示一块地址空间的。
指针的大小在32位平台是4个字节,在64位平台是8个字节
指针和指针类型
指针的定义方式是: type + *
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
int main()
{
int a = 0;
int* pa = &a;
char* pc = &a;
printf("pa=%p\n", pa);
printf("pa+1=%p\n", pa+1);
printf("pc=%p\n", pc);
printf("pc+1=%p\n", pc+1);
return 0;
}
我们看到pa和pa+1之间的地址相差四个字节,而pc和pc+1之间的地址相差一个字节,所以int的指针加1跳过四个字节,而 char的指针加1跳过一个字节,还有short的指针加1跳过两个字节,double的指针加1跳过八个字节。
总结:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。扫描二维码关注公众号,回复: 16856500 查看本文章
接下来就是我们要注意的了,如何避免野指针。
野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
指针未初始化就会出现野指针
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
printf("%d\n", *p);
return 0;
}
当我们运行的时候就会发现系统报错了。
还有就是指针越界访问也会导致野指针的出现
#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;
}
printf("%d", *p);
return 0;
}
这里就是因为访问越界了,所以才导致了野指针。
那我们如何有效的规避野指针呢?
1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放即使置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
NULL这个叫做空指针,暂时不明确该指针指向哪个变量,则需要赋予NULL值,这个就是在我们不知道指针指向哪个变量是要运用的。
int *p=NULL;
int *p=0;
我们就用这两种方式来获取空指针。
#include <stdio.h>
int main()
{
int *p = NULL;
//....
int a = 10;
p = &a;
if(p != NULL)
{
*p = 20;
}
printf("%d", a);
return 0;
}
在这里我们发现获取了空指针之后就可以运行了。
指针运算
指针± 整数
指针-指针
指针的关系运算
指针± 整数
int main()
{
int arr[] = {
1,2,3,4,5,6,7,8,9,10 };
// 0 1 2 3 4 5 6 7 8 9
//使用指针打印数组的内容
int * p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
//printf("%d ", *(arr + i));
//printf("%d ", arr[i]);
//printf("%d ", i[arr]);
}
return 0;
}
这里我们通过指针加整数,对*(p+i)进行解引用打印数组,p指向的是数组首元素,p+i是数组中下标为i的元素的地址,起始时跳过了i*sizeof(int)个字节。
指针-指针
int main()
{
int arr[10] = {
0 };
printf("%d\n", &arr[9] - &arr[0]);
printf("%d\n", &arr[0] - &arr[9]);
return 0;
}
注意:
指针-指针的前提:两个指针指向同一块区域,指针类型时相同的
指针-指针差值的绝对值,指针和指针之间的元素个数
指针的关系运算
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
指针和数组
接下来我们看到代码:
#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]);
return 0;
}
可见数组名和数组首元素的地址是一样的。所以我们明白了数组名表示的是数组首元素的地址。
int arr[10] = {
1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址
这样为我们就将数组首元素的地址存入了指针p。
#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(i=0; i<sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i);
}
return 0;
}
这样我们也可以发现p+i 其实计算的是数组 arr 下标为i的地址。
int main()
{
int arr[] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int* p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
这里我们先把数组首元素存入指针中,在通过指针加整数运算,对(p+i)进行解引用就能完整地打印出整个数组了。
二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
这就是 二级指针 。通俗的讲二级指针就是来存放指针变量的。
**ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , ppa 其实访问的就是 pa .
int b = 20;
*ppa = &b;//等价于 pa = &b;
**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;
这里**ppa相对于对pa进行解引用得到*pa=30。
好了今天的分享就到这里,谢谢大家。