目录
简单的来说,指针就是地址。我们口头上说的指针其实指的是指针变量。
指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。
1.5.2这就有点像程序创建了一个很大的对象,但在函数调用的时候,一般不会把这么大的一个对象作为参数传递,因为效率太低了,我们一般会传递对象的地址。
1.5.3如果纸条被弄丢了被遗忘了,那么葡萄还会一直存放着占用地方,这叫内存泄漏
1.5.4万一葡萄已经被提走了,你拿着纸条再出提货,有可能发生严重异常,这样纸条就是所谓的野指针
野指针顾名思义,指针指向的位置不可知,就像没有主人的流浪狗。
1.小心越界2.及时把指针赋成空指针3.避免返回局部变量的地址4.使用指针前检查有效性
定义:存放指针的数组(本质上是数组)(int* arr[])。我们知道有整型类型的数组int arr[],还有字符类型的数组char arr[],指针数组就是指针类型的数组。
定义: 指向数组的指针(本质上是指针)int (*)[ ]。应用:遍历整个二维数组
1.什么是指针,如何去理解指针呢?
概念
简单的来说,指针就是地址。我们口头上说的指针其实指的是指针变量。
指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。
想搞懂指针,需要了解一下四个方面
1.1指针的类型
1.指针的类型
从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。
这是指针本身所具有的类型。让我们看看例一中各个指针的类型:
(1)int*ptr;//指针的类型是int*
(2)char*ptr;//指针的类型是char*
(3)int**ptr;//指针的类型是int**
(4)int(*ptr)[3];//指针的类型是int(*)[3]
(5)int*(*ptr)[4];//指针的类型是int*(*)[4]
1.2指针所指向的类型
2.指针所指向的类型
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:
(1)int*ptr; //指针所指向的类型是int
(2)char*ptr; //指针所指向的的类型是char
(3)int**ptr; //指针所指向的的类型是int*
(4)int(*ptr)[3]; //指针所指向的的类型是int()[3]
(5)int*(*ptr)[4]; //指针所指向的的类型是int*()[4]
在指针的算术运算中,指针所指向的类型有很大的作用。"指针的类型"(即指针本身的类型)和(指针所指向的类型)是两个概念.
当你对C 越来越熟悉时,你会发现,把与指针搅和在一起的"类型"这个概念分成"指针的类型"和"指针所指向的类型"两个概念.
是精通指针的关键点之一。
1.3指针的值或者说所指向的内存区
3.指针的值----或者叫"指针所指向的内存区"或"地址"
(指针的值)是"指针本身存储的数值",这个值将被编译器当作一个地址,而不是一个一般的数值.在32位程序里,
所有类型的指针的值都是一个32 位整数,因为32 位程序里内存地址全都是32 位长。
指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为 sizeof (指针所指向的类型)的一片内存区。
以后,1我们说一个指针的值是XX,1就相当于说该指针指向了以XX为首地址的一片内存区域;
我们说一个"指针指向了某块内存区域",就相当于说"该指针的值是这块内存区域的首地址。"
指针所"指向的内存区"和"指针所指向的类型"是两个完全不同的概念。
在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,
或者说是无意义的。"不进行初始化的指针成为野指针,可能会段错误或者各种错误,谨记"
以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?"重点注意"
1.4指针所在的地址
4.指针本身所占据的内存区
指针本身占了多大的内存?你只要用函数 sizeof (指针的类型)测一下就知道了。
在32 位平台里,指针本身占据了4 个字节的长度。
我的电脑是64为的,指针本身就占据了八个字节。
#include <stdio.h>
int main(){
int *p;
printf("%d",sizeof(p));
return 0;
}
1.5对指针的理解
1.5.1去取快递,提别人给你寄的一百斤葡萄,工作人员递给你一张纸条给你,纸条上写的是葡萄的存放地址,你根据地址找到了葡萄。那么这张纸条就是所谓的指针,为啥工作人员给你字条而不直接给你葡萄,因为就像菜鸟驿站一样,快递太多,太大,所以他直接选择个你一个地址。
1.5.2这就有点像程序创建了一个很大的对象,但在函数调用的时候,一般不会把这么大的一个对象作为参数传递,因为效率太低了,我们一般会传递对象的地址。
1.5.3如果纸条被弄丢了被遗忘了,那么葡萄还会一直存放着占用地方,这叫内存泄漏
1.5.4万一葡萄已经被提走了,你拿着纸条再出提货,有可能发生严重异常,这样纸条就是所谓的野指针
1.6指针类型的作用
上面涉及到指针的大小与指针类型无关,那么指针类型的作用是什么呢?
当一个int* 的指针+1,跳过4个字节;当一个char* 的指针+1,跳过1个字节。
同理,对于(指针+-整数)类的题也是如上的原理。
如图,当int *的指针偏移量加1,int类型的数组也偏移了一位,char* 的则需要偏移四个字节才可以
2.野指针
野指针顾名思义,指针指向的位置不可知,就像没有主人的流浪狗。
2.1野指针产生的原因
指针未被初始化
指针越界访问
指针指向的空间释放
对于前两点比较好理解,下面对第三点进行解释:
int* test( )
{
int a = 5;
return &a;
}
int main()
{
int* p = test(); //test函数返回的是变量a的地址,但函数执行完后被释放
*p = 10;
return 0;
}
变量a的地址只在test()函数内有效,当把a的地址传给指针p时,因为出了test函数,变量a的空间地址释放,导致p变成了野指针,如果想要正确操作,将变量a设为全局变量即可。
2.2 如何规避野指针
1.小心越界
2.及时把指针赋成空指针
3.避免返回局部变量的地址
4.使用指针前检查有效性
3.指针运算
3.1指针+-整数
此部分内容跟指针类型那部分一致
4. 二级指针
二级指针就是存放指针地址的指针变量。就像有三个抽屉,第一个抽屉的钥匙放在第二个抽屉,第二个抽屉的钥匙放在第三个抽屉。
例如:int a=10;int* p1=&a;int** p2=&p1;
对int* * 做解释:第一个*号代表指针指向的类型是int*的,第二个*代表这个是指针。
5. 数组名
数组名大家都很熟悉,但要区分以下几种情况:
sizeof(arr):表示的是整个数组的大小。
&arr: 表示整个数组,取出的是整个数组的大小。
其他情况数组名都表示数组首元素地址。
6.指针数组和数组指针
数组名就是地址
二维数组的本质还是数组,可以理解为数组中的数组,例如;int arr[3][4]={{1,2},{},{}}; arr是父数组的名,父数组包括arr[0],arr[1],arr[2],而arr[0],arr[1]又是子数组的名,是一维数组。
那arr+1地址偏移了多少,arr[0]+1呢?
解:arr是父数组的名,也就是整个地址的首地址,当父数组发生偏移的时候,偏移的是整个数组,而arr[0]+1的话,它是个子数组,当他发生偏移的时候,u/..偏移的是一个元素,也就是一个数据结构的长度。
6.1指针数组
定义:存放指针的数组(本质上是数组)(int* arr[])。我们知道有整型类型的数组int arr[],还有字符类型的数组char arr[],指针数组就是指针类型的数组。
int main()
{
int a = 0;
int b = 1;
int* p1 = &a;
int* p2 = &b;
int* arr[] = { p1,p2 };//指针数组就是存放指针变量的数组
int* arr[] = { &a,&b };//指针数组
return 0;
}
*6.2数组指针
定义: 指向数组的指针(本质上是指针)int (*)[ ]。
应用:遍历整个二维数组
/************************
*函数名:my_print
* 参数:int(*p)[5]数组指针
************************/
void my_print(int(*p)[5], int x, int y)
{
int i = 0;
for (i = 0; i < x; i++)
{
int j = 0;
for (j = 0; j < y; j++)
{
printf("%d ", *(*(p + i) + j));
//printf("%d ", p[i][j]);相似的写法
}
printf("\n");
}
}
int main()
{
int arr1[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
my_print(arr1, 3, 5);
return 0;
}
6.3举例区别含义
int arr[5] //名为arr的数组,数组里有5个元素,每个元素是int
int* p1[5] //指针数组,数组里有5个元素,每个元素是int*
int (*p2)[5] //数组指针,一个指向数组(里面有五个元素,每个元素是int)的指针
int (*p3[5])[5] //p3[ 5]是一个有5个元素的数组,数组里每个元素是int(*)[5]
7.数组参数和指针参数
7.1数组参数
一维数组传参: int arr[10](形参部分数组大小可以不写)、int* arr
二维数组传参: int arr[3][5]、int arr[][5](行可以省略,列不能)、int (*arr)[5] 不能直接传数组名,二维数组的首地址是第一行元素的首地址
7.2指针传参
一级指针传参:int* p
二级指针传参:int** p
8.函数指针
1.定义:指向函数的指针int (*pf) (int,int),pf函数返回的类型是int (*)(int,int)
2.对于函数来说如:Add和&Add,意义和值都一样。这一定要区别于数组。
3.特别的,当要调用函数时,定义一个pf的函数指针指向函数。那么int ret=(*pf)(2,3)和int ret=pf (2,3)等价。
**对下面代码的理解:
(*(void(*)())0)() // 将0处强制类型转换为函数指针类型,再对0地址 进行调用
void(*signal(int,void(*)(int)))(int) //这是函数的声明,singal是一个函数,传进去两个 参数的类型是int和void(*)(int),signal函数的返回值 类型是void(*)(int)。
9.函数指针数组
定义:存放函数指针类型元素的数组
应用:实现计算器
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void menu()
{
printf("****************\n");
printf("* 1.Add 2.Sub *\n");
printf("* 3.Mul 4.Div *\n");
printf("*** 0.exit ***\n");
printf("****************\n");
}
int main()
{
menu();
int input = 0;
printf("请选择:");
scanf("%d",&input);
int ret = 0;
//转移表
int(*pfarr[])(int, int) = { 0,Add,Sub,Mul,Div };//存放函数指针类型元素的数组
do
{
if (input == 0)
{
printf("退出\n");
break;
}
else if (input >= 1 && input <= 4)
{
int x = 0;
int y = 0;
printf("请输入两个操作数:");
scanf("%d%d",&x,&y);
ret = pfarr[input](x,y);
printf("结果是%d\n",ret);
break;
}
else
{
printf("选择错误!");
}
} while (input);
return 0;
}
9.1指向函数指针数组的指针
1.定义:int (*(*parr)[4])(int int)=&parr
*10.回调函数
qsort()函数: 是一个库函数,基于快速排序算法实现的一个排序的函数。优点是,任意类型的数据都能排序。
qsort()函数的形参定义:
void qsort(void* base(起始地址),size_t num(元素个数),size_t width(一个元素的字节长度),int (*cmp)(const void* e1,const void* e2)(自定义比较函数))qsort()函数应用:模拟计算器,排序结构体