目录
指针
野指针
二级指针
- 字符指针
- 数组指针
- 指针数组
- 数组传参和指针传参
- 函数指针
- 函数指针数组
- 指向函数指针数组的指针
- 回调函数
- 指针和数组面试题解析
10.相关问题
- 指针是用来存放地址的,地址是唯一标识一块地址空间的。
- 指针的大小,或者说是指针存放的地址的大小在32位平台是4个字节,在64位平台是8个字节。
&a的意义
int main()
{
int a=10;//a占4个字节。
int *pa = &a;//拿到的a的4四个字节中第一个字节的地址。
return 0;
}
指针不同类型是有意义的。
int main()
{
char * pa;//+1的化变化一个字节的空间。
int * pb;//+1的话变化4个字节的空间。
float * pd;//+1的话变化4个字节的空间。
return 0;
}
指针类型的意义
- 指针类型决定了:指针解引用的权限有多大。
- 指针类型决定了,指针走一步,能走多远(步长);
不会创建一种通用类型的指针:
访问类型发生了变化,访问权限也发生了变化。
int * pa;
char* pc;
float* pf;
printf("%dn",sizeof((pa));//4
printf("%dn",sizeof((pc));//4
printf("%dn",sizeof((pf));//4
在调试过程中,可以看到窗口内存。
int main()
{
int arr[10] ={0};
int *p = arr;
char *pc = arr;
printf("%pn",p); //004ffc40
printf("%pn",p+1); //004ffc44
printf("%pn",pc); //004ffc40
printf("%pn",pc+1);//004ffc41
return 0;
}
野指针
int *test()
{
int a = 10;//如果a的变量类型是static int类型的话,就可以不报错。
return &a;
}
int main()
{
int *p = test();
*p = 20;//返回的a的地址被销毁了。
return 0;
}//编写错误。
int main()
{
//p就是一个野指针
int* p;//p是一个局部的指针变量,局部变量不初始化的化,是随机值。
//补充:全局变量会自动初始化成0.
*p = 20;//非法访问内存。
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i = 0;i<=10;i++)
{
*p = i;
p++;
}//for循环要循环的次数要超过数组的大小。
return 0;
}
规避野指针
- 指针初始化,置成NULL
- 小心指针越界
- 指针指向空间释放置成NULL
- 指针使用之前检查有效性。
int main()//1.指针初始化
{
int *p = NULL;//方法一置成NULL;
int a = 10;
int * ptr = &a;//方法二:明确初始化的值。
}
int main()
{
//C语言本身不会检查数据的越界行为的。
int *p = NULL;
if( p !=NULL)
*p = 10;
return 0;//?
}
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* p = arr;
int* pend = arr + 9;
while(p<=pend)
{
printf("%dn",*p);
p++;
}
return 0;
//会将数组依次打印出来。
指针的±和指针的关系运算
#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数:指针的关系运算
for(vp = &values[0];vp<&values[N_VALUES];)//指针的关系运算!
{
*vp++ = 0;
}//指针+整数
指针允许数组元素的指针与数组最后一个元素后面的那个内存位置的指针比较,但不允许与指向第一个元素之前的那个内存位置的指针进行比较。
//实现了strlen的逻辑运算。
int my_strlen(char* str)
{
char* start = str;
while(*str != ' ')
{
str++;
}
return str - start;
}
//指针地址相减
//得到数组中中间元素的个数。
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%dn", &arr[9] - &arr[0]);/
int arr[10] = {1,2,3,4,5,6};
int *(*parr)[10] = &arr;//数组指针 - 取出的是数组的地址。
//arr - 数组名是首元素的地址 - arr[0]的地址。
double* d[5]; //存放double*类型的数组。
double* (*pd)[5] = &d;//pd是数组指针。
return 0;
}
int arr[10] = {0};
printf("%pn",arr);//首元素地址
printf("%pn",&arr);//数组地址
//两者的打印的值 相同,表示的意义不同
//就像
char c = 'a'; //ASDII 值 97 -char
int i = 97; // 也是97 -int 类型不同
char c = 97;//使用%c打印的结果也是a。
指针+1
int main()
{
int arr[10]={0};
int *p1 = arr;//指针数组
int (*p2)[10] = &arr;//数组指针
printf("%pn",p1);
printf("%pn",p1+1);//变化4个字节
printf("%pn",p2);
printf("%pn",p2+1);//变化40个字节,十进制
return 0;
}
- 数组指针解引用,再解引用。
int main ()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int (*pa)[10] = &arr;
int i = 0;
for(i = 0;i<10;i++)
{
/
return 0;
}
一维数组传参
一维数组传参,分为 test(int arr[]) | test( * arr) 两种,
一维 数组指针 传参,test(int *arr[ ]) | test(int * *arr)
//一维数组传参
void test(int arr[])
{
printf("%d", arr[0]);
}
void test(int* ptr)
{
printf("%d", *ptr);
}
//一维指针数组传参
void test2(int* arr2[])
{
printf("%d", arr2[0]);
}
void test2(int* *arr)/
{
printf("%d", *arr);//必须通过*arr才能调用,传递的参数。使用for循环也可以打印数组
}
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);
return 0;
}
关键:从括号包裹来看,int *arr[10] 、int (*arr)[10] 。操作符的优先级和结合级
- 二维数组传参
//二维数组传参
void test(int arr[10][10])
{ }
void test(int arr[][10])
{ }
void test(int (*arr)[10])/数组指针充当二维数组的参数
{
//数组指针
//调用
*arr[0] = 10;//yes
}
void test(int **arr)
{
//错误的接受参数。
}
int main()
{
int arr[10][10]= {0};//初始化
test(arr);
return 0;
}
二级指针也是指针,其空间大小和一级指针一样,只是指向的内存层次更深
二级指针,用来保存一级指针的地址
二级指针的大小在32位系统中是4个字节,在64位中是8个字节
指针能比较大小,就是比较地址。
整型指针解引用操作访问4个字节
指针-指针得到指针和指针之间的元素个数
函数指针&(&p) - 不行,&p不是一个变量,&pa就行,pa是一个变量。
指向含函数的指针,
int Add(int x,int y)
{
return x + y;
}
int main()
{
//pf是一个函数指针变量。
//int (*pf)(int,int) = Add;
//int (*)(int,int) ->这是数据类型,函数指针类型
int (*pf)(int,int) = &Add;//Add == pf
int ret = (*pf)(3,5);//1
int ret = pf(3,5);//2
int ret = Add(3,5);//3 三者等价。
return 0;
}
数组名 != &数组名 arr != &arr
函数名 == &函数名 Add ==&Add
相关问题Add 和pf是等价的,不用加 *,加不加*,都一样。
- ((void()( ))0) ( );
(*(void(*)( ))0) ( );
//调用0地址的函数;
//(void(*)()) - 强制类型转换,为函数指针类型。
//将地址0,强制转化为函数指针
// (* (p))( );
//通过*将其转换为函数名。
//
//调用0地址的函数
//该函数无参,返回类型void
//返回类型,这一点不知道,不过,硬要说的话,就是void
void (*signal(int, void(*)(int) ) )(int);
//分成两块看void(* )(int);and another
//signal(),是一个函数,参数两个,一个int(返回类型),一个函数
//一个void(*)(int) ->(返回类型),
//该函数指针,指向一个参数为int,返回类型是void的函数。
//剩下的是强制类型转换。err
//signed函数的返回类型也是一个函数指针。
//该函数指针,指向一个参数为int,返回类型是void的函数。
//signed是一个函数的声明。
//不是函数调用,定义
//还可以写成
typedef void (*pfun_t)(int);
pfun_t signed(int, pfun_t);
//对void(*)(int)的函数zhi'zhen类型重命名为pfun_t.
函数指针数组
int main()
{
int (*pf1)(int,int) = &Add;
int (*pf2)(int,int) = Sub;
int(*pfArr[2])(int,int) = {pf1,Sub};//pfArr就是一个函数指针数组
int (*pfArr) (int,int) = Add;//函数指针
return 0;
}
- 解决一些 代码冗余,使用函数指针数组的方法
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
int x = 0;
int y = 0;
int ret = 0;
if (input >= 1 && input <= 4)
{
printf("请输入两个数字:>n");
scanf("%d %d", &x, &y);
//也叫转移表。
int(*pfArr[5])(int, int) = { NULL,Add,Sub,Mul,Div };//函数指针数组。
ret = (pfArr[input])(x, y);
printf("ret = %dn", ret);
}
else if (input == 0)
{
printf("结束循环n");
break;
}
else
{
printf("输入错误请重新输入n");
}
} while (input);
return 0;
}
指向函数指针数组的指针
函数指针的数组 - 数组
取出函数指针数组的地址
//整型数组
int arr[5];
int* parr =&arr;
//整型指针的数组
int * arr[5]={&a,&b};
int*(*pa)[5] = &arr;
int main()
{
int(*p)(int,int);//函数指针
int(*p2[4])(int,int);//函数指针的数组
int(*(*p3)[4])(int,int) = &p2;//取出的是函数指针数组的地址。
//p3就是一个指向【函数指针的数组】的指针
//结合加减乘除代码
//学习这个主要是为了学习 回调函数
return 0;
}
回调函数int *arr[5];
分为两部分,
int* —— 数组中的元素的数据类型
int* [5] —— arr的数据类型
-
把一个函数的地址,作为参数传递给别的函数调用的函数方法
-
加减乘除代码
void menu()
{
printf("*****************************n");
printf("****** 1.Add 2.Sub *****n");
printf("****** 3.Mul 3.Div *****n");
printf("****** 0.exit *****n");
printf("*****************************n");
}
void Add(int x, int y)
{
return x + y;
}
void Sub(int x, int y)
{
return x - y;
}
void Mul(int x, int y)
{
return x * y;
}
void Div(int x, int y)
{
return x / y;
}
//不知道回调函数的参数怎么写?
//就是函数指针的形式。
void Calc(int (*pf)(int,int))
{
int x = 0;
int y = 0;
scanf("%d %d", &x, &y);
printf("打印的东西n");
printf("%dn", (pf)(x, y));
}
int main()
{
int input = 0;
menu();//打印菜单
do
{
scanf("%d", &input);
//函数指针数组
int (*pf[5]) (int, int) = { NULL,Add,Sub,Mul, Div};
switch (input)
{
case 1:
Calc(Add);
break;
case 2:
Calc(Sub);
break;
case 3:
Calc(Mul);
break;
case 4:
Calc(Div);
break;
case 0:
printf("退出函数n");
break;
default:
printf("请输入正确的数字n");
break;
}
} while (input);
return 0;
}
- void*类型的指针
int main()
{
int a = 0;
char ch = 'w';
void* p = &a;//ok,无具体类型的指针。
p = &ch;//ok
//*p 就报错了,不知到+1后,跳过几个字节。,err
// 是不知道访问几个字节,
//p++ 也不行,因为不知道跳过几个字节。
return 0;
}
- 梳理
int main()
{
int a = 10;
int b = 30;
int* arr[2] = { &a,&b };//指针数组
int* (*pa)[2] = &arr;//指向指针数组的指针
int* (*pb)[2] = arr;//指针
printf("%dn", *(*pa));//地址
printf("%dn", *(*pb));
printf("%dn", (*pa));
printf("%dn", (*pb));
printf("%dn", **(*pa));//10
printf("%dn", **(*pb));//10
printf("%dn", ***pa);//10
printf("%dn", ***pb);//10
printf("%dn", ***pa + 1);
printf("%dn", ***pb + 1);
printf("%dn", *(**pa + 1));
printf("%dn", *(**pb + 1));
printf("%dn", **(*pa + 1));//30
printf("%dn", **(*pb + 1));//30
return 0;
}
指针和数组梳理
| 指针 | 数组 | 传参 |
|---|---|---|
| 一级指针,int* p;char* pc; void* pv; | 一维数组 int arr[5]={0}; | |
| 二级指针 ,int**pa; char **pc; | 二维数组 char arr[5][5] = {0}; | |
| 数组指针,int (*arr)[5]=&arr; | 指针数组 int *arr[5] = {&a,&b,&c}; | |
| 函数指针,void (*pf)(int,int) =Add; | 函数指针数组 void (*pf[5])(int,int) ={NULLAdd,Sub,Mul,Div}; | |
| 指向函数指针数组的指针,void (*(pf2)[5])(int,int)=&pf;//函数指针数组的地址 |
int main()
{
int *p = NULL;//int*类型
int arr[10] = {0};//数组的形式,每个元素是int类型
//
//p = arr;//arr是int类型的,存放到p中可以,但是为什么啊?int的地址放到整型指针里去,
//换一种思路思考,arr表示数组首元素的地址,是可以理解成 int*类型的,放到p整型指针中去就可以了。
//int (*ptr)[10] = &arr;//ptr - 数组指针,存放整个数组的地址没问题
//数组的地址放到,数组指针中去。
//p = &arr[0];
//p = &arr;//err
return 0;
}



