C语言基础 5 - 数组

Updated on in 编程语言 with 0 views and 0 comments

数组知识详解与优化汇总

数组基础概念

数组是一种数据结构,它允许批量产生多个相同类型的变量,这些变量称为数组元素。数组在内存中顺序存放,使得数据的访问和管理变得高效。

为什么要引入数组的概念呢?

问题: 一个班有40名学生,存储这40名学生某一门课程的成绩,计算出总成绩、平均成绩等,如何实现?

思路: 我们可以定义:40个浮点变量,例如: float score1,score2,.....,score40;

写出的程序,是否太麻烦了?!如果4000名学生的成绩呢?

既然都是相同类型的变量,我是否可以写在一起呢,这就可以用数组来实现:

float score[40]={88.5,90.0,78.3,...,98.0};//把40个学生的成绩存放在长度为40的数组里,表示起来就很简洁

下面我们就来学习如何定义数组和使用数组。

一维数组

定义数组

一维数组的一般形式为:类型说明符 数组名[长度];

  • 长度决定了数组元素的个数;
  • 长度可以是整型常量或整型常量表达式,或是整型符号常量;
    • 例如:int a[2+3]; //整型常量表达式
    • 例如:#define N 5 int a[N]; //整型符号常量
  • 长度必须用方括号括起来;
    例如:int a[5]; 定义了一个长度为5的整型数组。

数组元素引用

数组定义完后,就可以使用其数组元素了,数组元素表示的一般形式为:数组名[下标]

  • 数组元素的下标值从0开始,最大值为长度-1。例如:a[0] 表示数组的第一个元素。
  • 数组元素在内存中是顺序存放的。
  • 数组元素的下标可以是整型常量或表达式,也可以是整型变量或表达式。
  • 不要尝试去访问超出数组长度位置的数据,虽然可以编译通过,但是会给警告,这些数据是毫无意义的

数组初始化

//直接声明int类型数组,数组长度为3,不初始化数组,元素的值为0(或不确定)
int a[3];

//对全部数组元素进行初始化
int a[3] = {3, 1, 5};

//省略数组长度,也可以根据后面的赋值来决定数组长度
int c[] = {1, 2, 3};

//对部分数组元素初始化,数组长度不能省略, 比如这里仅仅是为前三个设定了初始值,其余未赋值元素的值为0(或不确定)
int b[10] = {1, 2, 4};

//我们也可以通过 [下标] = 赋值, 的形式来指定某一位的初始值,注意下标是从0开始的,第一个元素就是第0个下标位置,比如这里数组容量为10,那么最多到9
int c[10] = {1, 2, [4] = 777, [9] = 666}; 

示例

输入学生 10 门课程的成绩,输出这些成绩,并输出总成绩和平均分

#include <stdio.h>
int main() {
    float scores[10];
    float sum = 0;
    for (int i = 0; i < 10; i++) {
        scanf("%f", &scores[i]);   
    }

    for (int i = 0; i < 10; i++) {
        sum += scores[i];   
    }
    float average = sum / 10;
    for (int i = 0; i < 10; i++) {
        printf("%f ", scores[i]);
    }
    printf("Sum:%.2f\nAverage: %.2f\n", sum,average);
    return 0;
}

二维数组(矩阵)

二维数组定义

  • 数组不仅仅只可以有一个维度,我们可以创建二维甚至多维的数组,简单来说就是,存放数组的数组(套娃了属于是):
int arr[][2] = {{20, 10}, {18, 9}};   //可以看到,数组里面存放的居然是数组
//存放的内层数组的长度是需要确定的,存放数组的数组和之前一样,可以根据后面的值决定
  • 二维数组的一般形式为:类型说明符 数组名[长度1][长度2];

例如:int a[2][3]; 定义了一个2行3列的整型二维数组,数组名是 a。

  • 比如现在我们要存放2020-2022年每个月的天数,那么此时用一维数组肯定是不方便了,我们就可以使用二维数组来处理:
int arr[3][12] = {{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, //2020年是闰年,2月有29天
                  {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
                  {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
  • 比如,线性代数如何表达N元一次方程组的?

$$
\begin{cases}
a_{11}x_1&+&a_{12}x_2&+&\cdots&+a_{1n}x_n&=&b_1\
&&&&\vdots\
a_{n1}x_1&+&a_{n2}x_2&+&\cdots&+a_{nn}x_n&=&b_n&
\end{cases}

$$

数组(矩阵)表示N元一次方程组:

$$
\left[ \begin{matrix}
a_{11} & a_{12} & \cdots & a_{1n} \
a_{21} & a_{22} & \cdots & a_{2n} \
\vdots & \vdots & \ddots & \vdots \
a_{n1} & a_{n2} & \cdots & a_{nn} \
\end{matrix} \right] \times
\begin{bmatrix} x_{1} \ x_{2} \ \vdots \ x_{n} \ \end{bmatrix} =
\begin{bmatrix} b_{1} \ b_{2} \ \vdots \ b_{n} \ \end{bmatrix}

$$

double a[n][n],x[n],b[n];

二维数组元素引用(同一维数组)

二维数组元素表示的一般形式为:数组名[下标1][下标2]

  • 二维数组元素的下标值也从0开始,最大值为:长度-1,例如:a[0][0] 表示第一行第一列的元素。
  • 二维数组元素在内存中也是顺序存放的,按行优先原则存储,即先存第一行的元素,再存下一行的元素,直至最后
  • 二维数组元素的下标可以是常量或表达式,也可以是整型变量或表达式

初始化(同一维数组)

  • 对全部数组元素进行初始化:int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
  • 省略第一维长度(必须知道初始化值的总数):int a[][3] = {{1, 2, 3}, {4, 5, 6}};
  • 部分数组元素初始化,长度不能省略:int a[2][3] = {1};

示例

输入10名学生物理、数学、外语3门课程的成绩,输出这些成绩,并输出这10名学生3门课程的平均分。

#include <stdio.h>
int main()
{
    float a[10][3];
    float sum[10]={0},ave[10];
    int i,j;
    for(i=0;i<10;i++)
    {
        for(j=0;j<3;j++)
        {
            scanf("%f",&a[i][j]);
        }
    }
    for(i=0;i<10;i++)
    {
        for(j=0;j<3;j++)
        {
            sum[i]=sum[i]+a[i][j];
        }
        ave[i]=sum[i]/3;
    }
    for(i=0;i<10;i++)
    {
        for(j=0;j<3;j++)
        {
            printf("%.0f ",a[i][j]);
        }
        printf("\n"); 
    }
    for(i=0;i<10;i++)
    {
        printf("ave[%d]=%.0f\n",i,ave[i]);
    }
}

多维数组

当然除了二维还可以上升到三维、四维、多维

多维数组的定义(同上)

  • 多维数组的一般形式为:类型说明符 数组名[长度1][长度2].....[长度N];
int arr[2][2][2] = {{{1, 2}, {1, 2}}, {{1, 2}, {1, 2}}};

多维数组元素引用(同上)

  • 多维数组元素的下标值也从0开始,最大值为:长度-1,例如:a[0][0] 表示第一行第一列的元素。
  • 多维数组元素在内存中也是顺序存放的,按行优先原则存储,即先存第一行的元素,再存下一行的元素,直至最后
  • 同一维二维数组一样,长度1...长度N是整型常量或整型常量表达式,或是整型的符号常量;
  • 多维数组长度必须分别用中括号括起来;

有关多维数组,因为使用有限,暂时先介绍到这里。

初始化(同上)

  • 对全部数组元素进行初始化:int arr[2][2][2] = {{{1, 2}, {1, 2}}, {{1, 2}, {1, 2}}};
  • 省略第一维长度(必须知道初始化值的总数):int arr[][2][2] = {{{1, 2}, {1, 2}}, {{1, 2}, {1, 2}}};
  • 部分数组元素初始化,长度不能省略:int a[2][3] = {1};

字符数组与字符串

  • C 语言中,字符串(String)是一系列的单个字符,最后一个是空字符‘\0’(ASCII码是0).
  • C语言使用字符型变量存储单个字符!例如:
char ch;
ch=‘a’; //内放的是ASCII值,//即,65(十进制)=0100 0001(二进制)
  • C语言如何存储字符串呢?

C语言使用字符数组来存储字符串:char str[6]=“Hello”;

实际存放的ASCII码值

定义

  • 字符数组用于存储字符或字符串。字符串以空字符‘\0’结尾。
  • 一维字符数组定义的一般形式:char 数组名[长度];
    • 例如:char a[6];
  • 说明:
    1. 字符数组就是类型说明符为char的数组;
    2. 字符数组遵循前面所介绍的数组的一般规则;
    3. 字符数组可以初始化;
    4. 字符数组可以存放多个字符,也可以存放带有结束标记的字符串常量。

初始化

  1. 字符数组用于存放多个字符时的初始化
    1. 全部数组元素初始化

例如:char a[4]={‘a’, ‘b’, ‘c’, ‘d’};

初始化结果 a[0]值为‘a’,a[1]值为‘b’,a[2]值为‘c’,a[3]值为‘d’

  1. 全部数组元素初始化,可以省略数组长度
    例如: char a[]={‘a’, ‘b’, ‘c’, ‘d’}; 可以省略数组长度 4;
  2. 部分数组元素初始化,长度不能省略,其余未赋值元素值为0,即‘\0’
    例如: char a[4]={‘a’, ‘b’};

初始化结果 a[0]值为‘a’,a[1]值为‘b’,a[2]值为‘\0’,a[3]值为‘\0’

  1. 字符数组用于存放字符串常量时的初始化
    1. 全部数组元素初始化

例如:char a[6]=“Hello”;

初始化结果 a[0]值为‘H’,a[1]值为‘e’,a[2]值为‘l’,a[3]值为‘l’, a[4]值为‘o’, a[5]值为‘\0’;

  1. 全部数组元素初始化,可以省略数组长度
    例如: char a[]=“Hello”; 可以省略数组长度6;
  2. 部分数组元素初始化,长度不能省略,其余未赋值元素值为0,即‘\0’
    例如: char a[10]=“Hello”;

初始化结果a[0]值为‘H’,a[1]值为‘e’,a[2]值为‘l’,a[3]值为‘l’, a[4]值为‘o’, a[5]到a[9]的值均为‘\0’

三种方法实现:字符(串)输入输出

  1. 利用循环结构以及%c格式说明符实现字符数组各元素的输入及输出;
#include <stdio.h>
int main()
{
    char arr[5]; 
    //输入字符
    for (int i = 0; i < 5; i++) 
      {
        printf("arr[%d] = ", i); 
        scanf(" %c", &arr[i]); // 输入{ 'a', 'b', 'c', 'd', 'e' }
      }
    //输出字符
    for (int i = 0; arr[i] != NULL; i++) 
      {
        printf("%c ", arr[i]); 
      }
      printf("\n");
    return 0;
}
  1. 用%s格式说明符,对存储字符串的字符数组实现输入输出;
#include <stdio.h>
int main()
{
    char arr[15]; 
    //输入字符串

    printf("请输入15个字符内的字符串:\n"); 
    scanf(" %14s", &arr); //使用%s格式说明符读取字符串,注意限制输入长度以防止溢出
  
    //输出字符串
    printf("您输入的字符串是:%s\n", arr); 
  
    return 0;
}
  1. 用C语言提供的字符串输入输出函数,实现对存储字符串的字符数组进行输入输出;

fgets, gets, fputs, puts, getchar, 和 putchar 是C语言标准库中用于处理输入输出的函数。它们各自有不同的用途和行为,下面是对它们的详细解释:

  1. fgets(charstr, int n, FILE stream)
    • 从指定的输入流(如标准输入stdin或文件)读取最多n-1个字符,或直到遇到换行符(换行符也会被读取并存储),或直到到达文件末尾。
    • 会在字符串末尾自动添加一个空字符(\0)作为字符串的结束标志。
    • 更安全,因为它能防止缓冲区溢出。
  2. gets(char str)(不推荐使用)
    • 从标准输入stdin读取一行,直到遇到换行符(换行符不会被存储)。
    • 不安全,因为它不检查目标数组的大小,可能导致缓冲区溢出。
    • 在C11标准中已被弃用。
  3. fputs(const char str, FILE stream)
    • 将字符串str(不包括空字符)写入到指定的输出流(如标准输出stdout或文件)。
    • 不自动添加换行符。
  4. puts(const char str)
    • 将字符串str和一个换行符写入到标准输出stdout
    • 自动在字符串末尾添加一个换行符。
  5. getchar(void)
    • 从标准输入stdin读取下一个可用的字符。
    • 返回读取的字符作为unsigned char强制转换为int,如果到达文件末尾或发生读取错误,则返回EOF
  6. putchar(int char)
    • 将指定的字符(转换为unsigned char)写入到标准输出stdout
    • 返回写入的字符,如果发生错误则返回EOF
  7. 总结
    • fgetsgetchar 用于输入,其中 fgets 更安全,因为它可以限制读取的字符数。
    • gets 不安全,已被弃用。
    • fputsputs 用于输出,其中 puts 自动添加换行符。
    • getcharputchar 分别用于读取和写入单个字符。
      在使用这些函数时,要注意它们的特性和潜在的局限性,特别是与安全和效率相关的方面。
#include <stdio.h>

int main() {
    char str[100]; // 定义一个字符数组来存储字符串

    // 输入字符串
    printf("请输入一个99字符内的字符串:");
    fgets(str, sizeof(str), stdin);
    // 使用fgets从标准输入读取一行,并存储在str中
    // fgets的第三个参数指定了最多读取的字符数(包括结尾的'\0')

/*  if (fgets(str, sizeof(str), stdin) != NULL) {
        // 检查是否读取到了换行符,并替换为字符串结束符'\0'(如果需要)
        // 注意:如果输入的字符串长度达到了数组的大小-1(即sizeof(str)-1),
        // 则换行符可能会被保留在字符串中,因为此时没有空间存储额外的'\0'了。
        // 但在这个例子中,由于我们限制了最多读取99个字符,所以数组的第100个位置
        // 是留给'\0'的,因此如果输入中包含了换行符,它会被替换掉。
        size_t len = strlen(str);
        if (len > 0 && str[len - 1] == '\n') {
            str[len - 1] = '\0'; // 替换换行符为字符串结束符
        }
    } else {
        // 处理读取错误(虽然在这个简单的例子中不太可能发生)
        fprintf(stderr, "读取输入时发生错误。\n");
        return 1; // 返回非零值表示程序异常结束
    } */

    //sizeof():数组变量的长度,strlen():数组字符串的长度
    printf("%d %d\n", sizeof(str), strlen(str)); 
    // 输出字符串
    printf("您输入的字符串是:%s\n", str);
    // 也可以使用fputs将字符串输出到标准输出(或其他文件流)
    fputs(str, stdout); // 这行代码会输出字符串但不包括换行符
    // 如果想要输出后换行,可以手动添加换行符
    putchar('\n'); // 或者 fputs("\n", stdout);

    return 0; // 返回零表示程序正常结束
}

字符串处理函数

字符串的运算,C语言中不能直接作+、-等运算,需要用函数做运算C库函数中提供了一些专门处理字符串的函数。下面介绍几种常见字符串处理函数:

  • 字符串复制:strcpy(dest, src);
  • 字符串连接:strcat(dest, src);
  • 字符串比较:strcmp(str1, str2);
  • 字符串长度:strlen(str);
  • 注意: 使用字符串处理函数时,需要在代码最前面加上#include <string.h>
  • 特别注意: C语言中有一个sizeof()运算,求变量的长度。不用#include <string.h>
    • 例如,i=sizeof(double);
    • 又例如,char str[10]; i=sizeof(str); //i=10, 是数组变量的长度

示例

//设有定义
    char str[] = "Hello";
//则语句
    printf("%d %d", sizeof(str), strlen(str)); 
//的输出结果是(6 5)。

数组的运用:修改、查找、排序

修改

修改:修改中间一个元素的值,很简单,直接赋值即可。例如:a[i]= 修改的值或计算表达式;

查找

查找:即,从数组所有元素中找到符合条件的第一个或全部元素。

查找数组中的特定元素,可以使用循环遍历数组。例如:

int findElement(int arr[], int size, int target) {
    for (int i = 0; i < size; i++) {
        if (arr[i] == target) {
            return i; // 找到元素,返回下标
        }
    }
    return -1; // 未找到元素,返回-1
}

排序

排序:把数组中的元素,按升序(从小到大)或降序(从大大小),重新排列

常见的排序算法有冒泡排序、选择排序、插入排序等。以冒泡排序为例:

  • 从第一和第二个数开始两两进行比较。
  • 比较过程中,若大的数在前,则将两个数交换,把小的数放在前面。若小的数在前,则不做操作。
  • 如此一轮下来,最大的数将会排在数列的最后
  • 接下来一轮,去掉最后最大的数,对数列中剩下的数重复第1步和第2步的操作,直至排序结束。
  • ![视频]
#include <stdio.h>
#include <conio.h>
int main()
{
	int a[5] = { 7,6,5,4,3 };
	int p, i, tmp;
	{//检查原始顺序
		printf("原始顺序:");
		for (i = 0; i < 5; i++) 	printf("%d ", a[i]);
		printf("\n"); 
	}
	for (p = 1; p < 5; p++)
	{
		for (i = 0; i < 5 - p; i++)
			if (a[i] > a[i + 1]) { tmp = a[i]; a[i] = a[i + 1]; a[i + 1] = tmp; }
		 {//中间结果检查
			printf("第%d轮: ", p);
			for (i = 0; i < 5; i++)  printf("%d ", a[i]);
			printf("\n");
		 }
		char a=_getch();// 等待输入一个字符(键盘)
	}
	return 0;
}

实例分析

字符串例子:用 ‘\0’清空字符串

#include <stdio.h>
int main()
{
    char arr[5] = {'a', 'b', 'c', 'd', 'e'}; // declaring the array
    printf("原先: ");
    for (int i = 0; arr[i] != NULL; i++) // 看一下
        printf("%c ", arr[i]);
    printf("\n");
    arr[0] = '\0'; // 清数组 ,让数组第1个元素为‘\0’
    printf("之后: ");
    for (int i = 0; arr[i] != NULL; i++) // 再看一下,整个数组都无法输出
        printf("%c ", arr[i]);
    return 0;
}

字符串例子:汉字字符串长度和占位

#include <stdio.h>
#include <string.h>
int main()
{
    char chin1[] = "我是一个学生";
    char chin2[] = "我是a学生";
    printf("第一个字符串长度:%d、占位:%d\n", strlen(chin1), sizeof(chin1));
    printf("第二个字符串长度:%d、占位:%d\n", strlen(chin2), sizeof(chin2));
    return 0;
}
//输出
//第一个字符串长度、占位18 19
//第二个字符串长度、占位13 14

字符串例子:汉字字符乱码问题

#include <stdio.h>
#include <string.h>
int main()
{
	char chin1[] = "我是一个学生";

	printf("原字符串是: %s\n", chin1);
	chin1[3] =65 ; // 字母a的编码
	chin1[6] = 7; 	//改为:chin1[6] = 7;还会响铃! 
	printf("修改后字符串是: %s\n", chin1);
 return 0;
}

运用:二分法查找

  • 一旦数组中的数是有序的,例如,从小到大,或相反,
  • 查找的速度就可以加快了:二分法查找
  • 例如,一个长度为10的有序数组为:{0,2,3,4,6,8,9,11,13},查找等于3的元素?
  • 先找到中间的数,即,第10/2=5个下标的数8,比3大,这个数一定在第5个下标的前面,
  • 接下来,找第5/2=3个下标的数,就是3,找到了!
  • 否则继续循环。
  • 平均每次查找,用lnN次(自个思考)
  • 而传统的查找:最坏要用循环N(数组长度)次,最好是一一次
#include <stdio.h>

// 二分查找函数,返回目标元素在数组中的索引,如果未找到则返回-1
int binarySearch(int arr[], int size, int target) {
    int left = 0;
    int right = size - 1;

    while (left <= right) {
        int mid = left + (right - left) / 2;  // 防止(left + right)溢出

        // 检查中间元素是否是目标值
        if (arr[mid] == target) {
            return mid;
        }

        // 如果目标值大于中间元素,忽略左半部分
        if (arr[mid] < target) {
            left = mid + 1;
        } else {  // 如果目标值小于中间元素,忽略右半部分
            right = mid - 1;
        }
    }

    // 如果未找到目标值,返回-1
    return -1;
}

int main() {
    int arr[] = {0,2,3,4,6,8,9,11,13};
    int size = sizeof(arr) / sizeof(arr[0]);
    int target = 11;
    int result = binarySearch(arr, size, target);

    if (result != -1) {
        printf("元素 %d 在数组中的索引为 %d\n", target, result);
    } else {
        printf("元素 %d 不在数组中\n", target);
    }

    return 0;
}

二维数组应用:矩阵转置

#include <stdio.h>
int main() {
    int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int b[3][2];
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            b[j][i] = a[i][j];
        }
    }
    printf("Transposed matrix:\n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 2; j++) {
            printf("%d ", b[i][j]);
        }
        printf("\n");
    }
    return 0;
}

注意事项

  • 数组定义时,长度必须是整型常量或整型常量表达式。
  • 字符数组存储字符串时,会自动在末尾添加空字符‘\0’。
  • 字符串处理函数需要包含头文件<string.h>
  • 排序算法有多种,选择适合的排序算法可以提高效率。

通过以上内容的详细解释和实例分析,初学者可以更加深入地理解数组的概念和运用,掌握数组的修改、查找和排序等基本操作。

备注:

因个人习惯和能力所限,该文档内容若存在表述不合理或错误之处,请大家留言多多指正

版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明


标题:C语言基础 5 - 数组
作者:谷国信
单位:北京邮电大学世纪学院
单位:北京谷梁科技有限公司
地址:http://blog.guoxinweb.com/articles/2024/12/30/1735519031130.html