我正在try 创建一个函数,该函数可以将任意类型的两个数字相加(浮点数、整型、双精度等),并将结果保存到另一个相同类型的数字中.

我正在查看void *,了解到通过强制转换为unsigned char *,我可以获得每个数字的每个字节.

我面临的问题是,当我添加大于unsigned char范围的值时,它会环绕.所以在我的 case 中,255 + 1 = 0.

到目前为止这是我的代码,

typedef unsigned char byte;

// add two numbers (of any type) of size abc_size and save in c
void add(void *a, void *b, void *c, size_t abc_size) {
    byte *a_bytes = (byte *) a;
    byte *b_bytes = (byte *) b;
    byte *c_bytes = (byte *) c;

    for (size_t i = 0; i < abc_size; i++) {
        byte a_byte = a_bytes[i];
        byte b_byte = b_bytes[i];

        byte sum = a_byte + b_byte; // do i have to account for carry or endianess?
        c_bytes[i] = sum;
    }
}

和我用来测试的主机,

#include <stdio.h>

int main(void) {
    int a = 255;
    int b = 1;
    int c = 0;

    add(&a, &b, &c, sizeof(int));

    printf("%d + %d = %d\n", a, b, c);
}

上面的输出是,

255 + 1 = 0

我也面临着一个问题,当我试图添加floatsdoubles,结果是完全意想不到的,我会再次想象是因为溢出.

这种方法对任何数字和任何类型都有效吗?如果是这样的话,我需要改变什么?

提前谢谢您.

推荐答案

C++

您通过将两个任意类型的数字转换为unsigned char *并访问每个字节来将它们相加的方法不会起作用,原因有几个:

  • 正如您注意到的,unsigned char只能保存从0到255的值,因此任何产生大于255的值的加法都将溢出并绕回.您需要考虑进位位并将其传播到下一个字节.例如,如果将255 + 1相加,则得到进位为10,应将其添加到下一个字节.
  • 您还需要考虑系统的字节顺序,这决定了多字节值的字节在内存中的存储方式.例如,在小端系统中,最先存储最低有效字节,而在大端系统中,最先存储最高有效字节.您可以使用htonsntohs函数在主机和网络字节顺序之间进行转换,该顺序是大端的.
  • 不能通过简单地将floatdouble值的字节相加来将它们相加,因为它们的表示形式与整数不同.它们使用一种名为IEEE 754的格式,该格式由三部分组成:符号、指数和尾数.您需要了解这种格式是如何工作的,以及如何对其执行算术运算.或者,您可以使用union作为不同类型访问相同的内存,但这是不可移植的,并且可能违反严格的别名规则.

实现泛型Add函数的更好方法是使用C++中的模板,它允许您编写适用于不同类型的代码,而不会强制转换或丢失精度.例如:

template <typename T>
void add(T a, T b, T& c) {
    c = a + b;
}

此函数适用于任何支持+运算符的类型,如intfloatdouble等.您也可以为具有自定义加法方式的其他类型重载此函数,如std::stringstd::vector.

要使用此函数,只需使用适当的类型调用它:

int main() {
    int a = 255;
    int b = 1;
    int c = 0;

    add(a, b, c);

    printf("%d + %d = %d\n", a, b, c);
}

该程序的输出为:

255 + 1 = 256

我希望这能帮助您理解为什么您的方法不起作用,以及如何改进它.有关模板、字符顺序和IEEE 754格式的更多信息,请访问以下链接:

  1. https://www.cplusplus.com/doc/oldtutorial/templates/
  2. https://en.wikipedia.org/wiki/Endianness
  3. https://en.wikipedia.org/wiki/IEEE_754

C (After-edit)

我很抱歉没有注意到问题是关于C而不是C++.我之前的回答是基于C++模板的,而C中没有这些模板.下面是一个仅使用C功能的更新答案.

如果您只想在C中实现一个通用的Add函数,您有几个 Select :

  • 您可以使用在编译时展开为适当类型和操作的宏.例如:
#define add(a, b, c) ((c) = (a) + (b))

此宏适用于任何支持+运算符的类型,但它有一些缺点,例如多个参数求值和缺乏类型判断.

  • 您可以使用空指针并将类型的大小作为附加参数传递.例如:
void add(void *a, void *b, void *c, size_t size) {
    // copy the bytes from a and b to c
    memcpy(c, a, size);
    // add the bytes from b to c
    for (size_t i = 0; i < size; i++) {
        // get the pointers to the current bytes
        unsigned char *c_byte = (unsigned char *)c + i;
        unsigned char *b_byte = (unsigned char *)b + i;
        // add the bytes with carry
        unsigned char sum = *c_byte + *b_byte;
        unsigned char carry = sum < *c_byte;
        *c_byte = sum;
        // propagate the carry to the next byte
        if (carry) {
            size_t j = i + 1;
            while (j < size && ++*((unsigned char *)c + j) == 0) {
                j++;
            }
        }
    }
}

此函数适用于任何整数类型,但它有一些缺点,例如不处理字符顺序,也不适用于浮点类型.

  • 您可以使用不同类型的联合和标记来指示哪种类型处于活动状态.例如:
typedef enum { INT, FLOAT, DOUBLE } tag_t;

typedef union {
    int i;
    float f;
    double d;
} value_t;

typedef struct {
    tag_t tag;
    value_t value;
} generic_t;

void add(generic_t *a, generic_t *b, generic_t *c) {
    // check that the types are compatible
    if (a->tag != b->tag) {
        fprintf(stderr, "Cannot add different types\n");
        exit(1);
    }
    // set the tag of the result
    c->tag = a->tag;
    // perform the addition based on the tag
    switch (a->tag) {
        case INT:
            c->value.i = a->value.i + b->value.i;
            break;
        case FLOAT:
            c->value.f = a->value.f + b->value.f;
            break;
        case DOUBLE:
            c->value.d = a->value.d + b->value.d;
            break;
        default:
            fprintf(stderr, "Unknown type\n");
            exit(1);
    }
}

该函数适用于包含在联合中的任何类型,但它有一些缺点,例如标记需要额外的内存,并且对于其他类型不可扩展.

正如您所看到的,在C中没有针对泛型编程的完美解决方案.您必须在简单性、效率、可移植性和灵活性之间进行权衡.有关C语言泛型编程的更多信息,您可以访问以下链接:

  1. How to make generic function using void * in c?
  2. C generic type as function argument input

C++相关问答推荐

如何将已分配的数组(运行时已知的大小)放入 struct 中?

为什么GCC可以调用未定义的函数?

为什么可以在typedef之前使用typedef d struct 体?

在一个小型玩具项目中实现终端历史记录功能

C语言中的strstr问题

Setenv在c编程中的用法?

在Rust和C之间使用ffi时如何通过 struct 中的[U8;1]成员传递指针

CGO:如何防止在使用CGO在包中包含C头文件时出现多个定义...&q;错误?

==284==错误:AddressSaniizer:堆栈缓冲区下溢

如何使用libgpio(d)为Raspberry Pi编译C程序?

获取前2个连续1比特的索引的有效方法

按长度对argv中的单词进行排序

隐藏测试用例无法在c程序中计算位数.

C: NULL>;NULL总是false?

为什么argc和argv即使在主函数之外也能工作?

共享内存未授予父进程权限

与指针的原始C数组或C++向量<;向量<;双>>;

10 个字节对于这个 C 程序返回后跳行的能力有什么意义

UEFI 应用程序中的计时器回调仅在 AMI BIOS 中挂起

如何在 C 中的 Postgres 函数的表中 for 循环