目录
1.字符指针
2.指针数组
3.数组指针
思考一下数组指针的形式
有什么内涵呢
实际应用:打印二维数组中的每个元素
判断下面代码的意思
4. 数组参数、指针参数
一维数组传参
二维数组传参
一级指针传参
二级指针传参
5.函数指针
6.函数指针数组
7.指向函数指针数组的指针
8.回调函数
实例1——计算器的实现
实例2——qsort函数的学习和模拟实现
先来学习下qsort函数的内涵
模拟实现
需要注意的点:
1.字符指针
以下代码输出的结果是什么?
回答:h hello the hello the
分析:说明字符指针变量可以存放字符串。
第一个h是因为*ps指针指向的就是hello的首字符h,故输出h;后两个因为数组名arr和ps一样都是hello的首字符地址,故均输出hello。
int main() { //本质上是把"hello the"这个字符串的首字符地址存储在了ps中 char* ps = "hello the"; char arr[] = "hello the"; printf("%cn", *ps);//h printf("%sn", ps); printf("%sn", arr); return 0; }
以下代码输出的结果是什么?
回答:应该是1和2不同,3和4相同
分析:数组名代表数组首元素的地址,str1和str2是两个不同的数组,分别需要开辟两个不同的内存空间,str1和str2分别指向两个hello的首地址,因此不同;
"hello bit"是常量字符串,指针变量无法通过解引用 *** 作改变它。对于内存而言既然"hello bit"是常量,那么只需为它开辟一个内存空间即可,地址编号确定,因此3和4指向了同一个地址即常量字符串"hello bit"的首元素地址
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 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; }2.指针数组
定义:指针数组是一个存放指针的数组,本质上是数组,其中存的内容是指针
如下代码中:arr数组中可以存放三个整形指针int*,而a,b,c分别为三个整形数组首元素地址放入其中arr[0],arr[1],arr[2]
int main() { //指针数组 //数组——数组中存放的是指针(地址) //int* arr[3];//存放整型指针的数组 int a[] = { 1,2,3,4,5 }; int b[] = { 2,3,4,5,6 }; int c[] = { 3,4,5,6,7 }; int* arr[3] = { a,b,c }; int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 5; j++) { printf("%d ", *(arr[i]+j)); printf("%d ", arr[i][j]); } printf("n"); } return 0; }3.数组指针
定义:是一种指针,应该能够指向数组的指针,数组指针中存放的应该是数组的地址
接下来会与之前的指针数组区别:
int *p1[10];——指针数组
[ ]的优先级高于*号的,所以必须加上()来保证p先和*结合
int (*p2)[10];
p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针
思考一下数组指针的形式首先得是指针(*)→指针指向的数组有多少元素呢([ ]) →指向的数组又是什么类型
int main() { int arr[10] = { 1,2,3,4,5 }; //arr——是arr[0]的地址 int(*parr)[10] = &arr;//取出的是数组地址 //parr就是一个数组指针——其中存放的是数组的地址 double* p[10]; double* (*pd)[10] = &p; return 0; }
有什么内涵呢观察以下代码:
int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,10 }; int(*pa)[10] = &arr;//数组指针 int i = 0; for (i = 0; i < 10; i++) { printf("%dn", (*pa + i)); printf("%dn", *(*pa + i)); } return 0; }运行后我们看一下:
*说明pa是指针变量,pa应该存放一个数组的地址,pa+1会跳过一个数组的地址大小;*pa是首元素地址,也就是arr数组名,*pa+1 也就是地址前进4位字节(与int指针类型匹配),反映数组中每个元素的地址解引用 *** 作:*(*pa)由此得出arr数组中的元素
实际应用:打印二维数组中的每个元素p是数组指针,指向一个数组,*p保存首元素地址(arr数组名),[5]反映指向数组中有5个元素,int表示数组元素类型
对于二维数组的首元素是二维数组的第一行,这里传递的arr,其实相当于第一行的地址,是一维数组的地址
(p+i)得到每一行数组的地址*(p+i)得到每一行数组的首元素地址(*(p+i)+j)获取每一行数组中每个元素的地址*(*(p+i)+j)获得每一行数组中的元素数组内容
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("%p ", (p + i) ); printf("%p ", *(p + i) ); printf("%p ", *(p + i)+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; }验证一下结果:
判断下面代码的意思4. 数组参数、指针参数int arr[5];——有着5个整型元素的数组
int *parr1[10];——存放10个整型指针的数组
int (*parr2)[10];——该指针能指向一个数组,一个数组中10个元素,每个元素类型是int
int (*parr3[10])[5];——parr3是一个存储数组指针的数组,该数组能存放10个指针,每个数组指针能够指向一个数组,数组5个元素,每个元素是int类型
一定要分析清楚,实参传过去的是什么。是地址的话是谁的地址,而形参对应又应该用什么形式接收。
一维数组传参二维数组传参判断哪些可以传参,哪些不能?
#includevoid test(int arr[])//ok? {} void test(int arr[10])//ok? {} void test(int *arr)//ok? {} void test2(int *arr[20])//ok? {} void test2(int **arr)//ok? {} int main() { int arr[10] = {0} int *arr2[20] = {0}; test(arr); test2(arr2); } 这里形参的形式都是正确的。
test()
1,2:当作数组接收 √正确
3:arr为数组名,此时传递的是首元素地址,用int*接收 √正确
test2()——int* arr2[20]是一个指针数组,存放int*类型的指针
4:传递指针数组,相应类型接收 √正确
5:arr2是指针数组首元素地址,首元素是int*类型的指针,也就成了二级指针 √正确
一级指针传参判断哪些可以传参,哪些不能?
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); }arr是一个二维数组,二维数组可以不知道行数,但必须知道列数,也就是一行有多少元素
因此1√正确,2×错误,3√正确
arr为首元素地址,代表第一行元素的地址,理解为数组指针,应该指向一个数组
1:理解arr为数组指针(能够指向第一行数组,第一行是有5个整型的数组 ),对应的形参格式错误,只是一级指针 ×错误
2:形参接收的应为一个指针数组 ×错误
3:arr为数组指针,指向的数组内有5个int类型元素 √正确
4:传过去的不是二级指针 ×错误
二级指针传参函数形参形式是接收一级指针时,实参传递的是地址即可
函数形参形式是接收二级指针时,实参可以传递二级指针,即一级指针的地址,也可以传存放一级指针的数组名
void test(char **p) { } int main() { char c = 'b'; char*pc = &c; char**ppc = &pc; char* arr[10]; test(&pc); test(ppc); test(arr);//Ok? return 0; }
5.函数指针分析:
pc是一级指针,ppc是二级指针,test(ppc)把二级指针ppc进行传参;
test(&pc)传一级指针变量的地址;
test(arr)传存放一级指针的数组,传递了arr数组名,数组名代表首元素的地址,而arr数组每个元素都是char *类型,char *的地址相当于char * *,符合 char(int ** p)中形参的接收要求
定义:指向函数的指针,存放函数地址的指针
函数指针的形式:
int(*pf)(int,int)
首先应该是指针(例如*pf),指向一个函数,函数的类型如何?
函数包括函数形参的类型,函数的返回类型,因此函数指针也应该指明所指向函数包括的组成部分。
//函数指针——存放函数地址的指针 //&函数名——取到的就是函数的地址 int Add(int x, int y) { return x + y; } int main() { //&函数名等价于函数名 printf("%pn",&(Add)); printf("%pn",Add); //函数指针变量 int(*pf)(int, int) = Add;//意味着Add===pf printf("%dn", (*pf)(3, 5)); printf("%dn", pf(3, 5)); printf("%dn", Add(3, 5)); return 0; }
6.函数指针数组观察上述代码的结果:
函数名==&函数名,说明函数名就是函数的地址 !(同时记得数组名 != &数组名)以往我们调用函数的形式Add(3,5)与int (*pf) (3,5)效果相同
pf是函数指针变量,存放函数的地址,由此可以得知pf==Add,函数还可以写成pf(3,5)
(*是对形式的理解,实际并没有起作用)
顾名思义——存放函数指针的数组,把函数的地址存到一个数组,同时是存放同类型的函数指针
int (*pf)(int,int)=Add; int (*pf1)(int,int)=Sub; int (*pfArr[2])(int,int);//函数指针数组pfArr
7.指向函数指针数组的指针设计一个计算器来理解:
//函数指针数组 //假设实现一个计算器,计算整型变量能够加减乘除 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 menu() { printf("************* 1.Add ***************n"); printf("************* 2.Sub ***************n"); printf("************* 3.Mul ***************n"); printf("************* 4.Div ***************n"); printf("************* 0.退出 ***************n"); } int main() { int input = 0; do { int(*pfArr[5])(int, int) = {NULL,Add,Sub,Mul,Div}; int x = 0; int y = 0; menu();//计算器界面 printf("请进行你所需要的 *** 作:>"); scanf("%d", &input); if (input >= 1 && input <= 4) { printf("请进行你所需要的计算的两个数值:>"); scanf("%d%d", &x, &y); printf("%dn", pfArr[input](x, y)); } else if (input == 0) { printf("退出程序n"); break; } else { printf("请重新选择n"); } } while(input); return 0; }
//整型数组 int arr[5]; int (*p1)[5]=&arr; //p1是指向(整型数组)的指针 //整型指针的数组 int* arr[5]; int* (*p2) [5]=&arr; //p2是指向(整型指针数组)的指针 //函数指针 int (*p)(int,int); //函数指针数组 int (*p2[4])(int,int); p3=&p2;//取出的是函数指针数组的地址 //p3是一个指向(函数指针数组)的指针 int (*(*p3)[4])(int,int);8.回调函数
回调函数就是一个通过函数指针调用的函数
如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
实例1——计算器的实现快速理解:一个函数本身没有直接被使用,其地址作为另一个函数的形参而被调用
//回调函数 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 menu() { printf("**************************n"); printf("**** 1. add 2. sub ****n"); printf("**** 3. mul 4. div ****n"); printf("**** 0. exit ****n"); printf("**************************n"); } int Calc(int (*pf)(int, int)) { int x = 0; int y = 0; printf("请输入2个 *** 作数>:"); scanf("%d %d", &x, &y); return pf(x, y); } int main() { int input = 0; //计算器-计算整型变量的加、减、乘、除 //a&b a^b a|b a>>b a<b do { menu(); int ret = 0; printf("请选择:>"); scanf("%d", &input); switch (input) { case 1: ret = Calc(Add); printf("ret = %dn", ret); break; case 2: ret = Calc(Sub); printf("ret = %dn", ret); break; case 3: ret = Calc(Mul);// printf("ret = %dn", ret); break; case 4: ret = Calc(Div);// printf("ret = %dn", ret); break; case 0: printf("退出程序n"); break; default: printf("选择错误,重新选择!n"); break; } } while (input); return 0; }实例2——qsort函数的学习和模拟实现
qsort函数可以排列任何类型的元素,包括整型变量、字符串变量、结构体等
先来学习下qsort函数的内涵base中存放的是待排序数组第一个元素的首地址 num是排序待数据数组中元素的个数 size一个数组元素字节的大小 int (*compar)(const void*,const void*)比较待排序数据中两个元素的函数
模拟实现使用:
int cmp_int(const void* e1, const void* e2) { return *(int*)e1 - *(int*)e2; } void test1() { //整型数据的排序 int arr[] = { 9,8,7,6,5,4,3,2,1 }; int sz = sizeof(arr) / sizeof(arr[0]); //排序 qsort(arr, sz, sizeof(arr[0]), cmp_int); //打印 int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } } 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() { //使用qsort函数排序结构体数据 struct Stu s[] = { {"zhangsan",30},{"lisi",40},{"wangwu",20}}; int sz = sizeof(s) / sizeof(s[0]); //按照年龄排序 //按照名字排序 qsort(s, sz, sizeof(s[0]), sort_by_name); } int main() { //test1(); test2(); return 0; }
//模仿qsort函数实现一个冒泡排序的通用算法 //不同类型数据比较方法交给使用者确定 void Swap(char* buf1, char* buf2,int size) { int i = 0; for (i = 0; i < size; i++) { char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; } } void bubble_sort(void* base, int num, int size, int(*cmp)(const void* e1, const void* e2)) { int i = 0; //躺数 for (i = 0; i需要注意的点:0) { //交换 Swap((char*)base + j * size, (char*)base + (j + 1) * size,size); } } } } int cmp_int(const void* e1, const void* e2) { return *(int*)e1 - *(int*)e2; } void test3() { //整型数据的排序 int arr[] = { 9,8,7,6,5,4,3,2,1 }; int sz = sizeof(arr) / sizeof(arr[0]); //排序 bubble_sort(arr, sz, sizeof(arr[0]), cmp_int); //打印 int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } } int main() { test3(); return 0; }
1.字节大小size的作用
作为qsort函数的设计者,字节大小size十分重要,因为不知道使用者是什么类型的数据进行比较;
2.前后两个数据进行比较时,如何得到地址?为什么要强制转换为char*类型的指针
因为使用者会通过形参size告知前一个元素与后一个元素差几个字节。而char*类型的指针为一个字节,因此跳过一个元素:(char*)地址+1*size,跳过两个元素:(char*)地址+2*size;
3.内部的交换函数如何实现?不知道什么类型的元素,如何将两个元素的内容进行交换呢?
由于我们不知道元素的类型,所以不能将指针转为相对应类型的指针进行交换,但我们知道一个元素的字节大小。因此我们依旧传递为char*类型的指针,通过size一个字节一个字节的交换
4.思考:为什么使用void*空指针
因为void*p空指针可以存放任意类型的指针,qsort函数并不明确知道需要排序的是什么类型的数组,因此先用void*接收,具体什么类型由使用者告知。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)