数组入门

概念

  1. 逻辑: 一次性定义多个相同类型的变量,并且给他分配一片连续的内存

  2. 语法:int arr [5] ;

    C-array1

  3. 初始化

    只有在定义的时候赋值,才可以称为初始化。数组只有在初始化的时候才可以统一赋值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    int arr [5] = {1,2,3,4,5} ; // 正确,定义并初始化数组
    int arr [5] = {1,2,3} ; //可以, 为不完全初始化,后面的两个为0;若完全的不初始化则为随机值

    int arr [5] = {1,2,3,4,5,6,7,8,9} ; // 错误(但是可以用),越界初始化, 越界部分将会被编译器舍弃

    int arr [ ] = {1,2,3,4,5,6,7,8,9} ; // 可以, 用户没有直接给定数组大小,
    // 但是有初始化, 因此数组的大小会在初始化时确定, 大小为 9

    int arr [ ]; // 错误, 没有给定大小也没有初始化, 因此数组的内存大小无法确定,系统无法分配

    C-array2

    注意:

    数组在定义的时候必须确定他的大小。
    说白了就是中括号中 [] 必须有数组的大小,如果没有就必须初始化

    数组元素引用:

    • 存储模式:一片连续的内存,按照数据的类型进行分割成若干个大小相同的格子
    • 元素的下标与偏移量:以数组开头为基础的偏移的量(数据类型大小)

    C-array3

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    int arr [5] = {1,2,3,4,5} ; // 定义并初始化数组
    printf("arr[0]:%d\n" , arr[0]);
    arr[0] = 99 ; // 把数组的第1个元素( 偏移量为0 )修改为 99
    printf("arr[0]:%d\n" , arr[0]);
    arr[5] = 250 ; //"错误" 越界访问, 很有可能造成非法访问
    printf("arr[5]:%d\n" , arr[5]);

    arr = {9,8,7,6,5,4}; // 整体赋值只允许在初始化中
    arr = 100 ; // 错误

    printf("sizeof(arr):%ld\n" , sizeof(arr));
    int len = sizeof(arr) / sizeof(int) ; // 求数组元素的个数
    for (size_t i = 0; i < len ; i++)
    {
    printf("arr[%ld]:%d\n" , i , arr[i]);
    }

字符数组

  1. 概念: 专门用来存放字符类型数据的数组,称为字符数组

  2. 初始化+引用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    char ch1 [5] = {'H','e','l','l','o'} ; // 定义一个字符类型的数组并把'H','e','l','l','o' 一个一个存进去

    char ch2 [5] = {"Hello"} ; // 定义一个字符型的数组, 并把 "Hello" 字符串存放到数组中 ,因此该数组为字符串数组(后面会有一个空字符`\0`),ch2[0]为'H',ch2[5]为空字符'\0',在终端中看不到

    char ch3 [5] = "Hello" ; // 与ch2 一样, 大括号可以省略

    ch3[1] = 'E' ; // 可以, 把数组中第二个元素‘e’修改为‘E’
    ch3 = "hi" ; // 不可以, 只有在初始化的时候才能整体赋值

    printf("%s\n" , ch1); // 在访问ch1的时候并没有发现结束符,因此很有可能会把后面ch2的内容一并输出

    注意:ch1 为字符数组,它没有结束符,因此在打印输出的时候应该避免使用 %s 进行输出,有可能会造成越界访问。

思考

  1. 定义数组时没有初始化会怎么样?

    没有初始化的情况下内存的内容是随机值(内存中原有的内容)

    C-array4

  2. 不完全初始化又会怎么样?

    不完全初始化,已经初始化部分是已知值,其它则是0

  3. 补充

    注意:

    1
    2
    char buf[5]={1,2,3,4,5};    //把将1,2,3,4,5当作字符的ASCII值存放在数组中
    char buf[5]={'1','2','3','4','5'} //存放在内存中的是字符1,2,3,4,5所对应的ASCII值
点击此处展开/收起内容,来自 chatgpt 的解释
  1. char buf[5]={1,2,3,4,5};

    表示将整数值 1、2、3、4 和 5 直接存储到 buf 数组中。

    在 C 语言中,char 类型是一种整数类型,可以存储整数值。因此,整数值 1、2、3、4 和 5 会被直接存储到 buf 数组的连续内存位置中。

    需要注意的是,这些整数值并不是字符的 ASCII 码值。当您使用整数值初始化 char 数组时,编译器会将这些整数值直接存储到数组的内存位置中,而不会将它们解释为字符。
    如果您希望将整数值解释为对应的字符,可以使用类型转换或字符操作函数来实现。例如,可以使用 printf 函数将 buf 数组中的整数值作为字符进行打印:for (int i = 0; i < 5; i++) { printf("%c ", buf[i]); }

  2. char buf[5]={‘1’,‘2’,‘3’,‘4’,‘5’}

    表示将字符 ‘1’、‘2’、‘3’、‘4’ 和 ‘5’ 的 ASCII 码值存储到 buf 数组中。

    具体地说,字符 ‘1’ 的 ASCII 码值是 49,字符 ‘2’ 的 ASCII 码值是 50,字符 ‘3’ 的 ASCII 码值是 51,字符 ‘4’ 的 ASCII 码值是 52,字符 ‘5’ 的 ASCII 码值是 53。这些 ASCII 码值会被存储到 buf 数组的连续内存位置中。


数组进阶

数组名的含义

数组名在不同的场合下的含义不同:

  1. 表示整个数组的首地址

    • 在数组定义的时候
    • 在使用 sizeof 运算符中数组名表示整个数组的首地址(求得整个数组的大小)
    • 在取地址符 中 &arr , arr 表示整个数组
  2. 表示整个数组首元素的首地址

    • 其它情况
1
2
3
4
5
int arr[10] ; // arr 表示整个数组的首地址
int len = sizeof arr ; // arr 表示整个数组的首地址 , sizeof 运算符后的括号可以省略
int (* p) [10] = &arr; //arr 表示整个数组的首地址

int *p1 = arr ; // arr 表示数组的首元素的首地址

数组下标

数组的下标实际上只是编译器提供一种简写,实际上如下:

1
2
int a [100] ;
a[10] = 250 ; // ====> *(a+10) = 250 ;

通过加法交换律,有如下结果:

1
2
3
4
5
a[10] = 250 ;

*(a+10) = 250 ;
*(10+a) = 250 ;
10[a] = 250 ;

字符串常量

字符串常量是一个被存放在常量区的字符串,实际上也可称为一个匿名数组

匿名数组,同样满足数组名的含义。

1
2
3
4
5
6
7
char * msg1 = "Hello" ;// "Hello" 字符串常量首元素的首地址
char * msg2 = "Hello"+1 ;

printf("%s\n", "Hello" ) ;// "Hello" 字符串常量首元素的首地址
printf("%s\n", &"Hello" ) ; // "Hello" 字符串常量的整个数组的地址

printf("%c\n", "Hello"[4] ) ; // "Hello" 字符串常量首元素的首地址 [4],[4] 相当于+4个单位(char) 得到 ‘o’

字符串在内存种的存储方式:

C-array5

零长数组

概念: 数组的长度为0 , char arr [0] ;

用途:一般会放在结构体的末尾, 作为可变长度的入口。(数组是唯一一个允许越界访问的载体)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct node
{
int a;
char b ;
float c ;

...

int len ;
char arr[0] ;
}

struct node *p = malloc(sizeof(struct node) + 20 );// + 20 就是在原有的基础上增加20字节
p‐>len = 20 ; // 设置额外增长的长度为 20

C-array6

变长数组

概念: 使用一个变量作为数组的长度(元素的个数)。

重点: 变长数组并不是说在任意时候他的长度可以随意变化,实际上只是在定义之前数组的长度是未知的由一个变量来决定,但是定义语句过后变长数组的长度由定义那一刻变量的大小来决定。

1
2
3
4
5
6
int a = 200 ; // a 作为一个普通的变量 , 200 则可以作为arr 的长度
a = 99 ; // 99 可以作为 arr 的长度

int arr[a]; // a 当前是 99 , 因此数组arr 的长度已经确定为 99,之后该数组的长度不会再变换

a = 10 ; // a = 10 并不会影响上面数组的长度

注意:

  • 因为数组的长度未确定,因此它不允许初始化。
  • 在使用的时候可以通过该变长数组来有限的节省内存空间。

多维数组

  1. 概念:数组内部的成员也是数组

    int a [2][3] ;

    C-array7

  2. 定义与初始化

    1
    2
    int arr[2][3] = { {1,2,3} , { 4,5,6} };
    int arr1[2][3] = { 1,2,3,4,5,6};
  3. 如何引用

    1
    2
    arr[0] [0] = 100 ; // 数组:(通过下标来访问)    //arr[0]指{1,2,3}
    *(*(arr+0)+0) = 100 ; // 通过指针偏移来访问
  4. 实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int arr1[2][3] = {1, 2, 3, 4, 5, 6};

    int *p = arr; // p指向数组arr 的首元素,即{1, 2, 3}的地址

    for (int i = 0; i < 2; i++)
    {
    for (int j = 0; j < 3; j++)
    {
    printf("arr[%d][%d]:%d\t", i, j, arr[i][j]);
    }
    }

    printf("\n");

    for (int i = 0; i < 6; i++)
    { // *arr 得到元素{1, 2, 3}中的 1 的地址, + 1 则是加一个 int 类型

    printf("*(*(arr+%d)):%d\t", i, *(*(arr) + i));
    }
    printf("\n");

    for (int i = 0; i < 6; i++)
    { // arr 指的是首元素的首地址 {1,2,3} 的首地址 + 1则 + 3个整型

    printf("*(*(arr+%d)):%d\t", i, *(*(arr + i)));
    }

    for (int i = 0; i < 6; i++)
    { // p 只是一个普通的整型指针, 与二维数组没有任何的关系

    printf("*(p+%d):%d\n" , i ,*(p+i));
    }