【c++】【基础】【primer_plus】【第七章】函数指针

函数和二维数组

1
2
3
4
5
6
7
8
9
10
int sum(int (*arr)[2], int n) {
int res = 0;
for(int i = 0; i < n; ++i)
for(int j = 0; j < 2; ++j) res += arr[i][j];
return res;
}

int a[3][2] = { {1, 2}, {3, 4}, {5, 6} };
cout << sum(a, 3) << endl;

使用二维数组作为参数,必须指定第二维的维数,用来表示元素的类型。

如上代码所示,表示arr为一个数组名,而数组的每一个元素也是一个数组,由2个int组成,即arr的类型是指向元素类型为2个int的数组的指针。

其中的括号必不可少,因为int *arr[2]表示由2个int型指针组成的数组,但函数参数不能为数组。

除此之外,也可以写为int sum(int arr[][2], int n);,二者含义相同。

只要记住arr为指针而非数组,即函数的参数不能为数组即可。

函数和字符串

字符串作参数

1
2
3
4
5
6
7
8
9
10
void show_str(const char* str) {
while (*str) {
cout << *str;
++str;
}
cout << endl;
}

char str[] = "hello world";
show_str(str);

可以将字符串作为参数,使用char*(指向char类型的指针)作为类型。

1
void show_str(const char* str);

也可使用如下格式,意义相同。

1
void show_str(const char str[]);

字符串作返回值

返回字符串的地址即可。即返回指向字符串首地址的指针。 注意不能返回栈空间的内存,因为函数执行完后便释放了,所以需要使用new关键字申请堆空间的内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;

char* build_str(char ch, int n) {
char* p_str = new char[1+n]; // 虽然p_str在函数结束时会被释放
p_str[n] = '\0';
while(n-- > 0) p_str[n] = ch;
return p_str; // 但 new 的内存还在
}

int main() {

char* str = build_str('d', 10);
cout << str << endl;
delete [] str; // 所以需要手动delete此内存块

}

函数和结构体

在函数里,我们只要像对待基本数据类型一样对待结构体即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

struct pos {
int x, y;
};

pos sum_pos(pos a, pos b) {
pos res;
res.x = a.x + b.x;
res.y = a.y + b.y;
return res;
}

int main() {
pos m{1, 2};
pos n = {3, 4};

pos r = sum_pos(m, n);
cout << r.x << " " << r.y << endl;

}

我们可以将结构体的地址作为参数或者返回值,以节省时间和空间,但是要注意不能返回栈空间的局部变量的临时内存。

1
2
3
4
5
6
7
8
9
10
11
pos* sum_pos(const pos* a, const pos* b) {
pos* res = new pos;
res->x = a->x + b->x;
res->y = a->y + b->y;
return res;
}

pos* r = sum_pos(&m, &n);
cout << r->x << " " << r->y << endl;
delete r;

也可以将结果作为指针直接放在参数中,而不用返回值。

1
2
3
4
5
6
7
void sum_pos(const pos* a, const pos* b, pos* res) {
res->x = a->x + b->x;
res->y = a->y + b->y;
}
pos r;
sum_pos(&m, &n, &r);
cout << r.x << " " << r.y << endl;

stringarrayvector等类对象,都可看作基本类型,像结构体一样在函数中使用。

递归函数

1
2
3
4
5
// 求n的阶乘
int factorial(int n) {
if(n == 1) return 1; // 循环终止条件
return n * factorial(n-1); // 递归
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 快速排序 -- O(nlogn)
void quick_sort(int a[], int L, int R) { //[L, R]
if(L >= R) return; // 循环终止条件
int x = L, y = R, p = a[L];
while(x < y) { //最后找到的x即为划分地
while(x < y && a[y] >= p) --y; a[x] = a[y]; //y向左到比p小的,移到左边去
while(x < y && a[x] <= p) ++x; a[y] = a[x]; //x向右到比p大的,移到右边去
}// 结束划分
a[x] = p;
quick_sort(a, L, x-1); quick_sort(a, x+1, R); // 递归
}

int a[] { 5, 3, 2 ,1, 4};
quick_sort(a, 0, 4);
for(int i = 0; i < 5; ++i) cout << a[i] << endl;

除了main函数外,函数都可以调用自身,这种在实现中调用自身的函数称为递归函数。

一个递归函数必须有递归调用的终点,否则将会无限调用下去。

每一个递归过程中的函数都是独立的,递归的过程与普通函数调用的过程是一样的。我们只需将函数里对自己本身的调用也看作是对其他函数的调用,只不过功能相同而已,就可以很清楚地理解递归函数了。

函数指针

c++中,函数名即为函数的地址(不带括号)。 如有一个函数bool cmp(int a, int b);,则cmp即为该函数在内存中的地址。

声明

1
2
bool cmp(int a, int b);         // 函数的声明
bool (*p_cmp)(int a, int b); // 函数指针的声明

如上代码所示,由于(*p_cmp)cmp等价,所以(*p_cmp)也是函数,则p_cmp就是指向该函数指针了。

如果不加括号的话,bool *p_cmp(int a, int b);表示一个返回bool*的函数,而非函数指针。所以声明函数指针一定要加括号。

赋值

我们可以将具体的某个函数赋值给函数指针,前提是该函数的参数和返回类型都与函数指针的一致。

1
p_cmp = cmp

调用

我们可以使用函数指针来调用其指向的函数。可以使用(*pf)调用,也可以直接使用指针名作为函数名调用。

下面两句的含义相同。

1
2
cout << (*p_cmp)(1, 2) << endl;
cout << p_cmp(1, 2) << endl;

下面代码为使用函数指针的一个绝佳例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;

bool cmp(int a, int b) { return a > b; }
bool cmp_2(int a, int b) { return a <= b; }

//返回两个数的较大值 -- 自定义比较规则
int max_i(int a, int b, bool (*p_cmp)(int a, int b)) {
if(p_cmp(a, b)) return a;
else return b;
}

int main() {
cout << max_i(1, 2, cmp) << endl;
cout << max_i(1, 2, cmp_2) << endl;
}

其输出结果如下。

1
2
2
1

以 函数指针为元素 的数组

1
int (*p_s[3])(int, int);

[]的优先级高于*,故(*p_s[3])表示一个包含三个指针的数组。

指向 函数指针数组 的指针

我们将上面的p_s替换成(*p_ps)

1
int (*(*p_ps)[3])(int, int);        // 有些小变态

加括号表示p_ps是一个指针,它指向一个包含3个元素的数组。

使用typedef简化函数指针

使用typedef可声明函数指针的别名,使代码更容易理解。如下所示。

1
typedef int (*p_fun)(int, int);

这样p_fun就可以看作一个数据类型来使用了,而之前的函数指针数组和指向函数指针数组的指针都可以使用如下方式声明。

1
2
3
p_fun p1 = s1;
p_fun pa[3] = {s1, s2, s3};
p_fun (*pd)[3] = &pa;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

int s1(int a, int b) { return a + b + 0; }
int s2(int a, int b) { return a + b + 1; }
int s3(int a, int b) { return a + b + 2; }

int main() {
// 函数指针的数组
int (*p_s[3])(int, int) = { s1, s2, s3};

for(int i = 0; i < 3; ++i) {
cout << p_s[i](1, 2) << " "; // 等价于 cout << (*p_s[i])(1, 2) << endl;
}

// 指向 函数指针数组 的指针
// 也可用 auto p_ps = &p_s; 简化
int (*(*p_ps)[3])(int, int) = &p_s;
for(int i = 0; i < 3; ++i) {
cout << (*p_ps)[i](1, 2) << " "; // 等价于 cout << (*(*p_ps)[i])(1, 2) << endl;
}

}

使用using简化函数指针

c++11增加了using关键字,也可以用来取别名,类似于typedef。如下所示。

1
using p_fun = void(*)(int a, int b);

这样写的好处是可以清楚地看到p_fun的数据类型便是右边的表达式,将名称与类型完全分离开了。