C语言进阶
指针
指针也是变量,在C语言中扮演者重要的角色。有许多使用指针的理由,比如:
- 字符串(
string
) - 函数里按引用传递参数
- 创建复杂的数据结构
- 指向函数
- 等等
什么是指针?
指针可以说是一种简单的整型变量,存储着某个值的内存地址,而不是存储值本身。
计算机的内存是一系列序列化的数据,一个指针指向内存特定的部分。我们的程序可以使用指针来指向一个很大的内存,这取决于我们怎么看待要存的数据。
字符串指针
我们已经讨论了string
,但现在我们可以更加深入一些,了解C中的string
实际上是什么。
下面这行代码:
char * name = "John";
做了两件事情:
- 分配一个叫
name
的局部变量,name
是一个指针,指向字符串John
的第一个字符;也就是说,name
变量存储的是字符J
的内存地址。 - 将
John
分配到程序内存的某个地方(当然,在需要编译和执行之后)。
如果我们尝试将name
作为数组来访问,这也是可以的,name
是字符J
,因为它存放着字符串John
的首个字符的地址。
既然我们已经知道内存单元是有顺序的,这意味着我们可以像数组一样来访问string
的每一个字符,直到到达最后一个终止字符\0
。
解引用
解引用是指我们可以获取一个指针变量的实际值,而不只是其内存地址。其实,之前我们已经在数组中使用了解引用,只是我们不知道而已。比如,中括号[0]
操作符,访问数组中的第一个元素。并且,数组实际上是指针,访问第一个元素相当于解引用。相应地,数组的名字存放的是数组的第一个元素的内存地址。我们可以使用星号(*
)来解引用。
如果我们想创建一个数组指向存放在栈中的不同元素,我们可以这样写:
/* define a local variable a */
int a = 1;
/* define a pointer variable, and point it to a using the & operator */
int * pointer_to_a = &a;
printf("The value a is %d\n", a);
printf("The value of a is also %d\n", *pointer_to_a);
注意:我们使用&
操作符来指向变量a
。
然后我们使用解引用操作符来获取指针变量的内容;我们也可以改变所引用的内容。
int a = 1;
int * pointer_to_a = &a;
/* let's change the variable a */
a += 1;
/* we just changed the variable again! */
*pointer_to_a += 1;
/* will print out 3 */
printf("The value of a is now %d\n", a);
结构体
使用
C中的结构体是一种特殊的、大型的变量,可以存储不同类型的各种变量。结构体是C中类和对象的基础,结构体用来:
- 序列化数据
- 在函数中通过一个参数传递多个值
- 构建数据结构,比如链表、二叉树等
结构体中最简单的例子就是point
了,point
包含了两个变量x
和y
,分别是点的横纵坐标。让我们定义point
:
struct point {
int x;
int y;
};
现在,我们来定义每一个点并使用它。假设函数draw
接收一个点的x
和y
坐标,并将其显示在屏幕上。如果没有struct
,我们就必须指定两个参数,每个坐标需要一个。
/* draws a point at 10, 5 */
int x = 10;
int y = 5;
draw(x, y);
使用struct
,我们可以使用指针来传递参数:
/* draws a point at 10, 5 */
struct point p;
p.x = 10;
p.y = 5;
draw(p);
为了访问point
的内部变量,我们使用.
操作符。
typedef
typedef
允许我们将类型重命名,这样,我们就可以摆脱很长的struct
类型的定义。typedef
定义结构体示例如下:
typedef struct {
int x;
int y;
} point;
这允许我们像这样定义一个point
:
point p;
结构体内部也可以包含指针,也就能够包含string
,或者包含其他结构体,这就是struct
的魅力所在。比如,我们如下定义vehicle
结构体:
typedef struct {
char * brand;
int model;
} vehicle;
因为brand
是一个字符指针,vehicle
类型可以包含string
。
vehicle mycar;
mycar.brand = "Ford";
mycar.model = 2007;
函数按照引用传递
含义
如果函数按值传递,那么实际参数只是原来的值的一份拷贝。但如果我们拷贝引用而不是值呢?这就能够让我们控制传来的参数,相应地,对原值的修改也会生效。
让我们实现一个函数addone
,将一个值加一,按照以下方式写则不会生效。
void addone(int n) {
n++;
}
int n;
printf("Before: %d\n", n);
addone(n);
printf("After: %d\n", n);
然而,这样却是可行的。
void addone(int * n) {
(*n)++;
}
int n;
printf("Before: %d\n", n);
addone(&n);
printf("After: %d\n", n);
第二个版本的addone
不同之处在于,它接收了一个指针变量n
作为参数,然后直接操纵它,因为它知道n
在内存中的地址。
注意:当调用addone
函数时,我们我们必须传递一个引用的值,而不是变量本身。当函数知道了变量的地址,它就不会拷贝变量本身。
指向结构体的指针
让我们创建一个函数叫call
,它使x
和y
坐标都向正方向移动。注意,我们不是传递两个参数,而是只传递一个结构体的指针。
void move(point * p) {
(*p).x++;
(*p).y++;
}
然而,如果我们希望解引用结构体并访问其内部的成员时,我们有一种更加简明的语法,这种方法在结构体中很常用。我们可以像下面那样重写函数:
void move(point * p) {
p->x++;
p->y++;
}
动态内存分配
动态内存分配是C的一个重要主题。它允许创建诸如链表之类的复杂数据结构。动态分配内存允许我们无需在程序中指定变量的初始存储空间。
为了动态地分配一块内存,我们需要指定一个指针来存放新分配的内存的首地址。这样,我们就可以通过这个指针来访问这块内存,并且一旦我们不再需要这块内存,我们将使用同样的指针来释放它。
让我们假设我们想要分配一段person
结构体的内存。person
定义如下:
typedef struct {
char * name;
int age;
} person;
使用下面的语法,来分配一个新的person
实例,并将地址传递给myperson
变量。
person * myperson = malloc(sizeof(person));
这就告诉编译器我们想要动态地分配一段内寻,只是用来存放person
这个结构体,然会返回一个指向该结构体的指针。
我们可以使用->
表示法来访问person
的成员。
myperson->name = "John";
myperson->age = 27;
在我们使用完所分配的struct
变量之后,我们可以使用free
来释放那段内存。
free(myperson);
注意:free
并不删除myperson
本身,它删除的是myperson
指向的变量所在的内存占用。myperson
变量仍然指向那段内存,我们不再使用那个内存区域了,并且我们应该确保我们不会再使用那段内存区域,不然可能会有问题,因为那段区域可能已经是其他数据了。
数组和指针
在先前指针
的章节中,你已经知道了指针可以用来存放变量的地址,比如,在下面的代码中,pc
存放字符变量c
的地址。
char c = 'A';
char *pc = &c;
这里,c
是一个标量,可以存放单一的值。然而,你已经熟悉了数组可以存放多个相同类型的值,并且在连续分配的内存空间中。所以,你可能会考虑,我们能不能使用指针?实际上,我们可以。
让我们从简单的代码开始看它输出什么。我们稍后讨论其每一个行为。
char vowels[] = {'A', 'E', 'I', 'O', 'U'};
char *pvowels = &vowels;
int i;
// Print the addresses
for (i = 0; i < 5; i++) {
printf("&vowels[%d]: %u, pvowels + %d: %u, vowels + %d: %u\n", i, &vowels[i], i, pvowels + i, i, vowels + i);
}
// Print the values
for (i = 0; i < 5; i++) {
printf("vowels[%d]: %c, *(pvowels + %d): %c, *(vowels + %d): %c\n", i, vowels[i], i, *(pvowels + i), i, *(vowels + i));
}
上面代码的典型输出如下:(不同机器会存在差别)
&vowels[0]: 4287605531, pvowels + 0: 4287605531, vowels + 0: 4287605531
&vowels[1]: 4287605532, pvowels + 1: 4287605532, vowels + 1: 4287605532
&vowels[2]: 4287605533, pvowels + 2: 4287605533, vowels + 2: 4287605533
&vowels[3]: 4287605534, pvowels + 3: 4287605534, vowels + 3: 4287605534
&vowels[4]: 4287605535, pvowels + 4: 4287605535, vowels + 4: 4287605535
vowels[0]: A, *(pvowels + 0): A, *(vowels + 0): A
vowels[1]: E, *(pvowels + 1): E, *(vowels + 1): E
vowels[2]: I, *(pvowels + 2): I, *(vowels + 2): I
vowels[3]: O, *(pvowels + 3): O, *(vowels + 3): O
vowels[4]: U, *(pvowels + 4): U, *(vowels + 4): U
你可能已经猜到了,&vowels[i]
给出了vowels
数组中的第i+1
个元素,并且,因为是一个字符数组,每一个元素占一个字节,所以其内存区域就简单地以字节分隔。我们创建一个指针pvowels
然后将数组的地址赋给它。pvowels+i
是一个合法的操作,即使在正常的情况下这看起来没什么意义。特别地,在上面的输出中,&vowels[i]
和pvowels+i
是相同的。所以在使用这种方法输出上不要有疑虑。
如果你仔细看一下之前的代码,你就会发现,我们还使用了另外一种更加奇怪的表示方法:vowels+i
,这也是一样的。另一方面,pvowels+i
和vowels+i
同样是返回数组vowels
的第i+1
个元素。为什么会这样?
这是因为数组本身也是一个常指针,指向数组的第一个元素。换句话说,vowels
、&vowels[0]
、和vowels+0
都指向同样的位置。
数组的动态内存分配
到现在为止,我们已经知道了可以使用指针来访问数组。除此之外,我们还可以使用块指针动态分配内存。这两个方面可以结合在一起,正如下面的代码所阐述的。
// Allocate memory to store five characters
int n = 5;
char *pvowels = (char *) malloc(n * sizeof(char));
int i;
pvowels[0] = 'A';
pvowels[1] = 'E';
*(pvowels + 2) = 'I';
pvowels[3] = 'O';
*(pvowels + 4) = 'U';
for (i = 0; i < n; i++) {
printf("%c ", pvowels[i]);
}
printf("\n");
free(pvowels);
在上面的代码中,我们分配了连续的5个字节去存储字符数据。我们使用数组下标遍历数组仿佛pvowels
是一个数组。然后,注意pvowels
实际上是一个指针。就像指针和数组一节所说的,数组和指针实际上都是一样的事情。
所以这有什么用?记住当定义数组的时候,数组的大小是必须知道的。但是,在一些场景下,我们并不知道数组的所需要的实际长度,我们可能就尽可能大地定义数组,这样会造成内存浪费。然而,使用动态分配内存的时候,我们可以将程序的内存按需分配。并且,不再使用的内存也可以使用free
函数来释放。在不好的方面,如果使用了动态内存分配,我们就必须显式地调用free
函数来释放内存,否则将有可能发生内存泄漏。
举一反三,我们可以按照同样的方式为二维数组动态分配内存,这也可以拓展到多维数组。不像一维数组,我们在这里需要使用指针的指针,正如下面的代码所示。
int nrows = 2;
int ncols = 5;
int i, j;
// Allocate memory for nrows pointers
char **pvowels = (char **) malloc(nrows * sizeof(char *));
// For each row, allocate memory for ncols elements
pvowels[0] = (char *) malloc(ncols * sizeof(char));
pvowels[1] = (char *) malloc(ncols * sizeof(char));
pvowels[0][0] = 'A';
pvowels[0][1] = 'E';
pvowels[0][2] = 'I';
pvowels[0][3] = 'O';
pvowels[0][4] = 'U';
pvowels[1][0] = 'a';
pvowels[1][1] = 'e';
pvowels[1][2] = 'i';
pvowels[1][3] = 'o';
pvowels[1][4] = 'u';
for (i = 0; i < nrows; i++) {
for(j = 0; j < ncols; j++) {
printf("%c ", pvowels[i][j]);
}
printf("\n");
}
// Free individual rows
free(pvowels[0]);
free(pvowels[1]);
// Free the top-level pointer
free(pvowels);
递归
函数调用自身时将发生递归现象。递归可以使代码更加整洁、优雅。但当递归树较深时,内存占用将很严重,时间复杂度也不佳。
使用递归的情况:
- 遍历递归的数据结构,比如链表、二叉树等
- 探索某些可能存在的路径
递归总是可以分为两个部分的。终止部分指示了递归到什么程度为止,而递归部分则解决子问题。
比如,下面的例子将使用递归加法来代替乘法:
#include <stdio.h>
unsigned int multiply(unsigned int x, unsigned int y)
{
if (x == 1)
{
/* Terminating case */
return y;
}
else if (x > 1)
{
/* Recursive step */
return y + multiply(x-1, y);
}
/* Catch scenario when x is zero */
return 0;
}
int main() {
printf("3 times 5 is %d", multiply(3, 5));
return 0;
}
链表
简介
链表是最好最简单的动态数据结构的例子,使用指针来实现。然而,理解指针对于理解链表如何工作是很关键的。
一般地,链表作为一种线性表,可以在链表的任何地方,按需增长和收缩。
- 数据项可以在链表的中间增加或者移除
- 没有必要定义初始化大小
然而,链表也有一些缺点:
- 不能随机访问,链表的迭代必须从第一个元素开始,按顺序存取。
- 需要动态第分配内存,增加了代码的复杂度,处理不好可能会造成内存泄漏
- 链表比数组有更大的头部,因为每一个节点都必须包含一个指针域,增加了空间的使用。
什么是链表?
链表是一系列动态分配的节点的集合,每个节点都包含一个值与一个指针。指针总是指向链表的下一个成员,做后一个成员指向为Null
。
通常使用一个局部的指针变量来指向链表的第一个元素,如果指针为NULL
,那就认为链表为空。
让我们来定义链表的节点:
typedef struct node {
int val;
struct node * next;
} node_t;
注意:我们使用struct
来实现数据上的递归,即node
内部的next
依然是node
类型。我们将节点命名为node_t
。
现在我们可以使用节点了。让我么创建一个局部变量指向链表的第一个节点。(叫head
)
node_t * head = NULL;
head = malloc(sizeof(node_t));
if (head == NULL) {
return 1;
}
head->val = 1;
head->next = NULL;
我们刚才只是创建了链表的第一个变量,我们还必须设置它的值,并且其下一个节点应该为空,如果我们的链表只有这个元素的话。注意:我们总是检查malloc
分配内存是否成功。
在链表的尾部增加一个节点,我们只需要更改next
域指向这个新的节点。
node_t * head = NULL;
head = malloc(sizeof(node_t));
head->val = 1;
head->next = malloc(sizeof(node_t));
head->next->val = 2;
head->next->next = NULL;
我们可以按照这样的方式不断地增加元素,直到最后一个元素的next
域为NULL
。
遍历链表
让我们创建一个打印链表所有节点的函数。使用current
指针来追踪当前要打印的节点。在打印玩一个节点之后,将current
的指针域指向下一个节点,然后再次打印,直到到达链表的结尾。(next
域为空)
void print_list(node_t * head) {
node_t * current = head;
while (current != NULL) {
printf("%d\n", current->val);
current = current->next;
}
}
在表尾增加元素
为了迭代整个链表,我们使用一个叫做current
的指针。我们在每一次循环中都把它推向下一个节点,直到我们到达最后。然后我们就可以在current
插入一个节点。
void push(node_t * head, int val) {
node_t * current = head;
while (current->next != NULL) {
current = current->next;
}
/* now we can add a new variable */
current->next = malloc(sizeof(node_t));
current->next->val = val;
current->next->next = NULL;
}
在表头增加元素
要在表头添加元素,我们只需要按照以下的步骤来做:
- 创建一个节点并赋值
- 将新节点的
next
域指向表头 - 修改表头指针
我们将使用函数来完成这个操作,为了修改表头的指针域,我们还需要传递一个指针的指针,这样才能修改next
域本身。
void push(node_t ** head, int val) {
node_t * new_node;
new_node = malloc(sizeof(node_t));
new_node->val = val;
new_node->next = *head;
*head = new_node;
}
移除表头元素
要移除变量,我们只需要进行反操作:
- 新建一个临时变量存放头指针的下一个元素的信息
- 释放头指针指向的节点
- 将临时节点设置为头节点
以下是代码:
int pop(node_t ** head) {
int retval = -1;
node_t * next_node = NULL;
if (*head == NULL) {
return -1;
}
next_node = (*head)->next;
retval = (*head)->val;
free(*head);
*head = next_node;
return retval;
}
移除表尾元素
移除链表的最后一个元素需要遍历整个链表,找到最后一个元素的地址,然后直接释放其内存空间,将上一个节点的next
域修改为NULL
。注意:要找到最后一个元素,我们还必须额外设置一个变量来检查是否到达倒数第二个,这是关键。
int remove_last(node_t * head) {
int retval = 0;
/* if there is only one item in the list, remove it */
if (head->next == NULL) {
retval = head->val;
free(head);
return retval;
}
/* get to the second to last node in the list */
node_t * current = head;
while (current->next->next != NULL) {
current = current->next;
}
/* now current points to the second to last item of the list, so let's remove current->next */
retval = current->next->val;
free(current->next);
current->next = NULL;
return retval;
}
移除特定的元素
无论是通过索引还是节点值的方式给出要删除的节点,我们都必须遍历整个链表,找到我们想要删除的元素,然后删除它。移除的过程只需要修改指针域。
这是删除的算法:
- 迭代链表,找到要删除元素的前一个元素,使用一个临时指针来指向它
- 使用一个额外指针指向待删除的节点
- 将临时指针指向的节点的
next
域修改为下一个节点的下一个节点的地址 - 释放待删除节点的地址
以下是一些边界案例,在删除的时候需要注意。
int remove_by_index(node_t ** head, int n) {
int i = 0;
int retval = -1;
node_t * current = *head;
node_t * temp_node = NULL;
if (n == 0) {
return pop(head);
}
for (i = 0; i < n-1; i++) {
if (current->next == NULL) {
return -1;
}
current = current->next;
}
temp_node = current->next;
retval = temp_node->val;
current->next = temp_node->next;
free(temp_node);
return retval;
}
联合体
C中的联合体和结构体比较像,结构体中的每个变量都有自己的存储空间,但联合体是多个变量使用同一个内存空间,因此,联合体的大小取决与最大的变量的占用空间大小。
所以,如果你想以不同的方式读取一个变量,比如,读取一个整数的每一个字节,你可以这样做:
union intParts {
int theInt;
char bytes[sizeof(int)];
};
这样,你就可以看到一个int
类型的每个字节的内容。
union intParts parts;
parts.theInt = 5968145; // arbitrary number > 255 (1 byte)
printf("The int is %i\nThe bytes are [%i, %i, %i, %i]\n",
parts.theInt, parts.bytes[0], parts.bytes[1], parts.bytes[2], parts.bytes[3]);
// vs
int theInt = parts.theInt;
printf("The int is %i\nThe bytes are [%i, %i, %i, %i]\n",
theInt, *((char*)&theInt+0), *((char*)&theInt+1), *((char*)&theInt+2), *((char*)&theInt+3));
// or with array syntax which can be a tiny bit nicer sometimes
printf("The int is %i\nThe bytes are [%i, %i, %i, %i]\n",
theInt, ((char*)&theInt)[0], ((char*)&theInt)[1], ((char*)&theInt)[2], ((char*)&theInt)[3]);
比如,你可能有一个struct
类型的数字,但你不想像下面那样使用:
struct operator {
int intNum;
float floatNum;
int type;
double doubleNum;
};
因为你的程序可能还有很多这样的变量,这种存储方式显然会浪费内存空间。所以,你可以这样做:
struct operator {
int type;
union {
int intNum;
float floatNum;
double doubleNum;
} types;
};
operator
这个结构体现在的大小是type
的大小再加上types
联合体的最大的变量的大小。
用法:
operator op;
op.type = 0; // int, probably better as an enum or macro constant
op.types.intNum = 352;
而且,如果你不指定联合体的名字,你甚至可以直接通过结构体访问!
struct operator {
int type;
union {
int intNum;
float floatNum;
double doubleNum;
}; // no name!
};
operator op;
op.type = 0; // int
// intNum is part of the union, but since it's not named you access it directly off the struct itself
op.intNum = 352;
另外,一种可能更加有用的特征是,当你有很多和相同类型的变量的时候,你想同时使用名称(可读性)和索引(迭代需要),在这种情况下,你可以这样做:
union Coins {
struct {
int quarter;
int dime;
int nickel;
int penny;
}; // anonymous struct acts the same way as an anonymous union, members are on the outer container
int coins[4];
};
联合体变量是共享内存的!
union Coins change;
for(int i = 0; i < sizeof(change) / sizeof(int); ++i)
{
scanf("%i", change.coins + i); // BAD code! input is always suspect!
}
printf("There are %i quarters, %i dimes, %i nickels, and %i pennies\n",
change.quarter, change.dime, change.nickel, change.penny);
指针运算
如前所述,指针其实是一个整型的变量,因此,可以对其进行基本的运算。
指针自增
就像任何变量一样,++
操作符对那个变量执行增加1的操作。在这里指针变量的自增将会使指针指向下一个内存地址。结合数组的操作,我们来看一下是如何实现的。
#include <stdio.h>
int main()
{
int intarray[5] = {10,20,30,40,50};
int i;
for(i = 0; i < 5; i++)
printf("intarray[%d] has value %d - and address @ %x\n", i, intarray[i], &intarray[i]);
int *intpointer = &intarray[3]; //point to the 4th element in the array
printf("address: %x - has value %d\n", intpointer, *intpointer); //print the address of the 4th element
intpointer++; //now increase the pointer's address so it points to the 5th elemnt in the array
printf("address: %x - has value %d\n", intpointer, *intpointer); //print the address of the 5th element
return 0;
}
指针自减
--
操作实现指针指向上一个内存地址。
#include <stdio.h>
int main()
{
int intarray[5] = {10,20,30,40,50};
int i;
for(i = 0; i < 5; i++)
printf("intarray[%d] has value %d - and address @ %x\n", i, intarray[i], &intarray[i]);
int *intpointer = &intarray[4]; //point to the 5th element in the array
printf("address: %x - has value %d\n", intpointer, *intpointer); //print the address of the 5th element
intpointer--; //now decrease the point's address so it points to the 4th element in the array
printf("address: %x - has value %d\n", intpointer, *intpointer); //print the address of the 4th element
return 0;
}
指针加法
我们可以将一个整数值加到一个指针变量上,就像下面这样。
#include <stdio.h>
int main()
{
int intarray[5] = {10,20,30,40,50};
int i;
for(i = 0; i < 5; i++)
printf("intarray[%d] has value: %d - and address @ %x\n", i, intarray[i], &intarray[i]);
int *intpointer = &intarray[1]; //point to the 2nd element in the array
printf("address: %x - has value %d\n", intpointer, *intpointer); //print the address of the 2nd element
intpointer += 2; //now shift by two the point's address so it points to the 4th element in the array
printf("address: %x - has value %d\n", intpointer, *intpointer); //print the addres of the 4th element
return 0;
}
注意:这里的内存地址一下子就改变了8。你可能会感到奇怪?答案很简单,因为我们的指针是一个int
类型的指针,而int
类型的大小是4个字节,所以指针每增加1,内存地址就增加4。
指针减法
同样地,我们可以对指针进行减法操作。
#include <stdio.h>
int main()
{
int intarray[5] = {10,20,30,40,50};
int i;
for(i = 0; i < 5; i++)
printf("intarray[%d] has value: %d - and address @ %x\n", i, intarray[i], &intarray[i]);
int *intpointer = &intarray[4]; //point to the 5th element in the array
printf("address: %x - has value %d\n", intpointer, *intpointer); //print the address of the 5th element
intpointer -= 2; //now shift by two the point's address so it points to the 3rd element in the array
printf("address: %x - has value %d\n", intpointer, *intpointer); //print the address of the 3rd element
return 0;
}
其他操作
还有更多的操作,比如比较>
,<
,==
。这个和常规变量的比较基本相同,只不过在这里比较的是内存地址而已。
函数指针
还记得指针吗?我们用它来指向一个字符数组和表示string
。当我们能灵活地操纵是真的时候,事情就变得有趣。我们现在就做一些有趣的事情,尝试将指针指向一个函数。
为什么指向一个函数?
可能这时你的脑海里第一个想法是,我可以简单地通过函数名来调用函数,为什么要使用一个指针来指向一个函数?!好问题。现在想一下你使用一个sort
函数来对数组进行排序,有时你想对其升序有时降序,你该如何选择?函数指针!
函数指针语法
void (*pf)(int);
我开始同意你开始的想法了。这样定义实在太复杂了!或者你可能会重新读代码,并且尝试去理解指针到底指向哪里。从外面开始读,*pf
是一个指向了一个函数。void
是函数的返回值。最后,int
是函数的参数。明白了?很好!
让我们向函数里面插入指针,然后再次读代码。
char* (*pf)(int*)
再次,*pf
是函数指针,char*
是函数的返回值,我们甚至猜到这是string
,int*
是参数的类型。
好了,理论就到此为止。让我们去看看实际环境的一些恶心代码吧!以下是一个例子。
#include <stdio.h>
void someFunction(int arg)
{
printf("This is someFunction being called and arg is: %d\n", arg);
printf("Whoops leaving the function now!\n");
}
int main()
{
void (*pf)(int);
pf = &someFunction;
printf("We're about to call someFunction() using a pointer!\n");
(pf)(5);
printf("Wow that was cool. Back to main now!\n\n");
}
记得我们之前所说的sort
函数吗?我们做一些相同的事情。这次我们不是对集合进行升序排序,我们来定义自己的排序规则。
#include <stdio.h>
#include <stdlib.h> //for qsort()
int compare(const void* left, const void* right)
{
return (*(int*)right - *(int*)left);
//go back to ref if this seems complicated: http://www.cplusplus.com/reference/cstdlib/qsort/
}
main()
{
int (*cmp) (const void* , const void*);
cmp = &compare;
int iarray[] = {1,2,3,4,5,6,7,8,9};
qsort(iarray, sizeof(iarray)/sizeof(*iarray), sizeof(*iarray), cmp);
int c = 0;
while (c < sizeof(iarray)/sizeof(*iarray))
{
printf("%d \t", iarray[c]);
c++;
}
}
现在,我们反思一下,为什么我们使用函数指针?因为灵活。