目录
之前我们已经知道了指针的概念:
●指针就是个变量,用来存放地址,地址唯一标识一块内存空间
●指针的大小是固定的4/8个字节(32位平台/64位平台)
●指针有类型,指针的类型决定了指针的加减整数的步长,指针解引用操作时的权限
一、字符指针char*
一般用法是指向一个字符:
int main()
{
char ch='w';
char* pc=&ch;
*pc='w';
return 0;
}
还可以指向一个字符串:
int main()
{
char* pstr="hello bit."; //不是把字符串放到了指针里,而是把字符串的首字符h的地址放到了指针里
printf("%s\n",pstr);
printf("%c\n",&pstr); //输出h
return 0;
}
例:
#include<stdio.h>
int main()
{
char str1[]="hello bit.";
char str2[]="hello bit.";
char* str3="hello bit.";
char* str4="hello bit.";
if(str1==str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if(str3==str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
//最后输出的是str1 and str2 are not same和str3 and str4 are same
//str3和str4是同一块内存空间
二、数组指针
1、概念
数组指针是一个指向数组的指针,存放的是数组的地址。
int *p1[10]; //是指针数组
int (*p2)[10];//是数组指针,因为[]的优先级要高于*号,所以必须加上()来保证p先和*结合
2、&数组名和数组
例:
//1
int main()
{
int arr[10]={0};
printf("%p\n",arr); //007FF94C
printf("&p\n",&arr); //007FF94C,表示的是数组的地址,而不是数组首元素的地址
return 0;
}
//2
int main()
{
int arr[10]={0};
int* p1=arr;
int (*p2)[10]=&arr;
printf("%p\n",p1); //00CFFCF8
printf("%p\n",p1+1); //00CFFCFC
printf("&p\n",p2); //00CFFCF8
printf("&p\n",p2+1); //00CFFD20,p2+1比p2多了40,跳过了整个数组的大小
return 0;
}
3、数组指针的使用
一般不会在一维数组上使用。
例:
//1
#include<stdio.h>
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,0};
int (*p)[10]=&arr; //把数组arr的地址赋值给数组指针变量p,但是我们一般很少这样写
return 0;
}
//2
void print(int(*p)[5],int r,int c)
{
int i=0;
int j=0;
for(i=0;i<r;i++)
{
for(j=0;j<c;j++)
{
printf("%d ",*(*(p+i)+j));
}
printf("\n");
}
}
int main()
{
int arr[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
print(arr,3,5);//二维数组的首元素是第一行
return 0;
}
三、指针数组
指针数组是一个存放指针的数组。
int* arr1[10]; //整型指针的数组
char* arr2[4]; //一级字符指针的数组
char** arr3[5]; //二级字符指针的数组
例:
int arr[10]; //整型数组
int *parr1[10]; //整型指针的数组
int (*parr2)[10]; //指向数组的指针,其指向的数组有10个元素,每个元素的类型是int
int (*parr3[10])[5]; //存放数组指针的数组。parr3是一个数组,数组里有10个元素,每个元素是一个有5个整型元素的数组指针
四、数组参数和指针参数
1、一维数组传参
void test(int arr[]) //可以
{}
void test(int arr[10]) //可以
{}
void test(int *arr) //可以
{}
void test(int *arr[20])//可以
{}
void test(int *arr[]) //可以
{}
void test(int **arr) //可以
{}
int main()
{
int arr1[10]={0};
int *arr2[20]={0};
test(arr1);
test(arr2);
return 0;
}
2、二维数组传参
二维数组传参,函数形参的设计只能忽略第一个[]的数字。
void test(int arr[3][5])//可以
{}
void test(int arr[][]) //不可以
{}
void test(int arr[][5]) //可以
{}
void test(int *arr) //不可以,第一行是一个一维数组的地址
{}
void test(int *arr[5]) //不可以,这样的参数是一个数组不是指针
{}
void test(int (*arr)[5])//可以
{}
void test(int **arr) //不可以
{}
int main()
{
int main[3][5]={0};
test(arr);
return 0;
}
3、一级指针传参
当一个函数的参数部分为一级指针时,函数能接受传【一级指针】和【&变量】。
void print(int *ptr,int sz)
{
int i=0;
for(i=0;i<sz;i++)
{
printf("%d\n",*(ptr+i));
}
}
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9};
int *p=arr;
int sz=sizeof(arr)/sizeof(arr[0]);
print(p,sz);
return 0;
}
4、二级指针传参
当一个函数的参数部分为一级指针时,函数能接受传【二级指针】,【&一级指针】,【一级指针的数组,如:arr】
void test(int** ptr)
{
printf("num=%d\n",**ptr);
}
int main()
{
int n=10;
int* p=&n;
int** pp=&p;
test(pp);
test(&p);
return 0;
}
五、函数指针
函数指针是指向函数的指针,可以存放函数地址。
语法:返回类型 (*函数指针名)(函数参数类型,...,函数参数类型)=&函数名
例:
int(*pf)(int,int)=&Add; //pf就是一个函数指针变量 void (*pf)(char*) {...} int ret=(*pf)(3,5); //三种情况相同 int ret=pf(3,5); int ret=Add(3,5);
注意:【数组名】不等于【&数组名】,【函数名】等于【&函数名】
例:
//1
(*(void(*)())0)();
//void(*)()是指函数指针类型,(void(*)())0是将0强制转换为函数指针类型,(*(void(*)())0)是指对地址为0的函数进行解引用操作,后面的()用来传参。
//整句代码的意思是调用0地址处的函数,且该函数无参,返回类型是void。
//2
void (*signal(int,void(*)(int)))(int);
//void(*)(int)是参数为整型的函数指针类型。
//整句代码的意思是一个名为signal函数指针的声明,函数参数为一个整型和一个参数为整型的函数指针,且返回类型是void。
六、函数指针数组
1、概念
函数指针数组是存放函数指针的数组,存放同类型的函数指针(返回类型和参数一致)。
2、定义方法
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)={Add,Sub}; //pfArr就是函数指针数组
}
3、使用方法:以计算器为例
void menu()
{
printf("*************************\n");
printf("******1.add 2.sub******\n");
printf("******3.mul 4.div******\n");
printf("****** 0.exit ******\n");
printf("************************\n");
}
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;
}
int main()
{
int input=0;
do
{
menu();
int x=0;
int y=0;
int ret=0;
printf("请选择:>");
scanf("%d ",&input);
switch(input)
{
case 1:
printf("请输入2个操作数:\n");
scanf("%d %d",&x,&y);
ret=Add(x,y);
printf("ret=%d\n",ret);
break;
case 2:
printf("请输入2个操作数:\n");
scanf("%d %d",&x,&y);
ret=Sub(x,y);
printf("ret=%d\n",ret);
break;
case 3:
printf("请输入2个操作数:\n");
scanf("%d %d",&x,&y);
ret=Mul(x,y);
printf("ret=%d\n",ret);
break;
case 4:
printf("请输入2个操作数:\n");
scanf("%d %d",&x,&y);
ret=Div(x,y);
printf("ret=%d\n",ret);
break;
case 0:
printf("退出程序!\n");
break;
default:
printf("选择错误,请重新选择!\n");
break;
}
}while(input);
return 0;
}
七、指向函数指针数组的指针
int (&p)(int,int); //函数指针
int (*p2[4])(int,int); //函数指针的数组
int (*(*p3)[4])(nt,int); //指向函数指针数组的指针
八、回调函数
回调函数是通过一个函数指针调用的函数。
如果把函数的指针地址作为参数传递给一个函数,当这个指针被用来调用其所指向函数时,这就是回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行相应。
例1:
//可以将上述计算器代码进行优化,将Add、Sub、Mul、Div函数内置在一个Calc函数里
int Calc(int (*pf)(int,int))
{
int x=0;
int y=0;
printf("请输入2个操作数:");
scanf("%d %d",&x,&y);
return pf(x,y);
}
//之后在case里可以直接调用calc函数
case 1:
ret=Calc(Add);
printf("ret=%d\n",ret);
break;
例2:
//1
//冒泡排序实现整型值排序
void bubble_sort(int arr[],int sz)
{
int i=0;
int j=0;
for(i=0;i<=sz-1;i++)
{
for(j=0;j<sz-1-i;j++)
{
if(arr[j]>arr[j+1])
{
int tmp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=tmp;
}
}
}
}
void print_arr(int arr[],int sz)
{
int i=0;
for(i=0;i<sz;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void test1()
{
int arr[10]={9,8,7,6,5,4,3,2,1,0};
int sz=sizeof(arr)/sizeof(arr[10]);
print_arr(arr,sz);
bubble_sort(arr,sz);
print_arr(arr,sz);
}
int main()
{
test1();
return 0;
}
//2
//qsort函数排序结构体数据
//void qsort(void* base,//base是待排序数据中第一个对象的地址
//size_t num,//num是待排序数据元素的个数
//size_t size,//size是待排序数据中一个元素的大小,单位是字节
//int (*cmp)(const void* e1,const void* e1)//是用来比较待排序数据中2个元素的函数);
int cmp_int(const void* e1,const void* e1)
{
return *(int*)e1-*(int*)e2;
}
struct Stu
{
char name[20];
int age;
}
int sort_by_age(const void* e1,const void* e2)
{
return ((struct Stu*)e1)->age-((struct Stu*)e2)->age;
}
int sort_by_name(const void* e1,const void* e2)
{
return strcmp(((struct Stu*)e1)->name,((struct Stu*)e2)->name);
}
void test2()
{
struct Stu s[]={{"zhangsan",30},{"lisi",34},{"wangwu",20}};
//按照年龄排序
int sz=sizeof(s)/sizeof(s[0]);
qsort(s,sz,sizeof(s[0]),sort_by_age);
qsort(s,sz,sizeof(s[0]),sort_by_name);
}
int main()
{
test2();
return 0;
}
//3
//模仿qsort实现一个冒泡排序的通用算法
void Swap(char* buf1,char* buf2,int width)
{
int i=0;
for(i=0;i<width;i++)
{
char tmp=*buf1;
*buf1=*buf2;
*buf2=tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base,int sz,int width,int (*cmp)(const void* e1,const void* e2))
{
int i=0;
int j=0;
for(i=0;i<=sz-1;i++)
{
for(j=0;j<sz-1-i;j++)
{
if(cmp((char*)base+j*width),(char*)base+(j+1)*width)>0)
{
Swap((char*)base+j*width,(char*)base+(j+1)*width,width);
}
}
}
}
void test3()
{
int arr[10]={9,8,7,6,5,4,3,2,1,0};
int sz=sizeof(arr)/sizeof(arr[10]);
bubble_sort(arr,sz,sizof(arr[0]),cmp_int);
print_arr(arr,sz);
}
int main()
{
test3();
return 0;
}
九、指针和数组面试题的解析
例1:
数组名的意义:
●sizeof(数组名):计算的是整个数组的大小
●&数组名:取出的是整个数组的地址
●除此之外,所有的数组名都是数组首元素的地址
int main()
{
//一维数组
int a[]={1,2,3,4};
printf("%d\n",sizeof(a)); //16
printf("%d\n",sizeof(a+0)); //4(32位平台)或8(64位平台),a+0是第一个元素的地址
printf("%d\n",sizeof(*a)); //4,*a是数组的第一个元素
printf("%d\n",sizeof(a+1)); //4,a+1是第二个元素的地址
printf("%d\n",sizeof(a[1])); //4,a[1]是第二个元素
printf("%d\n",sizeof(&a)); //4/8
printf("%d\n",sizeof(*&a)); //16,*&可以理解为抵消了
printf("%d\n",sizeof(&a+1)); //4/8
printf("%d\n",sizeof(&a[0])); //4/8
printf("%d\n",sizeof(&a[0]+1));//4/8
//字符数组
char arr[]={'a','b','c','d','e','f'};
printf("%d\n",sizeof(arr)); //6
printf("%d\n",sizeof(arr+0)); //4/8,是字符的地址,不是字符所占空间大小
printf("%d\n",sizeof(*arr)); //1
printf("%d\n",sizeof(arr[1])); //1
printf("%d\n",sizeof(&arr)); //4/8
printf("%d\n",sizeof(&arr+1)); //4/8
printf("%d\n",sizeof(&arr[0]+1));//4/8
printf("%d\n",strlen(arr)); //随机值,因为不知道什么时候遇到\0
printf("%d\n",strlen(arr+0)); //随机值
printf("%d\n",strlen(*arr)); //报错,因为传的是97
printf("%d\n",strlen(arr[1])); //报错,因为传的是98
printf("%d\n",strlen(&arr)); //随机值
printf("%d\n",strlen(&arr+1)); //随机值-6
printf("%d\n",strlen(&arr[0]+1));//随机值-1
char arr[]="abcdef";
printf("%d\n",sizeof(arr)); //7
printf("%d\n",sizeof(arr+0)); //4/8,是字符的地址,不是字符所占空间大小
printf("%d\n",sizeof(*arr)); //1
printf("%d\n",sizeof(arr[1])); //1
printf("%d\n",sizeof(&arr)); //4/8
printf("%d\n",sizeof(&arr+1)); //4/8
printf("%d\n",sizeof(&arr[0]+1));//4/8
printf("%d\n",strlen(arr)); //6
printf("%d\n",strlen(arr+0)); //6
printf("%d\n",strlen(*arr)); //报错,因为传的是97
printf("%d\n",strlen(arr[1])); //报错,因为传的是98
printf("%d\n",strlen(&arr)); //6
printf("%d\n",strlen(&arr+1)); //随机值,因为不知道\0后面是什么
printf("%d\n",strlen(&arr[0]+1));//5
char *p="abcdef";
printf("%d\n",sizeof(p)); //4/8
printf("%d\n",sizeof(p+1)); //4/8
printf("%d\n",sizeof(*p)); //1
printf("%d\n",sizeof(p[0])); //1,p[0]等价于*(p+0)
printf("%d\n",sizeof(&p)); //4/8
printf("%d\n",sizeof(&p+1)); //4/8
printf("%d\n",sizeof(&p[0]+1));//4/8
printf("%d\n",strlen(p)); //6
printf("%d\n",strlen(p+1)); //5
printf("%d\n",strlen(*p)); //报错,因为传的是97
printf("%d\n",strlen(p[0])); //报错,因为传的是97
printf("%d\n",strlen(&p)); //随机值
printf("%d\n",strlen(&p+1)); //随机值(与上面的随机值不同)
printf("%d\n",strlen(&p[0]+1));//5
//二维数组
int a[3][4]={0};
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,是第一行第二个元素的地址
printf("%d\n",sizeof(*(a[0]+1))); //4,是第一行第二个元素
printf("%d\n",sizeof(a+1)); //4,是第二行的地址
printf("%d\n",sizeof(*(a+1))); //16,是第二行的大小
printf("%d\n",strlen(&a[0]+1)); //4,是第二行的地址
printf("%d\n",strlen(*(&a[0]+1)));//16,是第二行的大小
printf("%d\n",strlen(*a)); //16,是第一行的大小
printf("%d\n",strlen(a[3])); //16,没有真正去访问,换成a[-1]也是16
return 0;
}
例2:
int main()
{
int a[5]={1,2,3,4,5};
int* ptr=(int*)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1)); //2,5
return 0;
}
例3:
//假设p的值是0x100000,则输出什么?
struct Test
{
int num;
char* pcName;
short sDate;
char ch[2];
short sBa[4];
}*p; //已知这里结构体的大小是20个字节
int main()
{
printf("%p\n",p+0x1); //0x100014
printf("%p\n",(unsigned long)p+0x1); //0x100001
printf("%p\n",(unsigned int*)p+0x1); //0x100004
return 0;
}
例4:
int main()
{
int a[4]={1,2,3,4};
int* ptr1=(int*)(&a+1); //指向数组最末尾
int* ptr2=(int*)((int)a+1); //指向第一个元素的第二个字节
printf("%x,%x",ptr1[-1],*ptr2); //ptr1[-1]等价于*(ptr1-1),这里是第四个元素,即4
return 0;
}
例5:
int main()
{
int a[3][2]={(0,1),(2,3),(4,5)}; //这里是逗号表达式,数组里实际存放的是{1,3,5,0,0,0}
int* p;
p=a[0];
printf("%d",p[0]); //1
return 0;
}
例6:
int main()
{
int a[5][5];//一行5个元素
int(*p)[4]; //一行4个元素
p=a;
printf("%p,%d\n",&p[4][2]-&a[4][2],&p[4][2]-&a[4][2]);//FFFFFFFC,-4
return 0;
}
例7:
int main()
{
int aa[2][5]={1,2,3,4,5,6,7,8,9,10};
int* ptr1=(int*)(&aa+1);//指向的是二维数组最后面
int* ptr2=(int*)(*(aa+1));//指的是第二行第一行元素的地址
printf("%d,%d",*(ptr1-1),*(ptr2-1)); //10,5
return 0;
}
例8:
int main()
{
char* a[]={"work","at","alibaba"};
char** pa=a;
pa++;
printf("%s\n",*pa); //at
return 0;
}
例9:
int main()
{
char* c[]={"ENTER","NEW","POINT","FIRST"};
char** cp[]={c+3,c+2,c+1,c}; //cp[]={"FIRST","POINT","NEW","ENTER"}
char*** cpp=cp; //cpp={"FIRST","POINT","NEW","ENTER"}
printf("%d\n",**++cpp); //POINT
printf("%d\n",*--*++cpp+3); //ER
printf("%d\n",*cpp[-2]+3); //ST
printf("%d\n",cpp[-1][-1]+1); //EW,cpp[-1][-1]等价于*(*(cpp-1)-1)
return 0;
}