- 1.字符指针
- (1)什么是字符指针
- (2)字符指针的两种用法
- (3)一道真题
- 2.指针数组
- 3.数组指针
- (1)数组指针定义
- (2)数组名的含义
- (3)单个数组指针的常用应用
- (4)小练习
- 4.数组指针传参设计问题
- (1)一维数组传参
- (2)二维数组传参
- (3)一级指针传参
- (4)二级指针传参
- 5.函数指针
- (1)什么是函数指针
- (2)函数指针的基本使用:
- (3)小练习
- 6.函数指针数组
- (1)函数指针数组是什么
- (2)函数指针和函数指针的综合应用
- 7.指向函数指针数组的指针
- (1)定义:
- (2)例子
- 8.回调函数
- (1)回调函数是什么
- (2)典型例子——qsort函数
指针指向类型为char的指针。
(2)字符指针的两种用法①直接指向某个字符
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
②指向常量字符串
int main()
{
const char* pstr = "hello";//这里不是把一个字符串放到pstr指针变量里了
printf("%sn", pstr);
return 0; }
其实是把常量字符串的首元素h地址放到了指针变量pstr中。
(3)一道真题有以下程序,则运行结果为?
#includeint 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 samen"); else printf("str1 and str2 are not samen"); if(str3 ==str4) printf("str3 and str4 are samen"); else printf("str3 and str4 are not samen"); return 0; }
答案:
分析:C/C++会把常量字符串存放在内存中的全局区,把局部变量存放在栈区。str3和str4都是常量字符串,都指向同一片区域,而str1和str2都是局部变量,指向不同的区域,所以str1和str2不同,str3和str4不同。
2.指针数组指针数组是每个数组元素都是指针的数组。
记作:type *数组名[个数],如int *pa[10];
3.数组指针 (1)数组指针定义数组指针定义:指向数组的指针叫做数组指针,记做:type (*指针名) [数组元素个数],例如int (*p)[10];。
定义理解
int (*p)[10]; //*先和p结合说明这是一个指针类型,然后指着的是一个大小为10的整形数组。所以p是一个指向大小为10的整形数组的指针。 //注意:由于[]的优先级高于*,所以必须加上小括号保证p先和*结合(2)数组名的含义
数组名绝大多数时候表示数组首元素地址,是type*类型;
只有以下两种特殊情况,&数组名和sizeof(数组名)时,这里的数组名是整个数组的地址【虽然地址编号与单独数组名相同】,是type (*) [数组元素个数]类型。
体现差异主要在指针运算结果上。
(3)单个数组指针的常用应用一般二维数组传参
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0,j=0;
for(i=0; i
for(j=0; j
printf("%d ", arr[i][j]);
}
printf("n");
}
}
数组指针传参法
void print_arr2(int (*arr)[5],int row,int col)//第一个参数就相当于传进一个指向数组元素为5个的,指向类型为数组的指针
//二维数组首元素是第一行
{
int i=0;
for(i=0;i
for(j=0;j
printf("%d ",arr[i][j]);
}
printf("n");
}
}
(4)小练习
解释下列代码的意义
int arr[5]; int *parr1[10]; int (*parr2)[10]; int (*parr3[5])[5];
//整形数组 //整形指针数组 //数组指针 //(*parr3[5])表明是一个指针数组,数组中每个元素类型是int (*)[5]——数组指针,parr3是每个元素是数组指针的数组。4.数组指针传参设计问题 (1)一维数组传参
#includevoid test(int arr[])//ok,可以直接传数组名 {} void test(int arr[10])//ok,这里的10传了也相当于没传等价于上边 {} void test(int *arr)//ok,此时本质上是数组首元素地址,等价于第一 {} void test2(int *arr[20])//ok,写成数组形式,跟原来的定义形式一样,20也可以省略 {} void test2(int **arr)//ok,指针形式,原来的数组名本质上是数组首元素的地址,而元素是int*类型的,它的地址就是二级指针,所以传成int**没毛病 {} int main() { int arr[10] = {0};//非指针数组 int *arr2[20] = {0};//指针数组 test(arr); test2(arr2); }
所以,一维数组传参可以写成数组形式也可以写成指针形式,写指针形式关键是抓住数组名的本质。
(2)二维数组传参void test(int arr[3][5])//ok,直接传成二维数组
{}
void test(int arr[][])//no,二维数组传参,函数形参的设计只能省略第一个[]的数字,因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素.
{}
void test(int arr[][5])//ok,数组形式,省略行,不省略列,类比可知多维数组只能省略第一维
{}
void test(int *arr)//no,二维数组首元素地址时int*类型的【首元素是第一行】,它的地址应为int**类型的
{}
void test(int* arr[5])//no,指针数组,本质上是数组,与传过来的二级指针类型不对应
{}
void test(int (*arr)[5])//ok,数组指针,可以
{}
void test(int **arr)//no,二级指针是专门存放一级指针变量的地址,而数组名此时是第一行数组的地址,类型不对应
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
二维数组传参的时候,也是可以写成数组形式,也可以指针形式。写成数组,行可以省略,列不可以省略;写成指针,必须是数组指针。
关键就是类型对应,这里指针需要细分。
(3)一级指针传参传一级指针用一级指针接收就行。
如果参数是一级指针,实参可以是一维数组中的非指针数组数组名,一级指针,一级地址例如print(&a)中的&a【类型匹配】。
(4)二级指针传参传二级指针用二级指针接收。
如果参数是二级指针,实参可以是一级指针的地址,二级指针,指针数组的数组名,三级指针解应用等。【只要类型匹配】
5.函数指针 (1)什么是函数指针每个函数也有自己的地址,只要定义了就占空间,这里可以与函数栈帧空间联想对比一下。
函数地址:&函数名得到的就是函数地址。不过,对于函数来讲,&函数名和函数名都是函数的地址。
函数指针:存放函数地址编号的变量就是函数指针。定义函数返回值类型 (*指针变量名)(参数类型),例如int (*pf)(int,int)=&Add;,这里边形参名字写不写无所谓。
(2)函数指针的基本使用:#includeint Add(int x, int y) { return x + y; } int main() { int (*pf)(int, int) = &Add; int ret1 = (*pf)(2, 3); int ret2 = pf(2, 3); int ret3 = Add(2, 3); printf("%dn",ret1); printf("%dn",ret2); printf("%dn",ret3); return 0; }
其中,定义函数指针时,&Add中的&符号可以省略。
注意,如果对于函数指针解应用时必须加括号。
所以,在函数指针这,&函数名的&其实只是一个摆设,(*函数指针)中的*也只是一个摆设,所以使用函数指针调用函数的时候我们可以直接简化成为函数指针(实参)或者函数名(实参)
(3)小练习①求下列程序的运行结果
#includevoid test() { printf("hehen"); } int main() { if(test==&test) printf("samen"); else printf("differentn"); return 0; }
答案:same
②分析下列代码
//代码1 (*(void (*)())0)(); //代码2 void (*signal(int , void(*)(int)))(int);
答案:
代码一:
代码二:
可以把void(*pf_t)(int)类型重定义达到简化代码二的目的
typedef void (*pf_t)(int) ;//把void (*)(int)重定义为pf_t
那么代码二可以简化为 pf_t signal(int,pf_t);
6.函数指针数组 (1)函数指针数组是什么函数指针数组:数组每个元素为类型都为相同函数指针类型的数组。
定义方法:返回值类型 (*数组名)[个数](返回值类型),例如int(*pa[5])(int x,int y);。可以类比指针数组。
函数指针数组的用途:转移表
(2)函数指针和函数指针的综合应用好处:降低代码冗余
例如:(计算器)
普通写法: switch语句中case每种都定义一个对应的变量接受结果
#includeint 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; } int main() { int x, y; int input = 1; int ret = 0; do { printf( "*************************n" ); printf( " 1:add 2:sub n" ); printf( " 3:mul 4:div n" ); printf( "*************************n" ); printf( "请选择:" ); scanf( "%d", &input); switch (input) { case 1: printf( "输入操作数:" ); scanf( "%d %d", &x, &y); ret = add(x, y); printf( "ret = %dn", ret); break; case 2: printf( "输入操作数:" ); scanf( "%d %d", &x, &y); ret = sub(x, y); printf( "ret = %dn", ret); break; case 3: printf( "输入操作数:" ); scanf( "%d %d", &x, &y); ret = mul(x, y); printf( "ret = %dn", ret); break; case 4: printf( "输入操作数:" ); scanf( "%d %d", &x, &y); ret = div(x, y); printf( "ret = %dn", ret); break; case 0: printf("退出程序n"); breark; default: printf( "选择错误n" ); break; } } while (input); return 0; }
高级写法——应用函数指针数组,将函数转移
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
ret = (*p[input])(x, y);
#include7.指向函数指针数组的指针 (1)定义: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; } int main() { int x, y; int input = 1; int ret = 0; int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表 while (input) { printf( "*************************n" ); printf( " 1:add 2:sub n" ); printf( " 3:mul 4:div n" ); printf( "*************************n" ); printf( "请选择:" ); scanf( "%d", &input); if ((input <= 4 && input >= 1)) { printf( "输入操作数:" ); scanf( "%d %d", &x, &y); ret = (*p[input])(x, y); } else printf( "输入有误n" ); printf( "ret = %dn", ret); } return 0; }
函数指针数组的数组名和*括起来前边加颗星。
例如:int (*(*pfArr)[5])(int,int)=&pfArr;pfArr先和*结合代表是一个指针指向的是数组,每个元素类型是int(*)(int,int).
函数指针、函数指针数组、指向函数指针数组的定义可以结合记忆,知一推二。
(2)例子void test(const char* str)
{
printf("%sn", 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;
}
8.回调函数
(1)回调函数是什么
回调函数就是通过函数指针调用的函数。简言之,回调函数的参数里有其他种类函数。回调函数不是由它的实现方直接调用,是在特定条件发生时的响应。
(2)典型例子——qsort函数qsort——可以排序任意类型的数据,可以升序可以降序,关键点是函数指针cmp,cmp可以根据具体排序规则对应实现。
void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*)); 第一个参数:指向待排序数组第一个对象的指针 第二个参数:数组的宽度【单位长度是第三个参数的值】数组元素的个数 第三个参数:每个数组元素的大小【以字节为单位】 第四个参数:指向比较两个元素的函数指针 对于函数指针cmp返回值 返回值小于0的话,执行交换操作 故升序效果——e1-e2 降序效果——e2-e1 注意:返回值处的强制类型转换和解引用问题
#include//qosrt函数的使用者得实现一个比较函数 int int_cmp(const void * p1, const void * p2) { return (*( int *)p1 - *(int *) p2);//前-后=从小到大 } int main() { int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 }; int i = 0; qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp); for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++) { printf( "%d ", arr[i]); } printf("n"); return 0; }
(3)使用回调函数,模拟实现qsort(采用冒泡排序)
实现时需要注意void*这个东西,然后比较的时候需要字节——用最小的char*进行之间,交换。
#includeint int_cmp(const void * p1, const void * p2) { return (*( int *)p1 - *(int *) p2); } void _swap(void *p1, void * p2, int size) { int i = 0; for (i = 0; i< size; i++) { char tmp = *((char *)p1 + i); *(( char *)p1 + i) = *((char *) p2 + i); *(( char *)p2 + i) = tmp; } } void bubble(void *base, int count , int size, int(*cmp )(void *, void *)) { int i = 0; int j = 0; for (i = 0; i< count - 1; i++) { for (j = 0; j if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0) { _swap(( char *)base + j*size, (char *)base + (j + 1)*size, size); } } } } int main() { int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 }; //char *arr[] = {"aaaa","dddd","cccc","bbbb"}; int i = 0; bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp); for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++) { printf( "%d ", arr[i]); } printf("n"); return 0; }



