字符指针
看下面的程序
void t() {
char* a = "hello";
*a = 'x';
printf("%c", a);
}
程序想把a的指向的值 ‘h’改成‘a' ,但结果引发异常,这是因为右边是常量,常量存放在在内存的常量区,是只读的。a指向这个常量的首位想改变*a,但常量是无法改变的,因此报错。
这里可以在指针变量前面声明这是常变量,预防异常。
void t1() {
const char* a = "hello";
const char* b = "hello";
if (a == b) printf("aEqualb.\n");
char c[] = "hello";
char d[] = "hello";
if (c == d) printf("cEquald.");
}
结果输出
aEqualb.
同样的常量字符串只在内存保存一份,指针共同指向这一块内存。
字符数组,在内存开辟2个数组,存的虽然是同样的内容,但是地址不是相同的。
即栈区:a,b,c,d 。其中:a,b指向的常量区的字符串"hello"。
指针数组跟数组指针
void t3() {
int* p1[10];//*与int先结合,为指针数组 存放指针(地址)的数组
int(*p2)[10]; //数组指针,指向数组的指针 跟 int* p一样?
int a[20] = { 0 };
p2 = a;
printf("%d\n", *(p2 + 15));
printf("%d", **(p2+15));
}
输出
12122996
0
第一个输出p2指向的数组首地址,第二个输出p2指向数组首地址指向的值。
数组首地址与数组地址的区别
int arr[2] = { 0 };
printf("%d\n", arr);
//printf("%d\n", &arr[0]);
printf("%d\n\n", &arr);
printf("%d\n", arr+1);
//printf("%d\n", &arr[0]+1);与上一条等效
printf("%d", &arr+1);
输出
7338352
7338352
7338356
7338360
可以看到首地址跟数组地址是一样的,但是+1时,往后一步的距离是不同的。数组首地址往后一个元素大小为一步(52+4=56),数组按数组大小往后一步(52+2*4=60)。
差异的根本就是因为指针类型的不同。
arr,&arr[0](数组的首地址)的类型是int * ,而&arr(数组的地址)的类型是int*[ ]
数组指针的写法
int (*p) [10] = &arr
让p 与*先结合说明这是个指针p,再让 int 与 [ ] 结合说明他的类型为数组。
int (*)[10] 即数组类型指针
再看一段代码
int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];
第一条指 int类型数组
第二条指 int指针类型数组
第三条指 in型数组指针
第四条 ,parr3与[10]结合为数组,再与*结合成指针数组:*parr3[10] 。
数组里面是什么类型的指针?把parr3[10]去掉 → int (*) [5] 大小5的int型数组指针
即大小为5的int型数组指针数组,大小为10 ,每个元素类型为 int (*) [5]
数组传参
形参的形式:
数组形式
数组名
除2个情况外,数组名都表示首地址。
1.sizeof(arr) arr表示整个数组
2.&arr arr表示整个数组的地址
把数组地址作为参数,且不计算数组大小,打印每个元素
//不传数组大小,打印每个元素
void t333(int (*arr)[10]) {
int* p = *arr;
while (p < arr+1) {
printf("%d", *p);
p++;
}
}
一维数组传参不需要写大小,写多少都不影响。这是因为数组传参时,本质上传的指针,而其读取范围只跟实参数组本来大小有关,跟形参无关。
a[10];
void a(arr[]){
}
void a(arr[10]){
}
void a(arr[100]){
}
指针形式
void (int* p){
}
二维数组的传参
int a[3][5] = { { 1,2,3 },{2,3,4,5} };
test(a);
因为数组名表示的是数组的首地址,所以a表示,a[3][5]的首地址,a[0]即表示第一行数组(所有数)的指针。其类型表示为 int (*p)[5]
下面的程序,传入二维数组的列指针p,输出每个元素
p+i表示第i行数组地址, *(p+i)表示i行第0个 , *(p+i)+j表示i行第j个的地址
void test(int (*p)[5]) {
int i = 0, j = 0;
for (i=0; i < 3; i++) {
for (j=0; j < 5; j++) {//p+i表示第i行数组地址,*(p+i)表示i行第0个,*(p+i)+j表示i行第j个的地址
printf("%d", *(*(p + i)+ j));
//printf("%d",p[i][j]);编译器最终把这个写法转为上一条写法
}
printf("\n");
}
}
输出
12300
23450
00000
可以对地址变量取地址,但不能对地址取地址
void te(char** a)
{
}
int main(){
char d = 'a';
char* w = &d;
char** s = &w;
te(s);
te(&(&d));//错误
}
二维数组指针传参不同形式
void t0(int* p) {
//报错,因为是按一个int类型的指针传进来的
//printf(p[1][2]);
printf("t0函数:\n%d\n", *(p + 2));//拿到3,也就是[0][2]的位置
printf("%d\n", *(p + 5));//拿到6,说明数组元素隔了一定字节大小放置
//printf("%d\n", *(p + 6));
}
void t3(int(*p)[5]) {
//每一行5个
printf("t3函数\n%d\n",p[0][2]);//不报错,因为类型是一行的指针
printf("%d\n", **(p + 1));//2次解引用拿一个int,等价于上面的*(p+5)
}
void t33(int(*p)[3][5]) {
//传的是一整个数组,是3行五列
}
int main() {
//t();
int arr[3][5] = { {1,2,3 }, {6,5,4} };
t0(arr);//传arr第一行,但是被转成了int*
t3(arr);//传arr第一行,解引用可以得到int*
t33(&arr);//可以传整个数组
}
输出
t0函数:
3
6
t3函数
3
6
函数指针
函数的地址同样可以用取地址符号拿到
int Add(int c,int a){
}
&Add;
其中&Add也可以写成Add
函数地址的类型, 首先指针 *pf,参数类型为int ,int ,返回类型为int
所以 函数指针变量: int (*pf)(int,int) =Add
通过地址找/调用函数,同样可以通过解引用实现:
int num = (*pf) (3,233);
可以写成
int num = pf (3,233);
因为,pf就等于*pf ,同&Add等于Add。
再看个例子:
void (*signal(int , void(*)(int)))(int);
函数名+参数
signal(int , void(*)(int))
去掉后就是函数的类型,返回值为函数指针类型
void (*)(int);
typedef void (*tf) (int);//重命名,但名字要在*后面
tf signal(int, tf);
函数指针数组
名字后面加方块就构成数组
void t1(int a) {
}
void t2(int b) {
}
void (*a1)(int) = t1;
void (*a2)(int) = t2;
void (*a[])(int) = { a1,a2 };
//void (*)(int);去掉函数名,方块就是类型
指向函数指针数组的指针
再看看如何表示指向函数指针数组的指针
int Arr1[3]={1,2,3};
int* Arr2[3];
指向Arr1的指针: 先声明这个指针(*pArr1),指向的是数组,(*pArr1)[3],再去掉名字Arr1得到类型,即 int (*pArr1) [3] 。
指向Arr2的指针:同样,声明(*pArr2),指向的是数组,(*pArr1)[3],去名得类型,即int* (*pArr2)[3] 。
int (*pArr1) [3] =&Arr1;
int* (*pArr2) [3] =&Arr2;
同样的函数指针数组,现有个函数指针数组 ,要表示指向函数指针数组的指针
void (*pfArr[4])(int);
指向ppfArr的指针: 先声明这个指针(*ppfArr),指向的是数组,(*ppfArr)[4],去名得类型,
即:void(*(*ppfArr)[4])(int)
void(*(*ppfArr)[4])(int)=&pfArr;
当然可以通过解引用拿到ppfArr数组的地址,再通过方块[]/解引用方式访问每一个元素。
回调函数
下面用加减函数案例做对比
int Add(int x, int y) {
return x + y;
}
int Sub(int x, int y) {
return x - y;
}
int main() {
int input;
int x, y;
int ret = 0;
scanf("%d", &input);
switch (input)
{
case 1:
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("%d\n", ret);
break;
case 2:
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("%d\n", ret);
break;
default:
break;
}
}
根据每次输入,判断要执行什么函数,但是重复的函数有很多。特别是当同类函数越多的时候,比如再增加乘除运算的函数,重复代码也越多。这里就可以用回调函数减少代码量。(用上面的函数指针数组也可以)
int Add(int x, int y) {
return x + y;
}
int Sub(int x, int y) {
return x - y;
}
void ret(int (*pf)(int ,int )) {
int ret = 0;
int x, y;
scanf("%d %d", &x, &y);
ret=(*pf)(x, y);//可以把*号去掉
printf("%d\n", ret);
}
int main() {
int input;
scanf("%d", &input);
switch (input)
{
case 1:
ret(&Add);
break;
case 2:
ret(&Sub);
break;
default:
break;
}
}
void*
int a=0;
float* pa=&a;
生成时会警报
函数指针.c(58,1): warning C4133: “初始化”: 从“int *”到“float *”的类型不兼容
int a = 0;
//float* pa = &a;
void* pb = &a;
用void* 就不会报错,这是因为void* 是不确定指向类型的指针,任何类型指针都能存进void*里。但是因为void*没有访问权限,解引用/跟操作数+-运算会有问题,因此解引用需要先转换类型。
函数指针的应用:
实现类似,可以排序任意类型的库函数qsort的排序函数bubble sort。把比较规则封装成函数,每次只要改变函数指针,就能排序不同类型的数据。
int cmp_by_int(const void* e1, const void* e2) {
return (*(int*)e1 - *(int*)e2);
}
int cmp_by_short(const void* e1, const void* e2) {
return (*(short*)e1 - *(short*)e2);
}
int cmp_by_short_reverse(const void* e1, const void* e2) {
return (*(short*)e2 - *(short*)e1);
}
//按一个字节去交换
void Swap(char* b1, char* b2,int width) {
//交换[宽度]次
while (width--) {
char tmp = *b1;
*b1 = *b2;
*b2 = tmp;
//8 : 08 00 00 00
//7 : 07 00 00 00
b1++;
b2++;
}
}
//数组地址,元素个数,类型大小(宽度),比较规则,利用回调函数回去找规则
void bSort(void* base,int n,int width,int (*cmp)(const void* e1,const void* e2)){
for (int i = 0; i < n-1;i++) {
//j从0开始
for (int j = 0; j < n-1-i ;j++) {
//这里只要得到,元素的首地址传给比较函数,不需要元素的整个地址
//如int不用传int*
//if(arr[j]-arr[j+1]>0)比较arr[j],arr[j+1]
if (cmp((char*)base + width*j, (char*)base + width*(j+1))>0) {
Swap((char*)base + width * j, (char*)base + width * (j + 1),width);
}
}
}
}
int main()
{
int arr[] = { 1,4,2 ,5,3,66,61,6};
bSort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(int), cmp_by_int);
//debug测试没有问题,顺序排序
short arr2[] = { 1,4,2 ,5,3,6,21,66 };
bSort(arr2, sizeof(arr2) / sizeof(arr2[0]), sizeof(short), cmp_by_short);
//debug里看
bSort(arr2, sizeof(arr2) / sizeof(arr2[0]), sizeof(short), cmp_by_short_reverse);
return 0;
}
练习
一、
int a[3][4] = { 0 };
//&a:整个数组的地址;a:第一行的地址;a[0]第一行第一个的地址;a[0][0]第一行第一个地址的值
// 只有上面名字这些被sizeof操作时,才会被当作整个数组的大小
//
//&a+1:跳过整个数组的下一个地址;a+1:第二行的地址;
//a[0]+1第一行第二个的地址;a[0][1]第一行第二个地址的值
//
//*(&a+1):跳过整个数组的下一个地址的值(随机值); *(a+1):第一行第二个的地址;
//*(a[0]+1)第一行第二个地址的值;*(a[0][1])不是地址不能解引用
注意a是首行地址,a[0]/*a是第一行第一个的地址
二、
int a[4] = { 1,2,3,4 };
int* p1 = (int*)(&a + 1);
//按数组跳
int* p2 = (int*)((int)a + 1);
//把地址加一个字节
//假设 01 00 00 00 02 00 00 00
//p2指的就是 ↑01后的第一个00
printf("%0X,%0X\n", *(p1 - 1), *p2);
//p1是int*,步长为4,往前走一个int拿到a[3]
//p2存的是 00 00 00 02 即0x02000000
printf("%d,%d", *(p1 - 1), *p2);
//转成10进制打印
三、
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
结果
FFFFFFFC,-4
图解:
0x00CFF960 -0x00CFF960
按地址打印就是FFFFFFFC,按%d打印就是-4
四、
int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
return 0;
}
图解:
cpp[-2] 等价于*(cpp-2)
结果
POINT
ER
ST
EW