目录
指针是什么?
1. 指针是内存中一个最小单元的编号,也就是地址
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量
指针变量
我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个
变量就是指针变量
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;//a变量占用4个字节,指针存储的是4个字节中首字节的地址
*p = 20;
return 0;
}
总结:
1、指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)
2、一个小的单元到底是多大?(1个字节)
3、如何编址?经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的指针是用4、来存放地址的,地址是唯一标示一块地址空间的。
5、指针的大小在32位平台是4个字节,在64位平台是8个字节。
指针和指针类型:
不同的指针拥有不同的类型
#include<stdio.h>
int main()
{
char* pc = NULL;//char*代表着存放char变量类型的指针
int* pi = NULL;//int*代表存放int*变量类型的指针
short* ps = NULL;
long* pl = NULL;
float* pf = NULL;
double* pd = NULL;
return 0;
}
指针+-整数
#include <stdio.h>
//演示实例
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}
打印出来的结果为
由此我们得知:指针的类型决定了指针向前或者向后走一步有多大(距离)
指针的解引用
我们来看这样一串代码
#include <stdio.h>
//1、指针类型决定了:指针解引用的权限有多大
//2、指针类型决定了,指针走一步,能走多远
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
char* pa = arr;
printf("%p\n", p);
printf("%p\n", p + 1);
printf("%p\n", pa);
printf("%p\n", pa + 1);
return 0;
}
打印出来的结果
我们发现+1后,int类型的指针地址+4,而char类型的指针地址只+1
总结:
1、指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节
2、如果你想要一个一个字节访问,可以使用char*指针
野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针成因
1、指针未初始化
#include <stdio.h>
int main()
{
int* p;//局部变量指针未初始化,默认为随机值
*p = 10;
return 0;
}
因为我们没有将指针初始化,所以指针指向一个随机的地址,形成了野指针
2、 指针越界访问
#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;
}
return 0;
}
当我们运行时候
3、指针指向的空间释放
#include <stdio.h>
int* tqm()
{
int a = 10;//当进入函数的时候,a创建
return &a;//返回&a
}
int main()
{
int* p = tqm();//当我们返回之后,a的生命周期截止
*p = 20;//当我们访问p的之后,就是非法访问了,因为我们没有a这块地址了
return 0;
}
程序会出现警告,虽然可以继续运行,但是属于非法访问
如何规避野指针
1、指针初始化
当我们不知道初始化成什么的时候,我们先初始化成空指针(NULL)
当我们使用空指针的时候
#include <stdio.h>
int main()
{
int* p = NULL;
*p = 10;
return 0;
}
程序就会出错
2、小心指针越界
3、 指针指向空间释放即使置NULL
4、避免返回局部变量的地址
5、指针使用之前检查有效性
指针运算
指针 + - 整数
指针 - 指针
指针的关系运算
指针 - 指针(指针+指针没有意义,就想日期减日期有意义,但是日期加日期没有意义一样)
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", &arr[9] - &arr[0]);//数组名相当于指针变量
return 0;
}
结果为9
这说明了指针-指针得到的是元素个数的绝对值
当我们用char类型指针与int类型指针相减
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
char ch[3];
printf("%d\n", &arr[9] - &ch[0]);//数组名相当于指针变量
return 0;
}
会出现警告,这说明指针减指针的时候,两个指针类型要相同
指针的关系运算
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
for (i=&arr[9];i>&arr[0];i--)
{
*arr = 0;
}
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;
发现数组名和数组首元素的地址是一样的
结论:数组名表示的是数组首元素的地址(两种情况除外)
1、sizeof(数组名)-数组名表示整个数组,单位是字节
2、&数组名,数组名表示整个数组,取出来的是整个数组的地址
所以我们可以将数组当成地址存放在一个指针中,指针的类型为数组的类型
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
int* p = arr; //指针存放数组首元素的地址
for (int i = 0; i < 10; i++)
{
printf("%d ", *(p + i));//打印结果为1,2,3,4,5,6,7,8,9,0
}
return 0;
}
于是我们可以通过指针p来访问arr中的每个元素
我们来扩展一个知识
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
int* p = arr; //指针存放数组首元素的地址
for (int i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
//arr[2] == *(p+2) == *(2+p) == *(2+arr) == 2[arr]
//在计算机内部arr[2]相当于*(arr+2),所以2[arr]在计算机内部也是*(arr+2),结果是一样的
return 0;
}
二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
#include <stdio.h>
int main()
{
int a = 0;//创建变量
int* p = &a;//创建指向a的指针
int** ppa = &p;//创建指向指针p的指针,要用**(两颗星),创建二级指针
return 0;
}
对于二级指针的运算有:
*ppa等于访问p的地址,**ppa相当于访问a的地址
指针数组
指针数组是指针还是数组?
答案:是数组。是存放指针的数组
int* arr3[5];
就是一个指针数组,存储的是类型为int的指针