/* 写了两个函数
int node_int_find_max(DListNode_t* head);
int node_int_calc_sum(DListNode_t* head);
*/
int node_int_find_max(DListNode_t* head)
{
int max;
max = (int)head->data;
while(head->pn != NULL){
if((int)head->data > max){
max = (int)head->data;
}
head = head->pn;
}
return max;
}
int node_int_calc_sum(DListNode_t* head)
{
int sum;
sum = (int)head->data;
while(head->pn != NULL){
sum += (int)head->data;
head = head->pn;
}
return sum;
}
dlist.c
============================================
第一次修改
============================================
根据书上提示,抽象出重复代码,其他的操作传入一个回调函数。
写遍历函数foreach,操作函数,操作函数的指针。
遍历函数:
void* node_foreach_op(DListNode_t* head, pFunOps pfun_op)
{
void* val;
while(head->pn != NULL){
val = pfun_op(head->data); //max or sum
head = head->pn;
}
return val;
}
dlist.c
-------------------------------------------------------
dlist.h
操作函数的指针:
typedef void* (*pFunOps)(void* val);
void* node_foreach_op(DListNode_t* head, pFunOps pfun_op);
-------------------------------------------------------
main.c
两个操作函数max/sum:
void* sum(void* val)
{
void* i=NULL;
i = (void*)((int)i + (int)val);
return i;
}
void* max(void* val)
{
void* i=NULL;
if((int)val > (int)i)
i = val;
return i;
}
main函数:
DListNode_t* p = node_create((void*)0);
pFunOps pfun_ops;
pfun_ops = ∑
print_int(node_foreach_op(p, pfun_ops));
pfun_ops = &max;
print_int(node_foreach_op(p, pfun_ops));
运行结果不对,发现max和sum两个函数,每次被调用之后的结果都没有保存记录。
max和sum要传入一个contex参数,用来记录计算结果。
不然就只能定义个static变量来记录。
但是这个static变量在第二次函数被调用的时候,已经不是初始值了,而是上一次调用结束的结果。
因此又会引入一个参数,用于标志把static函数初始化。
书上代码的指示:传入个指针就没事了,如果想传进来0,直接在传入之前清零就好了。
============================================
第二次修改
============================================
修改的地方已标红
dlist.c
void* node_foreach_op(DListNode_t* head, pFunOps pfun_op)
{
void* val;
while(head->pn != NULL){
pfun_op(val, head->data); //max or sum
head = head->pn;
}
return val;
}
--------------------------------------------------------------------
dlist.h
typedef void (*pFunOps)(void* ctx, void* val);
void* node_foreach_op(DListNode_t* head, pFunOps pfun_op);
--------------------------------------------------------------------
main.c
两个操作函数;
void sum(void* ctx, void* val)
{
ctx = (void*)((int)ctx + (int)val);
}
void max(void* ctx, void* val)
{
if((int)val > (int)ctx)
ctx = val;
}
main函数没有变化
pFunOps pfun_ops;
pfun_ops = ∑
print_int(node_foreach_op(p, pfun_ops)); //<! ##
pfun_ops = &max;
print_int(node_foreach_op(p, pfun_ops));
这样写法程序运行到 ## 处时崩溃!
出现的bug、槽点:
一、遍历函数 node_foreach_op 中的void* val没有初始化地址,是野指针!
而且这个需要传给下层的操作函数sum/max使用的指针,不应该由foreach函数提供。
是用户想要得到一个结果,这个变量保存着这个结果,所以这个变量应该由调用foreach的地方定义!!!
二、第一个问题解决之后,再想想。
由调用foreach的地方定义一个指针,让他指向哪里???指向NULL那不是空指针吗,也不能对其的指向(那个地址)进行操作啊!?
原来,我的操作函数sum/max操作的,竟然一直是对指针的指向操作,是对这些个地址的操作!!!
ctx = (void*)((int)ctx + (int)val); 把两个指针指向的地址加起来,赋给ctx!!!
if((int)val > (int)ctx) ctx = val; 比较两个指针指向的地址的大小(高低),把高地址赋给ctx!!!
我的天!!
这里一半对了,一半错了。
1.对了的一半是val这个指针,他的指向,确实保存了链表节点的data。
2.错了的一半是ctx不能这么用,原因见见第四次修改。
val指针虽然可以这么用,但你一定不要对这样的指针进行取值操作,只能把他强制转换为int类型来使用。
其他情况下,这个指针会指向结构,不会有这种尴尬的情况存在。
由调用foreach的地方定义一个指针,让他指向哪里???指向NULL那不是空指针吗,也不能对其的指向(那个地址)进行操作啊!?
答:这里就必须要定义一个实体了,原因见第四次修改。
============================================
第三次修改
============================================
修改的地方已标红
dlist.c
void node_foreach_op(DListNode_t* head, pFunOps pfun_op, void* ctx)
{
while(head->pn != NULL){
pfun_op(ctx, head->data); //max or sum
head = head->pn;
}
}
--------------------------------------------------------------------
dlist.h
typedef void (*pFunOps)(void* ctx, void* val);
void node_foreach_op(DListNode_t* head, pFunOps pfun_op, void* ctx);
--------------------------------------------------------------------
main.c
两个操作函数;
void sum(void* ctx, void* val)
{
(*(int*)ctx) = (*(int*)val); //<! ## 崩溃
}
void max(void* ctx, void* val)
{
if(*(int*)val > *(int*)ctx)
*(int*)ctx = *(int*)val;
}
main函数没有变化
pFunOps pfun_ops;
pfun_ops = ∑
int vl = 0;
node_foreach_op(p, pfun_ops, &vl)
print_int(&vl);
pfun_ops = &max;
vl = 0;
node_foreach_op(p, pfun_ops, &vl)
print_int(&vl);
崩溃原因,节点数据类型是void*,他没有指针指向地址的实体,对void*指向的地址内容进行操作。非法访问内存。。。
这里也是一半对了,一半错了。
1.对了的一半是ctx这个指针,他的指向,确实有实体vl。
2.错了的一半是val不能这么用,原因见上。
============================================
第四次修改
============================================
那就把max/sum改回去:
void sum(void* ctx, void* val)
{
//(*(int*)ctx) = (*(int*)val);
ctx = (void*)((int)ctx + (int)val);
}
void max(void* ctx, void* val)
{
//if(*(int*)val > *(int*)ctx)
// *(int*)ctx = *(int*)val;
if((int)val > (int)ctx)
ctx = val;
}
pFunOps pfun_ops;
pfun_ops = ∑void* val = NULL;
node_foreach_op(p, pfun_ops, val);
print_int(val);val = NULL;
pfun_ops = &max;
node_foreach_op(p, pfun_ops, val);
print_int(val);
这里要从定义int val = 0,改回void* val = NULL
如果继续使用int val = 0,并且使用&val的话,后边计算的时候会发现,&val不是0,是随机的。。。
运行之后,结果是0。。。
调试发现,
void sum(void* ctx, void* val)
{
//(*(int*)ctx) = (*(int*)val);
ctx = (void*)((int)ctx + (int)val);
/* 这里每次的ctx是变化的。 */
}
void node_foreach_op(DListNode_t* head, pFunOps pfun_op, void* ctx)
{
while(head->pn != NULL){ //<! 打断点,单步执行
pfun_op(ctx, head->data); //max or sum
/* 返回之后的ctx值,重新变成了0 */
head = head->pn;
}
}
因为。。。是ctx指针的拷贝,指向的地址被修改了,外边这位ctx并没有改变。。。
要是这样做的话,要涉及到二级指针了,遂放弃。。。
看看人家书上是怎么做的。
============================================
第五次修改
============================================
书上的写法:
节点的数据用void*直接存放整数,这是比较危险的做法。原因见第二次修改时的记录,标红处。
这里传入的ctx指针看来是在调用foreach之前,外边有实体的。不然结果存到了指针指向的地址啊!!!
static DListRet sum_cb(void* ctx, void* data)
{
long long* result = ctx;
*result += (int)data;
return DLIST_RET_OK;
}
所以有了最后:
void sum(void* ctx, void* val)
{
long long* result = (long long*)ctx;
*result += (int)val;
}
void max(void* ctx, void* val)
{
long long* rslt = (long long*)ctx;
if((int)val > *rslt)
*rslt = (int)val;
}
main部分:
pFunOps pfun_ops;
pfun_ops = ∑
long long val = 0;
node_foreach_op(p, pfun_ops, &val);
print_int((void*)val);
val = 0;
pfun_ops = &max;
node_foreach_op(p, pfun_ops, &val);
print_int((void*)val);
foreach没问题:
void node_foreach_op(DListNode_t* head, pFunOps pfun_op, void* ctx)
{
while(head->pn != NULL){
pfun_op(ctx, head->data); //max or sum
head = head->pn;
}
}
总结:
1. 不要写重复的代码
2. 任何回调函数都要有上下文
3. 求和和求最大值不是 dlist 应该提供的功能,放在 dlist 里面实现是不应该的。
为了能实现这些功能,我们提供一种满足这些需求的机制就好了。热心肠是好的,但一定不能违背原则,否则就费力不讨好了 。
修改的记录,其实就是对这两个函数的不断认知:
void sum(void* ctx, void* val)
{
//(*(int*)ctx) = (*(int*)val);
//ctx = (void*)((int)ctx + (int)val);
long long* result = (long long*)ctx;
*result += (int)val;
}
void max(void* ctx, void* val)
{
//if(*(int*)val > *(int*)ctx)
// *(int*)ctx = *(int*)val;
//if((int)val > (int)ctx)
// ctx = val;
long long* rslt = (long long*)ctx;
if((int)val > *rslt)
*rslt = (int)val;
}