C语言-指针进阶
一、字符指针
在指针类型中,有一种指针类型为char*。
一般的使用方式:
char ch = 'a';
char* pc = &ch;
*pc = 'w';
还有一种使用方式如下:
const char* p = "abcdef";
//这里的"abcdef"是一个常量字符串,是存储在代码段(常量区)中的
//这里的意思是把"abcdef"首字符的地址放入了p中
//const限制的是*p,也就意味着*p不能被改变,或者说"abcdef"这个常量字符串不能被改变
并且常量字符串不允许被修改,并且只会在常量区存在一份。
二、指针数组
指针数组,本质上是一个数组,里面存放的类型是指针。
int* arr[10]; //一级指针数组,数组里面存放的是int*
char* arr1[10]; //一级指针数组,里面存放的是char*
char** arr2[10]; //二级字符指针数组,里面存放的是char**
三、数组指针
3.1 数组指针的定义
数组指针本质上是一个指针,是一个能指向数组的指针。
int arr[10] = { 0 };
//arr的类型是int [10]
int(*p)[10] = &arr;
//p和*结合说明p是一个指针变量,指向的类型是int [10]也就是arr的类型,所以p是一个数组指针
//[]的优先级是要比*的优先级高的,所以*p需要用(),以保证*和p的结合性
3.2 &数组名和数组名
一般来说数组名通常是指数组首元素的地址,但有两个例外sizeof(数组名)和&数组名,这里的两个数组名是指整个数组。
所以说&数组名是指取出整个数组的地址。
实际上:&arr是整个数组的地址,而不是数组首元素的地址。这个例子中&arr的类型是int(*)[10],是一种数组指针类型。&arr和&arr+1的差值是40,跳过的是整个数组的大小。
3.3 数组指针的使用
一般数组指针会经常使用在二维数组的传参上。
一位数组的传参,形参部分可以是数组,也可以是指针。
//数组传参
void test(int arr[10])
{
}
//指针传参
void test1(int* arr)
{
}int arr[10];
test(arr);
test1(arr);
二维数组的传参,形参部分可以是数组,也可以是指针。
//数组传参
void test(int arr[5][10])
{
}
void test1(int arr[][10]) //可以省略行,但不能省略列
{
}
//指针传参
void test2(int(*arr)[10])
{
}int arr[5][10];
test(arr);
test1(arr);
test2(arr);
一维数组和二维数组传参都是传首元素地址,一位数组首元素的地址是一维数组类型的地址,二维数组首元素的地址是一维数组的地址,也就是数组指针。
int arr[5]; //这是一个整型数组,有5个元素
int* parr1[10]; //这是一个指针数组,有10个元素,元素类型是int*
int(*parr2)[10]; //这是一个数组指针,指针指向的类型是int [10]
int(*parr3[10])[5]; //这是一个数组指针数组,有10个元素,元素类型是int [5]
四、数组参数、指针参数
4.1 一维数组传参
void test(int arr[10])
{}
void test1(int arr[])
{}
void test2(int* arr)
{}
void test3(int* arr1[10])
{}
void test4(int** arr1)
{}int arr[10];
test(arr);
test1(arr);
test2(arr);
int* arr1[10];
test3(arr1);
test4(arr1);
4.2 二维数组传参
void test(int arr[5][10])
{}
void test1(int arr[][10]) //可以省略行,但不能省略列
{}
void test2(int(*arr)[10])
{}int arr[5][10];
test(arr);
test1(arr);
test2(arr);
4.3 一级指针传参
void test(int* p)
{}int a = 10;
int* p = &a;
test(p);
当参数为一级指针时,可以接收的参数为:
void test(char* p)
{}
void test1(const char* p)
{}char ch;
char* pc = &ch;
char arr[10];
test(&ch); //可以传字符的地址
test(pc);
test(arr); //也可以传字符数组首元素的地址
const char* p = "abcdef";
test1(p); //也可以传常量字符串的地址,但类型需要匹配
4.4 二级指针传参
void test(char** p)
{}char ch;
char* pc = &ch;
char** ppc = &pc;
test(ppc);
当参数为二级指针时,可以传递的参数为:
void test(char** p)
{}char ch;
char* pc = &ch;
char** ppc = &pc;
test(&pc); //可以传一级指针的地址
test(ppc);
char* arr[10];
test(arr); //也可以传一级指针数组的首元素的地址
五、函数指针
函数名是函数的地址。
&函数名同样也是函数的地址。
函数地址的存储:
int Add(int x, int y)
{return x + y;
}//Add函数的类型是函数去掉函数名后剩余的部分,也就是int (int x,int y)
int(*pf)(int, int) = &Add; //这里参数可以省略,但参数类型不能省略
//*和pf结合说明pf是一个指针,pf指向的是int (int,int)的类型
//pf就是一个函数指针变量,存放的是函数的地址
注:函数去掉函数名就是函数的类型,和数组一样,数组去掉数组名也是数组的类型。
函数指针的使用:
//函数指针可以通过解引用调用,也可以直接调用
(*pf)(3, 5); //解引用调用
pf(4, 8); //直接调用函数指针
数组指针和函数指针的重命名:
//数组指针的重命名,这里的操作是将数组指针int (*)[10]重命名为parr_t
typedef int(*parr_t)[];
//函数指针的重命名,这里的操作是将函数指针int(*)(int,int)重命名为pf_t
typedef int(*pf_t)(int, int);
例如:
(*(void(*)())0)();
//void(*)()是一个函数指针,没有参数,返回值是void类型
//(void(*)())0是将0强转成一个void(*)()类型的函数指针
//(*(void(*)())0)()是指将0强转后然后进行解引用调用void(*signal(int, void(*)(int)))(int);
//signal是一个函数,有两个参数,参数类型分别为int和void(*)(int)
//signal函数的返回类型为void(*)(int)
六、函数指针数组
函数指针数组是一个数组,里面存放的元素类型是函数指针。
函数指针的定义:
//函数指针数组正确的定义方式:
int(*parr[10])();
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;
}//Add、Sub、Mul、Div这四个函数类型相同是int (int,int)
//这四个函数的函数指针是int(*)(int,int)
//因为函数名就是函数的地址,那么存放这四个函数的数组就是函数指针数组
int(*p[4])(int, int) = {Add,Sub,Mul,Div};
//p和[4]结合说明p是一个数组,数组元素类型是函数指针int(*)(int,int)
函数指针数组的用途:转移表。
七、指向函数指针数组的指针
数组:
int arr[10];
//数组的类型:int [10]
指针数组:
int* arr[10];
//指针数组的类型:int* [10]
数组指针:
int(*p)[10];
//指针指向的类型:int [10]
//指针的类型:int(*)[10]
指向指针数组的指针:
int* (*p)[10];
//指针指向的类型:int* [10]
//指针的类型:int*(*)[10]
函数:
int Add(int x,int y);
//函数的类型:int (int,int)
函数指针:
int(*p)(int, int) = &Add;
//函数指针的类型:int(*)(int,int)
函数指针数组:
int(*p[10])(int, int);
//函数指针数组的类型:int(* [10])(int,int)
//函数指针数组内元素的类型:int(*)(int,int)
指向函数指针数组的指针:
int(*(*p)[10])(int, int);
//指针指向的类型:int(* [10])(int,int)
//指针的类型:int(*(*)[10])(int,int)
总结:去掉名字就是类型,将名字换成(*p),p就是指向这个类型的指针了。
八、回调函数
回调函数就是一个调用函数指针的函数。当把函数指针当做参数传给另外一个函数时,当这个指针被用来调用其指向的函数时,这就是回调函数。回调函数不是由该函数实现方直接调用,而是在特定的事件或条件下由另外一方调用的,用于对该事件的响应。
例如qsort中的回调函数:
#include<stdlib.h>void qsort(void* base, //数组首元素地址size_t num, //数组元素个数size_t size, //数组每个元素的大小,单位字节int (*compar)(const void*, const void*) //比较函数,需要自己实现,qsort内部会进行调用);
#include<string.h>
#include<stdlib.h>
struct Stu
{char name[20];int age;
};
//比较函数
int cmp_by_name(const void* p1, const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
int main()
{//结构体数组struct Stu arr[3] = { {"zhangsan",19},{"lisi",25},{"wangwu",18}};//利用qsort对arr的name进行排序int sz = sizeof(arr) / sizeof(arr[0]);//qsort用比较函数的函数指针作为参数,在内部进行调用qsort(arr, sz, sizeof(arr[0]), cmp_by_name);return 0;
}
按名字排序后的结果: