C语言——指针进阶

在这里插入图片描述
本章重点

  1. 字符指针
  2. 数组指针
  3. 指针数组
  4. 数组传参和指针传参
  5. 函数指针
  6. 函数指针数组
  7. 指向函数指针数组的指针
  8. 回调函数
  9. 指针和数组面试题的解析

1. 字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char*

int main()
{
    
    
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}

还有一种方式是

int main()
{
    
    
const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
printf("%s\n", pstr);
return 0;
}

这样就能完整打印整个字符串了。
但是我们不是把整个字符串放到pstr,而是放了字符串首元素的地址。

上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。

下面看一道笔试题。

#include <stdio.h>
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;
}

我们运行起来的结果是什么呢,让我们看一下编译器给的结果吧。
在这里插入图片描述
这里原因是我们第一个是数组,数组创造会开辟空间的,而开辟的是两个不同的空间,这样他们的地址肯定也是不同的,而第二个为什么相同呢,首先我们的hello bit.是常量字符串,只给常量字符串开辟了空间所以str3和str4是指向同一个空间的,这样的话地址是相同的,我们这道题其实就是比较他们的地址是不是相同的。我们也可以通过调试来看到我们想要的结果。
在这里插入图片描述
这样就很明显的可以看出我们给数组的时候是重新分配空间的。

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当
几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化
不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。

#include<stdio.h>
int main()
{
    
    
	int a = 10;
	int* p = &a;
	int* ps = &a;
	return 0;
}

在这里插入图片描述
其实就是和上面这个代码一样的效果,这样大家可能就好理解了。
2. 指针数组

在我们学这个的时候,大家经常搞混,一下子字符数组,一下字指针数组,还有数组指针,其实我们只要抓住它是什么就可以解决好多问题,比如数组指针,他就是指向数组的指针,那相反的话,指针数组就是存放指针的数组。

在《指针》章节我们也学了指针数组,指针数组是一个存放指针的数组。
这里我们再复习一下,下面指针数组是什么意思?

int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

3. 数组指针
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。

int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?

首先p1先和括号联系,在和联系,那表示的意思就是指针数组,元素个数是10个。
p2就是先和
联系,在和[]联系,那就是代表指向数组的指针。

//这里要注意:[]的优先级要高于号的,所以必须加上()来保证p先和结合

3.2 &数组名VS数组名
这个内容其实我们在之前就讲过好几次了,数组名是首元素的地址,除了两个特例之外,一个是&,一个是sizeof,这两个代表整个数组。

#include <stdio.h>
int main()
{
    
    
int arr[10] = {
    
    0};
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}

看上面这个例子。
在这里插入图片描述
我们可以看到他们的内容虽然是一样的,但是效果就是不同,让我们来看看他们+1的结果。
在这里插入图片描述
很明显不一样了,这是因为取出地址+1是跳过整个数组。

实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是(16进制是28)40,刚刚好整个数组。

3.3 数组指针的使用
那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

#include <stdio.h>
int main()
{
    
    
int arr[10] = {
    
    1,2,3,4,5,6,7,8,9,0};
int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
//但是我们一般很少这样写代码
return 0;
}

我们用数组指针来打印数组。

#include<stdio.h>
void print_arr(int(*arr)[5], int row, int col)
{
    
    
	int i = 0;
	for (i = 0; i < row; i++)
	{
    
    
		int j = 0;
		for (j = 0; j < col; j++)
		{
    
    
			printf("%d ", arr[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(arr, 3, 5);
	return 0;
}

在这里插入图片描述
学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:

int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];

第一个就是存放int 类型的数组。
第二个就是存放int的数组,指针数组。
第三个就是我们数组指针。
第四个parr3先和[10]结合,然和在和
结合,所以parr3它的意思就是指向数组的数组指针

画个图让大家更好的理解

在这里插入图片描述
4. 数组参数、指针参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

4.1 一维数组传参
在这里插入图片描述
那我们看上面的传参那些结果是正确的吧
从上往下,第一个数组传参,以数组的形式接收,肯定对,其实它的本质就是int* arr,
下一个[]中的参数其实没有意义,甚至可以乱写,所以也对,
第三个就是我们说i的本质。
那我们再看看下面的指针数组传参是怎样的。
第四个也是对,我们以数组的形式来接收。
第五个也是对的,我们认为后一个*表示的是它是一个指针,指向的内容是是int 的,也可以认为arr是属于int*
4.2 二维数组传参

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);
}

那再让我们来看看这个二维数组传参。
我直接写代码里面了,这样看起来就能一一对应

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);
}

我们上面讲过二维数组的传参,所以判断起来就快一点了

4.3 一级指针传参
其实对于这个问题,我们只要掌握一个诀窍就可以了,一级指针传用一级指针接收,二级指针传参用二级指针来接收,但是一级指针的地址得用二级指针来接收。

#include <stdio.h>
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]);
//一级指针p,传给函数
print(p, sz);
return 0;
}

思考 当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

void test1(int *p)
{
    
    }
//test1函数能接收什么参数?
void test2(char* p)
{
    
    }
//test2函数能接收什么参数?
void test1(int *p)
{
    
    }
//test1函数能接收什么参数?
可以是一维数组的数组名,可以是一个常量的地址,可以是一级指针
void test2(char* p)
{
    
    }
//test2函数能接收什么参数?
字符指针,字符数组的数组名,一个字符的地址

4.4 二级指针传参

#include <stdio.h>
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;
}

当函数的参数为二级指针的时候,可以接收什么参数?

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. 函数指针

我们先看一下下面的代码

#include <stdio.h>
void test()
{
    
    
printf("hehe\n");
}
int main()
{
    
    
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}

我们看看地址输出的是什么
在这里插入图片描述
其实地址是一样的,我们函数加取地址其实和没加一样,都是对的
输出的是两个地址,这两个地址是 test 函数的地址。
那我们的函数的地址要想保存起来,怎么保存?
下面我们看代码

void test()
{
    
    
printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();

pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参
数,返回值类型为void。

其实函数的指针和我们数组指针特别相似,我们下面看两个特别有趣的代码,有点难度,但是有趣
在这里插入图片描述

//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

那我们先看第一代码,先给结论,在0这个地址给了一个函数指针,没有返回类型,参数也没有
我们0之前有个大括号,这明显就是强制类型转换,在0这个地址放一个函数指针的类型,然后调用这个函数

下一个是signal这个指针。
我们拆分开来signal(int , void()(int)),先看这个,是一个函数,函数第一个参数是int 第二个参数是一个函数指针,类型就是void()(int),然后在看signal外面有一个*,代表就是一个指针,返回是void 参数是int 其实本质上就是一个函数指针。

typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

也可以这样写。
今天的分享就到这里,我们下次见!!!!

猜你喜欢

转载自blog.csdn.net/2301_76895050/article/details/132188574