数组的强化学习
在 C 中,数组是非常重要的,我们需要了解更多有关数组的细节。下面列出了 C 程序员必须清楚的一些与数组相关的重要概念:概念 | 描述 |
---|---|
多维数组 | C 支持多维数组。多维数组最简单的形式是二维数组。 |
传递数组给函数 | 您可以通过指定不带索引的数组名称来给函数传递一个指向数组的指针。 |
从函数返回数组 | C 允许从函数返回数组。 |
指向数组的指针 | 您可以通过指定不带索引的数组名称来生成一个指向数组中第一个元素的指针。 |
多维数组
C 语言支持多维数组。多维数组声明的一般形式如下:type arrayname[size1][size2]...[sizeN];
例如,下面的声明创建了一个三维 5 . 10 . 4 整型数组:
int threedim[5][10][4];
二维数组
多维数组最简单的形式是二维数组。一个二维数组,在本质上,是一个一维数组的列表。声明一个 x 行 y 列的二维整型数组,形式如下:type arrayName [ x ][ y ];
其中,type 可以是任意有效的 C 数据类型,arrayName 是一个有效的 C 标识符。一个二维数组可以被认为是一个带有 x 行和 y 列的表格。下面是一个二维数组,包含 3 行和 4 列:
int x[3][4];
column | 1 | 2 | 3 | 4 |
---|---|---|---|---|
row | a[0][0] | a[0][1] | a[0][2] | a[0][3] |
row | a[1][0] | a[1][1] | a[1][2] | a[1][3] |
row | a[2][0] | a[2][1] | a[2][2] | a[2][3] |
初始化二维数组
多维数组可以通过在括号内为每行指定值来进行初始化。下面是一个带有 3 行 4 列的数组。int arr[3][4] = {
{1,2,3,4}, /* 初始化索引号为 0 的行 */
{5,6,7,8}, /* 初始化索引号为 1 的行 */
{9,10,11,12} /* 初始化索引号为 2 的行 */
};
内部嵌套的括号是可选的,下面的初始化与上面是等同的:
int arr[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
访问二维数组元素
二维数组中的元素是通过使用下标(即数组的行索引和列索引)来访问的。例如:int val= arr[2][3];
上面的语句将获取数组中第 3 行第 4 个元素。您可以通过上面的示意图来进行验证。让我们来看看下面的程序,我们将使用嵌套循环来处理二维数组:
#include <stdio.h>
int main() {
//定义一个二维数组
int arr[3][4];
//赋值
求出二维数组的长度
行数 sizeof(arr)指这个数组总共占用的位置,为1*当前类型所占字节*当前行的列数/sizeof(*arr)是指向一行所占用的大小=行数
int row = (int)sizeof(arr) / sizeof(*arr);
列数 **arr指向*arr的地址 sizeof(*arr)是指向一行所占用的大小/sizeof(**arr)当前-行一列所占的大小=列数
int column = sizeof(*arr) / sizeof(**arr);
//赋值外循环为行数
for (int i = 0; i < row; i++)
{
//内循环为列数
for (int f = 0; f < column; f++)
{
arr[i][f] = f + i;
}
}
//输出
for (int i = 0; i < row; i++)
{
for (int f = 0; f < column; f++)
{
printf("arr[%d][%d]=%d \t",i,f, arr[i][f]);
}
printf("\n");
}
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
传递数组给函数
如果您想要在函数中传递一个一维数组作为参数,您必须以下面三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。同样地,您也可以传递一个多维数组作为形式参数。方式1
形式参数是一个指针:void myFunction(int *param)
{
}
方式2
形式参数是一个已定义大小的数组:void myFunction(int param[10])
{
}
方式3
形式参数是一个未定义大小的数组:void myFunction(int param[])
{
}
实例
现在,让我们来看下面这个函数,它把数组作为参数,同时还传递了另一个参数,根据所传的参数,会返回数组中各元素的平均值:#include <stdio.h>
/* 函数声明 */
double getAverage(int arr[], int size);
int main ()
{
/* 带有 5 个元素的整型数组 */
int balance[5] = {1000, 2, 3, 17, 50};
double avg;
/* 传递一个指向数组的指针作为参数 */
avg = getAverage( balance, 5 ) ;
/* 输出返回值 */
printf( "平均值是: %f ", avg );
return 0;
}
double getAverage(int arr[], int size)
{
int i;
double avg;
double sum=0;
for (i = 0; i < size; ++i)
{
sum += arr[i];
}
avg = sum / size;
return avg;
}
当上面的代码被编译和执行时,它会产生下列结果:
平均值是: 214.400000
从函数返回数组
C 语言不允许返回一个完整的数组作为函数的参数。但是,您可以通过指定不带索引的数组名来返回一个指向数组的指针。我们将在下一章中讲解有关指针的知识,您可以先跳过本章,等了解了 C 指针的概念之后,再来学习本章的内容。如果您想要从函数返回一个一维数组,您必须声明一个返回指针的函数,如下:
int * myFunction()
{
}
另外,C 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/* 要生成和返回随机数的函数 */
int* getRandom() {
static int arr[10];
//设置种子
srand((unsigned)time(NULL));
for (int i = 0; i < 10; i++)
{
arr[i] = rand();
printf("arr[%d] = %d\n", i, arr[i]);
}
return arr;
}
int main() {
/* 一个指向整数的指针 */
int* p = getRandom();
for (int i = 0; i < 10; i++)
{
printf("*(p + %d) : %d\n", i, *(p + i));
}
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
指向数组的指针
如果您对 C 语言中指针的概念有所了解,那么就可以开始本章的学习。数组名是一个指向数组中第一个元素的常量指针。因此,在下面的声明中:double balance[50];
balance 是一个指向 &balance[0] 的指针,即数组 balance 的第一个元素的地址。因此,下面的程序片段把 p 赋值为 balance 的第一个元素的地址:
double *p;
double balance[10];
p = balance;
使用数组名作为常量指针是合法的,反之亦然。因此,*(balance + 4) 是一种访问 balance[4] 数据的合法方式。
一旦您把第一个元素的地址存储在 p 中,您就可以使用 p、(p+1)、*(p+2) 等来访问数组元素。下面的实例演示了上面讨论到的这些概念:
#include <stdio.h>
int main ()
{
/* 带有 5 个元素的整型数组 */
double balance[5] = {1000.0, 2.0, 3.4, 17.0, 50.0};
double *p;
int i;
p = balance;
/* 输出数组中每个元素的值 */
printf( "使用指针的数组值\n");
for ( i = 0; i < 5; i++ )
{
printf("*(p + %d) : %f\n", i, *(p + i) );
}
printf( "使用 balance 作为地址的数组值\n");
for ( i = 0; i < 5; i++ )
{
printf("*(balance + %d) : %f\n", i, *(balance + i) );
}
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
使用指针的数组值
*(p + 0) : 1000.000000
*(p + 1) : 2.000000
*(p + 2) : 3.400000
*(p + 3) : 17.000000
*(p + 4) : 50.000000
使用 balance 作为地址的数组值
*(balance + 0) : 1000.000000
*(balance + 1) : 2.000000
*(balance + 2) : 3.400000
*(balance + 3) : 17.000000
*(balance + 4) : 50.000000
在上面的实例中,p 是一个指向 double 型的指针,这意味着它可以存储一个 double 类型的变量。一旦我们有了 p 中的地址,*p 将给出存储在 p 中相应地址的值,正如上面实例中所演示的。
知识
srand((unsigned)time(NULL))是初始化随机函数种子:- 是拿当前系统时间作为种子,由于时间是变化的,种子变化,可以产生不相同的随机数。计算机中的随机数实际上都不是真正的随机数,如果两次给的种子一样,是会生成同样的随机序列的。 所以,一般都会以当前的时间作为种子来生成随机数,这样更加的随机。
- 使用时,参数可以是unsigned型的任意数据,比如srand(10);
- 如果不使用srand,用rand()产生的随机数,在多次运行,结果是一样的。
void test_rand(void)
{
unsigned long n;
srand((unsigned)time(NULL));
for(int i = 0; i < 100; i++)
{
n = rand();
printf("d\n", n);
}
}
关于 double 类型与 float 类型:
printf() 只会看到双精度数,printf 的 %f 格式总是得到 double,所以在 printf() 中使用 %f 跟 %lf 的输出显示效果是一样的。但是对于变量来说,double 类型比 float 类型的精度要高。double 精度更高,是指它存储的小数位数更多,但是输出默认都是 6 位小数,如果你想输出更多小数,可以自己控制,比如 %.10lf 就输出 10 位小数。所以一般情况下 double 类型的占位符可以用 %lf。