第 13 章 高级指针话题
# 第 13 章 高级指针话题
# 13.1 进一步探讨指向指针的指针
书本内容
# 13.2 高级声明
书本内容
笔记
- 用于声明变量的表达式和普通表达式在求值时所使用的规则相同
- 需要注意运算符的优先级
int (*f)(int)
是一个函数指针,其指向的函数接受一个int
类型的参数, 返回一个整型值int *(*f[])(float, int)
, 其中f
是一个数组- 数组元素的类型是函数指针
- 函数指针所指向的函数
- 接受一个
float
和int
类型的参数 - 返回一个指向整型的指针
- 接受一个
UNIX 系统中的 cdecl
可以在 C 语言声明和英语之间进行转换
cdecl> explain int *(*f[])(float, int)
declare f as array of pointer to function (float, int) returning pointer to int
cdecl> declare f as pointer to function(int) returning int
int (*f)(int )
1
2
3
4
5
2
3
4
5
- 可能需要安装
# 13.3 函数指针
书本内容
笔记
- 函数名在使用时, 总是由编译器将其转换为函数指针
- 所以对于函数名来说,
&
取地址操作符可省略 - 对于函数指针来说,
*
间接访问操作符也可以省略
- 所以对于函数名来说,
# 13.3.1 回调函数
书本内容
笔记
- 回调函数(callback function) 是一种技巧
- 用户把一个函数指针作为参数传递给其他函数, 后者将 "回调" 用户的函数
- 函数指针的参数的类型声明为
void *
, 表明一个指向未知类型的指针, 用时将指针所指向的类型声明为const
, 防止回调函数修改- 比如
int (*compare)(void const *, void const *)
- 回调函数在使用参数时, 需要进行强制类型转换
- 比如
# 13.3.2 转移表
书本内容
笔记
- 转移表是一个指向数组的指针, 其中数组元素为函数指针
- 根据不同的情况, 选择相应的函数进行操作
- 把具体的操作和选择操作的代码分开是一种良好的设计方案
- 比使用
switch
更加简洁
# 13.4 命令行参数
书本内容
# 13.4.1 传递命令行参数
书本内容
笔记
argc
指argument count
argv
指argument vector
argv
为char **
, 指向一个指针数组- 指针数组的末尾是一个
NULL
指针, 也可用于指示参数的数目 - 第一个参数是程序的名称
# 13.4.2 处理命令行参数
书本内容
# 13.5 字符串常量
书本内容
笔记
- 当字符串常量出现在表达式中时, 它的值是个指针常量
# 13.6 总结
书本内容
# 13.9 问题
#
问题 1
答案
声明 | 描述 |
---|---|
a | VIII |
b | III |
c | X |
d | XI |
e | IV |
f | IX |
g | XVI |
h | VII |
i | VI |
j | XIX |
k | XXI |
l | XXIII |
m | XXV |
#
问题 2
答案
ptr
加上1
后,其将指向array[1]
array[1]
中存放的是指向char
的指针
#
问题 3
答案
- 需要使用表达式
***arg
来获得这个参数所指代的整数 - 示意图如下
#
问题 4
答案
- 可以将
trans->product
声明为局部寄存器变量,同时将trans
也声明为寄存器变量- 一方面可以提升可读性
- 另一方面有助于编译器生成优化代码
- 优化目的是为了减少读取内存的次数
register Transaction *trans;
register Product *the_product;
the_product = trans->product;
the_product->orders += 1;
the_product->quantity_on_hand -= trans->quantity;
the_product->supplier->reorder_quantity += trans->quantity;
if( the_product->export_restricted ){
...
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
#
问题 5
答案
item | value |
---|---|
a | &p |
b | p |
c | p.x |
d | &a |
e | Illegal |
f | Illegal |
g | a |
h | Illegal |
i | Illegal |
j | Illegal |
k | Illegal |
l | p.x |
m | p |
#
问题 6
答案
a. 将 y
中各项的值复制到 x
b. 非法操作,因为 a
是一个指针, 而 y
不是
c. 将指针值从 b
复制到 a
d. 非法操作,因为 a
是一个指针, 而 *b
不是
e. 将 y
中各项的值复制到 x
注意结构名是一个标量
#
问题 7
答案
- 优点
- 使得处理命令行参数更加容易
- 缺点
- 只能使用这个函数所支持的方式来处理参数
- 由于其并不是标准的一部分,所以使用
getopt
会降低程序的可移植性
#
问题 8
答案
错误是程序尝试修改字符串常量
可以将
pathname
定义为字符串数组,从而来避免错误char pathname[] = "/usr/temp/xxxxxxxxxxxxxxx"; printf("%s\n", pathname); strcpy(pathname+10, "abcde"); printf("%s\n", pathname);
1
2
3
4
#
问题 9
答案
- 问题
- 数组
pathname
的空间已经被字符串全部占用,再使用strcat
会导致溢出,可能会覆盖掉其它有用的变量
- 数组
- 解决方案
- 根据文件名的大小,增大数组的空间
#
问题 10
中文书中题目的代码有错误,正确的应该为
char pathname[20] = "/usr/temp/"; /* ** Append the filename to the pathname. */ strcat( pathname, filename );
1
2
3
4
5
答案
- 问题
- 如果
filename
大于 11 个字符,则会溢出数组
- 如果
- 解决方案
- 根据分配给数组的空间和路径名的大小,限制文件名的大小
- 采用动态内存分配的方法来给
pathname
分配空间,这样可以根据filename
的大小动态的分配内存空间
#
问题 11
答案
- 有些编译器将字符串常量存放在无法进行修改的内存区域
- 在这种情况下,如果尝试修改字符串常量,则程序会异常中止
- 有些编译器只保存字符串常量的一份拷贝
- 在这种情况下,当在不同的地方使用了相同的字符串常量时
- 修改其中一个字符串常量将影响程序中所有出现这个字符串常量的地方
# 13.10 编程练习
#
编程练习 1
答案
与 第 9 章编程练习 1 题目相同,但是这里限制了不能使用一系列的
if
语句借助转移表,采用
for
循环来取代一系列的if
语句代码
#include <stdio.h> #include <stdlib.h> #include <ctype.h> int is_not_print(int ch) { return !isprint(ch); } static int (*type_func[]) (int) = { iscntrl, isspace, isdigit, islower, isupper, ispunct, is_not_print, }; #define TYPES ((sizeof(type_func))/(sizeof(type_func[0]))) static char *labers[TYPES] = { "control", "whitespace", "digits", "lower case", "upper case", "punctuation", "non-printable" }; static int count[TYPES]; int main(void) { int ch; int total = 0; int i; while ((ch = getchar()) != EOF) { total++; for(i = 0; i < TYPES; i++) if(type_func[i](ch)) count[i] += 1; } if (total == 0) printf("No characters in the input!\n"); else for(i = 0; i < TYPES; i++) printf("%3.0f%% %s\n", count[i] * 100.0 / total, labers[i]); return EXIT_SUCCESS; }
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#
编程练习 2
答案
需要知道链表节点的 link 字段的具体表示
代码
#include <stdio.h> #include "node.h" int sll_traverse(Node *current, void (*func)(Node *)) { while(current != NULL) { func(current); current = current->link; } }
1
2
3
4
5
6
7
8
9
10
11
#
编程练习 3
原函数
Node *list; Node *current; Transaction *transaction; typedef enum { NEW, DELETE, FORWARD, BACKWARD, SEARCH, EDIT } Trans_type; switch( transaction->type ){ case NEW: add_new_trans( list, transaction ); break; case DELETE: current = delete_trans( list, current ); break; case FORWARD: current = current->next; break; case BACKWARD: current = current->prev; break; case SEARCH: current = search( list, transaction ); break; case EDIT: edit( current, transaction ); break; default: printf( "Illegal transaction type!\n" ); break; }
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
答案
代码
Node *list; Node *current; Transaction *transaction; void func_add_new_trans(Node *list, Node **current, Transaction *transaction) { add_new_trans(list, transaction); } void func_delete_trans(Node *list, Node **current, Transaction *transaction) { delete_trans(list, *current); } void func_search(Node *list, Node **current, Transaction *transaction) { search(list, transaction); } void func_edit(Node *list, Node **current, Transaction *transaction) { edit(*current, transaction); } void func_forward(Node *list, Node **current, Transaction *transaction) { *current = (*current)->next; } void func_backward(Node *list, Node **current, Transaction *transaction) { *current = (*current)->prev; } typedef enum { NEW, DELETE, FORWARD, BACKWARD, SEARCH, EDIT } Trans_type; void (*func[])(Node *list, Node **current, Transaction *transaction) = { func_add_new_trans, func_delete_trans, func_forward, func_backward, func_search, func_edit }; # define N_TRANSACTIONS (sizeof(func) / sizeof(func[0])) if (transaction->type < 0 || transaction->type >= N_TRANSACTIONS) printf("Illegal transaction type!\n"); else func[transaction->type](list, ¤t, transaction);
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
提示
- 因为可能需要修改
current
的值,所以传递参数时要传递current
的地址 - 为了保证函数指针数组中的元素所指向函数的参数相同,需要重写一些函数
#
编程练习 4
答案
- 代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void swap(char *a, char *b, size_t element_size)
{
char temp;
while(element_size-- != 0)
{
temp = *a;
*b++ = *a;
*a++ = temp;
}
}
void
sort(void *array, int len, size_t element_size, int (*compare)(void*, void*))
{
char *i, *j, *base, *last;
base = (char *)array;
last = base + len * element_size;
for(i = base; i < last; i+=element_size)
for(j = i + element_size; j <= last; j+=element_size)
if(compare(i, j) > 0)
swap(i, j, element_size);
}
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
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
提示
- 注意,不可对
void
指针进行算术运算操作 - 可将
void*
转换为char*
后再进行相应的操作- 此程序要求
char
占用一个字节,在可移植性方面有一定的损失
- 此程序要求
#
编程练习 5
- 结合示例来理解具体的功能要求
- 在字符串中查找一个字符是否存在,使用
strchr
- 如果调用了
illegal_arg
, 则继续进行处理
答案
代码
#include <stdio.h> #include <string.h> char **do_args(int argc, char **argv, char *control, void (*do_arg)(int ch, char *value), void (*illegal_arg)(int ch)) { char *current_argv, *argv_in_control; while((current_argv = *++argv) != NULL && *current_argv == '-') { while(*++current_argv != '\0') { argv_in_control = strchr(control, *current_argv); if(argv_in_control == NULL) { illegal_arg(*current_argv); continue; } if(*(argv_in_control + 1) == '+') { if(*++current_argv != '\0') do_arg(*argv_in_control, current_argv); else if(*++argv != NULL) do_arg(*argv_in_control, *argv); else { illegal_arg(*argv_in_control); return argv; } break; } else do_arg(*argv_in_control, NULL); } } return argv; }
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
34
35
36
37原代码嵌套的
if-else
过多,可读性差,换为switch
语句后的代码如下:#include <stdio.h> #include <string.h> typedef enum {NONE, FLAG, ARGUMENT} Arg_Type; Arg_Type arg_type(char ch, char *control) { while(*control != '\0') if(ch == *control++) return (*control == '+') ? ARGUMENT : FLAG; return NONE; } char **do_args(int argc, char **argv, char *control, void (*do_arg)(int ch, char *value), void (*illegal_arg)(int ch)) { char *current_arg, ch; int skip_current_arg; while((current_arg = *++argv) != NULL && *current_arg == '-') { skip_current_arg = 0; while(!skip_current_arg && (ch = *++current_arg)!= '\0') { switch (arg_type(ch, control)) { case NONE: illegal_arg(ch); break; case FLAG: do_arg(ch, NULL); break; case ARGUMENT: if(*++current_arg != '\0' || (current_arg = *++argv) != NULL) { do_arg(ch, current_arg); skip_current_arg = 1; break; } illegal_arg(ch); return argv; } } } return argv; }
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48提示
if-else if
语句合并为一个if
语句,使用||
进行判断,代码更加简洁代码示例
#include <stdio.h> #include <string.h> typedef enum {NONE, FLAG, ARGUMENT} Arg_Type; Arg_Type arg_type(char ch, char *control) { while(*control != '\0') if(ch == *control++) return (*control == '+') ? ARGUMENT : FLAG; return NONE; } char **do_args(int argc, char **argv, char *control, void (*do_arg)(int ch, char *value), void (*illegal_arg)(int ch)) { char *current_arg, ch; int skip_current_arg; while((current_arg = *++argv) != NULL && *current_arg == '-') { skip_current_arg = 0; while(!skip_current_arg && (ch = *++current_arg)!= '\0') { switch (arg_type(ch, control)) { case NONE: illegal_arg(ch); break; case FLAG: do_arg(ch, NULL); break; case ARGUMENT: if(*++current_arg != '\0' || (current_arg = *++argv) != NULL) { do_arg(ch, current_arg); skip_current_arg = 1; break; } illegal_arg(ch); return argv; } } } return argv; }
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
编辑 (opens new window)
上次更新: 2022/02/19, 13:02:00