目录
指针一直很难理解的知识点,希望通过整理的方式做到梳理和融会贯通。附上本文学习的视频:
一、指针初探
1、指针的基础概念
指针是什么?
指针是编程语言中的一个用于存储地址的变量,利用地址,它的值直接指向存储在电脑存储器中另一个地方的值。
int main(){
int a = 10;
int* p = &a; // p是一个指针,p中存储的是a的地址
return 0;
}
指针有多大?
指针要能够表示一个“地址的大小”。
在一台32位的计算机上,地址的大小是32 bit = 4 B,故一个指针的大小为四个字节。
在一台64位的计算机上,地址的大小是64 bit = 8 B,故一个指针的大小为八个字节。
int main(){
printf("%d\n",sizeof(char*));
printf("%d\n",sizeof(int*));
printf("%d\n",sizeof(double*));
//输出全为8(4)
}
指针的类型?
指针的大小既然相同,那为什么还要区分类型呢?
int main(){
int a = 10;
int* pa = &a;
char* pc = &a;
printf("%p\n",pa); //000000a37c9ff7cc
printf("%p\n",pc); //000000a37c9ff7cc
// 无论是用哪种类型的指针来接收a的地址,都是一样的
// 那指针为什么要区分类型?
}
1) 指针类型决定了指针进行解引用操作的时候,能够访问空间的大小。
int* p; p能访问4个字节;
char* p; p能访问1个字节;
2) 指针类型决定了指针的步长(单位:字节)
int main(){
int a = 10;
int* pa = &a;
char* pc = &a;
printf("pa = %p\n",pa);
printf("pa+1 = %p\n",pa+1);
printf("pc = %p\n",pc);
printf("pc+1 = %p\n",pc+1);
}
//pa = 000000eaacfff98c
//pa+1 = 000000eaacfff990
//pc = 000000eaacfff98c
//pc+1 = 000000eaacfff98d
int* p ;p+1走4个bit;
char* p; p+1走1个bit;
如下这个例子可以更好的理解指针的移动步长。
int main(){
int arr[10] = {0};
int* p =arr;
for(int i = 0;i<10;i++){
*(p+i) = 1;
}
for(int i = 0;i<10;i++){
printf("%d ",arr[i]);
}
// 输出1 1 1 1 1 1 1 1 1 1
printf("\n");
int arr2[10] = {0};
char* p2 =arr2;
for(int i = 0;i<10;i++){
*(p2+i) = 1;
}
for(int i = 0;i<10;i++){
printf("%d ",arr2[i]);
}
// 输出16843009 16843009 257 0 0 0 0 0 0 0
// 00000001000000010000000100000001 第一个int 32bit 4B
// 00000001000000010000000100000001
// 0000000100000001
}
野指针?
野指针就是指针指向的位置是不可知的指针。
在这个例子中,p1不是野指针,故它存储的就是a的地址;p2是野指针,地址随机,可能指向一份受保护的地址空间,因此,使用p2去操作这块未知的地址是危险的。
int main(){
int a = 20;
int* p1 = &a;
printf("%p\n",&a);
printf("%p\n",p1);
int* p2;// 野指针
printf("%p\n",p2);
// p2 = 20;
return 0;
}
在这个例子中,test函数释放了局部变量a,于是p也成为了野指针。
int* test(){
int a =10;
return &a;
}
int main(){
int* p = test();
// *p = 20;
return 0;
}
在这个例子中,由于数组越界,指针对某块未被定义的空间进行了修改。
int main(){
int arr[10] = {0};
int* p = arr;
for(int i = 0; i <= 12;i++){
*p = i;
p++;
//*p++ = i;
}
}
为了避免野指针的存在,请务必为每个指针变量初始化。
暂时不知道指向谁?
int* p = NULL;
2、指针运算
指针加减整数
在看下一个例子之前,需要强调的是,对于一个数组a来说,数组名可以作为数组第一个元素的指针,即 a 等价于 &a[0],a + i 等价于&a[i]。
那么&a的意义是什么呢?&a数值上等于整个数组的起始位置地址,含义上代表整个数组所占内存的大小,于是,&a + 1 等价于数组结束之后的下一段的起始位置地址。
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr)/sizeof(arr[0]);
int* p = arr;
for(int i=0;i<sz;i++){
printf("%d ", *p);
p = p + 1; // 指针与整数运算(等价于p++) 指向下一个元素
}
}
// 1 2 3 4 5 6 7 8 9 10
以下这段代码于上述代码等价哦:
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
for(int* p=&arr[0];p<&arr[10];){
printf("%d ", *p++);
}
}
指针与指针运算
指针(大地址)—指针(小地址) = 中间元素个数
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%d\n",&arr[9]-&arr[0]);// 9
printf("%d\n",&arr[3]-&arr[4]);// -1
}
用指针实现strlen函数:
int mystrlen(char* str){
char *p = str;
int len = 0;
while(*p++!='\0'){
len++;
}
return len;
}
// 运用到了指针之间的减法运算 本质上与第一种方法一致
int mystrlen2(char* str){
char *start = str;
char *end = str;
while(*end !='\0'){
end++;
}
return end-start;
}
int main(){
char s[] = "tangchujiang";
printf("%d\n",mystrlen(s));
printf("%d\n",mystrlen2(s));
}
利用指针的大小比较(即指针之间的运算),逆向输出数组:
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
for(int* p=&arr[10];p>&arr[0];){
printf("%d ", *--p);
}
// for(int* p=&arr[9];p>=&arr[0];p--){
// printf("%d ", *p);
// }
// 等价写法 关键是理解 a++ 和 ++a 的预算顺序
// 然而这种写法是不推荐的 原因是C语言的标准中规定:
// 允许拿数组空间向后一位的越界元素进行比较,不允许拿数组空间向前一位的越界元素进行比较
// 第二种写法在判断终止条件时,会用到“arr[-1]”这个越界元素
}
// 10 9 8 7 6 5 4 3 2 1
3、指针与数组
前文已经提及,数组名就是首元素地址,arr 等价于 &arr[0]。以下这个例子充分说明了二者之间的关系:
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = arr;
for(int i = 0;i<10;i++){
printf("%p=======%p\n",p+i,&arr[i]);
}
printf("\n");
for(int i = 0;i<10;i++){
printf("%d=======%d\n",*(p+i),arr[i]);
}
}
但需要注意以下两种写法:
1、&arr[0]——数组名表示的表示整个数组
2、sizeof(arr)——数组名表示的是整个数组(必须只有arr本身 不能在表达式中)
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%d",sizeof(arr)); // 40
}
对于数组名、取地址数组名、sizeof(数组名)等指代的理解,可以下划本文至练习部分的1、2题。
4、二级指针
int main(){
int a = 10;
int* pa = &a;
// pa指针也是一个变量 也有他的地址
int** ppa = &pa;
// ppa是个二级指针 即 指向指针的指针
printf("%d\n",*pa);
printf("%d\n",**ppa);
}
二、指针进阶
1、字符指针
字符指针有两种用法:
其一,它可以存储一个字符的地址,指向一个字符,如pc1;
其二,它可以存储一个字符数组的首地址,如pc2;
int main(){
char ch = 'w';
char* pc1 = &ch;
printf("%c\n",*pc1);
char arr[] = "abcdef";
char* pc2 = arr; // 数组名就是字符数组首地址
printf("%s\n",arr);
printf("%s\n",pc2); // attention!!!
return 0;
}
等一下,华生,你有没有发现盲点!
printf("%s\n",pc2);
解引用去哪里了?
其实我们无需感到奇怪,记得我们在使用scanf函数时,输入一个字符scanf("%c",&a)和一个字符串scanf("%s",b)时就已经在提示我们原因了,附上某乎上一位大佬的解释。
总结:字符指针数组不需要解引用!
常量字符串
int main(){
char *pc3 = "abcdef";//"abcdef"是一个常量字符串
printf("%s\n",pc3);
}
这样定义的字符串叫做常量字符串,如果我们试图修改它:
*pc3 = 'w';
不会成功。它是如何存储的呢?
int main(){
char arr1[]="abcdef";
char arr2[]="abcdef";
if(arr1==arr2){
printf("equal1");
}
char* p1 = "abcdef";
char* p2 = "abcdef";
if(p1==p2){
printf("equal2");
}
// 输出结果为 equal2
}
事实证明,arr1和arr2在内存中开辟了两片不同的地址空间,而既然常量字符串不能被修改,无法对他进行操作,那为什么要存两份?
于是,p1和p2其实指向了同一份地址空间,故p1 == p2。
常量字符串有且仅有一片存储空间。
最后,在定义常量字符串时用const修饰,是我们该形成的书写习惯。
const char* p1 = "abcdef";
2、指针数组和数组指针
指针数组
指针(修饰语)数组(主语)仍然是一个数组,区别于后面的知识点——数组指针。
字符数组——存放字符,整形数组——存放整形,则指针数组,顾名思义,存放指针的数组。
int main(){
int arr1[] = {1,2,3,4,5};
int arr2[] = {2,3,4,5,6};
int arr3[] = {3,4,5,6,7};
int *p2[3] = {arr1,arr2,arr3};
for(int i = 0;i<3;i++){
for(int j =0;j<5;j++){
printf("%d",*(p2[i]+j));// p2[i]就是每个arr的首元素地址
}
printf("\n");
}
//12345
//23456
//34567
return 0;
}
数组指针
顾名思义,数组指针是指向数组的指针。
int main(){
// int *pi = NULL; //可以存放整形地址
// char *pc = NULL; //可以存放字符地址
// 数组指针—指向数组的指针—存放数组的地址
// 数组的地址?? &arr!!!
int arr[10] = {1,2,3,4,5,6,7,8};
// 优先级 []>* 故如果写为 int* p[10]——p是一个数组
// 为了让p是一个指针 请用()
// 理解:p是一个指针 指向一个数组 数组中的每个元素是整形
int(*p)[10] = &arr;
}
请试图理解如下代码,它同时包括了数组指针和指针数组:
int main(){
char* ch_arr[5];
// p是一个指针 指向一个指针数组 数组中的每个元素是char*类型
char* (*p)[5] = &ch_arr;
}
数组指针指向了一个数组,那他当然可以指向一个指针数组,这很合乎逻辑,不是吗?
数组指针——指向数组
当我们试图用数组指针指向一个一维数组:
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int (*pa)[10] = &arr;
printf("%p ",(*pa));// 对pa解引用 可以拿到整个数组
printf("%p ",arr);
printf("%p ",&arr[0]);
// *pa == arr
}
我们可以拿到每一个元素:
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int (*pa)[10] = &arr;
for(int i = 0;i<10;i++){
printf("%d ",(*pa)[i]); // *pa = arr
// 等价于 printf("%d ",arr[i]);
}
printf("\n");
for(int i = 0;i<10;i++){
printf("%d ",*(*pa+i));
// 等价于 printf("%d ",*(arr+i));
}
}
总感觉怪怪的,实现用指针输出数组中的每一个元素我们不早已经掌握了吗?
// 指针指向首元素 而不是指向整个数组 会更好!!!
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = arr;
for(int i = 0;i<10;i++){
printf("%d ",*(p+i));
}
}
所以,我们在实际运用中,应该让数组指针指向二维数组。
遍历输出二维数组(非指针实现):
void print1(int arr[3][5],int x,int y){
for(int i = 0; i<x;i++){
for(int j = 0;j<y;j++){
printf("%d ",arr[i][j]);
}
printf("\n");
}
}
int main(){
int arr[3][5] = {{1,2,3,4,5},{2,4,6,8,10},{1,3,5,7,9}};
print1(arr,3,5); // attention!!!
return 0;
}
在这段程序中,我们需要注意的是,当我们向print1函数传递参数的时候,我们传递的是二维数组的数组名,他应该代表着“二维数组的首地址”。需要注意,二维数组的首地址并不是元素arr[0][0]的地址,而是数组arr[0]的地址。我们要将这个二维数组看作一维数组考虑。
print1(arr[0],3,5);
因此,这里传参数arr和arr[0]是等价的。
遍历输出二维数组(指针实现):
void print2(int(*p)[5],int x,int y){
for(int i = 0; i<x;i++){
for(int j = 0;j<y;j++){
// 以下四种写法完全等价
// 写法一:p就是arr 就是数组名 所以p[i][j]便可以输出arr[i][j]
printf("%d ",p[i][j]);
// 写法二:p[i]是第i个一维数组 p[i]+j是这个数组内第j个元素的地址 解引用即可得到这个元素
printf("%d ",*(p[i]+j));
// 写法三:p+i是第i个一维数组的地址 解引用就是第i个一维数组 *(p+i)+j是这个数组内第j个元素的地址 解引用即可得到这个元素
printf("%d ",*(*(p+i)+j));
// 写法四:p+i是第i个一维数组的地址 解引用就是第i个一维数组 *(p+i))[j]等价于arr[i][j]
printf("%d ",(*(p+i))[j]);
}
printf("\n");
}
}
int main(){
int arr[3][5] = {{1,2,3,4,5},{2,4,6,8,10},{1,3,5,7,9}};
print2(arr,3,5);
return 0;
}
时刻注意[]的优先级比*高,就不会出现加括号上的错误。
练习:这些分别是什么?
1、int arr[5]; 2、int *parr1[10]; 3、int (*parr2)[10]; 4、int (*parr3[10])[5];
解答:
1、整形数组
2、parr1是一个有10个元素的数组,每个元素的类型是int*类型
3、parr2是一个指针,指向了一个有10个元素的数组,这个数组中的元素类型是int
4、parr3是一个有10个元素的数组,每个元素是一个数组指针,该数组指针指向的数组有5个元素,每个元素的类型是int。
3、数组参数和指针参数
写代码时,我们要传递数组的参数和指针的参数,这也是困扰了我很久的问题。
首先,我们从数组的视角来看:
1)讨论整形数组传参。
// 三种方式接受整形数组
void test1(int arr[]){} // 拿数组接收数组 数组大小可以省略
void test2(int arr[10]){} // 拿数组接受数组
void test3(int *arr){} // 传一个数组名 即首元素地址 故接收类型是int*
int main(){
int arr[10] = {0};
test1(arr);
test2(arr);
test3(arr);
}
2)讨论指针数组传参。
void test1(int* arr[]){} // 传递一个指针数组 就拿一个数组指针接收
void test2(int* arr[20]){} // 传递一个指针数组 就拿一个数组指针接收
void test3(int **arr){} // 传入一个一级指针地址 拿二级指针接收
int main(){
int* arr2[10] = {0};
test1(arr2);
test2(arr2);
test3(arr2);
// p2可以接收一级指针的地址
// int a = 20;
// int *p = &a;
// int **p2 = &p;
// printf("%d",**p2);
}
3)讨论二维数组传参。
void test1(int arr[3][2]){} // 拿二维数组接收二维数组
void test2(int arr[][2]){} // 只可以省略行 不能省略列
/* 为什么不能省略列?这里引用CSDN博主 谷歌玩家 的解释
* 将二维数组看成一个个一维数组的组合,则列意味着一维数组的容量;
* 只有确定每个一维数组存放多少个元素才能正确定义二维数组;
* 如果省略列的定义,那么操作系统不知道每个一维数组要分配多少个元素,
* 有可能是平均分配,有可能全都给第一个一维数组分配,这就造成了歧义。
*/
void test3(int (*arr)[2]){} // 用一个数组指针接收 指向一个数组 这个数组每个元素是int
// 恰好与arr[3][2]的第一行一致
int main(){
int arr[3][2] = {0};
test1(arr);
test2(arr);
test3(arr);// 二维数组的数组名表示首个一维数组(第一行)的地址
}
其次,我们从指针的视角来看:
1)讨论一级指针参数:
void test1(int* p){}
int main(){
int a = 20;
test1(&a); // 当然可以传递一个a的地址
int *p = &a;
test1(p); // 传递一个一级指针
}
2)讨论二级指针参数:
void test2(int** pp){}
int main(){
int a = 20;
int *p = &a;
int** pp = &p;
test2(&p);//可以传一级指针地址
test2(pp);//可以传二级指针本身
int* arr[10] = {0};
test2(arr); //指针数组的数组名意味着数组的首元素地址 一个int*变量的地址可以用二级指针接收
}
4、函数指针和函数指针数组
函数也有地址吗?我们写一个简单的加法函数。
int add(int x,int y){
return x+y;
}
int main(){
printf("%p ",add);
printf("%p ",&add);
// 00007ff68b8f1731 00007ff68b8f1731
}
函数也有地址,且与数组不同,add和&add的含义是一样的。既然函数有地址,我们就可以定义指向函数的指针。
int (*pa)(int,int) = add;
// 函数的返回类型 + *指针名 + 函数参数类型
// 如果不加括号 则pa会和(int,int)结合 pa则成为一个函数而不是指针
*pa就是函数,(*pa)(2,3)等价于add(2,3)。
int main(){
int (*pa)(int,int) = add;
(*pa)(2,3);
}
再看一个同样的例子,以加深理解:
#include <stdio.h>
void Print(char* str){
printf("%s",str);
}
int main(){
void (*p)(char *) = Print;
(*p)("tangchujiang");
}
补充:调用函数指针时,*是可以省略的。
int main(){
int (*pa)(int,int) = add;
(*pa)(2,3);
(pa)(2,3);
}
理解了函数指针的概念,我们来看函数指针数组。函数指针数组里存放着若干相同类型的函数的地址。
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 main(){
int (*parr[4])(int,int) = {add,sub,mul,div};
for(int i = 0;i<4;i++){
printf("%d\n",parr[i](6,3));
}
}
函数指针有什么作用呢?我们以下面这个计算器的小程序为例:
四则运算计算器(非函数指针):
#include <stdio.h>
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 main(){
int input = 0;
int x,y = 0;
do {
menu();
printf(">>>>>>>>>>>\n");
scanf("%d",&input);
switch(input){
case 1:
printf("input x y>>>>>\n");
scanf("%d %d",&x,&y);
printf("result = %d\n",add(x,y));
break;
case 2:
printf("input x y>>>>>\n");
scanf("%d %d",&x,&y);
printf("result = %d\n",sub(x,y));
break;
case 3:
printf("input x y>>>>>\n");
scanf("%d %d",&x,&y);
printf("result = %d\n",mul(x,y));
break;
case 4:
printf("input x y>>>>>\n");
scanf("%d %d",&x,&y);
printf("result = %d\n",div(x,y));
break;
case 0:
printf("exit!\n");
break;
default:
printf("wrong!\n");
}
}while(input);
}
他的问题是繁琐、冗余,有着大量重复性操作,我们可以利用函数指针进行简化:
int main(){
int input = 0;
int x,y = 0;
int (*pfArr[5])(int,int) = {0,add,sub,mul,div};
do {
menu();
printf(">>>>>>>>>>>\n");
scanf("%d",&input);
if(input>=1 && input <=4){
printf("input x y>");
scanf("%d %d",&x,&y);
int ret = pfArr[input](x,y);
printf("result = %d\n",ret);
}
else if(input == 0){
printf("exit!");
}
else{
printf("wrong!");
}
}while(input);
}
5、回调函数
理解回调函数的定义之前,我们先思考这个冒泡排序算法:它实现了对一个数组的排序。
void bubble_sort(int arr[],int sz){
for(int i = 0;i<sz-1;i++){ //排序躺数
for(int j = 0;j<sz-i-1;j++){ //每一躺比较的对数
if(arr[j]>arr[j+1]){
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
int main(){
int arr[] = {9,5,2,4,0};
int sz = sizeof(arr)/sizeof(arr[0]);
bubble_sort(arr,sz);
for(int i = 0;i<sz;i++){
printf("%d ",arr[i]);
}
}
函数完美运行了,但是这个函数的问题是不沟通用,比如,当我试图排序一个浮点数数组:
float arr2[] = {1.3,3.3,7.8,0.9,1.4};
比如我想排序学生的年龄,而学生的结构是结构体类型:
struct Stu
{
char name[20];
int age;
};
typedef struct Stu student;
如何解决?我们分析C语言的库函数qsort是如何解决的。
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*)) {}
分析C语言库函数qsort
1)void* 是一个无类型指针,他可以接收任意类型的变量。但不能对他进行解引用操作,因为我们不知道void*指向的到底是int还是char,解引用该访问四个字节还是一个字节。
int a = 10;
char c = 'w';
void* p = &a;
void* q = &c;
// *p = 20; 报错 不能进行解引用
2)size_t nitems接收要比较的元素的数量,size接收每个元素的大小;
size_t是标准C库中定义的,在64位系统中为long long unsigned int,非64位系统中为long unsigned int,即表示了表示C中任何对象所能达到的最大长度。
为什么要接收每个元素的大小?这是一种隐晦传类型的方式。因为C语言无法直接传数据的类型,所以通过传递每个元素的大小体现类型。
3)int (*compar)(const void *, const void*):compare是一个函数指针,它接受两个元素的地址。由于这两个元素的类型未知,所以我们是用void*接收的。
compare函数的返回值是int,那自然正数,0,负数就对应着三种不同的大小关系。
compare函数的具体实现正是我们要定义并传入的。
排序整形数组:
#include <stdio.h>
#include <stdlib.h> //qsort在此头文件中
int cmp_int(const void* e1,const void* e2){
// 无法对void*类型解引用 就先进行强制类型转换
return *(int *)e1 - *(int*)e2;
}
int main(){
int arr[] = {9,5,2,4,0};
int sz = sizeof(arr)/sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_int);
for(int i = 0;i<sz;i++){
printf("%d ",arr[i]);
}
}
排序浮点数数组:
int cmp_float(const void* e1,const void* e2){
// 无法对void*类型解引用 就先进行强制类型转换
return *(float *)e1 - *(float*)e2;
}
int main(){
float arr[] = {1.3,3.3,7.8,0.9,1.4};
int sz = sizeof(arr)/sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_float);
for(int i = 0;i<sz;i++){
printf("%.2f ",arr[i]);
}
排序结构体:
struct Stu
{
char name[20];
int age;
};
typedef struct Stu student;
int cmp_struct_by_age(const void* e1,const void* e2){
return ((student*)e1)->age-((student*)e2)->age;
}
int main(){
student stu[] = {{"zhangsan",20},{"lisi",5},{"tangchujiang",30}};
int sz = sizeof(stu)/sizeof(stu[0]);
qsort(stu,sz,sizeof(stu[0]),cmp_struct_by_age);
for(int i = 0;i<sz;i++){
printf("%s %d\n",stu[i].name,stu[i].age);
}
}
了解了C语言自带的qsort函数后,我们将自己的冒泡排序函数完成:
#include <stdio.h>
void swap(char* buf1,char* buf2,int width);
void bubble_sort(void* base,int sz,int width,int (*cmp)(void* e1,void* e2)){
for(int i = 0;i<sz-1;i++){
for(int j = 0;j<sz-i-1;j++){
if(cmp((char*)base+j*width,(char*)base+(j+1)*width)>0){
// 这就是传入宽度的意义——模拟类型
swap((char*)base+j*width,(char*)base+(j+1)*width,width);
}
}
}
}
void swap(char* buf1,char* buf2,int width){
for(int i = 0;i<width;i++){
// 交换方式:一个一个字符进行交换
char temp = *buf1;
*buf1 = *buf2;
*buf2 = temp;
buf1++;
buf2++;
}
}
int cmp_int(const void* e1,const void* e2){
// 无法对void*类型解引用 就先进行强制类型转换
return *(int *)e1 - *(int*)e2;
}
int main(){
int arr[10] = {1,3,5,7,9,2,4,6,8,10};
int sz = sizeof(arr)/sizeof(arr[0]);
bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);
for(int i = 0;i<sz;i++){
printf("%d ",arr[i]);
}
}
函数回调不是什么高大上的概念:把一段可执行的代码像参数传递那样传给其他代码,而这段代码会在某个时刻被调用执行,这就叫做回调。
如果一个函数的名字被当做参数使用,那么这个函数就可以叫做回调函数。
三、练习与巩固
后续更新!不断更新!
1、一维数组与指针
int main(){
int a[4] = {1,2,3,4};
// sizeof(数组名)表示整个数组
// &(数组名)表示整个数组
// 除此之外 所有的数组名都代表首地址!!
printf("%d\n",sizeof(a)); // 16
printf("%d\n",sizeof(a+0)); // 8(32位计算机为4)a+0 仍未首元素地址
printf("%d\n",sizeof(*a)); // 4 a是首元素地址 *a找到首元素 首元素有4个字节
printf("%d\n",sizeof(a+1)); // 8
printf("%d\n",sizeof(a[1])); // 4
printf("%d\n",sizeof(&a)); // 8 &a是数组的地址 不是首元素的地址 但他仍为一个地址
printf("%d\n",sizeof(*&a)); // 16 这样才能找到数组
printf("%d\n",sizeof(&a + 1)); // 8
//无论是谁的地址 只要是地址 就是8位(4位)
printf("%d\n",sizeof(&a[0])); // 8
printf("%d\n",sizeof(&a[0] + 1)); // 8
}
2、二维数组与指针
int main(){
int a[3][4] = {0};
printf("%d\n",sizeof(a)); // 48 共有12个int类型
printf("%d\n",sizeof(a[0])); // 16 共4个int
// 相当于把数组名单独放在sizeof()中 计算的是第一行的大小
printf("%d\n",sizeof(a[0][0])); // 4 第一个
printf("%d\n",sizeof(a[0]+1));// 8
// a[0]出现在表达式里面表示第一个元素的地址 a[0]+1表示了第一行第二个元素的地址
printf("%d\n",sizeof(*(a[0]+1)));// 4
printf("%d\n",sizeof(a+1));// 8
// a是二维数组的数组名 代表了“首元素”地址 但二维数组的首元素抽象为第一个一维数组(第一行)
// 故(a+1)代表了第二个一维数组的地址
printf("%d\n",sizeof(*(a+1)));// 16
printf("%d\n",sizeof(&a[0]+1));// 8
// a[0]是第一行的数组名 &a[0]是第一行的地址 &a[0]+1是第二行的地址
printf("%d\n",sizeof(*(&a[0]+1)));// 16
printf("%d\n",sizeof(*a));// 16
// a是第一行地址
printf("%d\n",sizeof(a[3]));// 16
}