数组-函数-指针



1. 注释方法

1
2
3
#if 0

#endif


2. 空指针与野指针

1
2
3
4
5
6
// 空指针
int *p = NULL;

// 野指针 : 指针无确定指向,就发生了指针的使用
如何杜绝: 定义指针开始就应该初始化,或者使其指向NULL
如:int *p = 123; // 野指针


3. 空类型指针

1
void *p; // 可以赋给任意类型的指针


4. 指针与数组

(1)指针与一维数组

可以使用一级指针指向一维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>

int main()
{
int a[3] = {1,2,3};
int i;
for(i = 0;i<sizeof(a)/sizeof(a[0]);i++)
printf("%p-->%d\n",&a[i],a[i]);
printf("\n");

// 定义指针指向一维数组(数组名a代表数组第一个元素地址(a为地址常量,右值))
int *p = a;
// 此时指针指向数组的第一个元素
printf("第一个元素地址: %p--> 第一个元素为: %d\n",p,*p);

for(i = 0;i<sizeof(a)/sizeof(a[0]);i++)
printf("%p-->%d\n",(p+i),*(p+i));
exit(0);
}

(2)指针与二维数组

Warning:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>

int main()
{
int a[2][2] = {1,2,3,4};
int *p = a;

printf("%p %p\n",a,a+1);

int i,j;
for(i = 0;i < 2; i++)
{
for(j = 0;j < 2; j++)
{
printf("%p --> %d\n",&a[i][j],a[i][j]);
}
}


exit(0);
}

编译之后会出现以下Warning:

image-20230304155807863

运行之后发现:

image-20230304155820132

数组名a是:a[0] [0]的地址 a,a+1是:a[1] [0]的地址,a+1 是在行间进行跳跃,而p指向的是二维数组首地址,p+1指向的是a[0] [1],p是在列之间进行跳跃

1
2
3
int *p = a;
// 上述代码进行修改:
int *p = *(a+0); // 对行地址取星,相当于降级为列地址 如此运行不会有Warning

对于二维数组也可以如此遍历:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>

int main()
{
int a[2][2] = {1,2,3,4};
int *p = *a;

printf("%p %p\n",a,a+1);

int i,j;
for(i = 0;i < 2; i++)
{
for(j = 0;j < 2; j++)
{
// *(a+i) 变为列地址在后续进行+j,地址在列上进行移动
// *(*(a+i)+j) 得到的是a[i][j] 的元素
printf("%p --> %d\n",*(a+i)+j,*(*(a+i)+j));
}
}
exit(0);
}


通过列指针p进行遍历二维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <stdlib.h>

int main()
{
int a[2][2] = {1,2,3,4};
int *p = *a;

printf("%p %p\n",a,a+1);

int i;
for(i = 0; i<4;i++)
{
// p指针为列指针,在列之间进行跳跃
printf("%p --> %d\n",p+i,*(p+i));
}

exit(0);
}


数组指针(重点)

指向数组的指针

1
数据类型 (*指针名)[] = 二维数组首地址;

例如:

1
2
3
4
5
int a[2][3] = {{1,2,3},{4,5,6}};

// 定义一个数组指针,指向数组a,指针 p+1 移动大小为 3个整型字节大小,类似于行指针,通过“*”号 *(p+i) 进行降级,变为列指针,在列之间进行操作
// 数组指针p 指向的对象为 int[3] 存有三个整型字节的一维度数组
int (*p)[3] = a;

例程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<stdio.h>
#include<stdlib.h>

int main()
{
int a[2][3] = {{1,2,3},{4,5,6}};
// 定义一个指针数组
int (*p)[3];

// p 指向的内存为大小为三个整型字节的一维数组地址,此时不会上一个程序有Warning,因为p+1移动大小为3个整型字节的大小,类似于行指针。而a+1同样如此移动三个整型字节的大小
// 同样可以通过 *(p+1)对行指针进行降级 变为列指针
p = a;

printf("%p---->%d--->%p---->%d\n",p,(*p)[0],p+1,*(p+1)[0]);
printf("%p---->%d---->%p--->%d\n",a,(*a)[0],a+1,*(a+1)[0]);


int i,j;
for(i=0;i<2;i++)
{
for(j=0;j<3;j++)
{
// *(p+i),用*号是的p降级从行指针变为列指针,可在列之间操作
printf("%p --> %d\n",*(p+i)+j,*(*(p+i)+j));
}
}
return 0;
}

运行结果

image-20230304163440412



5.指针数组

定义一个数组,里面的元素为指针

1
2
3
4
5
数据类型 *数组名 [长度];

int a = 1,b = 2, c = 3;
// 指针数组p,里面的元素均为int *,地址
int *p[3] = {&a,&b,&c};

例程:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int a = 1,b = 2, c = 3;
// 指针数组p,里面的元素均为int *,地址
int *p[3] = {&a,&b,&c};
printf("%p---%p---%p\n",&a,&b,&c);
printf("%d---%d---%d\n",*p[0],*p[1],*p[2]);
exit(0);
}


6.指针函数与函数指针

(1)指针函数

指针函数本质还是一个函数,返回为一个指针,地址

1
ret *func(args, ...);

func是一个函数,args是形参列表,ret *作为一个整体,是 func函数的返回值,是一个指针的形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

//定义指针函数
int *sum(int a, int b)
{
static int result; //此处变量为静态局部变量
int *p;
result = a + b;
p = &result;
return p;
}

int main()
{
int a = 10;
int b = 10;
int *p = sum(a, b);
printf("the result is %d",*p);
}

示例二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

//定义指针函数
int *sum(int a, int b)
{
int result; //此时变量为,普通局部变量
int *p;
result = a + b;
p = &result;
return p;
}

int main()
{
int a = 10;
int b = 10;
int *p = sum(a, b);
printf("the result is %d",*p);
}

image-20230304165649028

上述两个实例,结果输出,并无差别,但是如果我们在main函数中做一些修改如下:

示例三:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

//定义指针函数
int *sum(int a, int b)
{
int result; //此时变量为,普通局部变量
int *p;
result = a + b;
p = &result;
return p;
}

int main()
{
int a = 10;
int b = 10;
int *p = sum(a, b);
printf("wait for a while...\n"); //此处加一句打印
printf("the result is %d",*p);
}

运行结果

image-20230304165818314

出现以上结果的原因:

  • 一般的局部变量存在在栈区,当函数结束,栈区变量就会释放,倘若在函数内部定义一个局部变量,在使用指针指向该变量,当函数调用结束,这个变量的空间就会被释放。这是放回该地址的指针,也不一定得到正确的结果,上述示例一、示例二,在返回指针后,立马访问也是巧合。但是如果我们等待一段时间再去访问,这是可能该地址,可能地址以及被其他变量所占用
  • 解决方法:
    • 在函数内,使用static去修饰需要返回地址的变量,该变量就会变成静态变量,静态变量存放在数据段。静态变量的生命周期为整个程序的运行周期
    • 使用全局变量,全局变量同样存放在数据段,其生命周期为整个程序的运行周期,但是不推荐!

(2)函数指针

函数指针本质还是指针,指向函数的指针

1
2
3
4
ret (*p)(args, ...);
//ret为返回值,*p作为一个整体,代表的是指向该函数的指针,args为形参列表。其中p被称为函数指针变量
typedef int (*fun_ptr)(int,int);
// 声明一个指向同样参数、返回值的函数指针类型

函数指针初始化:

1
函数指针变量 = 函数名;

实例程序

以下实例声明了函数指针变量 p,指向函数 max:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

int max(int x, int y)
{
return x > y ? x : y;
}

int main(void)
{
/* p 是函数指针 */
int (* p)(int, int) = & max; // &可以省略
int a, b, c, d;
printf("请输入三个数字:");
scanf("%d %d %d", & a, & b, & c);
/* 与直接调用函数等价,d = max(max(a, b), c) */
d = p(p(a, b), c);
printf("最大的数字是: %d\n", d);
return 0;
}

以下实例声明了函数指针变量p,指向函数sum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

int sum(int a,int b)
{
return a + b;
}

int main()
{
int(*p)(int a, int b);//定义一个函数指针---指向函数的指针
p = &sum; //此处的&可以省略,p=sum---->满足:函数指针变量=函数名
int a = 1;
int b = 2;
int c = 3;
int result = p(p(a,b),c);//等价sum(sum(a,b),c)
printf("the result is %d",result);
}

(3)函数指针数组

定义

1
2
3
类型 (*数组名[下标]) (形参)
如:
int (*arr[N])(int) 如何理解:先看括号,首先是一个数组其次是一个指针 括号中整体就是 指针数组;最后还是一个函数;三者组合就是 函数指针数组

(4)回调函数

回调函数通过函数指针调用

的函数。其将函数指针作为一个参数,传递给另一个函数。回调函数并不是由实现方直接调用,而是在特定的事件或条件发生时由另外一方来调用的。

函数指针的一个非常典型的应用就是回调函数

回调函数是由别人的函数执行时调用你实现的函数。

实例程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdlib.h>

// 函数功能:累加求和
int func_sum(int n)
{
int sum = 0;
if (n < 0)
{
printf("n must be > 0\n");
exit(-1);

for (int i = 0; i <= n; i++)
{
sum += i;
}
return sum;
}

// 回调函数,其中第二个参数是一个函数指针,通过该函数指针来调用求和函数,并把结果返回给主调函数
int callback(int n,int (*p)(int))
{
return p(n);
}

int main()
{
int n = 5;
printf("thr sum is from 0 to %d is %d\n",n,callback(n,func_sum));
exit(0);


}

使用回调函数的优势:

在这个程序中,回调函数callback无需关心func_sum是怎么实现的,只需要去调用即可。
这样的好处就是,如果以后对求和函数有优化,比如新写了个func_sum2函数的实现,我们只需要在调用回调函数的地方将函数指针指向func_sum2即可,而无需去修改callback函数内部。

回调函数广泛用于开发场景中,比如信号函数、线程函数等,都使用到了回调函数的知识。