C和指针(二)



一、数组指针

数组指针就是指向数组的指针。首先它是一个指针,这个指针指向的是一个数组。

  • 例子1
int calendar[12][31];

可以把calendar看成是一个有12个元素的数组,每个元素又是一个有着31个整型元素的数组。可以把calendar当作一个二维数组,也可以把它当作一维数组组成的一维数组。

  • 例子2
int calendar[12][31];
int (*monthp)[31] = calendar; // 等价于 int (*monthp)[31] = &calendar[0]; 

monthp是数组指针。它是一个指向拥有31个整型元素的数组的指针。monthp指向了数组calendar的第一个元素(保存了数组第一个元素的地址)。*monthp是一个佣有31个整型元素的数组(可以想象为int a[31];中的a

比如新的一年开始时,需要对calendar数组进行清空。下面展示几种不同的实现方式。

  • calendar.c
int main(void)
{
	int calendar[12][31];

	int month;
	for (month = 0; month < 12; month++) {
		int day;
		for (day = 0; day < 31; day++) {
			calendar[month][day] = 0;
			// *(calendar[month] + day) = 0;
			// *(*(calendar + month) + day) = 0;
		}
	}

	return 0;
}

这是用数组取下标的方式。calendar[month][day] = 0;等价于*(calendar[month] + day) = 0;,也等价于*(*(calendar + month) + day) = 0;

  • calendar2.c
int main(void)
{
	int calendar[12][31];
	int (*monthp)[31];

	for (monthp = calendar; monthp < calendar + 12; monthp++) {
		int day;
		for (day = 0; day < 31; day++) {
			*(*monthp + day) = 0;
		}
	}

	return 0;
}

这是用数组指针的方式来遍历数组calendar。
其中for语句中的monthp = calendar; monthp < calendar + 12; monthp++可以换成monthp = &calendar[0]; monthp < &calendar[0] + 12; monthp++

  • calendar3.c
int main(void)
{
	int calendar[12][31];
	int (*monthp)[31];

	for (monthp = calendar; monthp < calendar + 12; monthp++) {
		int *dayp;
		for (dayp = *monthp; dayp < *monthp + 31; dayp++) {
			*dayp = 0;
		}
	}

	return 0;
}

像用指针的方式遍历一维数组那样,用指针来遍历*monthp
内层的for语句中的dayp = *monthp; dayp < *monthp + 31; dayp++可以换成dayp = &(*monthp)[0]; dayp < &(*monthp)[0] + 31; dayp++,也可以换成dayp = *monthp; dayp < &(*monthp)[31]; dayp++

二、指针数组

指针数组它的类型是一个数组,数组中每个元素是一个指针。

我们用一个二维字符数组来说明下,C中的字符串可以当作以NULL字符结尾的一维字符数组,如下面的例子

  • 一维字符数组
char message[] = "hello";
char message[] = {'h', 'e', 'l', 'l', 'o', '\0'};

这两种写法是等价的。但与char *message = "hello";还是有区别的,

message[]```声明message是一个**数组**,```char *message```声明message是一个**指针**。
- 二维字符数组 ```c const char keyword[][9] = { {'a', 'u', 't', 'o', '\0', '\0', '\0', '\0', '\0'}, {'s', 't', 'a', 't', 'i', 'c', '\0', '\0', '\0'}, {'e', 'x', 't', 'e', 'r', 'n', '\0', '\0', '\0'}, {'r', 'e', 'g', 'i', 's', 't', 'e', 'r', '\0'}, {'c', 'o', 'n', 's', 't', '\0', '\0', '\0', '\0'}, {'r', 'e', 's', 't', 'r', 'i', 'c', 't', '\0'}, {'v', 'o', 'l', 'a', 't', 'i', 'l', 'e', '\0'}, };
const char keyword[][9] = {
	"auto",
	"static",
	"extern",
	"register",
	"const",
	"restrict",
	"volatile"
};

上面这两种写法也是等价的,它们都可以当作二维字符数组,数组中每个元素是一个一维字符数组,或者每个元素以字符串的方式存储在数组中。

  • 指针数组
#include <stdio.h>

int main(void)
{
	const char *keyword[] = {
		"auto",
		"static",
		"extern",
		"register",
		"const",
		"restrict",
		"volatile",
		NULL
	};

	const char **kwp;

	for (kwp = keyword; *kwp != NULL; kwp++) {
		printf("%s\n", *kwp);
	}

	return 0;
}

这种声明方式表示keyword是个数组,数组中每个元素是一个指向字符的指针。所以keyword是指针数组。它比二维字符数组声明方式更节约内存,特意数组最后加一个NULL指针,是为了可以在遍历是不需要知道数组的长度。

三、函数指针

函数指针就是指向函数的指针。它是一个指针,这个指针指向的是一个函数。

  • pf.c
#include <stdio.h>

int f(int, int);

int main(void)
{
	/* 声明函数指针并初始化 */
	int (*pf)(int, int) = f;	// 或者 int (*pf)(int, int) = &f;

	int result;
    
    /* 三种方式调用函数 */
	result = (*pf)(1, 2);
	result = pf(1, 2);
	result = f(1, 2);

	printf("result = %d\n", result);

	return 0;
}

int f(int x, int y)
{
	return x + y;
}

上面的例子是pf就是一个函数指针,这个指针指向的是一个函数,这个函数需要满足有两个整型参数,返回值是整型。关于函数指针需要注意:

1)声明一个函数指针并不意味着它马上可以使用。和其它指针一样,对函数指针执行间接访问之前必须它把初始化为指向某个函数。

2)在函数指针初始化之前具有f的原型是很重要的,否则编译器就无法检查f的类型是否与pf所指向的类型一致(参数的个数、参数的类型、返回值的类型)

3)上面三种函数调用方式是等价的,pf是指向函数的指针,(*pf)就是pf所指向的那个函数。(*pf)()调用方式相比于pf()调用方式能提醒程序员pf是个函数指针而不是函数名。

提示:

函数它也像变量一样占用内存单元,所以每个函数都有一个地址,就像每个变量都有地址一样。C语言把指向函数的指针当作指向其它数据类型的指针一样对待,可以把它存储到变量中,或者可以当作数组的元素,或者作为结构或联合的成员,或者可以当前函数的参数或返回值。

  • 函数指针的用途回调函数
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

struct node {
	int value;
	struct node *next;
};

struct node *add_to_list(struct node *, int);
struct node *search_list(struct node *, const void *, int (*compare)(const void *, const void *));
int compare_ints(const void *p, const void *q);

int main(void)
{
	struct node *first = NULL;
	struct node *p;
	int expected_value = 20;

	first = add_to_list(first, 10);
	first = add_to_list(first, 20);
	first = add_to_list(first, 30);

	p = search_list(first, &expected_value, compare_ints);
	if (p != NULL) {
		printf("Node value:%d\n", p->value);
	} else {
		printf("Node not found.\n");
	}

	return 0;
}

/**
 * 在链表开始处插入一个结点
 *
 * @param  first 	指向旧链表首结点的指针
 * @param  value    需要存储到新结点的值
 * @return new_node 返回指向新结点的指针
 */
struct node *add_to_list(struct node *first, int value)
{
	struct node *new_node;

	new_node = malloc(sizeof(struct node));
	if (new_node == NULL) {
		perror("malloc error");
		exit(EXIT_FAILURE);
	}
	new_node->value = value;
	new_node->next = first;

	return new_node;
}

/**
 * 在一个单向链表中查找一个指定的值,第一个参数是指向链表首结点的指针,
 * 第二个参数是需要查找的值,第三个参数是函数指针。
 * 这个函数查找存在结点中的值与类型无关,可以查找整型,字符串等
 */
struct node *search_list(struct node *first, const void *value,
						int (*compare)(const void *, const void *))
{
	struct node *p;

	for (p = first; p != NULL; p = p->next) {
		if (compare(&p->value, value) == 0) {
			return p;
		}
	}

	return NULL;
}

int compare_ints(const void *p, const void *q)
{
	if (*(int *) p < *(int *)q) {
		return -1;
	} else if (*(int *) p == *(int *) q) {
		return 0;
	} else {
		return 1;
	}
}

四、指针函数

指针函数就是返回值为指针的函数。

  • max.c
#include <stdio.h>

int *max(int *, int *);

int main(void)
{
	int *p, x = 10, y = 20;

	p = max(&x, &y);
	printf("max = %d\n", *p);

	return 0;
}

int *max(int *x, int *y)
{
	if (*x > *y) {
		return x;
	} else {
		return y;
	}
}

max函数就是一个指针函数,它返回值的类型是一个指向整型的指针。

max函数还可以返回指向外部变量的指针

#include <stdio.h>

int z = 30;

int *max(int *, int *);

int main(void)
{
	int x = 10, y = 20;

	printf("%d\n", *max(&x, &y));

	return 0;
}

int *max(int *x, int *y)
{
	return &z;
}

max函数还可以返回指向函数内部声明为static变量的指针。

#include <stdio.h>

int *max(int *, int *);

int main(void)
{
	int x = 10, y = 20;

	printf("%d\n", *max(&x, &y));

	return 0;
}

int *max(int *x, int *y)
{
	static int s = 50;

	return &s;
}

五、typedef定义函数指针类型

可以用typedef定义函数指针,然后简化一些复杂的定义。比如C中的的信号处理函数

void (*signal(int signum, void (*func)(int)))(int);

typedef定义函数指针,简化上面的定义

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

typedef定义函数指针类型的一个例子

#include <stdio.h>

typedef double (*ptrfun)(double, double);

double add(double x, double y);

int main(void)
{
	ptrfun pf = add;

	printf("%f\n", (*pf)(3.1415, 1.1111));

	return 0;
}

double add(double x, double y)
{
	return x + y;
}
*```定义的变量是指向整型的指针类型一样。
```c int *p;
*```类似都是指向某种类型的指针。一个是指向函数的指针,一个是指针整型的指针。
## 六、typedef定义函数类型 可以用typedef来**定义函数**,然后简化一些复杂的定义。比如C中的信号处理函数```signal``` ```c void (*signal(int signum, void (*func)(int)))(int);

typedef定义函数,来简化上面的定义

typedef void Sigfunc(int);
Sigfunc *signal(int signum, Sigfunc *func);

typedef定义函数类型的一个例子

#include <stdio.h>

typedef double Fun(double, double);

double add(double, double);

int main(void)
{
	Fun *pf = add;

	printf("%f\n", pf(3.1415, 1.1111));

	return 0;
}

double add(double x, double y)
{
	return x + y;
}

注意:Fun pf = add;这是错误的,因为add函数指针,而Fun的类型是函数;

使用typedef定义的Fun是函数。可以把它理解成像定义了整型一样:

typedef int int_t;

Fun来定义指针变量和用int_t来定义指针变量一样都要加*号。

int_t *pi;
Func *pf;

文章作者: 张权
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 张权 !
评论
  目录