指针第一篇,万人浏览:
https://blog.csdn.net/weixin_46913665/article/details/120402568
指针的主题,我们在初级阶段的《指针》章节已经接触过了,我们知道了指针的概念:
1.指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。
4. 指针的运算
这个章节,我们继续探讨指针的高级主题
在指针的类型中我们知道有一种指针类型为字符指针 char* 。
思考以下代码,pc和p分别指向何方?
int main()
{
char ch = 'www';
char *pc = &ch;//pc是指向一个字符变量的
const char* p = "hello boy";//"hello boy"是一个常量字符串
//上面表达式的作用是:把常量字符串“hello bit”的第一个字符h的地址赋给p(指向首字符地址)
return 0;
}
代码 char* pstr = “hello bit.”; 特别容易让同学以为是把字符串 hello boy 放到字符指针
p里了,但是/本质是把字符串 hello boy首字符的地址放到了p中。
思考下面代码,输出的是什么?
int main()
{
char str1[] = "hello boy.";
char str2[] = "hello boy.";
//两个字符数组,独立开辟空间。
//数组名是数组首元素地址
char *str3 = "hello boy.";
char *str4 = "hello boy.";
//二者都是指向常量字符串,(常量字符串,是不能修改的)
//二者指向的是同一个地址
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;
}
指针数组是指针还是数组?
答案是:数组。
数组指针是指针还是数组?
答案是:指针。
举个例子:
int *p1[5]; //指针数组
int (*p2)[5]; //数组指针
二者形式很相似,那么我们如何区分呢?
【指针数组】
首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身 决定。它是“储存指针的数组”的简称。
指针数组是数组元素为指针的数组(例如 int *p[5],定义了p[0],p[1],p[2],p[3],p[4]五个指针),其本质为数组。
int *p[5];
这里涉及到一个优先级的问题。
我们知道数组下标的优先级比取值运算符的优先级高。所以,p先被定义为具有5个元素的数组。它的类型是int*,所以它是指向整型变量的指针。
指针数组如何初始化呢?
int main()
{
//char *arr[5];//arr是存放字符指针的数组
//int * arr2[4];//arr2是存放整型指针的数组
int a = 10;
int b = 20;
int c = 30;
int d = 40;
//int* int* int* int*
int * arr2[4] = {
&a, &b, &c, &d };//arr2就是整型指针的数组
printf("%d\n", *arr2[0]);//取出第一个地址的内容
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d\n", *(arr2[i]));
}
return 0;
}
数组指针中&a,&b,&c,&d分别指向10,20,30,40
大家有没发现,如果这样定义的话,会有些繁琐。
所以我们可以采用以下的方法:
int main()
{
const char* arr[5] = {
"abcedf", "bcedfg", "hehe" ,"hhh","zhangsan"};
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%s\n", arr[i]);
}
return 0;
}
【数组指针】
首先它是一个指针,它指向一个数组。在 32 位系统下永远是占 4 个字节,
至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。
数组指针是指向数组地址的指针,其本质为指针。
int (*p)[5];
在上面代码中,圆括号和数组下标位于同一优先级队列,所以从左到右执行。
因此,p先被定义为一个指针变量,后边[5]表示的是一个具有5个元素的数组,p指向的就是这个数组。
由于指针变量的类型事实上就是它所指向的元素的类型,所以这个int定义数组元素的类型为整型。
int main()
{
int a = 10;
int*pi=&a;//整型的地址存放到整型指针中
char ch = 'w';
char* pc=&ch;//字符的地址存放在字符指针中
int arr[10] = {
0 };
int*p = arr;//arr-是数组首元素的地址
//int* parr[10]; //这样写是数组
int (*parr)[10]=&arr;//取出的是数组的地址,应该存放到数组指针中
return 0;
}
那么我们如何进行初始化呢?
我们在学习指针的时候,是将指针指向数组名,因为数组名是数组首元素地址,知道了第一个元素的地址,后面的元素就可知道。如下:
int main()
{
int arr[] = {
1, 2, 3, 4, 5 };
int *p = arr;
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d\n", *(p + i));
}
return 0;
}
所以,上面的指针p是一个指向整型变量的指针,它并不是指向数组的指针。而数组指针,才是指向数组的指针。
所以,在初始化的时候,应该将数组的地址传递给数组指针,而不是传递数组第一个元素的地址。它们值虽然相同,但含义不一样。
int main()
{
int arr[] = {
1, 2, 3, 4, 5 };
int (*p)[] = &arr;
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d\n", *(*p + i));
}
return 0;
}
我们以arr和&arr来举例说明:
a,&a 的值是一样的。
但意思不一样,
a 是数组首元素的首地址,也就是 a[0]的首地址。
&a 是数组的首地址,表示的是数组的地址。
例如:
int main()
{
int arr[5] = {
0 };
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
可以看到,它们的值是一样的。
但是,如果它们+1呢?
如下:
#include
int main()
{
int arr[5] = {
0 };
printf("arr = %p\n", arr);
printf("&arr= %p\n", &arr);
//+1看看
printf("arr+1 = %p\n", arr+1);
printf("&arr+1= %p\n", &arr+1);
return 0; }
a 是数组首元素的首地址,也就是 a[0]的 首地址。
&a 是数组的首地址。
a+1 是数组下一元素的首地址,即 a[1]的首地址。
&a+1 是下一 个数组的首地址。
数组指针指向的是数组,存放的是数组的地址
那怎么使用,举个例子:
#include
int main()
{
int arr[10] = {
0};
int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
//这里*先与p结合,再与 [ ] 结合,由于上面定义的数组是int类型,所以取地址的类型也是int类型。
return 0;
}
来看下面代码,思考我们如何利用数组指针打印我们想要的结果呢?
void print(int (*parr)[10], int sz)//传上来地址,用数组指针接受
{
int i = 0;
for (i = 0; i < sz; i++)
{
//以下3种方式都能打印
//printf("%d ", parr[0][i]);//把一维数组当成二维数组,[0]表示第一行,[i]表示遍历元素
//printf("%d ", (*(parr + 0))[i]);//*(parr + 0)解引用首元素地址
printf("%d ", (*parr)[i]);//(*parr) 相当于 parr指向的数组的数组名
}
}
int main()
{
int arr[10] = {
1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr) / sizeof(arr[0]);
print(&arr, sz);//&arr把数组的首元素地址传上去函数
return 0;
}
我们都知道参数分为形参和实参。
形参是指声明或定义函数时的参数
实参是在调用函数时主调函数传递过来的实际值。
一维数组传参是怎样的呢?
我们先来看一个例子:
请大家思考一下,下面能否传参成功?
#include
void test(int arr[])//ok?
{
}
void test(int arr[10])//ok?
{
}
void test(int *arr)//ok?
{
}
void test2(int *arr[20])//ok?
{
}
void test2(int **arr)//ok?
{
}
int main()
{
int arr[10] = {
0};
int *arr2[20] = {
0};
test(arr);
test2(arr2);
}
二维数组的传参跟一维数组类似。
举个例子:
同样思考能否传参成功?
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);
}
首先,用一级指针传参,那就用一级指针接收
#include
void print(int *p, int sz) //一级指针接收int* p
{
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 test(int *p)//可以接收什么?
{
}
int main()
{
int a = 10;
int* p1 = &a;
int arr[10] = {
0};
//怎样传给函数?
return 0;
}
其实我们可以有下面的方式:
void test(int *p)
{
}
int main()
{
int a = 10;
int* p1 = &a;
int arr[10] = {
0};
test(&a);//传地址上去可以
test(arr);//传个数组名过去可以
test(p1);//传个指针也可以
test(NULL);//传空指针也行,考虑清楚,因为传空指针就是传0,并且空指针不能解引用,不支持访问空间
return 0;
}
如果是二级指针怎么传参呢?
同样的,我们可以有下面的方法。
void test(int **ppa)
{
}
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
int* arr[5];
test(ppa);
test(&pa);
test(arr);
return 0;
}
函数指针是是什么?
我们说,数组指针就是数组的指针。是指向数组的指针。
同理
函数指针就是函数的指针。它是一个指针,指向一个函数
我们思考一下下面三个代码:
char * (*fun1)(char * p1,char * p2);
char * *fun2(char * p1,char * p2);
char * fun3(char * p1,char * p2);
什么意思?
char * (*fun1)(char * p1,char * p2);
char *fun2(char * p1,char * p2);
//fun2是函数名,p1,p2 是参数,其类型为 char *型,函数的返回值为 char *类型。
char ** fun3(char * p1,char * p2);
//与 第二个表达式相比,唯一不同的就是函数的返回值类型为 char**,是个二级指针。
那么第一个代码是什么意思?
这里 fun1 不是什么函数名,而是一个
指针变量,它指向一个函数。这个函数有两个指针类型的参数,函数的返回值也是一个指针。
那么我们如何使用函数指针呢?
#include
#include
char * fun(char * p1, char * p2)
{
int i = 0;
i = strcmp(p1, p2);
if (0 == i)
{
return p1;
}
else
{
return p2;
}
}
int main()
{
char * (*pf)(char * p1, char * p2);
pf = &fun;
(*pf) ("aa", "bb");
return 0;
}
我们使用指针的时候,需要通过钥匙(“*”)来取其指向的内存里面的值,函数指针使用也如此。通过用(*pf)取出存在这个地址上的函数,然后调用它。
给函数指针赋值时,可以用&fun 或直接用函数名 fun。这是因为函数名被编译之后其实就是一个地址,所以这里两种用法没有本质的差别。
接下来看一下下面这个代码什么意思?
(*(void(*) ())0)(
第一步:void(*) (),可以明白这是一个函数指针类型。这个函数没有参数,没有返回值。
第二步:(void(*) ())0,这是将 0 强制转换为函数指针类型,0 是一个地址,也就是说一个函数存在首地址为 0 的一段区域内。
第三步:((void() ())0),这是取 0 地址开始的一段内存里面的内容,其内容就是保存在首地址为 0 的一段区域内的函数。
第四步:((void() ())0)(),这是函数调用。
把函数的地址存到一个数组中,那这个数组就叫函数指针数组
char * (*pf[3])(char * p);//一个函数指针数组,pf为数组名,类型是char*(*)()
//pf先于[3]结合,说明是一个数组,数组内存储了3个指向函数的指针
//指针再与*结合,说明是一个函数指针数组
看起来很复杂,其实仔细分析也不难。
这里的函数指针数组指针不就是一个指针嘛。只不过这个指针指向一个数组,这个数组里面存的都是指向函数的指针。仅此而已。(套娃)
那如何定义呢?下面代码介绍
void test(const char* str) {
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[10])(const char*) = &pfunArr;
return 0; }
根据维基百科的解释:
把一段可执行的代码像参数传递那样传给其他代码,而这段代码会在某个时刻被调用执行,这就叫做回调。如果代码立即被执行就称为同步回调,如果在之后晚点的某个时间再执行,则称之为异步回调
比如:
函数 F1 调用函数 F2 的时候,函数 F1 通过参数给函数 F2 传递了另外一个函数 F3 的指针,在函数 F2 执行的过程中,函数F2 调用了函数 F3,这个动作就叫做回调(Callback),而先被当做指针传入、后面又被回调的函数 F3 就是回调函数。
举个例子:
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
void Cale(int(*pf)(int, int))//通过指针传地址
{
int ret = pf(3, 5);
printf("%d\n", ret);
}
int main()
{
//Cale(Add);
Cale(Sub);//调用函数
return 0;
}