下面是阿鲤对指针更深学习的一些总结,再此分享,若有错误请指出,若有不懂评论区见哦
我将按照以下目录向大家介绍指针的细节
- 字符指针
- 数组指针
- 指针数组
- 数组传参和指针传参
- 函数指针
- 函数指针数组
- 指向函数指针数组的指针
- 指针和数组面试题的解析
ps:如果觉得下面内容有难度的话,请先看阿鲤的这篇文章https://blog.csdn.net/belongHWL/article/details/92136822
一:字符指针
字符指针就是指向字符的指针,其类型为char*
eg1:
int main()
{
char *str = "BelongAl";
printf("%s\n", str);
system("pause");
return 0;
}
假设BelongAl的地址是0x1100ffc2,那么如图;
字符指针str里存的是“BelongAl”的地址(也就是B的地址),所以str指向了“BelongAl”;
输出结果:
eg2:
eg1中说到了字符指针中存放的是首元素的地址,那么请看以下代码;
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");
system("pause");
return 0;
}
输出结果:
结果显示str1不等于str2 但是 str3等于str4
这是因为指针在访问时只会访问内存中的只读区,且系统在分配内存时只会给相同字符串相同的地址,所以str3和str4相等。(只读所以字符指针不可修改)
二:指针数组
是存放指针的数组
eg1:
int main()
{
int arr1[] = { 1,2,3};
int arr2[] = { 4,5,6};
int arr3[] = { 7,8,9 };
int arr4[] = { 10,11,12 };
int *arr[4] = { &arr1,&arr2,&arr3,&arr4 };
printf("%d\n", *(arr[1] + 1));
printf("%d\n", *(*(arr + 1) + 1));
system("pause");
return 0;
}
*arr[4]是指针数组,它里面存放的是数组的地址
输出结果:
三:数组指针
数组指针是指针不是数组,是指向数组的指针。
比如int(*p)[5] 首先p与*结合,说明了p是一个指针变量,然后指向的是一个大小为十的数组,所以数组指针是指向数组的指针
知道了清楚了什么是数组指针,接下来说说数组指针是怎样使用的;
下面这个例子是把数组指针使用在了二维数组中;
eg1:
void print_arr1(int arr[3][5],int row,int col)
{
int i = 0;
int j = 0;
for(i=0;i<row;i++)
{
for (j = 0; j < col; j++)
{
printf("%-3d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int(*arr)[5], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf("%-3d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
print_arr1(arr, 3, 5);
print_arr2(arr, 3, 5);
system("pause");
return 0;
}
运行结果:
在这段代码中void print_arr1和void print_arr2 的功能是一样的,而void print_arr1所接受的参数是
int arr[3][5]而void print_arr2接受的是int(*arr)[5],在c中,数组名代表的是数组首元素的地址,而在二维数组中,数组名代表二维数组第一行的地址,而每行又是一个一维数组, 所以可以用数组指针来接受它;如下图
如下图。
总结:数组指针就是指向数组,常在二维数组中使用。
四:数组传参和指针传参
在写代码时候我们往往需要把数组或指针传给函数,传参的方法有很多种,那么怎样正确的传参是很重要的。
下面请看一段代码
eg1:(一维数组的传参)
void test(int arr[])
{}
void test(int arr[10])
{}
void test(int *arr)
{}
void test2(int *arr[20])
{}
void test2(int **arr)
{}
int main()
{
int arr[10] = { 0 };
int *arr2[20] = { 0 };
test(arr);
test2(arr2);
system("pause");
return 0;
}
上述传参均正确,其中int *arr2[20]是指针数组,它里面存放的是int*
类型,所以可以使用二阶指针接收
eg2:(二维数组的传参)
void test(int arr[3][5])//正确
{}
void test(int arr[][])//错,不能省略列,必须知道一行有多少元素
{}
void test(int arr[][5])//正确,可以省略行
{}
void test(int *arr)//错误
{}
void test(int *arr[5])//错误,指针数组,存放五个地址的数组
{
printf("%d\n",*(arr+5));
}
void test(int (*arr)[5])//正确,处理每行五个长度的数组//数组指针,指向长度为5的数组
{}
void test(int **arr)//错误,最终只能将二维数组当作一维数组(会发生内存冲突)
{}
int main()
{
int arr[3][5] = { 1,2,3,4,5,6,7,8,9,0,0,0,0,0,0 };
test(arr);
system("pause");
return 0;
}
eg3(一级指针的传参)
void show(int *p, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d\n", *(p + i));
}
}
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
int *p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
show(p, sz);
system("pause");
return 0;
}
运行结果:
总结:当函数参数是一级指针时,只能接受一维数组,如果是二维数组,可按照一维数组输出
eg4:(二级指针的传参)
void test(int **ptr)
{
printf("n = %d\n", **ptr);
}
int main()
{
int n = 10;
int *p = &n;
int **pp = &p;
test(pp);
test(&p);
system("pause");
return 0;
}
运行结果:
那麽当函数参数是二级指针时,他都可以接受什么类型,请看下面一段代码
void test(char **ptr)
{
printf("n = %d\n", **ptr);
}
int main()
{
char c = 'b';
char *pc = &c;
char **ppc = &pc;
char * arr[10];
test(&pc);
test(ppc);
test(arr);//不可以传
system("pause");
return 0;
}
五:函数指针
在讨论函数指针之前,我们先看一下这段代码:
eg1:
void test()
{}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
system("pause");
return 0;
}
运行结果:
从运行结果可以看出,函数名就是函数的地址,那么函数的地址怎样保存起来呢?请看下面代码;
eg2:
void test()
{
printf("BelongAl");
}
int main()
{
void(*pfun)()= test;
pfun();
system("pause");
return 0;
}
输出结果:
由运行结果可以看出,pfun就是函数指针,它存放了函数的地址。
接下来我给大家介绍两端有趣的代码(代码来源c陷阱和缺陷);
eg3:
int main()
{
//void(*pfun)()= test;
//pfun();
(*(void(*)())0)();
void(*signal(int, void(*)))(int);
system("pause");
return 0;
}
(*(void(*)())0)();
首先void()():这是一个函数指针,指向一个返回值是void,无参的函数;然后我们把它看成pf;
(pf*)0:这是将0强制转换成函数指针,且他无参,返回值是void,令pf = (pf*)0
(*pf)():pf此时就是一个调用函数:这个调用将在0的地址进行调用,且返回值为void,无参。
void (*signal(int , void(*)(int)))(int);
Signal是一个函数指针,他有两个参数,分别是int型的和void(*)(int)型;返回值是函数指针,
六:函数指针数组
什么是函数指针数组?
答:函数指针数组,就是存放函数地址的数组;而函数指针数组是这样定义的,
int(*parr[10])()
parr先和[ ]结合证明了他是数组,而数组里存的是int(*)()
的。
而对函数指针数组的使用就是转移表:如下:
eg1:(转移表)
设计一个加减乘除的计算器,体现出函数指针数组的使用
int Add(int a, int b)
{
return a + b;
}
int Sub(int a, int b)
{
return a - b;
}
int Mul(int a, int b)
{
return a * b;
}
int Div(int a, int b)
{
return a / b;
}
void menu()
{
printf("*****************************\n");
printf("****** 1.add **** 2.sub *****\n");
printf("****** 2.mul **** 4.div *****\n");
printf("*****************************\n");
}
int main()
{
int(*arr[5])(int, int) = { 0,Add,Sub,Mul,Div };
int input = 0;
int x = 0, y = 0;
int ret = 0;
int k = 0;
do
{
printf("请选择正确的操作方式:");
scanf("%d", &input);
menu();
if (input < 5 && input >0)
{
printf("请输入两个操作数");
scanf("%d", &x);
scanf("%d", &y);
ret = arr[input](x, y);
printf("%d\n", ret);
}
} while (input);
system("pause");
return 0;
}
7:指向函数指针数组的指针
在我理解,你可以把 指向函数指针数组的指针看成”二级指针“,而“一级指针”便是函数指针数组;
eg1:
void Test(const char* str)
{
printf("%s\n", str);
}
int main()
{
void(*pfun)(const char*) = Test;
void(*pfunArr[5])(const char* str);
pfunArr[0] = test;
void(*(*ppfunArr)[10])(const char*) = &pfunArr;
return 0;
}
代码解释图:
这就是 指向函数指针数组的指针 ,而对函数指针数组的应用就是——回调函数;
下面这个qsort(快排函数)就是使用了回调函数;
eg2:
qsort函数是有四个参数的分别为:
1: void *base 参数是对接受需要排序的数组
2: size_t num 参数是接受数组的总长度
3: size_t width 参数是接受数组一个单位的长度
4: int(_cdcel *compare)(const void *elem1,const void *elem2) 比较相邻两个元素的大小,该参数需要程序员自己设计;
void Show(int *arr, int len)
{
int i = 0;
for (i = 0; i < len; i++)
{
printf("%5d", arr[i]);
}
printf("\n");
}
void Swap(const void *elem1, const void *elem2,int size)
{
int i = 0;
for (i = 0; i < size; i++)
{
char tmp = *((char*)elem1+i);
*((char*)elem1 + i) = *((char*)elem2 + i);
*((char*)elem2 + i) = tmp;
}
}
void Myqsort(void *base, int len,
int size,
int( *Int_cmp)(const void *elem1, const void *elem2))
{
int i = 0;
int j = 0;
for (i = 0; i < len - 1; i++)
{
for (j = 0; j < len - 1 - i; j++)
{
if (Int_cmp( (char*)base+j*size, (char*)base + (j+1) * size)>0)
{
Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
int Int_cmp(const void *elem1, const void *elem2)
{
return *((int*)elem1) - *((int*)elem2);
}
int main()
{
int arr[10] = { 3,5,32,9,7,45,4,655,45,8 };
Show(arr, sizeof(arr) / sizeof(arr[0]));
Myqsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), Int_cmp);
Show(arr, sizeof(arr) / sizeof(arr[0]));
system("pause");
return 0;
}
输出结果:
8:指针和数组面试题的解析
以下是我收集的8道指针方面的面试题,并做了简单的讲解,如果有错误欢迎大家提出,如果大家有看不懂的地方,评论区见哦
1:
int main()
{
int a[5] = { 1,2,3,4,5 };
int *ptr = (int *)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
system("pause");
return 0;
}
输出结果:
解析:
a代表了数组首元素的地址所以*(a+1) 等于2 ; 这道题中int *ptr = (int *)(&a + 1)中的&a代表了整个数组的地址,给&a+1等同于加了整个数组的长度,所以*(ptr - 1) 等于5。
//
//
2:
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
int main()
{
p = (struct Test*)0x100000;
printf("%p\n", p + 0x1);
printf("%p\n",(unsigned long) p + 0x1);
printf("%p\n",(unsigned int*)p+0x1);
printf("%p\n", (unsigned char*)p + 0x1);
printf("%p\n", (unsigned char**)p + 0x1);
system("pause");
return 0;
}
输出结果:
解析:
这道题里的结构体struct Test 的大小为20个字节,故printf("%p\n", p + 0x1)输出为00100014(16进制满16进一);
接下来的printf("%p\n",(unsigned long) p + 0x1)将p强制转化成了unsigned long型,变成了一个数字,所以就,直接加1;
接下来的printf("%p\n",(unsigned int*)p+0x1)将p强制转化成了unsigned int*型,变成了一个指向int型的指针,所以加4;
接下来的printf("%p\n", (unsigned char*)p + 0x1)将p强制转化为了 unsigned char*型,变成了一个指char型的的指针,所以加1;
接下来的printf("%p\n", (unsigned char**)p + 0x1)将p强制转化为了unsigned char**型,变成了一个指向char*类型的指针,所以加4。
//
//
3:
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);
system("pause");
return 0;
}
输出结果:
解析:
int *ptr1 = (int*)(&a + 1),&a代表整个数组的大小,所以加1,直接加到了数组的最后,所以ptr[-1] = *(ptr - 1) = 4;
此题牵扯到了数组的存储,数组在运行时会存储到栈里,且由低地址向高地址存储,存储方式如下: 01000000 02000000 03000000 04000000 ([小端存储),而在题中的(int*)((int)a + 1)中的
(int)a,强制把a从地址转化成了int型的数字,所以加1,就直接加1字节,把a地址的指向从 01000000 加成了 指向00000020,而因为为小端存储,所以以十六进制输出时就直接输出了2000000;
//
//
4:
int main()
{
int a[3][2] = { (0,1),(2,3),(4,5) };
int *p;
p = a[0];
printf("%d", p[0]);
system("pause");
return 0;
}
输出结果:
解析:
这道题的考点是逗号表达式,逗号表达式的执行结果是保留最后一个表达式,所以此二维数组就变成了{{1,3},{5,0},{0,0}};而p = a[0]中,p[0]等于*(*(a+0)+0) == 1。
//
//
5:
int main()
{
int a[5][5];
int(*p)[4];
p = (int(*)[4])a;
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
system("pause");
return 0;
}
输出结果:
解析:
这道题中的考点是 p = (int(*)[4])a 等同于*(*(a+4)+0), 而&p[4][2] 等同于*(*(a+4)+2))
而他的类型是 int(*) [4]所以每次加都是加四列,在加2列最后加到的位置是[3][3] 所以
&p[4][2] - &a[4][2] = -4(%d输出),当以%p输出时要对-4求反码,补码
//
//
6:
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));
system("pause");
return 0;
}
输出结果:
解析:
(int*)(&aa + 1)是对整个数组加1;
(int*)(*(aa + 1))是对数组的第一行整体加一;
//
//
7:
int main()
{
char *a[] = { "work","at","alibaba" };
char**pa = a;
pa++;
printf("%s\n", *pa);
system("pause");
return 0;
}
输出结果:
解析:
pa指向a,a指向数组的地址,所以pa++ =》 a++ 等于 *(a+1)等于 at;
//
//
8:
int main()
{
char *c[] = { "ENTER","NEW","POINT","FIRST" };
char **cp[] = { c + 3,c + 2,c + 1,c };
char***cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
system("pause");
return 0;
}
输出结果:
解析:
本题里的c指向{ "ENTER","NEW","POINT","FIRST" }的地址,cp指向{ c + 3,c + 2,c + 1,c }的地址,cpp指向cp
所以:
printf("%s\n", **++cpp)里面的 **++cpp 等于 *(*(cpp+1)+0) 等于*(c+2) 所以输出为
POINT
printf("%s\n", *--*++cpp+3`)里面的*--*++cpp+3 是在第一步cpp++的基础上执行的 所以
*--*++cpp+3 等于 *(--(c+1))+3 等于*c+3 , 所以输出为ER
printf("%s\n", *cpp[-2]+3)此时的cpp也是在前两位的基础上执行的,所以*cpp[-2]+3 等于*(c+3)+3
,所以输出为ST
printf("%s\n", cpp[-1][-1]+1)此时的cpp也是在前三位的基础上执行的,所以cpp[-1][-1]+1等于
*(*(cpp-1)-1)+1 = *(*(c+3 - 1)-1)+1 所以输出为ER