[流畅的 C] const 在函数中的使用技巧
测试 const 作用的代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
int bar = 8;
char cfoo = 'A';
char *cp = &cfoo;
printf("*cp: %c\n", *cp);
*cp = 'B'; printf("*cp = 'B';\n");
printf("cfoo: %c\n", cfoo);
printf("*cp: %c\n", *cp);
#if 1
const char *ccp = NULL;
ccp = &cfoo; printf("\nccp = &cfoo;\n");
printf("*ccp: %c\n", *ccp);
#endif
#if 0
const char *ccpchg = NULL;
ccpchg = &cfoo; printf("ccpchg = &cfoo;\n");
printf("\n*ccpchg: %c\n", *ccpchg);
*ccpchg = 'C'; printf("*ccpchg = 'C';\n");
printf("cfoo: %c\n", cfoo);
printf("*ccpchg: %c\n", *ccpchg);
#endif
#if 0
char * const rccp = NULL;
rccp = &cfoo; printf("rccp = &cfoo;\n");
printf("\n*rccp: %c\n", *rccp);
#endif
#if 0
const char * const tripcp = &cfoo;
printf("\n*tripcp: %c\n", *tripcp);
tripcp = &bar; printf("tripcp = &bar;\n");
*tripcp = 777; printf("*tripcp = 777;\n");
printf("bar: %d\n", bar);
printf("*tripcp: %d\n", *tripcp);
#endif
return 0;
}
usage
$ gcc const_practice.c && ./a.out
..
$
$ vim const_practice.c
...依次将 #if 0 改为 #if 1 观察编译器报错。
...
Summary
函数定义传递(结构体)指针使用 const:
int example_func(const struct theProtocol *packet_p);
or
int example_func(struct theProtocol const *packet_p);
const 在类型前后都是一样的,不同的是在 *
的前后。
(从上面的测试代码的测试结果我们可以得出结论:)
const 在*
前面,packet_p 指向的地址上储存的内容不可改变。const 在
*
后面, packet_p 变量本身不能修改
(和最简的const int integer=99;
的作用/含义 一致)。所以,只需要记住这一点:
const float PI=3.14; ==作用范围等同== char * const ARR = &arr;
因为这个 const 都是修饰 变量本身,也就是说,这些变量的使用限制不用在你的大脑里做一层“转化”,不用涉及“地址”。
你想要修改
PI=3.1415926;
是不行的,而 ARR 也一样,ARR = &name;
这样的做法也是不行的。
而我们在函数需要使用 const 限定传递的(结构体)指针时,一般是不会希望可以修改结构体中的内容的。所以使用方式是将 const 放在 *
的前面。
(再强调一遍,不需要记住 const 在 *
前面是修饰什么,只需要记住
float const PI=3.14; ==作用范围等同== char * const ARR = &arr;
这一点就可以。)
一般我个人会偏向与这么使用:
int func_need_use_content_in(const struct theProtocol * const packet);
因为我追过数次协议栈实现的源码,里面对一个 receive 的 packet 比如命名为
packet
, 那么这个 packet 原本是从 接收过来的 frame 拆包之后的 struct,在对里面的数据使用和执行相应的操作之后(往往 3~10 层函数调用),现在需要回复了,然后就直接在 packet 上修改内容作为回复了(当然,修改 packet 地函数调用一般也有数层)。
好吧,你可以这么干,毕竟是你厉害地将协议栈实现成了实际代码,但是:
如果每个函数调用有 const struct theProtocol * packet
这样的限定标识,在追一个 packet 中的某一处的改变是在哪里做的,而不是要分析每个函数都在 packet 的基础上做了什么,会节省不少生命。
当然,两个 const, 我还真的还少看到,这里面有什么原因不这样用吗???
但是至少一个 const: func( const struct theProtocol * packet, ... );
还是需要保证的。
另外,如果在会修改、需要修改结构体内部数据的函数上这么使用:
func( struct theProtocol * const packet, ... );
既然有人这么用了,然后你还很负责任地不断学习,补充自己,然后你就会嗅到代码中有一种特别地含义,为什么不是一般人这么用:
func( struct theProtocol * packet, ... );
?
当然,我上面说了,我个人观点这种用法很原始(初级)。
你就会思考,这个函数的声明, packet 指针变量本身储存的内容不会改变(指针储存了一个地址值),但是它指向的地址上的数据是可以改变的,为什么要特意这么做?
难道不是这个函数就是为了修改这个特别限定的指针指向的地址上的内容而特意这么写的 const?
当然,如果要这么做,最好能形成一定约定。
于是,如果这么配合了使用:
func_only_use_content( const struct theProtocol *packet );
&
func_need_change_content( struct theProtocol * const packet);
不需要任何注释,function 对于这个 packet 的使用/作用方式呼之欲出。
Reference
一个没有 const 限定的函数隐式提升了权限的的编译器警告