数组

对应教学日历:讲次 11-16(4月23日-5月14日)


一、数组的概念

数组:一组相同类型数据的集合,在内存中连续存放

  • 数组名代表首元素地址(即 a 等价于 &a[0]
  • 下标从 0 开始
  • C 语言不检查数组下标越界,程序员需要自己保证

二、一维数组

定义

类型 数组名[常量表达式];
int a[10];          // 定义 10 个 int 的数组,下标 0~9
float b[5];         // 定义 5 个 float 的数组
char c[26];         // 定义 26 个 char 的数组

常量表达式中不能含变量(C99 之前),如 int a[n]; 是错误的(n 为变量)。

初始化

int a[5] = {1, 2, 3, 4, 5};     // 全部初始化
int b[5] = {1, 2};              // 部分初始化,剩余为 0
int c[] = {1, 2, 3};           // 不指定长度,自动定为 3
int d[5] = {0};                 // 全部初始化为 0

注意int a[5] = {0}; 是将全部元素初始化为 0 的简洁写法。

引用

a[0] = 10;        // 给第一个元素赋值
scanf("%d", &a[i]);  // 读入第 i 个元素,注意要加 &
printf("%d", a[i]);  // 输出第 i 个元素,不加 &

遍历

for (i = 0; i < n; i++)
    printf("%d ", a[i]);

三、一维数组常见操作

1. 排序

冒泡排序(Bubble Sort)

// 从小到大
for (i = 0; i < n-1; i++)
    for (j = 0; j < n-1-i; j++)
        if (a[j] > a[j+1])
        {
            temp = a[j];
            a[j] = a[j+1];
            a[j+1] = temp;
        }
  • 每轮将最大值”冒泡”到末尾
  • 外层循环 n-1 轮
  • 内层 n-1-i:每次比较的终点递减

选择排序(Selection Sort)

// 从小到大
for (i = 0; i < n-1; i++)
{
    min = i;
    for (j = i+1; j < n; j++)
        if (a[j] < a[min]) min = j;
    temp = a[i]; a[i] = a[min]; a[min] = temp;
}

2. 查找

顺序查找(线性查找)

for (i = 0; i < n; i++)
    if (a[i] == target)
    {
        printf("找到,位置:%d\n", i);
        break;
    }
if (i == n) printf("未找到\n");

二分查找(折半查找,要求数组有序)

int low = 0, high = n-1, mid;
while (low <= high)
{
    mid = (low + high) / 2;
    if (a[mid] == target) { printf("找到\n"); break; }
    else if (a[mid] < target) low = mid + 1;
    else high = mid - 1;
}

3. 插入与删除

  • 插入:从末尾开始向后移动元素,腾出位置
  • 删除:从删除位置开始向前移动覆盖

四、二维数组

定义

类型 数组名[行数][列数];
 
int a[3][4];     // 3 行 4 列,元素总数 = 3×4 = 12
float b[5][10];

初始化

int a[2][3] = {{1,2,3}, {4,5,6}};     // 按行初始化
int a[2][3] = {1,2,3,4,5,6};          // 按存储顺序
int a[2][3] = {{1}, {4}};             // 部分初始化,未指定的为 0
int a[][3] = {1,2,3,4,5,6};          // 只能省略行数,不能省略列数

重要:二维数组定义时可以省略行数,不能省略列数

遍历

for (i = 0; i < row; i++)
    for (j = 0; j < col; j++)
        printf("%d ", a[i][j]);

矩阵常见运算

// 对称矩阵判断
for (i=0; i<n; i++)
    for (j=0; j<n; j++)
        if (a[i][j] != a[j][i])
        { isSym = 0; break; }
 
// 矩阵转置
for (i=0; i<row; i++)
    for (j=0; j<col; j++)
        b[j][i] = a[i][j];

五、字符数组与字符串

字符数组

char s[10];                       // 字符数组
char s[10] = {'H','e','l','l','o'};  // 不是字符串(没有 \0)

字符串

C 语言中字符串以 空字符 '\0' 结尾。定义时要多留一个位置\0

char s[10] = "Hello";   // 等价于 {'H','e','l','l','o','\0', 0,0,0,0}
char s[] = "Hello";     // 自动分配 6 个空间(5个字符 + 1个\0)

字符串与字符数组的区别

字符数组字符串
结尾不一定要 \0必须 \0
输出逐个字符printf("%s", str)puts(str)
长度sizeof(str)strlen(str)(不含 \0

常用字符串函数(<string.h>

函数格式作用
strlen(s)len = strlen(s);返回字符串长度(不含 \0
strcpy(dst, src)strcpy(a, b);将 b 复制到 a(含 \0
strncpy(dst, src, n)strncpy(a, b, 5);最多复制 n 个字符
strcat(dst, src)strcat(a, b);将 b 连接到 a 末尾
strcmp(a, b)strcmp(a, b)比较大小:0 相等,>0 a 大,<0 b 大
strchr(s, ch)strchr(s, 'a')查找字符首次出现位置
strstr(a, b)strstr(a, b)查找子串

strcmp 返回值记忆:a 大于 b → 正数;a 小于 b → 负数;相等 → 0。

字符串输入

gets(str);          // 读入一行(含空格),遇换行结束,不安全
fgets(str, 100, stdin);  // 安全的带空格输入
scanf("%s", str);   // 遇空格/换行结束

数字字符串转整数

// 手工转换(atoi 的原理)
int n = 0, i = 0, sign = 1;
if (s[0] == '-') { sign = -1; i++; }
while (s[i] >= '0' && s[i] <= '9')
{
    n = n * 10 + (s[i] - '0');
    i++;
}
n = n * sign;

作业示例

ex9-1:一维数组——跳水评分(去掉最高最低)

#include <stdio.h>
 
int main() 
{
    double scores[7];
    double difficulty;
    double sum = 0, max, min;
    double result;
    int i;
    printf("请输入7个裁判的打分:\n");
    for (i = 0; i < 7; i++) {
        scanf("%lf", &scores[i]);
    }
    
    printf("请输入动作的难度系数:\n");
    scanf("%lf", &difficulty);
    max = scores[0];
    min = scores[0];
    for (i = 0; i < 7; i++) {
        sum += scores[i];
        if (scores[i] > max) max = scores[i];
        if (scores[i] < min) min = scores[i];
    }
    
    sum = sum - max - min;            // 去掉最高最低分
    result = sum * difficulty / 5 * 3;
    printf("该动作实得分为: %lf\n", result);
    return 0;
}

要点

  • 一维数组的遍历和元素访问
  • 同时求最大值和最小值
  • 先给 max/min 赋第一个元素的值作为初值

ex9-3:数组筛选——埃拉托色尼筛法求素数

#include<stdio.h>
int main()
{
    int a[101],i,j,n=0;
    for(i=2;i<=100;i++)
        a[i]=i;
    for(i=2;i<=100;i++)
        for(j=i-1;j>1;j--)
            if(a[i]%j==0)
                a[i]=0;         // 标记非素数
    for(i=1;i<=100;i++)
        if(a[i]!=0)
        {
            printf("%5d",a[i]);
            n++;
            if(n%5==0) printf("\n");
        }
    return 0;
}

要点

  • 利用数组下标直接作为数值
  • 将非素数标记为 0(筛除)
  • 最后输出非 0 元素

ex9-4:字符串转整数(手工 atoi)

#include<stdio.h>
int main()
{
    char s[10];
    int n=0;
    int i=0;
    printf("Enter a string:\n");
    gets(s);
    if(s[0]=='-') i++;      // 处理负号
    while(s[i] >= '0' && s[i] <= '9')
    {
        n=n*10+(s[i] - '0');  // 关键公式
        i++;
    }
    if(s[0]=='-') n=-n;
    printf("%1d\n",n);
    return 0;
}

要点

  • s[i] - '0' 将数字字符转为数值
  • n = n*10 + digit 逐位移入
  • 负数处理:跳过首字符 -,最后取反

ex10-1:二维数组——对称矩阵判断

#include<stdio.h>
int main(){
    int jieguo=1,a[5][5],i,j;
    printf("请输入二维数组的数据:\n");
    for(i = 0; i < 5; i++)
        for(j = 0; j < 5; j++)
            scanf("%d", &a[i][j]);
    
    for(i = 0; i < 5; i++)
    {
        for(j = 0; j < 5; j++)
        {
            if(a[i][j] != a[j][i])
            {
                jieguo = 0;
                break;
            }
        }
        if(jieguo == 0) break;  // 提前退出
    }
    
    if(jieguo == 1)
        printf("该矩阵是对称矩阵!\n");
    else
        printf("该矩阵不是对称矩阵!\n");
    
    return 0;
}

要点

  • 对称矩阵的定义:a[i][j] == a[j][i]
  • 使用标志变量 jieguo(非 0 表示是对称的)
  • 发现不对称后立即 break 跳出

ex10-2:循环矩阵生成

#include <stdio.h>
int main()
{
    int n;
    int a[100][100];
    int i, j;
    
    scanf("%d", &n);
    for(j = 0; j < n; j++)
        scanf("%d", &a[0][j]);   // 输入第一行
    
    for(i = 1; i < n; i++)
        for(j = 0; j < n; j++)
            a[i][j] = a[i-1][(j+1) % n];  // 每一行 = 上一行循环左移 1 位
    
    for(i = 0; i < n; i++)
    {
        for(j = 0; j < n; j++)
            printf("%d ", a[i][j]);
        printf("\n");
    }
    return 0;
}

要点

  • 二维数组作为矩阵
  • (j+1) % n 实现循环索引(模运算)
  • 先用第一行推导后续行

ex10-3:字符串加密(凯撒密码变体)

#include<stdio.h>
#include<string.h>
int main()
{
    char a[80],b[80];
    int k=0,i;
    gets(a);
    k=strlen(a);
    for(i=0;i<k;i++)
        if(a[i]>='A'&&a[i]<='Z')
            b[i]=(a[i]+3)>'Z'?a[i]+3-26:a[i]+3;   // 大写右移3
        else if(a[i]>='a'&&a[i]<='z')
            b[i]=(a[i]-3)<'a'?a[i]-3+26:a[i]-3;   // 小写左移3
        else b[i]=a[i];
    b[i]='\0';     // 字符串结束符不能忘!
    puts(b);
    return 0;
}

要点

  • 凯撒加密:大写字母后移 3,小写字母前移 3
  • 越界处理:> 'Z' 则回绕(-26
  • 非字母字符原样保留
  • 最后必须加 \0

ex10-4:字符串比较函数(模拟 strcmp)

#include<stdio.h>
#include<string.h>
int main()
{
    char a[80],b[80];
    int i,j,k,m;
    gets(a); gets(b);
    j=strlen(a), k=strlen(b);
    if(j>=k) m=j; else m=k;
    for(i=0;i<m;i++)
        if(a[i]!=b[i]){
            printf("第%d个字符不相等,ASCII码相差:%d",i+1,a[i]-b[i]);
            return 0;
        }
    if(i==m)
        printf("两个字符串相等");
    return 0;
}

要点

  • 逐字符比较 ASCII 码
  • 比较次数取两串长度的较大值
  • a[i]-b[i] 得到 ASCII 差值

ex10-5:字符串统计——元音字母计数

#include<stdio.h>
#include<string.h>
int main()
{
    char a[1000];
    int i,k,am=0,em=0,im=0,om=0,um=0;
    gets(a);
    k=strlen(a);
    for(i=0;i<k;i++){
        if(a[i]=='a'||a[i]=='A') am++;
        if(a[i]=='e'||a[i]=='E') em++;
        if(a[i]=='i'||a[i]=='I') im++;
        if(a[i]=='o'||a[i]=='O') om++;
        if(a[i]=='u'||a[i]=='U') um++;
    }
    printf("%d %d %d %d %d",am,em,im,om,um);
    return 0;
}

要点

  • 逐字符检查,大小写均需统计
  • 多个独立计数器同时工作
  • || 连接大小写判断

作业重点

  • 数组下标从 0 开始,定义长度必须为常量表达式
  • 数组名即首元素地址(a&a[0] 等价)
  • 字符串以 \0 结尾,定义时留一个空间给 \0
  • strlen() 不含 \0sizeof()\0
  • 二维数组初始化可省略行数不可省略列数
  • 排序(冒泡、选择)、查找(顺序、二分)必须手写
  • scanf("%s") 遇空格停止,gets 可读带空格的字符串
  • gets 读取后手动处理的字符串最后要加 \0