将数组传递给函数(以及它在C ++中不起作用的原因)

时间:2018-02-21 08:46:54

标签: c++ c

我遇到了一些编译的C代码,但我不明白为什么。具体来说,我有一个C库,它有很多使用这种格式的代码:

void get_xu_col(int i_start,
                int n,
                double x[n],
                int n_x,
                int n_u,
                int n_col,
                double xu_col[n_col][n_x + n_u]){
    ... 
}

int main(){
    ...
    double xu_col[n_col][n_x + n_u];
    get_xu_col( ..., xu_col );
    ...
}

我不明白为什么编译器允许在数组中调整大小。据我所知,大小必须固定(例如xu_col[9][7])或未定义(例如xu_col[][])。在上面的代码中,似乎大小不是编译时常量。

编译器是否只是忽略了这里的参数?或者它是否真的在维度上进行编译时检查?

如果是后者,那么分别传递尺寸似乎容易出错。

问题的第二部分是:

为什么相同的版本在C ++中不起作用?当我将文件扩展名从.c更改为.cpp并尝试重新编译时,我得到了

candidate function not viable: no known conversion from 'double [n_col][n_x + n_u]' to 'double (*)[n_x + n_u]' for 7th argument
void get_xu_col(int i_start, int n, double x[n], int n_x, int n_u, int n_col, double xu_col[n_col][n_x + n_u]);

我想知道我应该使用哪个成语来将此代码转换为C ++,因为显然以前的习惯用法是用C语言编写的,而不是C ++。

7 个答案:

答案 0 :(得分:59)

在C中,只要大小位于参数列表中的数组之前,就可以使用函数参数来定义可变长度数组参数的大小。这在C ++中不受支持。

答案 1 :(得分:36)

它在C中工作的原因,而不是在C ++中工作的原因仅仅是因为它是C代码而不是C ++。这两种语言共享历史,而不是语法。

传递可变大小数组的C ++方法是std::vector,如果您打算修改函数中的向量,可能是 reference ,或const 参考如果你没有。

答案 2 :(得分:11)

  

我不明白为什么编译器允许在数组中调整大小。据我所知,大小必须是固定的(例如xu_col [9] [7])或未定义的(例如xu_col [] [])。在上面的代码中,似乎大小不是编译时常量。

你是对的,大小不是编译时常量。如果你有一个二维数组,x [line] [col],编译器需要一行中的元素数来计算一个元素的地址。查看get_char_2()和get_char_3()示例代码。

如果使用可变长度数组(VLA)作为函数参数,则必须提供这些数字(请参阅get_char_1示例)。你可以写:

 my_func( x[][width] )

或者你可以写

 my_func( x[999][width] )
  

编译器是否只是忽略了这里的参数?或者它是否真的对维度进行了>编译时检查?

编译器将忽略第一个数字(999)。第二是需要的。如果没有行大小,编译器就无法计算这些2D数组中的地址。 编译器不对C中的VLA进行运行时或编译时检查。

/* file: vla.c
 *
 * variable length array example
 *
 * compile with:
 *   
 *    gcc -g -Wall -o vla vla.c 
 *
 */

#include <stdio.h>
#include <wchar.h>


/* 4 Lines - each line has 8 wide-characters */
wchar_t tab[][8] = {
{ L"12345678" },
{ L"abcdefgh" },
{ L"ijklmnop" },
{ L"qrstuvwx" }
};

/* memory layout:   
   0x00:   0x0031  0x0032 0x0033  0x0034  0x0035  0x0036  0x0037  0x0038 
   0x20:   0x0061  0x0062 0x0063  0x0064  0x0065  0x0066  0x0067  0x0068 
   ...

*/



/* get character from table w/o variable length array and w/o type */
char get_char_3(int line, int col, int width, int typesize, void *ptr )
{
char ch = * (char *) (ptr + width * typesize * line + col * typesize ); 

printf("line:%d col:%d char:%c\n", line, col, ch ); 
return ch;
}


/* get character from table w/o variable length array */
char get_char_2(int line, int col, int width, wchar_t *ptr)
{
char ch = (char) (ptr + width * line)[col]; 

printf("line:%d col:%d char:%c\n", line, col, ch ); 
return ch;
}

/* get character from table : compiler does not know line length for 
   address calculation until you supply it (width). 
*/
char get_char_1(int line, int col, int width, wchar_t aptr[][width] )
{
/* run-time calculation: 
   (width * sizeof(char) * line)  + col 
     ???    KNOWN          KOWN     KNOWN
*/
char ch = (char) aptr[line][col];

printf("line:%d col:%d char:%c\n", line, col, ch ); 
return ch;
}


int main(void)
{
char ch;

ch = tab[1][7]; /* compiler knows line length */
printf("at 1,7 we have: %c\n",  ch );

/* sizeof tab[0][0] == sizeof(wchar_t) */ 

ch = get_char_1(1,7, sizeof(tab[0])/sizeof(tab[0][0]), tab);
printf("1 returned char: %c\n", ch );

ch = get_char_2(1,7, sizeof(tab[0])/sizeof(tab[0][0]), (wchar_t*)tab);
printf("2 returned char: %c\n", ch );

ch = get_char_3(1,7, sizeof(tab[0])/sizeof(tab[0][0]),
        sizeof( wchar_t), tab);
printf("3 returned char: %c\n", ch );

printf("table size: %lu, line size: %lu,  element size: %lu\n",
       sizeof(tab),
       sizeof(tab[0]),
       sizeof(tab[0][0])
       );

printf("number of elements per lines: %lu\n",
       sizeof(tab[0])/sizeof(tab[0][0]));


printf("number of lines: %lu\n",
       sizeof(tab)/sizeof(tab[0]));

return 0;
}

答案 3 :(得分:5)

它所做的一切(在C中)允许你在被调用的函数中编写索引代码而无需自己进行地址计算,例如:

double d= xu_col[i*row_size + j]; //get element [i,j]

double d= xu_col[i][j];

答案 4 :(得分:5)

当参数声明为具有一维数组类型时,C忽略给定的大小,而是将参数视为指向元素类型的指针。对于嵌套(多维)数组,此类处理仅应用于外部数组。在C89中,内部尺寸必须具有固定尺寸,但在C99中尺寸可以是表达式。如果直到数组之后才列出计算数组大小所需的参数,则必须使用旧语法和新语法的奇怪混合来声明该函数,例如

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>

#define ARRAY_SIZE 1000

int main() {

    int numbers[ARRAY_SIZE]; /* array to lookup */
    int n;                   /* the number to find */
    time_t t;                /* needed to initialize random number generator (RNG) */
    int i;

    srand((unsigned)time(&t));

    for (i = 0; i < ARRAY_SIZE; i++) {
        numbers[i] = rand() % 10000;
    }

    /* initialize n */
    n = rand() % 10000;

    int half = ARRAY_SIZE / 2;
    int count = 0;
    int pid_status = 0;
    pid_t pid_f = fork();

    if (pid_f == -1) {
        return EXIT_FAILURE;
    } else {
        if (pid_f == 0) {
            for (i = half; i < ARRAY_SIZE; ++i) {
                if (numbers[i] == n) {
                    count++;
                }
            }
            printf("Sons counter:%d\n", count);
            exit(count);
        } else {
            for (i = 0; i < half; ++i) {
                if (numbers[i] == n) {
                    count++;
                }
            }
            waitpid(pid_f, &pid_status, 0);
            printf("Father counter:%d\n", count);
            if (WIFEXITED(pid_status)) {
                count += WEXITSTATUS(pid_status);
            }
            printf("Sum is=%d\n", count);
        }
    }
    return 0;
}

请注意,数组大小在函数原型中指定为int findNonzero(short dat[*][*], int rows, int cols); int findNonzero(dat, rows, cols) int rows,cols; short dat[static rows][cols]; { for (int i=0; i<rows; i++) for (int j=0; j<cols; j++) if (dat[i][j] != 0) return i; return -1; } ,并且函数定义未在参数列表中指定类型,而是描述所有参数&#39;参数列表和左大括号之间的类型。另请注意,虽然编译器可能会忽略数组声明中的行数,但智能编译器可能能够使用它来促进优化。实际上,奇怪的静态&#34;语法邀请编译器读取数组的任何部分,直到给定的大小,如果它认为合适,无论代码是否读取了这些值。这在某些平台上可能会有所帮助,在这些平台上,代码可能会同时处理多个数组项目而受益。

答案 5 :(得分:3)

您的代码示例的难点在于其中一个函数参数是原型,double xu_col[n_col][n_x + n_u],其中n_xn_u是变量,而不是常量。如果您只是将其作为double[]传递,则某些C ++编译器可能允许诸如double (&table)[n_col][n_x + n_u] = (double(&)[n_col][n_x + n_u])xu_col;之类的转换作为非标准扩展,但可移植方法是编写{{1}之类的访问

,如果太难看,你可以用辅助函数简化它

另一种方法,可能更符合STL的精神,可能是编写一个知道其尺寸的最小容器类,将元素存储在线性数组中以提高效率。然后,您可以声明xu_col[i*(n_x+n_u) + j]并访问redim_array<double> table = redim_array<double>(xu_col, n_col*(n_x+n_u)).redim(n_col, n_x+n_u);

其他几个答案描述了可变长度数组的语法,但问题的另一个方面是将矩形¹二维数组隐式转换为一维数组是合法的。

矩形数组在内存中作为连续元素布局,因此它可以退化为指向元素的指针,然后函数参数可以将其解释为具有不同几何的数组。

这是一个简短的小程序,用于演示此行为。

table(i,j)

在下面的评论中有一些语言律师关于在没有显式强制转换的情况下传递数组参数在技术上是否合法。我已选择将其降级为脚注,只删除没有强制转换的示例。在现实世界中,您有时会看到没有指向不同几何体的指针的代码,并且它可能会生成警告。标准要求两个阵列的内存布局相同。

要转换为C ++,您可以使用指针转换技巧,或者您现在可以使用引用对其进行代码高尔夫操作。

这是上面程序的C ++翻译。它要求传入的数组的第一个维度除#include <stddef.h> #include <stdio.h> #include <stdlib.h> #define ROWS 2 #define COLS 4 #define ELEMS (ROWS*COLS) int flatten_array( const ptrdiff_t n, const int a[n] ) { int printed = 0; for ( ptrdiff_t i = 0; i < n; ++i ) printed += printf( "%d ", a[i] ); return printed + printf("\n"); } int rectangular_array( const ptrdiff_t m, const ptrdiff_t n, const int a[m][n] ) { int printed = 0; for ( ptrdiff_t i = 0; i < m; ++i ) { for ( ptrdiff_t j = 0; j < n; ++j ) printed += printf( "%d ", a[i][j] ); printed += printf("\n"); } return printed + printf("\n"); } int main(void) { static const int matrix[ROWS][COLS] = { {11, 12, 13, 14}, {21, 22, 23, 24} }; static const int vector[ELEMS] = {11, 12, 13, 14, 21, 22, 23, 24}; flatten_array( ELEMS, *(const int (*const)[ELEMS])matrix ); printf("\n"); rectangular_array( ROWS, COLS, *(const int (*const)[ROWS][COLS])vector ); return EXIT_SUCCESS; } 之外的所有维度,但是一些编译器支持C99样式的可变长度数组作为扩展。

constexpr

¹C程序员有时会调用像#include <stddef.h> #include <stdio.h> #include <stdlib.h> constexpr ptrdiff_t rows = 2; constexpr ptrdiff_t cols = 4; constexpr ptrdiff_t elems = rows * cols; int flatten_array( const ptrdiff_t n, const int a[] ) { int printed = 0; for ( ptrdiff_t i = 0; i < n; ++i ) printed += printf( "%d ", a[i] ); return printed + printf("\n"); } int rectangular_array( const ptrdiff_t n, const int a[][cols] ) { int printed = 0; for ( ptrdiff_t i = 0; i < n; ++i ) { for ( ptrdiff_t j = 0; j < cols; ++j ) printed += printf( "%d ", a[i][j] ); printed += printf("\n"); } return printed + printf("\n"); } int main(void) { static const int matrix[rows][cols] = { {11, 12, 13, 14}, {21, 22, 23, 24} }; static const int vector[elems] = {11, 12, 13, 14, 21, 22, 23, 24}; flatten_array( elems, (const int(&)[elems])matrix ); printf("\n"); rectangular_array( rows, (const int(&)[rows][cols])vector ); return EXIT_SUCCESS; } 这样的数组或像int matrix[ROWS][COLS]“二维数组这样的数组。”在这里,我称之为矩形而后者是参差不齐

²C11标准中对函数参数的约束是'每个参数的类型应使其值可以分配给具有相应参数类型的非限定版本的对象。'此外'一个声明参数作为''数组类型''应调整为''限定指向类型''',如果这是递归适用的,某种类型的多维数组将被调整为该类型的平面指针。

答案 6 :(得分:0)

关于你问题的第二部分:

为什么相同的版本在C ++中不起作用?当我将文件扩展名从.c更改为.cpp并尝试重新编译时,我得到了

该问题的根源是C ++破坏了名称。

在运行C ++并尝试访问C库时避免名称错位。

在C库的头文件顶部附近,在多个包含保护插入之后:

#ifdef __cplusplus
extern "C" {
#endif

并且在头文件末尾附近,在多重包含守卫的#endif之前,插入:

#ifdef __cplusplus
}
#endif

这将消除未找到关联库文件中的函数问题