15 字符数组与字符串,格式占位符 %s 的注意事项,二维数组的使用及其内存分析,数组的综合编程练习

目录

1 字符数组与字符串

1.1 字符数组

1.2 字符串

1.3 字符数组与字符串的关系

1.4 格式占位符 %s

1.4.1 printf 中使用 %s

1.4.2 scanf 中使用 %s 

1.5 字符数组的初始化

1.5.1 使用字符列表进行初始化

1.5.2 使用字符串字面量进行初始化

1.6 字符数组元素的访问与遍历

2 多维数组

2.1 介绍

2.2 二维数组的定义

2.3 二维数组的初始化

2.3.1 逐个元素赋值

2.3.2 以矩阵的形式初始化

2.3.3 通过连续的数值初始化

2.3.4 省略行数

2.4 二维数组的内存分析

2.5 二维数组元素的访问和遍历

2.5.1 行数与列数的计算

2.5.2 案例:计算元素和

2.5.3 二维数组的越界访问

2.6 案例:计算三个班级的成绩平均分及总平均分

3 数组的编程练习

3.1 循环输入并保存成绩到数组

3.2 计算鸡的总体重与平均体重

3.3 创建并打印 26 个字母的数组

3.4 求出数组的最小值及其索引

4 测试题


1 字符数组与字符串

1.1 字符数组

        字符数组是 C 语言中的一种基本数据结构,用于存储一系列字符。字符数组的定义方式与其他类型的数组类似,使用 char 类型指定数组的元素类型。例如:

char arr[10]; // 定义了一个可以存储 10 个字符的数组

        这个数组 arr 可以存储 10 个字符,包括字符串的结束符 \0(如果用于存储字符串的话)。字符数组的元素可以是任何有效的 char 类型值,包括字母、数字、标点符号、控制字符(如换行符 \n)以及空字符 \0。

1.2 字符串

        在 C 语言中,字符串是一系列以空字符 \0 结尾的字符。由于这个特性,字符串可以通过字符数组来表示,但字符串本身并不是 C 语言中的一种独立的数据类型。字符串的结束符 \0 非常重要,因为它告诉程序字符串在哪里结束

        字符串字面量(如"Hello")在 C 语言中会被编译器自动转换为一个字符数组,并在末尾添加一个 \0 作为结束符。例如,当写:

char str[] = "Hello";

        编译器实际上会分配足够的空间来存储 H、e、l、l、o 和 \0 这六个字符,并将它们存储在 str 数组中。

1.3 字符数组与字符串的关系

        字符数组可以存储字符串:由于字符串以 \0 结尾,所以任何以 \0 结尾的字符数组都可以被视为一个字符串

        字符串通过字符数组实现:在 C 语言中,字符串实际上是通过字符数组来实现的。字符串字面量被编译器转换为字符数组,并在末尾添加 \0

        字符数组不一定存储字符串:字符数组可以存储任何 char 类型的值,包括非 \0 结尾的字符序列,这样的数组就不能被视为字符串

1.4 格式占位符 %s

        %s 是 C 语言中用于 printf、scanf 等函数中的格式占位符,专门用于处理 C 风格的字符串。当使用 %s 时,它告诉函数期望一个指向字符数组(或字符串)的指针作为参数。函数将从该指针指向的位置开始读取字符,直到遇到第一个 \0(空字符)为止,这个序列就是需要处理的字符串。

1.4.1 printf 中使用 %s

        在 printf 函数中,%s 用于将指定的字符串(从字符串的第一个字符开始读取,直到遇到 \0 为止)输出到控制台或字符串缓冲区。字符串以 \0(ASCII 码表中的第 0 个字符,也称为 NUL 或空字符)作为结束标志,但 \0 本身不会被输出,因为它仅用于在内存中标识字符串的终点。这个空字符既不会显示任何内容,也不是控制字符,在 C 语言中它的作用仅仅是标记字符串的结束。

#include <stdio.h>

int main()
{
    char str[] = "Hello World!";
    printf("The string length is: %zu\n", sizeof(str) / sizeof(str[0])); // 13
    printf("The string is: %s\n", str);
    return 0;
}

        输出结果如下所示: 

1.4.2 scanf 中使用 %s 

        在 scanf 等输入函数中,%s 用于从标准输入(通常是键盘)读取字符串,直到遇到第一个空白字符(如空格、制表符或换行符)为止,将第一个空白字符(空格、制表符或换行符)之前的所有字符存储在指定的字符数组中scanf 会在读取的字符串末尾自动添加 \0,使其成为一个有效的 C 语言字符串。

        由于 scanf 会在遇到第一个空白字符时停止读取,因此它不能用来读取包含空格的字符串。如果需要读取包含空格的字符串,应该使用 fgets 或其他类似的函数(后续学习)。

#include <stdio.h>

int main()
{
    // 声明一个字符数组来存储输入的字符串
    char str[50];

    printf("Enter a string (no spaces): ");
    // 使用 scanf 和 %s 读取字符串,直到遇到空白字符
    scanf("%s", str);

    // 由于 scanf 在读取的字符串末尾自动添加了 \0,
    // 我们可以直接使用 printf 来输出这个字符串,而不需要担心字符串的终止
    printf("You entered: %s\n", str);

    // 检查字符串长度
    printf("String length: %zu\n", sizeof(str) / sizeof(str[0])); // 50

    return 0;
}

        输出结果如下所示:

        调试运行,查看内存空间,如下所示: 

        使用 scanf 的 %s 读取字符串时,需特别注意缓冲区溢出的风险。如果输入的字符串长度超过了目标数组的大小,scanf 会持续写入字符,直到遇到空白字符或输入流的末尾

        此外,scanf 不会在目标数组的末尾自动添加 \0 来终止字符串,这将导致无法通过 printf 的 %s 格式说明符正确输出字符串,并可能引起未定义的行为,包括数据损坏和程序崩溃。

#include <stdio.h>

int main()
{
    // 声明一个字符数组来存储输入的字符串
    char str[5];

    printf("Enter a string: ");
    // 使用 scanf 和 %s 读取字符串,直到遇到空白字符
    scanf("%s", str);

    // 如果输入的字符串长度超过了目标数组的大小
    // 不会自动在目标数组的末尾添加 \0 来终止字符串
    // 错误用法,慎用!!!
    printf("You entered: %s\n", str);

    // 检查字符串长度
    printf("String length: %zu\n", sizeof(str) / sizeof(str[0])); // 5

    return 0;
}

        输出结果如下所示:

        调试运行,查看内存空间,如下所示:

1.5 字符数组的初始化

1.5.1 使用字符列表进行初始化

        在使用字符列表为字符数组赋值时,如果没有显式添加结束符 \0:

  • 如果赋值的元素数量少于字符数组的长度,系统会在末尾自动添加 \0 作为字符串的结束标志
  • 如果赋值的元素数量等于数组的长度或未指定数组长度,则不会自动添加 \0
  • 如果赋值的元素数量超过数组的长度,编译器会报错,因为多余的初始化值无法存储

        正如之前提到的,整型数组在初始化时,若指定的元素少于数组长度,未初始化的元素将默认初始化为 0。同样地,对于字符数组,未初始化的元素将默认初始化为 \0

        如果字符数组未以 \0 结尾,则不应将其视为有效字符串进行字符串操作。字符串操作函数(如 strlen、strcpy、strcat、printf 与 %s 等)会一直读取直到遇到 \0。如果数组缺少 \0 结尾,这些函数将继续读取后续内存区域,直到遇到某个位置上的 \0,这可能导致访问非法内存,从而引发安全问题。

#include <stdio.h>

int main()
{
    // 显式地为字符串数组 str1 的每个字符赋值
    // 包括空字符 '\0' 作为字符串的结束标识
    // 这样定义字符串是安全的,因为明确指定了字符串的结束。
    char str1[12] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\0'};

    // 为字符串数组 str2 的前三个字符赋值,数组大小是 4,编译器会自动在末尾添加 '\0'
    // 这里编译器会自动添加 '\0',因为数组大小足够容纳一个额外的 '\0'
    char str2[4] = {'t', 'o', 'm'};

    // str3 数组大小是 3,不会自动添加 '\0',这会导致未定义行为
    // 后续不应该使用 str3 进行字符串操作,因为它不是以 '\0' 结尾的
    // 由于 str3 没有以 '\0'结尾,尝试将其作为字符串(如使用 %s 格式说明符来输出)是未定义行为。
    char str3[3] = {'t', 'o', 'm'};

    // 如果赋值的元素数量超过数组的长度,编译器会报错,因为多余的初始化值无法存储
    //  char str3[3] = {'t', 'o', 'm', 'm', 'm'}; // 报错

    // 为字符数组 str4 的前四个字符赋值,但没有显式地添加 '\0'
    // 这里数组大小是自动计算的,但不包括 '\0'。
    // 使用 str4 作为字符串(如使用 %s 格式说明符来输出)是不安全的,因为它不是以'\0'结尾的。
    // 由于 str4 没有以'\0'结尾,尝试将其作为字符串将读取未定义的数据,直到遇到'\0'。
    char str4[] = {'j', 'a', 'c', 'k'};

    // 打印字符串
    printf("str1=%s \n", str1); // 正确打印 "Hello World"
    printf("str2=%s \n", str2); // 正确打印 "tom",因为编译器自动添加了 '\0'

    // 下面的两行是未定义行为,因为 str3 和 str4 都不是以 '\0' 结尾的字符串
    printf("str3=%s \n", str3); // 这里的行为是未定义的,因为 str3 没有以 '\0' 结尾
    printf("str4=%s \n", str4); // 这里的行为也是未定义的,因为 str4 没有以 '\0' 结尾

    // 替代方案:显式地打印 str3 和 str4 的前三个和前四个字符(不将它们视为字符串)
    // 注意:这里仅为了演示,实际使用中应避免这种潜在的内存越界
    printf("str3 first 3 chars: %c%c%c \n", str3[0], str3[1], str3[2]);
    printf("str4 first 4 chars: %c%c%c%c \n", str4[0], str4[1], str4[2], str4[3]);

    return 0;
}

        输出结果如下所示:

        对于上面这个程序,为什么输出结果是这样的呢? 

        下面我们通过调试程序来揭秘答案,首先在最后一行代码 return 0; 处添加一个断点,然后开启调试模式,如下图所示:

        在左侧变量区域中,我们可以看到,str3(初始化时赋值的元素的个数等于该数组的长度)和 str4(数组定义时不指定数组长度)这两个字符数组最后没有结束符 '\0',如下图所示:

        然后开始监视这四个数组,这需要添加监视的表达式,在左侧监视区域中,点击 + 号,依次输入 &str1,&str2,&str3,&str4,这样可以看到各个数组的内存地址,如下图所示:

        通过监视区域,可以看出这四个数组所在的内存地址,然后通过查看对应的二进制数据,就可以看到各个数组元素的存储情况,如下图所示:

        数组 str1 中存储的是: Hello World\0, H: 0x48,e: 0x65,l: 0x6c,l: 0x6c,o: 0x6f,(空格): 0x20,W: 0x57,o: 0x6f,r: 0x72,l: 0x6c,d: 0x64 ,\0:0x00,完整 ASCII 十六进制表示为:48 65 6c 6c 6f 20 57 6f 72 6c 64 00,其在内存中的存储情况如下图所示:

        由于数组 str1 最后一个元素存储了结束符 '\0',所以通过 printf 的格式占位符 %s 可以正确输出。  

        数组 str2 中存储的是:tom 以及编辑器自动添加的 '\0',其在内存中的存储情况如下图所示:

        由于数组 str2 最后一个元素也存储了结束符 '\0',所以通过 printf 的格式占位符 %s 可以正确输出。   

        数组 str3 中存储的是:tom ,由于初始化时赋值的元素的个数等于该数组的长度,编译器不会自动添加 '\0'。如果通过 printf 的格式占位符 %s 进行输出,会一直读取输出字符直到遇到 '\0' 为止。所以,通过 printf 的格式占位符 %s 输出的内容是:tomtom,其在内存中的存储情况如下图所示:

        数组 str4 中存储的是:jack,由于初始化时未指定数组长度,编译器不会自动添加 '\0'。如果通过 printf 的格式占位符 %s 进行输出,会一直读取输出字符直到遇到 '\0' 为止。所以,通过 printf 的格式占位符 %s 输出的内容是:jacktomtom,其在内存中的存储情况如下图所示:

1.5.2 使用字符串字面量进行初始化

        如果使用字符列表的方式进行数组的初始化,当字符较多时,就会显得不够便捷,因此现在介绍一种更简便的方法。

        在 C 语言中,字符串字面量(如 "I am happy")在内存中是以字符数组的形式存储的,且每个字符串字面量都自动以空字符('\0')作为结束符

        所以,直接使用双引号包围的字符串字面量来初始化字符数组时,编译器会自动在字符串的末尾添加 '\0' 作为结束符

#include <stdio.h>

int main()
{
    // 因为字符串字面量本身就是以 '\0' 结尾的字符序列。
    // 编译器会忽略大括号,并自动在字符串末尾添加 '\0'。
    char str1[] = {"I am happy"}; // 后面自动添加 \0,{} 的使用是不必要的

    // 这是更常见的初始化字符串的方式,直接使用双引号包围的字符串字面量。
    // 编译器会自动在字符串末尾添加 '\0'。
    char str2[] = "I am happy"; // 省略 {} 号,后面自动添加 \0

    printf("str1=%s \n", str1); // str1=I am happy
    printf("str2=%s \n", str2); // str2=I am happy

    return 0;
}

        当使用字符串字面量来初始化字符数组时,初始化列表中如果还使用大括号 {} 来包围字符串字面量,虽然语法上允许,但通常不推荐,因为它可能会降低代码的可读性,并且容易让人误解为可以修改字符串的内容(实际上字符串字面量是不可修改的)。建议直接使用双引号包裹进行数组的初始化! 

1.6 字符数组元素的访问与遍历

        字符数组元素或字符串的访问和遍历,按照一般数组的方式访问和遍历即可。

#include <stdio.h>

int main()
{
    // 定义字符串(实际上是一个字符数组,包含字符串 "Hello" 和结尾的空字符 '\0')
    char string[] = "Hello!";

    // 计算包含字符串的字符数组的总长度(包含结尾的空字符'\0')
    int len1 = sizeof string / sizeof string[0];
    // char 类型的数据占 1 字节,即 sizeof string[0] = 1
    // 所以可以不用除以 sizeof string[0],
    int len2 = sizeof string;

    // 打印整个字符数组的长度(包含结尾的空字符'\0')
    printf("数组长度(包括结尾的空字符): %d <-> %d\n", len1, len2); // 7 <-> 7

    // 打印字符串
    printf("使用 printf 输出的字符串:%s \n", string); // Hello!

    // 访问字符串的字符
    printf("第1个字符: %c \n", string[0]);             // H
    printf("第2个字符: %c \n", string[1]);             // e
    printf("第3个字符: %c \n", string[2]);             // l
    printf("第4个字符: %c \n", string[3]);             // l
    printf("第5个字符: %c \n", string[4]);             // o
    printf("第6个字符: %c \n", string[5]);             // !
    printf("第7个是结束符: %c \n", string[6]); // 空字符,不会有任何显示

    // 修改字符串的第 1 个字符(索引为 0 )
    string[0] = 'h';
    // 修改字符串的第 6 个字符(索引为 5 )
    string[5] = '?';

    // 遍历整个字符数组(包括结尾的空字符'\0',但'\0'在输出时不会显示)
    printf("第一次for循环遍历字符数组:\n");
    for (int i = 0; i < len1; i++)
    {
        printf("string中的第%d字母为:%c \n", (i + 1), string[i]);
    }

    // 如果不想打印 '\0',可以减少最后一次的循环
    printf("第二次for循环遍历字符数组:\n");
    for (int i = 0; i < len2 - 1; i++)
    {
        printf("string中的第%d字母为:%c \n", (i + 1), string[i]);
    }

    return 0;
}

        在上面这个程序的第一次循环中,虽然 '\0' 也会被计算在内,但由于它是空字符,所以在控制台上不会显示。如果不想输出 '\0',可以减少最后一次的循环,如上面这个程序的第二次循环。

        输出结果如下所示:


2 多维数组

2.1 介绍

        如果数组的元素还是数组,这样的数组就称为多维数组。这种多层次的结构允许我们以表格矩阵的方式组织数据,其中每个维度都对应于不同的行、列或更多的维度,使数据更加结构化和有组织。

        多维数组可以分为二维数组、三维数组、四维数组 …… 等,这里我们以二维数组为例进行演示。

        下图是一个四行六列的二维数组示意图:

2.2 二维数组的定义

        二维数组的定义类似于一维数组,但需要指定两个维度的大小:行数列数。语法格式如下:

数据类型 数组名[行数][列数];

        定义一个 3 行 4 列的整数二维数组,如下所示:

int matrix[3][4];

2.3 二维数组的初始化

2.3.1 逐个元素赋值

        在定义完二维数组后,可以通过逐个元素赋值的方式对其进行初始化。

// 定义一个 4 行 6 列的二维数组
int a[4][6]; 

// 进行初始化赋值
a[0][0] = 10;  // 第一行第一列的元素
a[0][1] = 20;  // 第一行第二列的元素
a[0][2] = 30;  // 第一行第三列的元素
a[0][3] = 40;  // 第一行第四列的元素
a[0][4] = 50;  // 第一行第五列的元素
a[0][5] = 60;  // 第一行第六列的元素
a[1][0] = 100; // 第二行第一列的元素
a[1][1] = 200; // 第二行第二列的元素
// ...

2.3.2 以矩阵的形式初始化

        在 C 语言中,我们不仅可以先定义二维数组然后再逐一初始化,还可以在定义的同时直接初始化数组。这种方式更加简洁并且易于阅读。

        定义并初始化一个 4 行 6 列的二维数组,以矩阵的形式初始化

int a[4][6] = {
    {10, 20, 30, 30, 40, 60},
    {100, 200, 300, 400, 500, 600},
    {1000, 2000, 3000, 4000, 5000, 6000},
    {10000, 20000, 30000, 40000, 50000, 60000}
};

        在这个例子中,每一行的元素被明确地放置在一个大括号 {} 中,形成一个矩阵式的布局。这样的初始化方式使得数组的结构非常清晰。

2.3.3 通过连续的数值初始化

        定义并初始化一个 4 行 6 列的二维数组,通过连续的数值初始化,编译器会自动匹配到各行和各列

int b[4][6] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24};

        这种方式中,虽然没有显式地使用大括号 {} 来分隔行,但是编译器会根据提供的元素数量和数组的维度自动填充到相应的行列中,就是看着不是很直观清晰。

2.3.4 省略行数

        完全初始化数组时,可以省略数组名后的第一个方括号中的大小(即行数),但列数(每个子数组的大小)是必须指定的。这是因为编译器可以通过初始化时提供的元素数量来推断出数组的行数。但是,这并不意味着可以省略列数。然而,出于清晰和可读性的考虑,通常建议同时指定行数和列数。

int b[][6] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24};
// 这里省略了行数,但编译器可以通过初始化列表推断出有 4 行

int matrix[][4] = {  
    {1, 2, 3, 4},  
    {5, 6, 7, 8},  
    {9, 10, 11, 12}  
};  
// 这里省略了行数,但编译器可以通过初始化列表推断出有 3 行


int matrix[][] = { // 错误:列数不能省略  
    {1, 2, 3, 4},  
    {5, 6, 7, 8},  
    {9, 10, 11, 12}  
};

2.4 二维数组的内存分析

        用矩阵形式(如 3 行 4 列形式)表示二维数组,是逻辑上的概念,能形象地表示出行列关系。而在内存中,各元素是连续存放的,不是二维的,而是线性的

        C 语言中,二维数组中元素排列的顺序是按行存放。即:先顺序存放第一行的元素,再存放第二行的元素。例如, 数组 a[3][4] 在内存中的存放如下所示:

        通过调试运行程序,查看内存空间,可以更直观的分析出二维数组各元素的存储情况,以下面这个二维数组为例:

int map[3][4] = {
        {1, 2, 3, 4},     // 第一行
        {11, 12, 13, 14}, // 第二行
        {21, 22, 23, 24}  // 第三行
};

        在 Clion 中进行调试运行,查看内存空间,如下所示: 

         在 VS Code中进行调试运行,查看内存空间,如下所示: 

2.5 二维数组元素的访问和遍历

        访问二维数组的元素,需要使用两个下标(索引),一个用于访问行(第一维),另一个用于访问列(第二维),我们通常称为行下标(行索引)列下标(列索引)。

        遍历二维数组,需要使用双层循环结构。

2.5.1 行数与列数的计算

        要计算一个二维数组的行数,可以使用 sizeof 运算符获取整个数组的大小,然后除以单行的大小:

int rows = sizeof(array) / sizeof(array[0]);

        要计算列数,可以通过获取单行的大小,然后除以单个元素的大小:

int cols = sizeof(arr[0]) / sizeof(arr[0][0]);

int cols = sizeof(arr[0]) / sizeof(基本数据类型);
“基本数据类型” 指的是数组中元素的数据类型,例如 int、char 等。

2.5.2 案例:计算元素和

#include <stdio.h>

int main()
{
    // 定义一个 3 行 4 列的二维数组
    int map[3][4] = {
        {1, 2, 3, 4},     // 第一行
        {11, 12, 13, 14}, // 第二行
        {21, 22, 23, 24}  // 第三行
    };

    // 计算第一维度(行数)的长度
    int rows = sizeof(map) / sizeof(map[0]);
    // 计算第二维度(列数)的长度
    int cols = sizeof(map[0]) / sizeof(int);
    int cols2 = sizeof(map[0]) / sizeof(map[0][0]);

    // 遍历并输出二维数组的每个元素
    printf("二维数组的元素:\n");
    for (int i = 0; i < rows; i++)
    {
        for (int j = 0; j < cols; j++)
        {
            printf("%d \t", map[i][j]); // 使用制表符 \t 使输出更加整齐
        }
        // 打印完一行就换行
        printf("\n");
    }

    // 计算二维数组中所有元素的和
    int sum = 0;
    printf("计算所有元素的和:\n");
    for (int i = 0; i < rows; i++)
    {
        for (int j = 0; j < cols; j++)
        {
            sum += map[i][j];
        }
    }
    printf("所有元素的和:%d\n", sum); // 150

    return 0;
}

        输出结果如下所示:

2.5.3 二维数组的越界访问

        在 C 语言中,数组越界访问是指访问数组之外的内存位置。对于二维数组来说,如果访问的索引超出了数组的定义范围,就会发生越界访问。数组越界访问是一种未定义行为,意味着编译器或运行时环境可以以任何方式处理这种情况,包括产生不可预测的结果、程序崩溃或安全漏洞。下面通过一个示例来说明这一点。

#include <stdio.h>

int main()
{
    int arr[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}};

    // 正确访问
    printf("arr[1][0] = %d\n", arr[1][0]); // 输出 5

    // 越界访问(这里仅用于说明,实际中应避免)
    // 输出不确定的值,可能是arr[1][0]的值 5,也可能是其他值
    printf("arr[0][4] (越界访问) = %d\n", arr[0][4]); 

    if(arr[0][4] == arr[1][0]){
        printf("在当前编译环境下,越界访问的arr[0][4] 等于 arr[1][0]");
    }

    return 0;
}

        输出结果如下所示:

        arr[0][4]:尝试访问第 0 行第 5 列的元素,数组只有 4 列,所以这是越界访问。

        关于 arr[0][4] == arr[1][0] 的比较,这并不表示这两个表达式访问的是同一个地址空间。实际上,arr[0][4] 是越界访问,而 arr[1][0] 是合法访问。它们在内存中的位置是不同的。即使在这个特定的程序运行中 arr[0][4] 似乎返回了 arr[1][0] 的值,这只是一个偶然现象,不能作为通用规则。在不同的编译环境、不同的编译器设置、不同的运行时环境中,结果可能会有很大不同

2.6 案例:计算三个班级的成绩平均分及总平均分

        现在有三个班,每个班五名同学,用二维数组保存他们的成绩,并求出每个班级平均分、以及所有班级平均分,数据要求从控制台输入。

#include <stdio.h>

int main()
{
    // 定义一个 3 行 5 列的数组,用于存储不同班级的学生的成绩
    double scores[3][5];

    // 定义变量存储第一个维度长度(班级数)和第二个维度长度(学生数)
    // int rows = 3, cols = 5;
    int rows = sizeof scores / sizeof scores[0];
    int cols = sizeof scores[0] / sizeof scores[0][0];

    /* 暂时先不对用户输入的数据做校验 */

    // 遍历二维数组进行赋值
    for (int i = 0; i < rows; i++)
    {
        for (int j = 0; j < cols; j++)
        {
            // 提示用户输入成绩
            printf("请输入第%d个班的第%d个学生的成绩:", i + 1, j + 1);
            // 读取用户输入的成绩
            scanf("%lf", &scores[i][j]);
        }
    }

    // 遍历数组,计算每个班级的平均分和总的平均分
    // 定义变量记录所有班级总分数
    double total_sum = 0;
    // 定义变量记录每个班级总分数
    double class_sum = 0;

    // 遍历班级(行)
    for (int i = 0; i < rows; i++)
    {
        // 将当前班级总分数重置为 0,至关重要!!!
        // 或者将这个变量的声明拿进来
        class_sum = 0;

        // 遍历当前班级的每个成绩(该行的每一列)
        for (int j = 0; j < cols; j++)
        {
            // 累加成绩
            class_sum += scores[i][j];
        }

        // 输出当前班级的平均分
        printf("第%d个班级的平均分为:%.2f \n", i + 1, class_sum / cols);

        // 将该班级总分加入到所有总分中
        total_sum += class_sum;
    }

    // 输出所有班级的平均分
    printf("所有班级的平均分为:%.2f \n", total_sum / (rows * cols));

    return 0;
}

        输出结果如下所示:


3 数组的编程练习

3.1 循环输入并保存成绩到数组

        从终端循环输入 5 个成绩,保存到 double 数组,并输出。

#include <stdio.h>

int main()
{
    // 定义一个 double 类型的数组,用于保存成绩
    double grades[5];

    // 计算数组的长度
    int length = sizeof(grades) / sizeof(grades[0]);

    // 遍历数组进行赋值
    for (int i = 0; i < length; i++)
    {
        // 提示用户输入成绩
        printf("请输入第 %d 个成绩:", i + 1);
        // 读取用户输入的成绩
        scanf("%lf", &grades[i]);
    }

    // 输出成绩
    printf("成绩为:");
    for (int i = 0; i < length; i++)
    {
        printf("%.2f ", grades[i]);
    }

    return 0;
}

        输出结果如下所示:

3.2 计算鸡的总体重与平均体重

        一个养鸡场有 6 只鸡,它们的体重分别是 3kg,5kg,1kg,3.4kg,2kg,50kg。请问这六只鸡的总体重是多少?平均体重是多少?

#include <stdio.h>

int main()
{
    // 定义一个 double 类型的数组,用于保存鸡的体重
    double weights[] = {3.0, 5.0, 1.0, 3.4, 2.0, 50.0};

    // 计算数组的长度
    int num_chickens = sizeof(weights) / sizeof(weights[0]);

    // 初始化总体重为 0
    double total_weight = 0.0;

    // 遍历数组累加总体重
    for (int i = 0; i < num_chickens; i++)
    {
        total_weight += weights[i];
    }

    // 计算平均体重
    double average_weight = total_weight / num_chickens;

    // 输出总体重和平均体重
    printf("总体重为:%.2f kg\n", total_weight);     // 64.40 kg
    printf("平均体重为:%.2f kg\n", average_weight); // 10.73 kg

    return 0;
}

3.3 创建并打印 26 个字母的数组

        创建一个 char 类型的 26 个元素的数组,分别放置 'A'-'Z‘。使用 for 循环访问所有元素并打印出来。

#include <stdio.h>

int main()
{
    // 长度 26 ,后面使用循环输出
    char letters1[26];
    // 长度 27 ,比 26 大一,用于保存结束符,后面使用 %s 输出
    char letters2[27];

    // 方法一
    for (int i = 0; i < 26; i++)
    {
        letters1[i] = 'A' + i;
    }
    // 循环打印数组元素 - 没有'\0'
    for (int i = 0; i < 26; i++)
    {
        printf("%c ", letters1[i]);
        // A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
    }
    printf("\n");

    // 方法二
    for (char c = 'A'; c <= 'Z'; c++)
    {
        letters2[c - 'A'] = c;
    }
    // 末尾添加结束符,方便后面操作字符串
    letters2[26] = '\0';
    // 使用 %s 输出最好末尾要有结束符 '\0'
    // %s 格式说明符被设计为仅输出字符串的可见部分,即直到第一个 '\0' 字符之前的所有字符。
    printf("%s", letters2); // ABCDEFGHIJKLMNOPQRSTUVWXYZ

    return 0;
}

提示:

        使用 %s 输出字符数组要确保数组末尾有结束符 '\0',%s 格式说明符被设计为仅输出字符串的可见部分,即直到第一个 '\0' 字符之前的所有字符。 

3.4 求出数组的最小值及其索引

        请求出一个数组的最小值,并得到对应的索引。

#include <stdio.h>

int main()
{
    // 定义一个 double 类型的数组,用于保存成绩
    double grades[] = {85.5, 92.0, 78.3, 90.1, 82.7};

    // 计算数组的长度
    int length = sizeof(grades) / sizeof(grades[0]);

    // 初始化最小值,假设第一个是最小值
    double min_value = grades[0];
    // 初始化索引,假设第一个是最小值,索引为 0
    int min_index = 0;

    // 寻找最小值及其索引
    // i 从 1 开始,循环比较
    for (int i = 1; i < length; i++)
    {
        if (grades[i] < min_value)
        {
            min_value = grades[i];
            min_index = i; // 保存索引
        }
    }
    printf("最小值为:%.2f,索引为:%d\n", min_value, min_index);
    // 最小值为:78.30,索引为:2

    for (int i = 1; i < length; i++)
    {
        min_value = grades[i] < min_value ? grades[i] : min_value;
    }

    printf("最小值为:%.2f,索引为:%d\n", min_value, min_index);
    // 最小值为:78.30,索引为:2

    return 0;
}

4 测试题

1. 字符串的最后一个元素是什么?

【答案】结束符 ‘\0’


2. 请写出下面程序的运行结果:

int arr[3][2] = {{10,20},{30,40},{50,60}};
printf("%d", arr[1][1] + arr[2][0]);

【答案】90

【解析】arr[1][1] 可以获取到 40,arr[2][0] 可以获取到 50, 40+50=90。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thanks_ks

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值