在二维数组上使用函数
Use of a function on a 2d array
我编写了代码,允许我通过传递数组元素来修改函数中的一维数组元素:
- 我打印原数组
- 我将数组的每个元素传递给函数
- 在函数中,我将值 50 添加到数组的每个元素
- 然后调用这个函数,打印出屏幕修改后的元素值(即每个元素的值+50)
我已经能够对一维数组执行此操作,数组中的示例值为 (10,20,30),修改后打印的值为 (60,70,80)。
我希望做的是使该代码适用于二维数组,您将在下面看到我的尝试。这段代码着重于 int 的使用,但是一旦我了解了如何实现这一点,我希望也能适应 2D 字符串的使用。
使用以下代码:
我的objective是
- 打印到屏幕原始二维数组
- 将二维数组的每个元素传递给函数
- 在函数内,将值 50 添加到数组的每个元素
- 然后调用该函数,将修改后的元素值打印到屏幕上(预期结果显示在屏幕60,61等)
到目前为止,我已经能够将原始二维数组打印到屏幕上。这是我认为我搞砸了的功能,不胜感激任何建议。谢谢。
#include <stdio.h>
#include <string.h>
#define M 4
#define N 2
int function(int **arr);
int main() {
int i, a;
int arr[N][M] = {10, 11, 12, 13, 14, 15, 16, 17};
// the int array first
for(i = 0; i < N; i++) {
for(size_t j = 0; j < M; j++) {
// Accessing each variable
printf("value of arr[%d] is %d\n", i, arr[i][j]);
}
}
printf("\n ***values after modification***\n");
a = function(&arr[i][0]);
// int array print results
for(int i = 0; i < N; i++) {
for(size_t j = 0; j < M; j++) {
printf("value of arr %d\n", arr[i][j]);
}
}
return 0;
}
int function(int **arr) {
int i;
int j;
for(int i = 0; i < 3; i++) {
for(size_t j = 0; j < 5; j++) {
arr[i][j] = arr[i][j] + 50;
}
}
}
我为愚蠢的错误提前道歉我是 C 的新手。
提前谢谢你。
- 函数
int function(int **arr)
没有 return 和 int
所以让它成为 void
.
- 当你调用它时,
a = function(&arr[i][0]);
,你不在赋值后使用a
。我建议您从程序中完全删除 a
,因为它没有在任何地方使用。
- 对函数
function(&arr[i][0]);
的调用应该只是 function(arr);
- 函数签名需要包括除最外层维度之外的所有维度的范围:
void function(int arr[][M])
- 在函数内部,您使用
3
和 5
而不是 N
和 M
。即越界访问数组。
- 在
function
中,您在函数开头声明的i
和j
未被使用。删除它们。
arr[i][j] = arr[i][j] + 50;
最好写成 arr[i][j] += 50;
- 初始化多维数组时,使用大括号使代码更易于阅读:
int arr[N][M] = {{10, 11, 12, 13}, {14, 15, 16, 17}};
- 在
main
中,您将 int
和 size_t
混合用于索引变量。我建议你选择一种。
- 删除不用的头文件(
string.h
)
示例:
#include <stdio.h>
#define N 2
#define M 4
void function(int arr[][M]) {
for(int i = 0; i < N; i++) {
for(size_t j = 0; j < M; j++) {
arr[i][j] += 50;
}
}
}
int main() {
int arr[N][M] = {{10, 11, 12, 13}, {14, 15, 16, 17}};
for(size_t i = 0; i < N; i++) {
for(size_t j = 0; j < M; j++) {
printf("value of arr[%zu][%zu] is %d\n", i, j, arr[i][j]);
}
}
printf("\n ***values after modification***\n");
function(arr);
// int array print results
for(size_t i = 0; i < N; i++) {
for(size_t j = 0; j < M; j++) {
printf("value of arr[%zu][%zu] is %d\n", i, j, arr[i][j]);
}
}
}
由于您多次打印数组,您还可以添加一个函数来执行此操作,而不必在 main
:
中重复该代码
void print(int arr[][M]) {
for(size_t i = 0; i < N; i++) {
for(size_t j = 0; j < M; j++) {
printf("value of arr[%zu][%zu] is %d\n", i, j, arr[i][j]);
}
}
}
C(和C++)中的二维数组实际上是一维数组,其元素是一维数组。索引运算符 [] 具有从左到右的语义,因此对于类型 arr[N][M]
,首先评估第一个索引(具有 N 个元素)。结果表达式,例如arr[0]
,arr
中的第一个元素,是一个有M个元素的一维数组。当然可以再次索引该数组,例如arr[0][1]
,导致第一个子数组中的第二个 int。
C 语言的一个怪癖是,如果使用数组作为函数参数,函数看到的是指向第一个元素的指针。用作参数的数组“衰减”,或者如标准所述,以这种方式“调整”。这对于二维数组没有什么不同,除了二维数组的元素本身就是数组。因此,接收函数得到的是指向int arr[M]
.
的指针
考虑:如果你想传递一个简单的整数数组,比如 intArr[3]
,给一个函数,函数看到的是指向第一个元素的指针。这样的函数声明可能看起来像 void f(int *intPtr)
,对于这个例子,简单地用 f(intArr)
调用。另一种写法是 void f(int intPtr[])
。意思完全一样:参数是指向 int 的指针,而不是数组。它指向一连串整数中的第一个——甚至可能是唯一的——元素。
二维数组的逻辑完全相同——除了所讨论的元素具有“M 整数数组”类型,例如int subArr[M]
。这种类型的指针参数可以用两种方式编写,例如使用简单的 int 数组:作为指针,如 void f(int (*subArrPtr)[M])
或在数组表示法中,顶级元素的数量未知,如 void f(int arr[][M])
.与简单的 int 数组一样,这两个参数符号是完全等价的并且可以互换。两者实际上都声明了一个指针,所以 (*subArrPtr)[M]
可以说更切题,但可能更晦涩。
(*subArrPtr)
中有趣的括号的原因是我们必须首先取消引用指针以获得实际数组,然后才对其进行索引。如果没有括号,索引运算符 []
将具有优先权。您可以在 this table 中查找优先顺序。 []
在第 1 组中具有最高优先级,而取消引用运算符 *
(不是乘法!)在第 2 组中。如果没有括号,我们将首先索引,然后才取消引用数组元素(必须因此是一个指针),也就是说,我们将声明一个指针数组而不是指向数组的指针。
因此,您的函数的两个可能的可互换签名是
void function( int (*arrArg)[M] ); // pointer notation
void function( int arrArg[][M] ); // "array" notation (but actually a pointer)
整个程序,也纠正了 Ted 提到的问题,并且没有打印原始值(毕竟我们知道它们),如下所示。我还调整了二维数组的初始化,以便子数组变得可见。 C 对初始化结构和数组非常宽松;它只是让您写入连续的值并在出现时填充嵌套子对象的元素。但我认为显示结构有助于理解代码并揭示错误,例如子数组中的元素数量错误。我以一种方式声明函数并以另一种方式定义它以表明函数签名是等效的。我还更改了定义和函数的名称,以赋予它们更多含义。
#include<stdio.h>
#define NUM_ELEMS_SUBARRAY 4
#define NUM_ELEMS_ARRAY 2
/// @arrArg Is a pointer to the first in a row of one-dimensional
/// arrays with NUM_ELEMS_SUBARRAY ints each.
void add50ToElems(int arrArg[][NUM_ELEMS_SUBARRAY]);
int main()
{
// Show the nested structure of the 2-dimensional array.
int arr[NUM_ELEMS_ARRAY][NUM_ELEMS_SUBARRAY] =
{
{10, 11, 12, 13},
{14, 15, 16, 17}
};
// Modify the array
add50ToElems(arr);
// print results
for (int i = 0; i < NUM_ELEMS_ARRAY; i++) {
for (int j = 0; j < NUM_ELEMS_SUBARRAY; j++)
{
printf("value of arr[%d][%d]: %d\n", i, j, arr[i][j]);
}
}
return 0;
}
// Equivalent to declaration above
void add50ToElems(int (*arrArg)[NUM_ELEMS_SUBARRAY])
{
for (int i = 0; i < NUM_ELEMS_ARRAY; i++)
{
for (size_t j = 0; j < NUM_ELEMS_SUBARRAY; j++)
{
//arrArg[i][j] = arrArg[i][j] + 50;
arrArg[i][j] += 50; // more idiomatic
}
}
}
为什么将二维数组传递给需要指针到指针的函数是错误的?让我们考虑一下 void f(int *p)
的含义。它接收一个指向 int 的指针,该 int 通常是数组的开头,即内存中一个接一个地放置的一系列 int 的开头。例如
void f(int *p) { for(int i=0; i<3; ++i) { printf("%d ", p[i]); }
可以用指向数组第一个元素的指针调用:
static int arr[3];
void g() { f(arr); }
当然,这个最小的例子是不安全的(f
怎么知道有三个整数?)但它达到了目的。
那么 void f(int **p);
是什么意思?类似地,它是一个指针,指向内存中一个接一个的 指针 中的第一个。我们已经明白为什么如果我们传递一个二维数组的地址会带来灾难:那里的对象 不是指针,而是所有整数! 考虑:
int arr1[2] = { 1,2 };
int arr2[2] = { 2,3 };
int arr3[2] = { 3,4 };
// This array contains addresses which point
// to the first element in each of the above arrays.
int *arrOfPtrToStartOfArrays[3] // The array of pointers
= { arr1, arr2, arr3 }; // arrays decay to pointers
int **ptrToArrOfPtrs = arrOfPtrToStartOfArrays;
void f(int **pp)
{
for(int pi=0; pi<3; pi++) // iterate the pointers in the array
{
int *p = pp[pi]; // pp element is a pointer
// iterate through the ints starting at each address
// pointed to by pp[pi]
for(int i=0; i<2; i++) // two ints in each arr
{
printf("%d ", pp[pi][i]); // show double indexing of array of pointers
// Since pp[pi] is now p, we can also say:
printf("%d\n", p[i]); // index int pointer
}
}
}
int main()
{
f(ptrToArrOfPtrs);
}
f
遍历指针数组。 认为那个地址的值,以及后面的地址,都是指针!这就是声明 int **pp
的意思。
现在如果我们传递一个全是整数的数组的地址,f
仍然会认为那里的内存全是指针。像上面的 int *p = pp[i];
这样的表达式将读取一个整数(例如 1)并认为它是一个地址。 p[i]
在 printf 调用中将尝试访问地址 1 处的内存。
让我们以讨论为什么应该将二维数组作为指针传递给指针的想法如此普遍的讨论作为结束。一个原因是,虽然将二维数组参数声明为 <strike>void f(int **arr);</strike>
是完全错误的,但你 can 访问它的第一个(但只是第一个)元素,例如int i = **arr
。这样做的原因是第一个解引用给你第一个子数组,你可以反过来应用解引用运算符,产生它的第一个元素。但是,如果您将数组作为参数传递给函数,它不会衰减为指向指针的指针,而是如前所述,指向其第一个元素的指针。
第二个混淆来源是访问指针数组中的元素使用与访问真正二维数组中的元素相同的双索引:pp[pi][i]
与 arr[i][j]
。 但是这些表达式生成的代码是完全不同的,如果传递了错误的类型,就会造成灾难。顺便说一下,您的编译器会对此发出警告。
我编写了代码,允许我通过传递数组元素来修改函数中的一维数组元素:
- 我打印原数组
- 我将数组的每个元素传递给函数
- 在函数中,我将值 50 添加到数组的每个元素
- 然后调用这个函数,打印出屏幕修改后的元素值(即每个元素的值+50)
我已经能够对一维数组执行此操作,数组中的示例值为 (10,20,30),修改后打印的值为 (60,70,80)。
我希望做的是使该代码适用于二维数组,您将在下面看到我的尝试。这段代码着重于 int 的使用,但是一旦我了解了如何实现这一点,我希望也能适应 2D 字符串的使用。
使用以下代码:
我的objective是
- 打印到屏幕原始二维数组
- 将二维数组的每个元素传递给函数
- 在函数内,将值 50 添加到数组的每个元素
- 然后调用该函数,将修改后的元素值打印到屏幕上(预期结果显示在屏幕60,61等) 到目前为止,我已经能够将原始二维数组打印到屏幕上。这是我认为我搞砸了的功能,不胜感激任何建议。谢谢。
#include <stdio.h>
#include <string.h>
#define M 4
#define N 2
int function(int **arr);
int main() {
int i, a;
int arr[N][M] = {10, 11, 12, 13, 14, 15, 16, 17};
// the int array first
for(i = 0; i < N; i++) {
for(size_t j = 0; j < M; j++) {
// Accessing each variable
printf("value of arr[%d] is %d\n", i, arr[i][j]);
}
}
printf("\n ***values after modification***\n");
a = function(&arr[i][0]);
// int array print results
for(int i = 0; i < N; i++) {
for(size_t j = 0; j < M; j++) {
printf("value of arr %d\n", arr[i][j]);
}
}
return 0;
}
int function(int **arr) {
int i;
int j;
for(int i = 0; i < 3; i++) {
for(size_t j = 0; j < 5; j++) {
arr[i][j] = arr[i][j] + 50;
}
}
}
我为愚蠢的错误提前道歉我是 C 的新手。 提前谢谢你。
- 函数
int function(int **arr)
没有 return 和int
所以让它成为void
. - 当你调用它时,
a = function(&arr[i][0]);
,你不在赋值后使用a
。我建议您从程序中完全删除a
,因为它没有在任何地方使用。 - 对函数
function(&arr[i][0]);
的调用应该只是function(arr);
- 函数签名需要包括除最外层维度之外的所有维度的范围:
void function(int arr[][M])
- 在函数内部,您使用
3
和5
而不是N
和M
。即越界访问数组。 - 在
function
中,您在函数开头声明的i
和j
未被使用。删除它们。 arr[i][j] = arr[i][j] + 50;
最好写成arr[i][j] += 50;
- 初始化多维数组时,使用大括号使代码更易于阅读:
int arr[N][M] = {{10, 11, 12, 13}, {14, 15, 16, 17}};
- 在
main
中,您将int
和size_t
混合用于索引变量。我建议你选择一种。 - 删除不用的头文件(
string.h
)
示例:
#include <stdio.h>
#define N 2
#define M 4
void function(int arr[][M]) {
for(int i = 0; i < N; i++) {
for(size_t j = 0; j < M; j++) {
arr[i][j] += 50;
}
}
}
int main() {
int arr[N][M] = {{10, 11, 12, 13}, {14, 15, 16, 17}};
for(size_t i = 0; i < N; i++) {
for(size_t j = 0; j < M; j++) {
printf("value of arr[%zu][%zu] is %d\n", i, j, arr[i][j]);
}
}
printf("\n ***values after modification***\n");
function(arr);
// int array print results
for(size_t i = 0; i < N; i++) {
for(size_t j = 0; j < M; j++) {
printf("value of arr[%zu][%zu] is %d\n", i, j, arr[i][j]);
}
}
}
由于您多次打印数组,您还可以添加一个函数来执行此操作,而不必在 main
:
void print(int arr[][M]) {
for(size_t i = 0; i < N; i++) {
for(size_t j = 0; j < M; j++) {
printf("value of arr[%zu][%zu] is %d\n", i, j, arr[i][j]);
}
}
}
C(和C++)中的二维数组实际上是一维数组,其元素是一维数组。索引运算符 [] 具有从左到右的语义,因此对于类型 arr[N][M]
,首先评估第一个索引(具有 N 个元素)。结果表达式,例如arr[0]
,arr
中的第一个元素,是一个有M个元素的一维数组。当然可以再次索引该数组,例如arr[0][1]
,导致第一个子数组中的第二个 int。
C 语言的一个怪癖是,如果使用数组作为函数参数,函数看到的是指向第一个元素的指针。用作参数的数组“衰减”,或者如标准所述,以这种方式“调整”。这对于二维数组没有什么不同,除了二维数组的元素本身就是数组。因此,接收函数得到的是指向int arr[M]
.
考虑:如果你想传递一个简单的整数数组,比如 intArr[3]
,给一个函数,函数看到的是指向第一个元素的指针。这样的函数声明可能看起来像 void f(int *intPtr)
,对于这个例子,简单地用 f(intArr)
调用。另一种写法是 void f(int intPtr[])
。意思完全一样:参数是指向 int 的指针,而不是数组。它指向一连串整数中的第一个——甚至可能是唯一的——元素。
二维数组的逻辑完全相同——除了所讨论的元素具有“M 整数数组”类型,例如int subArr[M]
。这种类型的指针参数可以用两种方式编写,例如使用简单的 int 数组:作为指针,如 void f(int (*subArrPtr)[M])
或在数组表示法中,顶级元素的数量未知,如 void f(int arr[][M])
.与简单的 int 数组一样,这两个参数符号是完全等价的并且可以互换。两者实际上都声明了一个指针,所以 (*subArrPtr)[M]
可以说更切题,但可能更晦涩。
(*subArrPtr)
中有趣的括号的原因是我们必须首先取消引用指针以获得实际数组,然后才对其进行索引。如果没有括号,索引运算符 []
将具有优先权。您可以在 this table 中查找优先顺序。 []
在第 1 组中具有最高优先级,而取消引用运算符 *
(不是乘法!)在第 2 组中。如果没有括号,我们将首先索引,然后才取消引用数组元素(必须因此是一个指针),也就是说,我们将声明一个指针数组而不是指向数组的指针。
因此,您的函数的两个可能的可互换签名是
void function( int (*arrArg)[M] ); // pointer notation
void function( int arrArg[][M] ); // "array" notation (but actually a pointer)
整个程序,也纠正了 Ted 提到的问题,并且没有打印原始值(毕竟我们知道它们),如下所示。我还调整了二维数组的初始化,以便子数组变得可见。 C 对初始化结构和数组非常宽松;它只是让您写入连续的值并在出现时填充嵌套子对象的元素。但我认为显示结构有助于理解代码并揭示错误,例如子数组中的元素数量错误。我以一种方式声明函数并以另一种方式定义它以表明函数签名是等效的。我还更改了定义和函数的名称,以赋予它们更多含义。
#include<stdio.h>
#define NUM_ELEMS_SUBARRAY 4
#define NUM_ELEMS_ARRAY 2
/// @arrArg Is a pointer to the first in a row of one-dimensional
/// arrays with NUM_ELEMS_SUBARRAY ints each.
void add50ToElems(int arrArg[][NUM_ELEMS_SUBARRAY]);
int main()
{
// Show the nested structure of the 2-dimensional array.
int arr[NUM_ELEMS_ARRAY][NUM_ELEMS_SUBARRAY] =
{
{10, 11, 12, 13},
{14, 15, 16, 17}
};
// Modify the array
add50ToElems(arr);
// print results
for (int i = 0; i < NUM_ELEMS_ARRAY; i++) {
for (int j = 0; j < NUM_ELEMS_SUBARRAY; j++)
{
printf("value of arr[%d][%d]: %d\n", i, j, arr[i][j]);
}
}
return 0;
}
// Equivalent to declaration above
void add50ToElems(int (*arrArg)[NUM_ELEMS_SUBARRAY])
{
for (int i = 0; i < NUM_ELEMS_ARRAY; i++)
{
for (size_t j = 0; j < NUM_ELEMS_SUBARRAY; j++)
{
//arrArg[i][j] = arrArg[i][j] + 50;
arrArg[i][j] += 50; // more idiomatic
}
}
}
为什么将二维数组传递给需要指针到指针的函数是错误的?让我们考虑一下 void f(int *p)
的含义。它接收一个指向 int 的指针,该 int 通常是数组的开头,即内存中一个接一个地放置的一系列 int 的开头。例如
void f(int *p) { for(int i=0; i<3; ++i) { printf("%d ", p[i]); }
可以用指向数组第一个元素的指针调用:
static int arr[3];
void g() { f(arr); }
当然,这个最小的例子是不安全的(f
怎么知道有三个整数?)但它达到了目的。
那么 void f(int **p);
是什么意思?类似地,它是一个指针,指向内存中一个接一个的 指针 中的第一个。我们已经明白为什么如果我们传递一个二维数组的地址会带来灾难:那里的对象 不是指针,而是所有整数! 考虑:
int arr1[2] = { 1,2 };
int arr2[2] = { 2,3 };
int arr3[2] = { 3,4 };
// This array contains addresses which point
// to the first element in each of the above arrays.
int *arrOfPtrToStartOfArrays[3] // The array of pointers
= { arr1, arr2, arr3 }; // arrays decay to pointers
int **ptrToArrOfPtrs = arrOfPtrToStartOfArrays;
void f(int **pp)
{
for(int pi=0; pi<3; pi++) // iterate the pointers in the array
{
int *p = pp[pi]; // pp element is a pointer
// iterate through the ints starting at each address
// pointed to by pp[pi]
for(int i=0; i<2; i++) // two ints in each arr
{
printf("%d ", pp[pi][i]); // show double indexing of array of pointers
// Since pp[pi] is now p, we can also say:
printf("%d\n", p[i]); // index int pointer
}
}
}
int main()
{
f(ptrToArrOfPtrs);
}
f
遍历指针数组。 认为那个地址的值,以及后面的地址,都是指针!这就是声明 int **pp
的意思。
现在如果我们传递一个全是整数的数组的地址,f
仍然会认为那里的内存全是指针。像上面的 int *p = pp[i];
这样的表达式将读取一个整数(例如 1)并认为它是一个地址。 p[i]
在 printf 调用中将尝试访问地址 1 处的内存。
让我们以讨论为什么应该将二维数组作为指针传递给指针的想法如此普遍的讨论作为结束。一个原因是,虽然将二维数组参数声明为 <strike>void f(int **arr);</strike>
是完全错误的,但你 can 访问它的第一个(但只是第一个)元素,例如int i = **arr
。这样做的原因是第一个解引用给你第一个子数组,你可以反过来应用解引用运算符,产生它的第一个元素。但是,如果您将数组作为参数传递给函数,它不会衰减为指向指针的指针,而是如前所述,指向其第一个元素的指针。
第二个混淆来源是访问指针数组中的元素使用与访问真正二维数组中的元素相同的双索引:pp[pi][i]
与 arr[i][j]
。 但是这些表达式生成的代码是完全不同的,如果传递了错误的类型,就会造成灾难。顺便说一下,您的编译器会对此发出警告。