【问题标题】:return matrix on CC 上的返回矩阵
【发布时间】:2018-02-25 07:31:51
【问题描述】:

下面是我的代码。

#include <stdio.h>
#define _USE_MATH_DEFINES
#include <math.h>

void rot(int angle);
static double R[3][3]={0};

int main(void) {
    int angle = 30;
    rot(angle);
    int i, j = 0;

   for (i = 0; i < 3;i++) {
      for (j = 0; j < 3; j++) {
        printf("%lf\n", R[i][j]);
    }
}
return 0;
}

void rot(int angle) {
    double cang = cos(angle*M_PI / 180);
    double sang = sin(angle*M_PI / 180);

    R[0][0] = cang;
    R[1][1] = cang;
    R[1][0] = -sang;
    R[0][1] = sang;
}

目前,函数 rot 本身不返回任何值。 但由于 R 是静态双精度,我能够打印出由 rot 函数更改的 R。 我想更改函数 rot 以返回 R[3][3](二维数组)。 所以我想在 main like 上使用 rot 函数

double R1=rot(30);
double R2=rot(60);

有什么办法可以实现吗?

【问题讨论】:

  • 会一直是 [3][3] 还是尺寸会改变?
  • 避免使用全局变量。无需将 R 声明为全局变量。在 main 中声明 R 并根据需要将其作为参数传递。
  • 您的编辑令人困惑。在您拥有double R1[3][3]=rot(30); 之前,这表明rot() 应该返回一个数组(这是不可能的)。现在您有了double R1=rot(30);,这表明rot() 应该返回double。这似乎与问题中的描述完全不同:“我想将函数 rot() 更改为返回 R[3][3](二维数组)。”您为什么要进行此更改,实际问题是什么?以下答案如何无法回答您的问题?

标签: c arrays matrix return-value


【解决方案1】:

副本不一定对此有好处。由于这些 R² 矩阵需要易于复制,因此表示它们的最佳方法是将它们放在 struct 中:

typedef struct Matrix3x3 {
    double v[3][3];
} Matrix3x3;

然后将它们作为返回值是微不足道的:

Matrix3x3 rot(int angle) {
    Matrix3x3 m = {0};
    double cang = cos(angle*M_PI / 180);
    double sang = sin(angle*M_PI / 180);

    m.v[0][0] = cang;
    m.v[1][1] = cang;
    m.v[1][0] = -sang;
    m.v[0][1] = sang;
    return m;
}

与数组不同,包含数组的结构也很容易复制:

Matrix3x3 x = y;

【讨论】:

  • 与静态数组(或返回指针的函数)的唯一区别是使用R.v[index1][index2] 而不是R[index1][index2] 访问矩阵元素。我尝试用返回指向矩阵的指针的函数来回答这个问题,但这要简单得多。
  • 当然,您可以使用指向矩阵的指针,而且有时它会更快,这取决于编译器的智能程度,但即便如此,包装在结构中也会让复制变得更加容易。跨度>
  • 请注意,m.v 中有 5 个不确定值是从 rot() 函数返回的。我在写答案时也被这个抓住了,但被 Valgrind 救了;)
【解决方案2】:

C 不允许从函数返回数组。可以在调用函数中分配数组,也可以在函数中动态分配数组并返回指向该数组的指针。请注意,在函数中定义的数组是局部的,返回指向此类数组的指针是没有用的,因为在函数返回后局部变量不再存在。另一方面,动态分配继续存在。第三种选择是利用这样一个事实,虽然数组不能从 C 中的函数返回,structs 可以从函数返回并赋值。

在调用者中定义一个数组

一个简单的解决方案是在调用函数中定义一个数组。该数组将在rot_3x3_a() 函数中进行修改。请注意,在函数中使用memset()(在string.h 中定义)将数组归零,而不是依赖函数的用户记住对数组进行零初始化。

使用这种方法的优点是可以避免动态分配,并且需要稍后释放以避免内存泄漏。所有的方法都需要在调用函数中声明一个变量来接收旋转矩阵,但是这种方法也需要将数组传递给函数,所以可能不符合OP的要求。

动态分配

一个更复杂的解决方案是使用动态分配。 rot_3x3_b() 函数只接受一个 int 参数,正如 OP 示例所暗示的那样,但返回一个指向 3 个 doubles 数组的指针(这是 3x3 数组在大多数表达式中衰减到的指针类型)。这里使用了typedef,所以Matrix_3x3是一个指向3个doubles的数组的指针;这样更容易编写函数原型。

这里,memset() 可以与malloc() 一起使用,但使用了calloc(),这会自动将分配初始化为零。请注意,在rot_3x3_b() 函数中,如果分配失败,则不会发生其他任何事情,并返回一个空指针。调用者必须对此进行检查并适当地处理错误。

使用这种方法的优点是生成的矩阵分配可以简单地索引为二维数组,并且函数的参数符合 OP 要求。缺点是这个代码比其他解决方案稍微复杂一些,而且更容易出错,而且这个函数的用户必须记住free分配的内存以避免内存泄漏。

将数组包装在结构中

虽然 C 不允许将一个数组分配给另一个数组,但可以将一个 struct 分配给另一个 struct。此外,struct 是一个可以从函数返回的左值。因此,该问题的一种解决方案是在 rot_3x3_c() 函数中创建一个包含 3x3 数组的 struct。在从函数返回struct 之前,可以填充struct 的数组成员。在这种情况下,已使用指定的初始化程序对 struct 的数组成员进行零初始化。

这里的优点是实现简单。没有动态分配,所以不用担心内存泄漏。缺点是数组必须作为struct成员访问,所以这个函数的用户必须声明struct Mtrx_3x3 R_c才能接收函数返回的值,并且必须记得用R_c.mx访问数组。

关于常量M_PI

C 标准不仅没有定义这个常量,而且规定一个符合要求的实现必须默认不定义它。这意味着如果一个特定的实现确实定义了M_PI,它是一个必须显式启用的扩展。作为一个通用扩展,定义了这个常量。例如,如果使用gcc -std=gnu11 编译,则无需显式启用M_PI。但如果使用更严格的选项之一,例如,gcc -std=c11M_PI 必须显式启用。 Here is an SO question that discusses the issue.

现在,_USE_MATH_DEFINES 适用于 Microsoft 实现,但不适用于 Linux 中的 GCC(据我所知)。如需更多便携性,请note that M_PI is defined in POSIX。这可以通过使用功能测试宏_XOPEN_SOURCE 700 来启用。在源文件的第一行添加:

#define _XOPEN_SOURCE  700

或在编译时从命令行启用:

gcc -std=c11 -D_XOPEN_SOURCE=700

请注意,当使用#define 启用功能时,它必须位于源文件的最开头才能工作。当然这里也可以用-std=c99-std=c89代替std=c11

这将适用于与 POSIX 密切相关的 Linux 系统,但我不确定它是否适用于与 POSIX 不太密切相关的 Microsoft 系统。为了获得最大的可移植性,最好简单地明确定义常量,使用:

#define M_PI  3.14159265358979323846

一个示例程序

这是一个说明上述每种方法的程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define M_PI  3.14159265358979323846

typedef double (*Matrix_3x3)[3];
struct Mtrx_3x3 {
    double mx[3][3];
};

void rot_3x3_a(int angle, Matrix_3x3 mtrx);
Matrix_3x3 rot_3x3_b(int angle);
struct Mtrx_3x3 rot_3x3_c(int angle);
void print_3x3_matrix(Matrix_3x3);

int main(void)
{
    int angle = 30;

    /* Allocate for matrix in caller */
    puts("Method a: pass an array");
    double R_a[3][3];
    rot_3x3_a(angle, R_a);
    print_3x3_matrix(R_a);
    putchar('\n');

    /* Use dynamic allocation in function; must remember to free()! */
    puts("Method b: use dynamic allocation");
    Matrix_3x3 R_b = rot_3x3_b(angle);
    if (R_b == NULL) {
        perror("Allocation failure in rot_3x3_b()");
    } else {
        print_3x3_matrix(R_b);
        putchar('\n');
    }

    /* Return the array inside a struct from the function */
    puts("Method c: wrap the array in a struct");
    struct Mtrx_3x3 R_c = rot_3x3_c(angle);
    print_3x3_matrix(R_c.mx);           // remember the array is R_c.mx
    putchar('\n');

    /* Cleanup */
     free(R_b);

    return 0;
}

/* Takes an array as an argument */
void rot_3x3_a(int angle, Matrix_3x3 mtrx)
{
    /* Zero the array first */
    memset(mtrx, 0, sizeof *mtrx * 3);

    double cang = cos(angle * M_PI / 180);
    double sang = sin(angle * M_PI / 180);

    mtrx[0][0] = cang;
    mtrx[1][1] = cang;
    mtrx[1][0] = -sang;
    mtrx[0][1] = sang;
}

/* Returns a pointer to a dynamically allocated array which must be
 * deallocated by the caller with free(), or returns NULL */
Matrix_3x3 rot_3x3_b(int angle)
{
    Matrix_3x3 mtrx = calloc(3, sizeof *mtrx);

    if (mtrx) {
        double cang = cos(angle * M_PI / 180);
        double sang = sin(angle * M_PI / 180);

        mtrx[0][0] = cang;
        mtrx[1][1] = cang;
        mtrx[1][0] = -sang;
        mtrx[0][1] = sang;
    }

    return mtrx;
}

/* Returns a Mtrx_3x3 struct with a 3x3 array in the mx field */
struct Mtrx_3x3 rot_3x3_c(int angle)
{
    struct Mtrx_3x3 mtrx = { .mx = {{ 0 }} };

    double cang = cos(angle * M_PI / 180);
    double sang = sin(angle * M_PI / 180);

    mtrx.mx[0][0] = cang;
    mtrx.mx[1][1] = cang;
    mtrx.mx[1][0] = -sang;
    mtrx.mx[0][1] = sang;

    return mtrx;
}

void print_3x3_matrix(Matrix_3x3 mtrx)
{
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%10.5f", mtrx[i][j]);
        }
        putchar('\n');
    }
}

程序输出:

Method a: pass an array
   0.86603   0.50000   0.00000
  -0.50000   0.86603   0.00000
   0.00000   0.00000   0.00000

Method b: use dynamic allocation
   0.86603   0.50000   0.00000
  -0.50000   0.86603   0.00000
   0.00000   0.00000   0.00000

Method c: wrap the array in a struct
   0.86603   0.50000   0.00000
  -0.50000   0.86603   0.00000
   0.00000   0.00000   0.00000

【讨论】:

  • 什么?你的math.h 没有定义M_PI?绝对值得为教育价值投票。很好的解释。
  • @DavidC.Rankin-- 谢谢。由于使用M_PI 存在可移植性问题,我只是明确定义了它(出于习惯,没有评论)。 _USE_MATH_DEFINES 无论如何都不能在我的 Linux 系统上运行。我在回答中添加了对情况的讨论;如果您发现任何问题,请告诉我;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-05-11
  • 2018-02-17
  • 2014-09-18
  • 2012-11-03
  • 1970-01-01
  • 2022-01-23
  • 1970-01-01
相关资源
最近更新 更多