1.什么是指针?
指针其实与电脑内存有关,所以在讲指针前应该讲讲电脑的内存。实际上,为了提高内存的利用率,电脑把一大块的内存依次划分为一个个基本的内存单元,而每一块内存单元的大小就是1个字节。这些内存单元可以存放电脑数据,同时每一个内存单元都有自己的编号,而这个编号也就是常说的地址。我们知道,在日常生活中通过房门号可以找到相应的房间。在电脑中也一样,我们可以通过地址找到相应的内存单元。既然地址如此重要,那么我们就想着创建一个变量来储存这个地址,而所创建的变量就是指针变量了,我们通常直接称它为指针。下面是一个例子:
int main()
{
int a=10;//由于我们创建了一个整形变量(占内存4个字节),需要向电脑申请四个内存单元组成的内存空间
//假设该内存空间的地址编码为0x11223344
//此时我们需要一个指针变量将该地址编码存储起来
int*p=&a;
//&是取地址操作符,&a即取a的地址,其数值为0x11223344
//*p表示p是一个指针变量,int表示p所指向的类型为整形
return 0;
}
2.指针类型
指针类型有很多,笔者作为初学者只列举几种最简单的类型。
1.int*p; //把声明指针的名称p去掉,该指针的指针类型就是int*
2char*pa;//同理把pa去掉,该指针的指针类型就是char*
3int**paa//同理把paa去掉,该指针的指针类型就是int**
//指针类型可以理解为去掉指针名称所剩下的声明部分
3.指针所指向的类型
作为初学者,此处只简单讲讲几句。其实,指针所指向的类型就是指针声明去掉指针名称及其左边第一的*所剩下的部分,以下是举例:
1.int*p;//去掉*p,指针声明剩下int,说明该指针所指向的类型是int,是整形;
2.char*p;//去掉*p,剩下char,说明该指针所指向的类型是char,是字符型;
3.int**p;//去掉*p,剩下int*,说明该指针所指向的类型是int*,依旧是个指针类型;
4.野指针
野指针是指指向不明的指针,其所指向的地址与解引用访问都是随机的。由于野指针对程序有危害性,所以应该尽力回避。产生野指针的原因有很多,以下分三种;
//没有初始化
int main()
{
int*p;
//这种情况下,p右边没有具体赋值,没有初始化,因此p是野指针
//p访问的地址是随机的,一般情况下编译器会报错
return 0;
}
//越界访问
int main()
{
int arr[10] = {0};
int*p = arr;
int i = 0;
for (i = 0; i <= 10; i++)
{
*(p++)= i;
}
return 0;
}
//在这种情况下,编译器会出现报错。
//因为当i=10时,p已经时srr数组中最后一个元素之外的地址,*p属于越界访问数组之外的地址
//指针所指向的空间释放
int*test()
{
//程序执行进入test()函数后,系统为变量a开辟空间,同时a是临时变量
int a = 10;
//a的地址也是临时地址,程序执行离开test()函数后会自动销毁
return &a;
}
//所以test()的返回值是一个已经销毁的地址
int main()
{
int*p = test();
printf("%d", *p);
//此时p去访问一个被销毁的地址,如果这个地址已经重新分配,编译器会自动报错
//这就是因临时变量的地址被销毁而产生的野指针
return 0;
}
5.指针的空置
当我们不再想用某个指针时,我们可以把它赋值为空指针。
int main()
{
int a=10;
int*p=&a;
p=NULL;
//当我们不再想使用某个指针时,可以将它改为p=NULL
*p=20;
//此时若再次访问p时,编译器会报错,说明p已被设置为空指针
return 0;
}
6.指针与整数的加减运算
指针是地址,而地址是以编码表示的,编码是一串二进制数或十六进制数,可见指针是有相应的整数数值的,因而可以与整数进行加减运算
int main()
{
int a = 0;
int*p = &a;
int**pa = &p;
printf("%d\n", sizeof(int));//4
printf("%d\n", sizeof(int*));//4
printf("%p\n", p);// 0019FA20
printf("%p\n", p + 1);//0019FA24
printf("%p\n", pa);//0019FA14
printf("%p\n", pa + 1);//0019FA18
char b = 'c';
char*ph = &b;
printf("%d\n", sizeof(char));//1
printf("%p\n", ph);//00D1F877
printf("%p\n", ph + 1);//00D1F878
return 0;
//由上面数值可见,指针的整数运算后的值等于原地址加上或减去n*sizeof(指针所指向的类型)
//其中n是指针所加或减的整数
}
7.指针间的减法运算
两个所指向类型相同的指针可以进行减法运算
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sz = &arr[9] - &arr[0];
int i = 0;
for (i = 0; i <= 9; i++)
{
printf("&arr[%d]=%p\n", i, &arr[i]);
}
printf("%d\n", sz);//9
return 0;
}
//指针间的减法运算的结果等于指针1的地址减去指针2的地址的值除以sizeof(指针所指向的类型)
8.指针的关系运算
int main()
{
int arr[10] = {0};
int i = 0;
int*vs;
for (vs = &arr[9]; vs >= &arr[0]; vs--)
{
*vs = 1;
}
//arr数组的值为 1 1 1 1 1 1 1 1 1 1
//可见指针间可以进行大小关系比较
return 0;
}
9.指针与数组
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int*p = arr;
int*p2 = &arr;
int*p3 = arr + 1;
int*p4 = &arr + 1;
printf("%p\n", p); //012FFC5C
printf("%p\n", p2);// 012FFC5C
printf("%p\n", p3); //012FFC60
printf("%p\n", p4); //012FFC84
//可见arr单独表示时是arr数组首元素的地址,arr前面加&表示的是整个数组(sizeof(数组名),此时数组名也代表整个数组)
//其他情况,arr都代表数组首元素的地址
//arr+1表示数组首元素后第一的元素的地址
//&arr+1表示数组后面第一个内存单元的地址
//由于arr数组中有10个整形元素,所以&arr+1跳过40个字节
return 0;
}
10.二级指针
int main()
{
int a = 0;
int*pa = &a;
int**paa = &pa;
//二级指针指所指向的类型为一级指针的指针
//由于指针是变量所以系统也会开辟空间存储这个变量,而这个变量的赋值就a的地址
return 0;
}
11.指针数组
指针数组不言而喻就是数组,只不过这些数组的内容是指针
int main()
{
int a = 10;
int b = 20;
int*arr[] = { &a, &b };
printf("%p %p", arr[0], arr[1]);
//008FFB40 008FFB34
//创建指针数组的方式是在数组名前加指针类型
//数组内容是地址
return 0;
}