系统程序员成长计划004

/*    写了两个函数
    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 = &sum;
    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 = &sum;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 = &sum;
    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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值