存放地址的变量称为指针变量
- 指针就是变量
- 指针的大小都是相同,指针是用来存放地址的,那么指针的大小取决于地址的大小.那么 32位–4byte,63位–8byte
- 指针的类型决定了:指针解引用的权限有多大(可以操作多少个字节)
- 指针的类型决定了:指针走一步,能走多远(步长)
指针初阶
野指针
指针指向的位置是不可知的.
- 未初始化产生野指针
- 指针越界访问
- 指针指向的空间释放
int* test()
{
int a = 10;
return &a;
}
如何防止野指针
4. 指针初始化.
5. 小心指针越界.
6. 指针指向的空间释放后及时设置为NULL
.
7. 指针使用之前检测其有效性.
指针运算
- 指针±整数
- 指针 - 指针(指向一个数组,间隔几个元素)
- 指针的关系运算(比较大小)
for(vp = &arr[size-1];vp>=&arr[0];vp--);
|| 实际上在绝大部分编译器上是可以运行成功的,但是我们应该避免这样的写法,因为标准并不保证他可行||
标注规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较.
指针和数组
- 数组名是首元素的地址
[]
是一个运算符,符合交换律: arr[2] === 2[arr]
int a[10] = {0};
cout<<a[1]<<" "<<1[a];
- 对于二级指针的理解
int *p;
int * *w = &p;
// 靠近我的*表面w是一个指针,而&p是int *类型
指针进阶
字符指针
char *p = "Hello World!"; //将首字符的地址放在p中
*p = '1'; //erro,*p存放的是常量字符串
上面会有警告:
const char* str11 = "12";
const char* str22 = "12";
char str1[] = "12";
char str2[] = "12";
char* str11 = "12";
char* str22 = "12";
if(str1 == str2){
cout<<" ==1 "<<endl;
}
if(str11 == str22){
cout<<" ==2 "<<endl; ///" ==2 "
}
数组指针
- 是一种指针,一种指向数组的指针
- &arr:表示数组的地址,arr表示数组首元素的地址
int *p1 = arr;
int (*p2)[10] = &arr;
// arr和&arr的值一样但是类型不一样,
//p2是一个数组指针
&arr
表示的是数组的地址而不是数组首元素的地址.数组的地址+1,跳过整个数组的大小.虽然&arr和arr的值是一样的.但是类型不同.就类似于:'a'
和97
,虽然值相同,但是类型不同,一个是int一个是char- 数组名是首元素的地址,但是有两个例外
- sizeof(数组名)-数组名表示整个数组,计算的是整个数组的大小
- &数组名-数组名表示整个数组,取出的是整个数组的地址.
其余的数组名都表示数组首元素的地址
理解:指向数组的地址为什么要用 int (*p)[10];
int a ;
int *p = &a;
int arr[10];
int (*parr)[10] = &arr;
a是一个int型,指向a的指针做加法应该走的步长是int大小的步长,而arr是一个数组,指向arr的指针做加法应该走的步长应该是10个int大小的步长也就是int[1o]大小.
int arr[10] = {0};
int (*p)[10] = &arr;
//int *p1 = &arr; //error: cannnot convert 'int (*)[10] to int '
int *p1 = arr;
cout<<p[9]<<endl;
cout<<p[0][9]<<endl; //数组指针用在一维指针非常尴尬 吧
- 二维数组
int arr[3][2];
int (*p)[2] = arr; //arr表示数组首元素的地址,而首元素的地址是第一行
注意二维数组arr是首元素的地址,而首元素是一个数组.
所以如何书写?
首先是一个指针 *p,指向的元素是一个数组 int[2],最后 int (*p)[2]
练习:
- 碰见既有
*
又有[]
时,首先确定他是数组还是指针,判断方法:变量名首先和谁结合,就是什么,先和*
结合就是指针,先和[]
结合就是数组
int a[10]; //a先和[]结合,表面他是一个数组,剩下的int就是数组每一个元素的类型:整型
int *a[10]; //a先和[]结合表明它是一个数组,剩下的int *是数组每一个元素的类型:指针
int (*a)[10]; //a先和*结合表明它是一个指针,剩下的int[10]是其指向的类型:长度为10数组
int (*parr[10])[5]; //parr先和[10]表明他是一个数组,剩下的int(*)[5]是它指向的类型:数组指针
int a[10]; //整型数组,存放整数的数组
int *a[10]; //指针数组,存放指针的数组---> []的优先级高于*
int (*a)[10]; //数组指针,一个指向数组的指针
int (*aprr[10])[5];//数组指针数组,是一个存放数组指针的数组.该数组能够存放10个数组指针,每个数组指针能够指向一个数组,数组5个元素,每个元素int
数组参数,指针参数
void test(int * (*p))
{}
int main()
{
int * arr[10];
test(arr);
}
上面参数的类型如何书写的?
首先参数是一个指针: *p
其二:arr是一个数组,表示数组首元素的地址,arr的类型是int*
最后:参数的类型 int *(*p)
一级指针作为形参
- 可以将一维数组的数组名作为实参
- 可以将一级指针作为实参
二级指针作为形参
- 可以将一级指针数组作为实参
- 可以将二级指针作为实参
- 可以将一级指针的地址作为实参
函数指针
- 指向函数的指针
数组名 != &数组名
函数名 == &函数名
int Add(int x,int y)
{
}
void test(char *str);
int main()
{
int (*pf)(int,int) = &Add;
void (*pf1)(char *) = &test;
//调用
int a = (*pf)(3,5);
int b = pf(3,5);
return 0;
}
(*((void(*)())0))()
(*( (void(*)()) 0))()
将0使用函数指针void(*)()
进行强制类型转化,然后对其进行解引用进行调用:即调用0地址出的函数,该函数无参,返回类型为voidvoid (*signal(int,void(*)(int))
void ( *signal(int,void(*)) (int))
void ( *signal(int,void(*)(int)) )(int);
// 首先确定signa的类型:是指针?是函数? signal首先与()结合所以是函数,
//它的参数有两个:一个是int,一个是函数指针.
//剩下的是它的返回值:函数指针 void(*)(int)
//总的来说:signal是一个函数,有两个参数,返回值是函数指针
函数过于复杂,使用typedef进行重新命名:
typedef void(*pfun_t)(int); //使用pfun_t对void(*)(int)进行重命名
//typedef void(*)(int) pfun_t;但是正规写法 要将pfun_t写入括号中
void (*signal(int,void(*)(int)) ;
pfun_t signal(int,pfun_t);
函数指针数组
- 将函数的地址存到一个数组中,那么这个数组就叫做函数指针数组
int Add(int x,int y)
{
return x+y;
}
int Sub(int x,int y)
{
return x-y;
}
int main()
{
int (*pf1)(int,int) = Add;
int (*pf2)(int,int) = Sub;
int(*pfArr[2])(int,int) = {pf1,pf2};
return 0;
}
int *(*p)[5] = &arr
p先和*结合表明他是一个指针,剩下的int *[5]
是p所指向的元素.
所以p是一个指向指针数组的指针
int * arr[5];
int * (*p)[5] = &arr;
int (*p[4])(int,int);
p首先和[]结合表明他是一个数组,剩下的int(*)(int,int)
是一个函数指针是p这个数组存储的元素的类型.
所以p是一个存储函数指针的数组
int (*p)(int,int);//函数指针
int (*p2[4])(int,int) = {&p};//函数指针的数组
int (*(*p2)[4])(int,int) = &p2; // 指向 函数指针的数组 的指针
回调函数
- 回调函数就是一个通过函数指针调用的函数.
面试题
32位
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a)); //16 这里的a:数组的地址
printf("%d\n",sizeof(a+0)); //4 这里的a+0:第一个元素的地址
printf("%d\n",sizeof(*a)); // 4
printf("%d\n",sizeof(a+1)); // 4
printf("%d\n",sizeof(a[1])); //4
printf("%d\n",sizeof(&a));//4 地址大小
printf("%d\n",sizeof(*&a)); //16 int (*p)[4] = &a; 这里可以这样理解: &与*为反运算
printf("%d\n",sizeof(&a+1)); // 4 &a+1跳过整个数组后的地址
printf("%d\n",sizeof(&a[0])); // 4
printf("%d\n",sizeof(&a[0]+1)); // 4
char a[6];
printf("%d\n",sizeof(a)); // 6
printf("%d\n",sizeof(a+0)); // 4
printf("%d\n",sizeof(*a)); // 1
printf("%d\n",sizeof(a[1])); // 1
printf("%d\n",sizeof(&a));// 4
printf("%d\n",sizeof(&a+1)); // 4
printf("%d\n",sizeof(&a[0]+1)); // 4
char a[] = {'1','2','3','4','5','6'};
printf("%d\n",strlen(a)); // 随机值
printf("%d\n",strlen(a+0)); //随机值
printf("%d\n",strlen(*a)); // error:这里传入的是1的ASCALL吗
printf("%d\n",strlen(a[1])); // error
printf("%d\n",strlen(&a));// 随机值 char (*p)[6] = &a; strlen(p);
printf("%d\n",strlen(&a+1)); // 随机值
printf("%d\n",strlen(&a[0]+1)); // 随机值
char a[] = "123456";
printf("%d\n",sizeof(a)); // 7
printf("%d\n",sizeof(a+0)); //7
printf("%d\n",sizeof(*a)); // 1
printf("%d\n",sizeof(a[1])); // 1
printf("%d\n",sizeof(&a));// 4
printf("%d\n",sizeof(&a+1)); // 4
printf("%d\n",sizeof(&a[0]+1)); // 4 第二个元素的地址
char a[] = "123456";
printf("%d\n",strlen(a)); // 6
printf("%d\n",strlen(a+0)); //6
printf("%d\n",strlen(*a)); // error:传入的是ASCALL值
printf("%d\n",strlen(a[1])); // error
printf("%d\n",strlen(&a));// 6 int (*p)[4];
printf("%d\n",strlen(&a+1)); // 随机值
printf("%d\n",strlen(&a[0]+1)); // 5
printf("%d\n",sizeof(a)); // 4
printf("%d\n",sizeof(a+0)); //4
printf("%d\n",sizeof(*a)); // 1
printf("%d\n",sizeof(a[1])); // 1
printf("%d\n",sizeof(&a));// 4
printf("%d\n",sizeof(&a+1)); // 4
printf("%d\n",sizeof(&a[0]+1)); // 4
char *a = "123456";
printf("%d\n",strlen(a)); // 6
printf("%d\n",strlen(a+1)); //5
printf("%d\n",strlen(*a)); // error:传入的是ASCALL值
printf("%d\n",strlen(a[1])); // error:传入的是ASCALL值
printf("%d\n",strlen(&a));// 随机值, 指针a的地址
printf("%d\n",strlen(&a+1)); // 随机值, 跟在指针a的地址
printf("%d\n",strlen(&a[0]+1)); // 5
int a[3][4] = {};
printf("%d\n",sizeof(a)); //48 整个数组的大小
printf("%d\n",sizeof(a[0][0])); // 4
printf("%d\n",sizeof(a[0])); //16
printf("%d\n",sizeof(a[0] + 1));
//4 a[0]作为第一行数组的数组名没有单独放在sizeof中,也没有取地址,
//所以a[0]就是第一行第一个元素的地址,
//所以a[0]+1就是第一行第二个元素的地址
printf("%d\n",sizeof(*(a[0]+1))); //4
printf("%d\n",sizeof(a+1));//4 a作为数组名并没有单独放在sizeof中也没有取地址,
//所以是表示二维数组的第一个元素:即第一行数组.
//a+1 表示第二行数组的地址
printf("%d\n",sizeof(*(a+1))); // 16 == sizeof(a[1])
printf("%d\n",sizeof(&a[0]+1)); //4 指向第2行元素的第一个位置,所以大小是第二行元素的大小
// == sizeof(a[0] + 1)
printf("%d\n",sizeof(*(&a[0]+1));//16 &a[0]+1就是第二行的地址,解引用之后就是就是第二行
printf("%d\n",sizeof(*a); //16 第一行数组的大小
int a[3][4] = {};
printf("%d\n",sizeof(a[3])); // 16 sizeof并不会计算括号中的值,只是确定类型,然后根据类型来求出大小.
对于一个表达式具有两个属性:值属性和类型属性.
例如: 3+5
1. 值属性为8
2. 类型属性为int
数组名的意义
- sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小
- &数组名,这里的数组表示整个数组,取出的是整个数组的地址
- 除此之外所有的数组名都表示首元素的地址
int a[5] = {1,2,3,4,5};
int *ptr = (int*)(&a+1);// 指向最后一个元素
printf("%d,%d",*(a+1),*(ptr-1));
//2,5