前段时间,学习了指针初阶的知识,这里再简单回顾下。
——————————————————————————-——
字符指针为char* 类型,一般的使用方法:
int main()
{
char u = 'a';
char* p = &u;//p就是字符指针
*p = 'b';
return 0;
}
另一种使用方法:
char* p = "abcdef";
这里大家会误以为是把字符串放在指针变量p里,但事实不是这样。本质是把字符串的第一个字符的地址放到p里。“abcdef” 是常量字符串,常量字符串存放在代码区,是不能改变的,所以可以稍作调整,在char* 前面加const,限制* p ,这样就不会改变后面的字符串了。
我们可以验证下p存放的是不是地址:
printf("%s\n", p);
printf("%c", *p);
1.p是地址,从这个地址开始向后打印字符串
2.*p 解引用后得到的是首字符a
一道例题:
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,另一个是str2,字符串hello bit.是用来初始化两个数组的,也就是把字符串hello bit.分别存放到str1和str2里面去,str1和str2是两个不同且独立的空间,并且我们知道数组名是首元素的地址,这两个首元素的地址不在一个空间里,所以他俩不相等。
就好比两个在不同位置或者说不同地址的房子,两个房子里都存放有一本相同的书,不能因为房子存放的某个东西相同就判定两个房子的地址相同。
接下来是两个字符指针,一个是str3,另一个是str4,这两个指针变量指向字符串hello bit.,存放的是首字符的h的地址,所以两者的地址是一样的。
定义:指针数组是存放指针的数组
int* arr[10];//指针数组
指针数组模拟实现二维数组
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[3] = { 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;
}
打印出:
打印出来的结果看起来是二维数组,但它不是真正的二维数组,是用指针数组模拟出来的。指针数组里的每个元素是不同的数组,这3个数组是独立开辟出来的不同的空间,(也可以理解为这3个数组毫无相关)但是把这3个数组存进指针数组里存的是数组的首地址,然后指针数组arr 通过这3个数组的首地址向后打印其数组的内容。
数组指针是什么,我们可以类比一下
整型指针:指向整型变量的指针,存放整型变量的地址的指针变量
字符指针:指向字符变量的指针,存放字符变量的地址的指针变量
数组指针 -> 指向数组的指针,存放的是数组的地址的指针变量
int(*p)[10];
数组指针与指针数组有时候容易混乱,这里给大家捋一捋
int* p1[10];//指针数组
int(*p2)[10];//数组指针
[ ] 的优先级对于 * ,所以p1与[10]先结合,是数组,存入数组的每个元素是 int *;
(*p2)先执行 *,说明p2是指针,指向的是int [10],int [10]是数组,所以p2是数组指针变量
数组名是数组首元素的地址
但是有两个例外:
sizeof(数组名)表示的是整个数组的大小,单位是字节;
&数组名表示的是整个数组,取出的是整个数组的地址。
int arr[10] = { 0 };//一个元素4个字节
printf("%p\n", arr);
printf("%p\n", &arr[0]);
printf("%d\n", sizeof(arr));
可以发现arr与&arr[0]的地址相同
那么arr与&arr有什么联系呢?
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
printf("%p\n", &arr);
我们已经知道数组名是首元素的地址, &arr[0]也是,但&arr为什么也是首元素的地址呢。其实单单只打印值看不出区别,因为它们的值都是从首元素地址开始的,但是它们的类型不一样。
我们可以打开调试窗口:
arr的类型是int * 类型
&arr[0]的类型是int * 类型
&arr的类型是int (*)[10](数组指针类型)
不同的类型属性和功能是不一样的
看下面代码:
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", arr+1);
printf("%p\n", &arr[0]);
printf("%p\n", &arr[0]+1);
printf("%p\n", &arr);
printf("%p\n", &arr+1);
从中可以发现arr到arr+1差4个字节;&arr[0]到&arr[0]+1差4个字节;&arr到&arr+1差40个字节,也就是说&数组名+1跳过的是整个数组。
总结:
对于数组名来说,&数组名得到的是数组的地址,而不是数组首元素的地址
&arr的类型是数组指针类型,即int (*)[10],对数组指针解引用得到的是数组
&数组+1,跳过的是整个数组的大小
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int(*p)[10] = &arr;//数组的地址,存储到数组指针变量
return 0;
}
打印数组里的元素:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int(*p)[10] = &arr;//p->&arr,*p->*&arr->arr
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *((*p) + i));
//printf("%d ", (*p)[i]);另一种写法
}
return 0;
}
但我们发现数组指针这样使用很变扭,所以数组指针不是这样使用的
数组指针一般使用在二维数组上
我们知道二维数组其实是一维数组的数组,数组名也是数组首元素的地址,即第一行的地址 = 一维数组的地址 = 数组的地址,数组的地址传过去应该用指针来接收,所以二维数组传参,形参可以是指针的形式。
void test(int(*p)[5], int r, int c)
{
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; 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 };
test(arr, 3, 5);
return 0;
}
p指向的是第一行,当p+i时随着i的值的变化,p指向第二行、第三行,解引用p+i,相当于得到这一行数组的地址,再加j,表示这一行数组的某个元素的地址,最后的解引用,就打印这一行这一列的元素。
补充一下:
一维数组传参,形参的部分可以是数组,也可以是指针
void test1(int arr[5],int a)
{}
void test2(int* p, int a)
{}
int main()
{
int arr[5] = { 0 };
test1(arr, 5);
test2(arr, 5);
return 0;
}
两种其实是一样的,但要注意的是传过去形式上是数组,本质是指针
二维数组传参,形参的部分可以是数组,也可以是指针
void test3(char arr[3][5], int r, int c)
{}
void test4(char (*p)[5], int r, int c)
{}
int main()
{
char arr[3][5] = { 0 };
test3(arr, 3, 5);
test4(arr, 3, 5);
return 0;
}
#include
1.void test(int arr[])//yes
{}
2.void test(int arr[10])//yes
{}
3.void test(int *arr)//yes
{}
4.void test2(int *arr[20])//yes
{}
5.void test2(int **arr)//yes
{}
int main()
{
int arr[10] = {0};//每个元素是整型,即整数
int *arr2[20] = {0};//每个元素是指针,即存放的是地址
test(arr);
test2(arr2);
}
1.数组传参,数组接收,正确。可省略数组的大小,因为传过去不会真实的创建数组,传的是地址。
2.还是数组数组传参,数组接收,正确。【】里的大小可自己定。
3.数组名是首元素的地址,传过去是指针,正确。
4.数组传参,数组接收,正确。
5.数组里的每个元素是一级指针,int *类型,传过去的是一级指针的地址,要用二级指针来接收,正确。
1.void test(int arr[3][5])//yes
{}
2.void test(int arr[][])//no
{}
3.void test(int arr[][5])//yes
{}
4.void test(int *arr)//no
{}
5.void test(int* arr[5])//no
{}
6.void test(int (*arr)[5])//yes
{}
7.void test(int **arr)//no
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
1.数组传参,数组接收,正确。
2.二维数组传参,形参部分行可以省略,列不能省略,错误。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素,这样才方便运算。
3.正确。
4.传过去的是一行的地址,错误。
5.形参是指针数组,传过去那么是数组指针,那么是二维数组,所以错误。
6.形参是数组指针,正确。
7.数组名是首元素的地址,要用一级指针来接收;二级指针是来接收一级指针的地址,所以错误。
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]);
print(p, sz);
return 0;
}
一级指针传参,形参部分用一级指针来接收。通过一级指针找到数组的首元素的地址,然后解引用打印。
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
void test(char* p)
{}
int main()
{
char ch = 'e';
test(&ch);
char* pa = &ch;
test(pa);
char arr[] = "abcd";
test(arr);
return 0;
}
只要传过去的类型与形参的类型是匹配的就OK,形参是一级指针,可以传ch的地址、char * 的指针、数组名(char类型)
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;
}
进入主函数,&n放在指针变量p里面(一级指针),然后再一级指针变量取地址,放在pp里面(二级指针)
&p—>形参是二级指针的形式
pp—>形参是二级指针的形式
当函数的参数为二级指针的时候,可以接收什么参数?
void test(char** pa)
{}
int main()
{
char n = 'a';
char* p = &n;
char** pp = &p;
char* arr[5] = { 0 };
test(&p);
test(pp);
test(arr);
return 0;
}
&p是一级指针的地址,传过去用二级指针来接收;pp(二级指针)传参,形参用二级指针来接收;arr是指针数组,数组名是首元素的地址,它的每个元素是char*,传的是char*的地址。
函数指针是什么?
我们知道数组指针是指向数组的指针,那么函数指针就是指向函数的指针。
函数的地址怎么表示?
int add(int x, int y)
{
return x + y;
}
int main()
{
printf("%p\n", add);
printf("%p\n", &add);
return 0;
}
如果我们要用一个变量把函数的地址存起来,假设用pf,那么pf的类型就是函数指针变量
写法:
先 *pf 说明pf是指针,用括号括起来,这个指针指向的是函数参数,函数参数用括号括起来(注意参数的类型),并且最左边要有函数的返回类型。
int add(int x, int y)
{
return x + y;
}
int main()
{
int arr[10] = { 0 };
int(*pa)[10] = &arr;//数组指针
int (*pf)(int, int) = &add;//函数指针,pf是函数指针变量
int (*)(int, int)//函数指针类型
return 0;
}
函数指针间接访问调用函数:
int add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = &add;//&去掉也行
int r = add(3, 4);
printf("%d\n", r);
int m = (*pf)(5, 6);
int m = pf(5, 6);//另一种写法,注意加上*时一定要带上括号
printf("%d\n", m);
return 0;
}
(*(void (*)())0)();
分析:
第一眼看到这样的代码,感觉很头晕,我们可以先拆分下,这样看得更通俗易懂点
这里的0是整数,可以把它当成整型;void (*)()是函数指针类型,()里放类型,说明是强制类型转换,把0强制转换成函数指针类型,然后地址解引用调用0地址处的函数,本质上是函数调用。
二:
void (*signal(int , void(*)(int)))(int);
分析:
signal是函数声名,函数参数是int 类型和void(*)(int)函数指针类型,该函数指针指向一个int类型的参数,返回类型是void;signal函数的返回类型也是函数指针类型,指向的函数有一个int 类型的参数,返回类型是void。
这段代码可以进行简化,(用typedef对类型进行重命名)
typedef的使用,比如:
typedef unsigned int uint;
typedef int* ptr;
unsigned int—>uint
int* ---->ptr
这里注意一下,对函数指针或数组指针重命名,重命名的名字要放在括号里面(语法规定)
typedef int(*par)[10];
typedef int(*pfr)(int, int);
接下来简化这段代码:
typedef void(*pf_t)(int);
pf_t signal(int, pf_t);
把函数指针类型重命名为pf_t,signal的参数是int和pf_t,返回类型也是pf_t
前面我们学习过整型指针数组和字符指针数组
int* arr1[5];//每个元素是整型指针类型
char* arr2[4];//每个元素是字符指针类型
那么函数指针数组就是数组的每个元素是函数指针类型
int (*pf1)(int, int) = add;
int (*pf2)(int, int) = mul;
int (*pf3)(int, int) = sub;
int (*pf4)(int, int) = div;
int (*parr[4])(int, int) = { add,mul,sub,div };//函数指针数组
以计算器整数的运算为例:(加、减、乘、除)
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 2.sub 3.mul 4.div\n");
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
//函数指针数组-转移表
int (*parr[5])(int, int) = { NULL,add,sub,mul,div };
// 0 1 2 3 4
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = parr[input](x, y);
printf("%d\n", ret);
}
else if (input == 0)
{
printf("退出计算器\n");
}
else
{
printf("选择错误\n");
}
} while (input);
return 0;
}
使用函数指针数组可以简化代码,当要选择进入某个函数时不需要大量的case,防止代码冗余。
定义:指向函数指针数组的指针是一个 指针 指针指向一个 数组 ,数组的元素都是 函数指针
int (*pf)(int, int);//函数指针
int (*pfarr[4])(int, int);//函数指针数组
//&pfarr----->函数指针数组的地址
int (*(*p)[4])(int, int) = &pfarr;
//p就是指向函数指针数组的指针
在p的旁边加上 * ,用括号括起来,说明p是指针;指针指向的是数组,数组的每个元素是函数指针。
总结:
数组:数组名是数组首元素的地址;&数组名是整个数组的地址
函数:函数名是函数的地址;&函数名也是函数的地址
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
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 2.sub 3.mul 4.div\n");
}
void calc(int (*pf)(int, int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
calc(add);
break;
case 2:
calc(sub);
break;
case 3:
calc(mul);
break;
case 4:
calc(div);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("输入有误,请重新输入\n");
break;
}
} while (input);
return 0;
}
qsort函数的特点:
1.快速排序的方法
2.适合于任意类型数据的排序
void qsort(void* base,//指向了需要排序的数组的第一个元素
size_t num,//排序的元素个数
size_t size,//一个元素的大小,单位是字节
int (*compar)(const void*, const void*));
//函数指针类型,指向的函数能够比较base指向数组中的两个元素
注意:
void * 的指针是无具体类型的指针,可以接收任意类型的地址,但不能直接解引用操作,也不能直接进行指针运算
比如:
int a = 10;
float f = 3.12;
int* pa = &a;//yes
char* pc = &a;//no
void* pv = &a;//yes
pv = &f;//yes
*pv;//no
pv++;//no
qsort函数排序整型数据:
int cmp_int(const void* p1, const void* p2)
{
// 强制类型转换为int*类型再解引用
return (*(int*)p1 - *(int*)p2);//将p1与p2的位置互换是降序
}
void print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
test1()
{
int arr[10] = { 3,5,6,2,1,9,7,8,4,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
//默认是升序
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print(arr, sz);
}
int main()
{
test1();
return 0;
}
struct stu
{
char name[20];
int age;
};
int cmp_stu_age(const void* p1, const void* p2)
{
return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}
void test2()
{
struct stu arr[] = { {"zhangsan",22},{"lisi",45},{"xiaoming",37} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_age);
}
int main()
{
test2();
return 0;
}
struct stu
{
char name[20];
int age;
};
int cmp_stu_name(const void* p1, const void* p2)
{
return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}
void test3()
{
struct stu arr[] = { {"zhangsan",22},{"lisi",45},{"xiaoming",37} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_name);
}
int main()
{
test3();
return 0;
}
注意:这里比较名字时,两个名字不能相减,因为名字是字符串,比较字符串要用strcmp函数
使用冒泡排序的思想,实现一个功能类似qsort的函数
我们之前写过冒泡排序:
void bubble_sort(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int t = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = t;
}
}
}
}
但如果还是按照这样写,难免有问题。
问题1:参数只能接收整型数组
解决方法:用void * 的指针,可以接收任意数据类型,同时传元素个数和一个元素的大小
问题2:对于不同类型的数据,整型比较大小可以用>、<、==,但是结构体比较大小就不一定了,如果是比较年龄,可以用前面的符号,如果是比较名字就不行了,也有可能是其他的比较方法
解决方法:参数的部分加上函数指针,将两个元素的比较方法,以函数参数的形式传递
排序整型数据:
void print(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
void swap(char* buf1, char* buf2, int size)
{
int i = 0;
char t = 0;
for (i = 0; i < size; i++)
{
t = *buf1;
*buf1 = *buf2;
*buf2 = t;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, int num, int size, int (*cmp)(const void*, const void*))
{
int i = 0;
for (i = 0; i < num - 1; i++)
{
int j = 0;
for (j = 0; j < num - 1 - i; 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 cmp_int(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
void test1()
{
int arr[10] = { 4,2,8,6,0,3,7,9,1,5 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);
print(arr, sz);
}
int main()
{
test1();
return 0;
}
分析:
一:bubble_sort函数传参
bubble_sort函数传过去第一个参数不能是int 类型,因为假如要传的数组是结构体的其他数据类型,就写死了,用void * 可以接收任意的数据类型,void * 后面的名字可以自定义;第二个是元素的个数;第三个是一个元素的大小,因为void * 只知道从哪开始,不知道传过来的是什么数据类型的元素;第四个是函数指针,把要传的两个元素的地址传过来(写成const void * 是因为都不知道要排序的是什么数据、元素是什么类型,所以用void * ;用const是因为仅仅比较两个元素,而不是改变元素)
二:cmp_int函数
cmp_int函数比较方法与前面同,通过这个函数内部来比较大小
三:bubble_sort函数内部
首先要确定趟数,和一趟内部比较的对数,两个元素比较要把arr[j]和arr[j+1]的地址传给cmp;假设是升序,cmp返回>0,,交换(如果不写>0,负数非0,为真,也进入条件)。
计算 j 和 j+1 的元素的地址:
四:swap函数----交换
交换的是判断条件两个指针所指向的元素,把它们作为参数传过去,还有一个元素的大小;因为参数是强转成char * 传过去的,所以swap函数参数是char * 和size。然后交换两个元素,一个元素是4个字节,每交换一个字节地址往后跳一个字节。