指针进阶
指针进阶
本章重点
- 字符指针
- 数组指针
- 指针数组
- 数组传参和指针传参
- 函数指针
- 函数指针数组
- 指向函数指针数组的指针
- 回调函数
1.字符指针
在指针的类型中我们知道有一种指针类型为字符指针 char*;
一般用法:
#includeM<stdio.h>
int main()
{
char ch='a';
char * p=&ch;
*p='w';
printf("%c",ch);
return 0;
}
还有一种使用方式如下:
int main()
{
const char* pstr = "hello bit.";//这里是把一个字符串的首地址放在了指针变量中,*pstr是字符h不是整个字符串
printf("%s\n", pstr);//或者printf(pstr);
return 0;
}
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char *str3 = "hello bit.";
const 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
分析:首先,创建了两个数组,那么这儿两个数组的地址肯定不一样,而数组名是首元素的地址所以输出结果是str1 and str2 are not same,其次,又创建了两个字符指针但是这两个字符指针指向的是同一块地方,即都是指向常量字符串的首地址,顾打印出str3 and str4 are same。
2.指针数组
- 指针数组是一个数组,是存放指针的数组。
- 用一级指针模拟写一个二维数组。
#include<stdio.h>
int main()
{
int arr1[]={1,2,3,4,5};
int arr2[]={2,3,4,5,6};
int arr3[]={3,4,5,6,7};
int *arr[]={arr1,arr2,arr3};
int i=0;
for(i=0;i<3;i++)
{
int j=0;
for(j=0;j<5;j++)
{
printf("%d",arr[i][j]);
}
printf("\n");
}
return 0;
}
运行结果:
12345
23456
34567
常见的还有:
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组
3.数组指针
数组指针是指针,是指向数组的指针
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p,*p==*&arr==arr
return 0;
}
首先,数组指针是一个指向数组指针,那么存的一定是一个数组的地址所以&arr,数组名是首元素的地址,而&arr是整个数组的地址。
为什么,加1就不一样了?
对于数组名和首元素地址而言,其类型是int类型,+1跳过一个整形,即4个字节,而对于&arr而言,其类型是int [10],即40个字节,顾&arr是整个数组的地址。
3.1数组指针的应用:
void print2(int(*p)[4], int r, int c)//用二维数组的首元素地址,即第一行的地址,即一维数组的地址
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
1.printf("%d", *(*(p + i) + j));//1与2等价
2.printf("%d", p[i][j]);
}
printf("\n");
}
}
void print1(int arr[][4], int r, int c)
{
int i = 0;
for (i = 0; i < r; i++)
{
int j = 0;
for (j = 0; j < c; j++)
{
printf("%d", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };
print1(arr, 3, 4);
print2(arr,3,4);
return 0;
}
学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:
int arr[5];整形数组
int * parr1[10];整形指针数组
int ( * parr2)[10];整形数组指针
int ( * parr3[10])[5];优先级:()>函数调用()和[ ]>*,因此,它肯定是一个数组,每个元素类型又是一个数组指针,因此是数组指针数组。
3.2数组参数,指针参数
1.一维数组传参
#include<stdio.h>
void test(int arr[])//正确
{
}
void test(int arr[10])//正确
{
}
void test(int* p)//正确
{
}
void test2(int* arr2[])//正确
{
}
void test2(int* arr2[5])正确
{
}
void test2(int** p)//正确
{
}
int main()
{
int arr[10] = { 0 };
int* arr2[5] = { 0 };
test(arr);
test2(arr2);
return 0;
}
2.二维数组传参
void test(int arr[3][5])//ok?正确
{}
void test(int arr[][])//ok?错误
{}
void test(int arr[][5])//ok?正确
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok?错误
{}
void test(int* arr[5])//ok?错误
{}
void test(int (*arr)[5])//ok?正确
{}
void test(int **arr)//ok?错误
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
3.一级指针传参
#include <stdio.h>
void print(int *p, int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+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]);
//一级指针p,传给函数
print(p, sz);//p==arr,顾用一级指针接收
return 0;
}
思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
数组名,一级指针变量,&某一变量
4.二级指针传参
#include <stdio.h>
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;
}
思考:
当函数的参数为二级指针的时候,可以接收什么参数?
&一级指针,二级指针变量,指针数组名
4.函数指针
函数指针,即指向函数的指针
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
输出结果:
00721253
00721253
得出结论:数组名与取地址数组名的地址是一样的。
那么函数的地址改怎么存呢?先写一个简单的函数。
int add(int x, int y)
{
return x + y;
}
int main()
{
int a = 2;
int b = 3;
int ret = add(a, b);
printf("%d\n", ret);
return 0;
}
对于整形指针:类型*,如int *
对于数组指针:类型*,如int()[ 5 ]
那么对于函数指针:类型,如int(*)(int,int)
函数指针调用函数:
int add(int x, int y)
{
return x + y;
}
int main()
{
int (*p)(int, int) = &add;
//由于&add==add,即int(*p)(int,int)=add;
int ret = (*p)(2,3);//p是指向函数的指针,解引用找到这个函数
int ret=p(2,3);//这种写法更为实用
printf("%d\n", ret);
return 0;
}
两段有趣的代码:
//代码1
(* (void ( * )( ))0)();
//代码2
void (* signal(int , void( * )(int)))(int);
解读
代码1:调用地址为0的函数,先将0进行强制转换为函数指针,解引用,调用。
代码2:函数signal有两个参数,第一个参数是整形,第二个参数是函数指针,signa的返回类型是函数指针类型,也许这样写更容易理解void( * )(int)signal(int , void( * )(int))但是语法上就错了
5.函数指针数组
首先什么是函数值针数组呢?
我们知道整形指针数组是存放整形指针的数组,那么函数指针数组就是存放函数指针的数组,即存放函数地址的数组。
我们通过写一段代码,认识函数指针数组的写法与方便之处。
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;
}
void test()
{
int a = 0;
int b = 0;
int input = 0;
int ret = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数\n");//相似
scanf("%d %d", &a, &b);
ret = add(a, b);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个操作数\n");//相似
scanf("%d %d", &a, &b);
ret = sub(a, b);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个操作数\n");//相似
scanf("%d %d", &a, &b);
ret = mul(a, b);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个操作数\n");//相似
scanf("%d %d", &a, &b);
ret = div(a, b);
printf("%d\n", ret);
break;
default:
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
是不是感觉switch语句太多了,而且我们发现这些函数的很相似,switch语句中的结构也很相似,那么用函数指针数组来改一改。
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 (*arr[])(int, int) = { NULL,add,sub,mul,div };//函数指针数组,下标引用操作符里可省略。
void test()
{
int a = 0;
int b = 0;
int input = 0;
int ret = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
if (input == 0)
{
break;
}
else if (input >= 1 && input <= 4)
{
printf("请输入两个操作数\n");
scanf("%d %d", &a, &b);
ret=arr[input](a, b);//调用函数,创建数组时[]里不能用变量,但是访问时是可以用变量的
printf("%d\n", ret);
}
else
{
printf("请重新输入\n");
}
} while (input);
}
int main()
{
test();
return 0;
}
现在的代码,比之前的更加方便,简略。
6.指向函数指针数组的指针
从该名字上看复杂,但本质它就是一个数组指针
int msin()
{
int arr[10] = { 0 };
int(*p)[10] = &arr;//数组指针,首先是指针(*p)类型照抄
int (*arr2[5])(int, int);
int(*(*pp))(int, int) = &arr2;//首先是指针,(*pp)类型照抄
return 0;
}
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}
7.回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
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;
}
void calc(int (*p)(int,int))
{
int a = 0;
int b = 0;
int ret = 0;
printf("请输入两个操作数\n");
scanf("%d %d", &a, &b);
ret = p(a, b);
printf("%d\n", ret);
}
void test()
{
int input = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 0:
break;
case 1:
calc(add);//传的是函数的地址,通过函数值针的的方式来调用函数
break;
case 2:
calc(sub);
break;
case 3:
calc(mul);
break;
case 4:
calc(div);
break;
default :
printf("请重新输入\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
补充:演示qsort函数的使用,即快排函数的使用
void qsort( void *base, size_t num, size_t width, int (*compare )(const void *e1, const void *e2 ) ); ——来自cplusplus.com
qsort函数使用时,要包含头文件#include<stdlib.h>,第一个参数是数组的首元素地址,第二个参数是数组的元素个数,第三个参数是数组每个元素占多少字节,第四个参数是函数地址。
之前我们学过冒泡排序(但是比较局限):
冒泡排序:
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;
}
}
}
}
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
快速排序:
//排整形
#include<stdlib.h>
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;//升序
//return *(int*)e2 - *(int*)e1;//降序
}
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_int);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
//排浮点数
#include<stdlib.h>
int cmp_float(const void* e1, const void* e2)
{
return (int)(* (float*)e1 - *(float*)e2);//注意返回的是整形,所以要强转
}
int main()
{
float arr[ ] = {9.0,8.0,7.0,6.0,5.0,4.0,3.0,2.0,1.0,0.0};
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_float);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%.1f ", arr[i]);
}
return 0;
}
//排结构体年龄
#include<stdlib.h>
struct stu
{
char name[20];
int age;
};
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}
int main()
{
struct stu s[3] = { {"张三",20},{"李四",55},{"王五",30} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", s[i].age);
}
return 0;
}
//排结构体名字
#include<stdlib.h>
#include<string.h>
struct stu
{
char name[20];
int age;
};
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
int main()
{
struct stu s[3] = { {"张三",20},{"李四",55},{"王五",30} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%s ", s[i].name);
}
return 0;
}
为什么要有四个参数? 首先比较不同类型的数据那么你不知道你要接收的是什么类型,还有知道首地址,所以第一个参数是void*首地址,你要比较多少是不知道的,所以需要第二个参数,你所比较的数据类型不知道占多少个字节,所以需要第三个参数,不同数据类型比较方法不同,所以需要分离出来,就需要第四个参数。
用冒泡排序的思想来写一个快排
int cmp_int(const void* e1, const void* e2)//不同数据比较方法不同
{
return *(int*)e1 - *(int*)e2;
}
void swap(char* p1, char* p2, int n)//我们一次访问一个字节,一个一个字节进行交换,这样无论什么类型都能交换
{
int i = 0;
for (i = 0; i < n; i++)
{
char tmp = *p1;
*p1 = *p2;
*p2 = tmp;
p1++;
p2++;
}
}
void bubble_sort(void* base, size_t num, size_t width, int (*cmp)(const void* e1, const void* e2))
{
size_t i = 0;
for (i = 0; i < num - 1; i++)
{
size_t j = 0;
for (j = 0; j < num - 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);
}
}
}
}
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
8.指针和数组笔试题解析
必须记住的原则:
- sizeof(单独数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
- &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
- 除此之外所有的数组名都表示首元素的地址。
- sizeof 只关注占用内存空间的大小,单位是字节,不关心内存中存放的是什么,sizeof 是操作符。
- strlen是求字符串长度的,统计的是\0之前出现的字符个数,一定要找到\0才算结束,所以可能存在越界访问的,strlen是库函数。
1.一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));
答案:
16byte
4或8byte
4byte
4或8byte
4byte
4或8byte
16byte
4或8byte
4或8byte
4或8byte
2.字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
答案:
6byte
4或8byte
1byte
1byte
4或8byte
4或8byte
4或8byte
随机值
随机值
error
error
随机值
随机值
随机值
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr+0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr+1));
printf("%d\n", sizeof(&arr[0]+1));
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr+0));
printf("%d\n", strlen(*arr));
printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr+1));
printf("%d\n", strlen(&arr[0]+1));
答案:
7byte
4或8byte
1byte
1byte
4或8byte
4或8byte
4或8byte
6
6
error
error
6
随机值
5
char *p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p+1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p+1));
printf("%d\n", sizeof(&p[0]+1));
printf("%d\n", strlen(p));
printf("%d\n", strlen(p+1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p+1));
printf("%d\n", strlen(&p[0]+1));
答案:
4或8byte
4或8byte
1byte
1byte
4或8byte
4或8byte
4或8byte
6
5
error
error
随机值
随机值
5
3.二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a[0][0]));
printf("%d\n",sizeof(a[0]));
printf("%d\n",sizeof(a[0]+1));
printf("%d\n",sizeof(*(a[0]+1)));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(*(a+1)));
printf("%d\n",sizeof(&a[0]+1));
printf("%d\n",sizeof(*(&a[0]+1)));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a[3]));
答案:
48byte
4byte
16byte
4或8byte
4byte
4或8byte
16byte
4或8byte
16byte
16byte
16byte
注意:sizeof()内部是不参与计算的,它只是计算类型的大小
#include<stdio.h>
int main()
{
int a=0;
1.sizeof(a);//值属性
2.sizeof(int);//类型属性,也就是说sizeof测的是a的数据类型
return 0;
}
9.指针笔试题
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
2 5
2.
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
0x100014
0x100001
0x100004
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);
return 0;
}
4 0x2000000
4.
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
}
1
//首先注意逗号表达式,那么二维数组a中存的就应该是1,3,5,0,0,0
数组名a[0]是首元素1的地址,那么*(p+0)==p[0]==1。
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
0xfffffffc -4
小指针-大指针是负数,注意-4在内存中是以补码的形式存储的即11111111111111111111111111111100,对于%p来讲它就是地址即0xfffffffc
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
at
分析:
7.
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);
return 0;
}
POINT
ER
ST
EW
分析:
总结:
以上就是指针进阶的所有内容了,如有关于这方面的问题不妨关注我,我们一起探讨,如果喜欢本篇,那就请求大佬们点个赞[卑微],谢谢观看!