云计算百科
云计算领域专业知识百科平台

C语言:数组

1. 数组的基本用法

1.1 概念

数组(array)是有序的元素序列

若将有限个类型相同的变量的集合命名,那么这个名称为数组名。

重点必须类型相同和连续的变量的集合

1.2 格式

存储类型 数据类型 数组名[元素个数];
例如:(auto) int a[5]; // 这个auto的其中一种存储类型,通常省略不写,后面会学到

数组名:代表数组的首地址(即第一个元素的地址),a是地址常量,不能为左值,不能被赋值。

数组的定义方法:
        1. 数组名定义规则跟变量名相同,遵循标识符定义规则
        2. 数组名后使用方括号括起来的常量表达式表示元素个数
        3. 常量表达式中可以包含常量和宏定义,不能包含变量

1.3 访问元素

数组名[下标];    // 这里需要注意的是下标从0开始
例如:访问第一个元素就是:a[0]

注:int a[5]; int b = a[5];
//这里的a[5]就属于数组越界错误,因为数组是从0开始的,数组下标只有{0,1,2,3,4}

数组的特点:数据类型相同    内存连续

总结注意:
        1. 数组的数据类型就是元素的数据类型
        2. 数组名要符合标识符命名规则
        3. 在同一个函数中,数组名不要与变量名相同

            int a;
            int a[5]; //错误
        4. 下标从0开始,到n-1结束

2. 一维数组

2.1 一维数组概念

只有一个下标的数组

格式: 存储类型 数据类型 数组名[元素个数];

访问元素: 数组名[下标], 下标从0开始

数组名: 首地址,也就是第一个元素的地址(等价于&a[0])

2.2 初始化

1. 定义时全部初始化

int a[5] = {1, 2, 3, 4, 5};

2. 定义时部分初始化,未初始化的元素值为0

int a[5] = {1, 2}; // 只初始化前两个元素,后面的未初始化元素为0

3. 未初始化:只定义未赋值,需要单个元素赋值。(不然局部变量是随机值)

int a[5];
a[0] = 1;
a[1] = 2;
a[2] = 3;
a[3] = 4;
a[4] = 5;

注意定义时不能数组越界!!!

int a[3]={1,2,3,4}; //错误,数组越界!!!

2.3 定义空数组

int a[5]={0,0,0,0,0};
int a[5]={0};
int a[5]={};

2.4 数组引用

  • 先定义后引用
  • 每次只能引用一个数组元素a[i],如果想引用所有的元素循环遍历(挨个访问每个数组元素)。
  • 引用时不要数组越界,虽然有时候编译器不会报错。
  • 打印数组元素的地址用%p格式。
  • 例如:
    #include <stdio.h>

    int main(int argc[], char const *argv[])
    {
    int a[5] = {1, 2, 3, 4, 5};
    for(int i = 0; i < 5; i++)
    printf("%d\\n",a[i]);
    printf("%p %p\\n", a, &a[0]);
    // 这里的%p是为了验证看一下数组名==首元素地址
    return 0;
    }

    2.5 数组大小

    例如:
            int a[5];    //20
            double b[2];    //16
            char c[32];    //32

    计算方式:

            1. 数组元素个数 * 数据类型大小
                //  数组元素个数 = sizeof(数组名) / sizeof(数据类型)

            2. sizeof(数组名)

    可以用下面的程序自己运行试一下:

    #include <stdio.h>
    int main(int argc, char const *argv[])
    {
    int a[5];
    double b[2];
    char c[32];

    printf("%ld\\n", sizeof(a)); // 20
    printf("%ld\\n", sizeof(b)); // 16
    printf("%ld\\n", sizeof(c)); // 32

    int n = sizeof(a) / sizeof(int);
    printf("%d\\n", n); // 5

    return 0;
    }

    这个地方也有一个非常经典的题目:

            计算斐波那契数列前15项并逆序输出        // 1 1 2 3 5 8 13 21…

    #include <stdio.h>
    int main(int argc, char const *argv[])
    {
    int a[15] = {1, 1}; // 赋值前两个元素

    for (int i = 2; i < 15; i++) // 从第三个元素到最后一个元素遍历赋值
    a[i] = a[i – 1] + a[i – 2];

    for (int i = 14; i >= 0; i–) // 逆序打印
    printf("%d ", a[i]);

    printf("\\n");

    return 0;
    }

    2.6 清零函数

    2.6.1 bzero()函数

    #include <strings.h>
    void bzero(void *s, size_t n);
    功能:将内存空间设置为0
    参数:
    s:要清空的空间首地址
    n:字节大小
    返回值:无

    2.6.2 memset()函数

    #include <string.h>
    void *memset(void *s, int c, size_t n);
    功能:将指定内存空间内容设置为0
    参数:s:指定内存首地址
    c:要设置的数,一般为0
    n:要设置的内存大小
    返回值:指定内存首地址(一般不用)

    这两个函数暂时用不着但是也很重要在后面学习单片机的时候

    2.7 数组的内存分配

    int a[5];

    a是数组名,是数组的首地址,也就是第一个元素的地址即&a[0]

    a+1是第二个元素的地址,即&a[1]

    注意:这里的地址+1表示下一个元素的地址,而不是数值上+1

    int a[5]; 地址 地址
    a[0] &a[0] a
    a[1] &a[1] a+1
    a[2] &a[2] a+2
    a[3] &a[3] a+3
    a[4] &a[4] a+4

    可以用下面的代码看一下两者表示的地址是一样的

    #include <stdio.h>
    int main(int argc, char const *argv[])
    {
    int a[5];
    for(int i = 0; i < 5; i++)
    printf("%p %p\\n", &a[i], a + i);
    return 0;
    }

    3. 字符数组

    因为C语言没有字符串类型,所以可以用字符数组的形式表示字符串。

    类型为字符的数组就是字符数组,也就是说数组中每个元素是字符类型,可以表示字符串。

    3.1 定义时初始化表示形式

    1. 单个字符:

    char s1[5] = {'h','e','l','l','o'}; //sizeof(s1)=5
    char s2[] = {'h','e','l','l','o'}; //sizeof(s2)=5

    2. 用字符串表示,结尾会自动加上'\\0'

    char s1[]={"hello"}; //sizeof(s1)=6 因为结尾会自动添加'\\0'
    char s2[]="hello"; //sizeof(s2)=6
    char s3[6]={"hello"}; //sizeof(s3)=6
    char s4[6]="hello"; //sizeof(s4)=6
    char s5[]={}; //错误

    3.2 输入输出

    1. 用 scanf / printf

    char s[32] = "hello";
    printf("%s\\n", s); // 打印字符串
    scanf("%s", s); // 输入字符串,输入完会自动添加'\\0'作为字符串结尾的标志,
    // 但是该写法不能接受空格的输入(空格、回车、Tab都会当成输入标志)
    printf("%s\\n", s); // 打印到\\0就停了
    /*验证*/
    for (int i = 0; i < 32; i++)
    printf("%d: %c\\n", i, s[i]);

    由于scanf("%s\\n", s);不能输入空格,如果有空格,空格后面的内容就不会存入到数组

    如果需要有空格
    scanf("%[^\\n]",str);
    //正则表达式,可以输入除了\\n以外的字符,遇到\\n结束

    2. 用for循环遍历输入输出

    #include <stdio.h>

    int main(int argc, char const *argv[])
    {
    char str[6] = "";
    for (int i = 0; i < 6; i++)
    scanf("%c", &str[i]);
    for (int i = 0; i < 6; i++)
    printf("%c", str[i]);

    return 0;
    }

    例题:输入一个字符串,计算其空格个数

    #include <stdio.h>

    int main(int argc, char const *argv[])
    {
    int n = 0;
    char s[32] = "";
    scanf("%[^\\n]", s);
    // for (int i = 0; i < 32; i++)
    for (int i = 0; s[i] != '\\0'; i++)
    {
    if (s[i] == ' ')
    n++;
    }
    printf("%d\\n", n);

    return 0;
    }

    易错点:

    char s[10] = {}; //对的
    s[10] = "hello"; //错误,不能将字符串赋值给数组元素
    //做题非常易错
    s = "hello"; //错误,s是地址常量,不能为左值
    strcpy(s, "hello"); //正确,这是一个函数可以了解一下

    3.3 计算字符串长度

    3.3.1 用循环统计

    这个时候用while是更加方便的

    #include <stdio.h>

    int main(int argc, char const *argv[])
    {
    char str[32] = "hello world";
    int i = 0;
    while (str[i] != '\\0')
    {
    i++;
    }
    printf("%d\\n", i);

    return 0;
    }

    3.3.2 用strlen()

    #include <string.h>
    size_t strlen(const char *s);
    功能:计算字符串长度
    参数:字符串的首地址
    返回值:返回字符串实际长度,不包括‘\\0’在内。

    3.3.3 sizeof 和 strlen  的区别

  • sizeof是关键字,strlen是函数。
  • sizeof是计算数据所占空间大小,strlen只能计算字符串的实际长度。
  • 定义字符串数组同时赋值且省略元素个数时,sizeof计算字符串包含'\\0',strlen计算不包含'\\0',计算字符串长度,sizeof比strlen计算多1
  • 4. 排序(重中之重!!!)

    4.1 冒泡排序

    排序过程:

  • 比较第一个和第二个元素,如果第一个大于第二个a[0]>a[1]则交换,然后比较第二个和第三个数,前面的大就交换;依次类推,直到第n-1和第n个数比较完为止。第一趟冒泡排序完成,结果最大的数被放在最后一个元素位置上了。
  • 对前n-1个数进行第二趟冒泡排序,结果使次大的数放在第n-1个元素位置上了。
  • 重复上述过程,共经过n-1趟冒泡排序后,排序结束

    外层循环轮数i=个数-1 = N-1(0到<N-1)
    内存循环比较次数j = 个数-1-轮数(从0开始) = N-1-i
    #include <stdio.h>
    #define N 5

    int main(int argc, char const *argv[])
    {
    int a[N] = {5, 4, 3, 2, 1}, t;
    for (int i = 0; i < N – 1; i++) // 外层循环轮数
    {
    for (int j = 0; j < N – 1 – i; j++) // 内层循环比较次数
    {
    if (a[j] > a[j + 1]) // 比较
    {
    // 交换
    t = a[j];
    a[j] = a[j + 1];
    a[j + 1] = t;
    }
    }
    }
    for (int i = 0; i < N; i++)
    printf("%d ", a[i]);
    printf("\\n");

    return 0;
    }

  • 4.2 选择排序

    排序过程:

  • 首先通过n-1次比较,从n个数中找到最小的数,将它与第一个数交换,第一趟选择排序结果最小数被安置在第一个元素位置上。
  • 再通过n-2次比较,从剩余的n-1个数中找到次小的数,将它与第二个数交换,第二趟选择排序将次小的数安置在第二个元素位置上。
  • 重复上述过程,共经过n-1趟排序后,排序结束。
  • 轮数=个数-1=N-1(同冒泡)
    每轮比较的j: 从i+1开始到最后一个元素N-1
    #include <stdio.h>

    int main(int argc, char const *argv[])
    {
    int a[5] = {76, 56, 1, 45, 10};
    int min = 0, t;
    for (int i = 0; i < 4; i++) // 外层循环轮数(同冒泡)
    {
    min = i; // 拿着下标为i的元素开始比较
    for (int j = i + 1; j < 5; j++) // 从i的后一个元素开始一直比较到最后一个元素
    {
    if (a[j] < a[min]) // 如果有更小的数
    min = j;
    } // 循环结束后最小元素的下标就被min保存了

    // 交换最小的元素和前面的元素
    if (a[min] != a[i])
    {
    t = a[min];
    a[min] = a[i];
    a[i] = t;
    }
    }
    for (int i = 0; i < 5; i++)
    printf("%d ", a[i]);
    printf("\\n");

    return 0;
    }

    5. 二维数组

    二维数组下标为两个的数组,下标分别是行下标和列下标。

    5.1 定义格式

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

    5.2 访问元素

    数组名[行下标][列下标]; (下标都从0开始)

    a[0][0]; //第一行第一列

    a[n-1][m-1]; //第n行第m列元素

    注意:

  • 行下标和列下标都是从0开始,不能越界!!!
  • 定义同时初始化时行数可以省略,列数不能省略
  • 数组名代表数组首行地址
  • 5.3 二维数组大小

    元素个数 = 行数 * 列数

    二维数组大小:

  • 行数*列数*数据类型大小
  • sizeof(数组名)
  • 5.4 初始化二维数组

  • 定义时全部初始化
    int a[2][3] = {{1,2,3},{4,5,6}}; //按行赋值
    int a[2][3] = {1,2,3,4,5,6}; //顺序赋值
  • 定义时部分初始化,未初始化的元素值为0
    int a[2][3] = {{1,2},{4,5}}; //按行赋值
    int a[2][3] = {1,2,3,4,}; //顺序赋值
  • 先定义后初始化,未初始化,需要给每个元素单独赋值
  • 5.5 遍历二维数组

    循环嵌套来遍历二维数组,外层循环行数,内层循环列数。

    #include <stdio.h>

    int main(int argc, char const *argv[])
    {
    int a[2][3] = {{1, 2}, {4, 5}};
    for (int i = 0; i < 2; i++)
    {
    for (int j = 0; j < 3; j++)
    printf("%d ", a[i][j]);
    printf("\\n");
    }
    return 0;
    }

    5.6 内存分配

    int[2][3]为例
    行地址 列地址
    a[0][0] &a[0][0] *a a[0]
    a[0][1] &a[0][1] *a+1 a[0]+1
    a[0][2] &a[0][2] *a+2 a[0]+2
    a[1][0] &a[1][0] *(a+1) a[1]
    a[1][1] &a[1][1] *(a+1)+1 a[1]+1
    a[1][2] &a[1][2] *(a+1)+2 a[1]+2
    a[i][j] &a[i][j] *(a+i)+j a[i]+j
    行地址前面加*代表降级为列地址(该行第一列的地址)
    数组名[行下标]代表这一行第列的地址 

    1. 行地址

    二维数组中,数组名a表示第一行的地址,其数值和第一行第一列的地址即&a[0][0]一样,但是含义不一样。

    a+1表示第二行的地址,其数值和第二行第一列的地址即&a[1][0]一样,但是含义不一样。

    2. 列地址

    在二维数组中a[0]表示第一行第一列的地址,即等价于&a[0][0],或者在数组名前面加*表示降级为列地址,也就是*a表示第一行第一列的地址,那么第一行第二列的地址就是*a+1

    可以记住以下三个列地址的方法:&a[i][j]、a[i]+j、*(a+i)+j

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » C语言:数组
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!