超详细最全C语言个人笔记【第三章节-数组】

掌握数组需求:

1.数组的定义和数组的初始化
2.数组下标的使用
3.数组名的含义

在这里插入图片描述

基本概念

  • 数组的概念

    • 由相同类型的多个元素所组成的一种复合数据类型
    • 在工程中同时定义多个相同类型的变量时,重复定义,可以使用数组
  • 逻辑:一次性定义多个相同的变量,并存储到一片连续的内存中

  • 格式

    • 类型说明符 数组名[整型常量表达式];
    • 类型说明符:指定数组元素的数据类型,任意c语言合法类型都可以
    • 数组名 : c语言标识符,其值为该数组的首地址(常量)
    #include <stdio.h>
    
    int main(int argc, char const *argv[])
    {
        char a[5];
        printf("a size is %d\n",sizeof(a));
    
        // 输出a的地址,数组名相当于这个数组的首元素地址
        printf("a addr : %p\n",a);
        // 输出a的首元素地址
        printf("a[0] addr : %p\n",&a[0]);
        printf("a[1] addr : %p\n",&a[1]);
    	// 数组的下标地址与首地址的偏移量有关,与数组的大小无关
        // 比如,int a[3] = a的基地址+偏移量2
        printf("a[2] addr : %p,%p,%p\n",&a[2],a+2,&a[0]+2);
       
        // 输出变量b的地址
        int b = 10;
        printf("%p\n",&b);
        return 0;
    }
    
    • 整型常量表达式:指定数组元素的个数
  • 示例:

int a[5];//定义一个数组a,该数组一个由五个元素,每个元素都是int类型

int size = 4int array[size];//变长数组,如果式局部变量则正确,全局变量编译出错
  • 语法释义:
    • a是数组名,即这片连续内存的名称
    • [5]代表这片连续内存总共分成5个相等的格子,每个格子称为数组的元素
    • int代表每个元素的类型,可以是任意基本类型,也可以是组合类型,甚至可以是数组
    • 数组中所有元素的类型都是一致
    • 数组申请的空间是连续的,从低地址到高地址依次连续存放数组中的每个元素
  • 数组定义
#include <stdio.h>

int main(int argc, char const *argv[])
{
    // 申请5块连续的空间,将a称为数组
    int a[5];
    // 将这5块空间逐一赋值,注意,数组下标从0开始
    a[0] = 1;
    a[1] = 20;
    a[2] = 30;
    a[3] = 40;
    a[4] = 50;
    //a[5] = 60; // 越界,无法使用
    printf("%d\n",a[4]);

    // 循环给数组a赋值
    for(int data = 10,i = 0; i < 5; i++,data+=10)
    {
        a[i] = data;
    }

    // 遍历输出
    for(int i = 0; i < 5; i++)
    {
        printf("%d\n",a[i]);
    }
    return 0;
}

  • 初始化:在定义的时候赋值,称为初始化
// 正常初始化
int a[5] = {100,200,300,400,500};

int a[5] = {100,200,300,400,500,600}; // 错误,越界了
int a[ ] = {100,200,300}; // OK,自动根据初始化列表分配数组元素个数
int a[5] = {100,200,300}; // OK,只初始化数组元素的一部分
// 不能在使用变长数组的情况下初始化数组
int a[size] = {1,2,3};//编译出错
// 变长数组只能先定义再使用
int a[size]; // 正确的
a[0] = 10;

在这里插入图片描述

​ 数组的真实存储

#include <stdio.h>

int main(int argc, char const *argv[])
{
    // 定义数组并初始化
    int Array[5] = {10,25,31,48,60};
    printf("%d\n",Array[2]);

    // 初始化的时候确定数组空间大小
    int Array0[] = {10,20,39};
    printf("%d\n",Array0[2]);
    
    // 初始化一部分空间,剩余的空间默认初始化为0
    int Array1[10] = {68,70};

    // 定义数组并清空数组
    int Array2[10] = {0};

    // 变长数组
    int len = 3;
    // 错误,初始化的时候,数组大小必须为常量
    //int Array3[len] = {100,200,300}; 
    // 哪怕不确定len的大小,但是可以确定Array3的空间最小值为一个
    // int类型的空间,所以至少可以存放一个int类型的数据
    int Array3[len];
    Array3[0] = 100;
    printf("Array3[0] = %d\n",Array3[0]);
    return 0;
}
  • 如果是定义的时候初始化,但是初始化列表个数小于数组的大小,那么无论这个数组是全局变量还是局部变量,没有赋值的成员都是0
int array[3] = {100,200,300,400};// 错误,越界访问没有申请到的空间
int array[3];// 局部变量为随机数,全局变量为0
int array[3] = {0};//清空数组
测量数组的总大小:sizeof(array)
测量数组元素个数:sizeof(array)/sizeof(array[0])
#include <stdio.h>

int main(int argc, char const *argv[])
{
    // 根据初始化的时候分配空间从而确定数组的大小
    int Array[] = {10,20};
    printf("%d\n",sizeof(Array));

    // 计算数组的元素个数
    int count = sizeof(Array) / sizeof(Array[0]);
    printf("数组空间个数: %d\n",count);
    return 0;
}

数组元素的引用

  • 存储模式:一片连续的内存,按数据类型分割成若干相同大小的格子

  • 元素下标:数组开头位置的偏移量

    在这里插入图片描述

    ​ 元素下标偏移量

数组名[下标]
"下标":C语言的下标是从0开始,下标必须是>=0的整数
a[0]、a[1]、a[n]

引用数组元素a[i]和普通变量一样,既可以作为左值,也可以作为右值
下标最小值为0,最大值为 元素个数 -1
  • 示例
int a[5]; // 有效的下标范围是 0 ~ 4
a[0] = 1;
a[1] = 66;
a[2] = 21;
a[3] = 4;
a[4] = 934;

a[5] = 62; // 错误,越界了
a    = 10; // 错误,不可对数组名赋值 
a是数组的名字,代表最大元素类型的首地址

在这里插入图片描述

#include <stdio.h>

int main()
{
	int size = 4;
	//1)多个元素具有相同的数据类型,可以用一个集合来表示,数组
	//int val1,val2,val3,val4;
	//int arr[size];
	//2)数组的初始化
	//int arr[4] = {10,20,30,40};
	//3)清空数组
	int arr[4] = {0}; //数组中 arr[0] == 0,后面没有赋值的默认都是0
	//数组元素的引用,通过数组的名字 + 下标   arr[0]  下标一定是大于等于0,下标最大值等于 元素的个数 -1
	//arr[0] ---》val1
	//arr[1] ---》val2
	//arr[2] ---》val3
	//arr[3] ---》val4
	//arr[4] --内存访问越界,此时程序不会给你报错
	for(int i=0; i<4; i++)
	{
		printf("arr[%d] addr:%p value:%d\n",i,&arr[i],arr[i]); //arr[0]
	}
	printf("&arr[0] :%p\n",&arr[0]);
	printf("&arr[0]+1 :%p\n",&arr[0]+1);
    return 0 ;
}

在这里插入图片描述

#include <stdio.h>

int main(int argc, char const *argv[])
{
    char ch[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};

    int index[16];
    int data,count = 0;
    printf("请输入十进制数: ");
    scanf("%d",&data);

    while (1)
    {
        if(data == 0)
            break;
        index[count++] = data % 16;
        data /= 16;
        
    }
    
    for(int i  = count-1; i >= 0; i--)
    {
        printf("%c",ch[index[i]]);
    }
    printf("\n");
    return 0;
}

字符数组

  • 概念:专门存放字符的数组,称为字符数组
  • 初始化与元素引用:
char s1[5] = {'a', 'b', 'c', 'd', 'e'};       // s1存放的是字符序列,非字符串
char s2[6] = {'a', 'b', 'c', 'd', 'e', '\0'}; // s2存放了一个字符串

char s[6] = {"abcde"}; // 使用字符串直接初始化字符数组
char s[6] =  "abcde" ; // 大括号可以省略

s[0] = 'A'; // 索引第一个元素,赋值为 'A'
#include <stdio.h>

int main(int argc, char const *argv[])
{
    char Array[6] = {'D','A','V','I','D','\0'};
    printf("%c\n",Array[3]); // 'k'
    for(int i = 0; i < 6; i++)
    {
        printf("%c",Array[i]);
    }
    printf("\n");
    // 输出字符串用%s
    printf("%s\n",&Array[0]);
    printf("%s\n",Array);

    char Array1[6] = {"DAVID"};// ""表示字符串,字符串末尾有\0 
    printf("%s\n",Array1);

    // 最常用的方法
    char Array2[6] = "DAVID";
    printf("%s\n",Array2);

    return 0;
}

练习1: 定义一个char str[6] = {‘h’,‘e’,‘l’,‘l’,‘o’}; //把这个数组的内存图画出来,并且把数组的名字的地址,还有每个元素的地址都打印出来,每个元素的地址相差几个字节?

在这里插入图片描述

#include <stdio.h>

int main(int argc, char const *argv[])
{
    char str[6] = {'h','e','l','l','o'};

    printf("%p\n",str);
    // []表示对地址的解引用,str[0] 等价于 (*(str+0))
    printf("%p,%p,%p\n",&str[0],&(*(str+0)),str+0);
    printf("%p,%p,%p,%p\n",&str[0]+1,&str[1],&(*(str+1)),str+1);
    printf("%p,%p,%p,%p\n",&str[0]+2,&str[2],&(*(str+2)),str+2);
    printf("%p,%p,%p,%p\n",&str[0]+3,&str[3],&(*(str+3)),str+3);

    return 0;
}

练习2:定义一个整型数组,遍历数组中的每个元素,并把数组中的每个元素的地址都打印出来

#include <stdio.h>

int main(int argc, char const *argv[])
{
    //char str[6] = {'h','e','l','l','o'};
    int str[6] = {1,2,3,4,5};
    printf("%p\n",str);
    // []表示对地址的解引用,str[0] 等价于 (*(str+0))
    printf("%p,%p,%p\n",&str[0],&(*(str+0)),str+0);
    printf("%p,%p,%p,%p\n",&str[0]+1,&str[1],&(*(str+1)),str+1);
    printf("%p,%p,%p,%p\n",&str[0]+2,&str[2],&(*(str+2)),str+2);
    printf("%p,%p,%p,%p\n",&str[0]+3,&str[3],&(*(str+3)),str+3);

    return 0;
}

**总结:**地址偏移+1是偏移一个对象类型的地址,比如char类型的地址+1是偏移1字节,int类型的地址+1是偏移4字节,依次类推。

数组偏移量
int Array[6] = {10,20,30,40,50,60};
// 数组地址偏移量
// 000000000061FE04,000000000061FE04
printf("%p,%p",&Array[1],&Array[0]+1);

在这里插入图片描述

数组元素地址解引用
通过对数组元素地址解引用,可以获取地址空间里面的数据
    int a = 10;
    printf("%d\n",a);

    printf("%p\n",&a);
    // * 表示将地址里面的内容取出,我们把它称为解引用
    printf("%d\n",*(&a));

    //--------------------------
    char Array[5] = {'j','a','c','k'};
    printf("%c\n",Array[0]);
    printf("%c,%c\n",*(Array+0),*(&Array[0]));
    printf("%c,%c\n",*(Array+1),*(&Array[1]));
    printf("%c,%c\n",*(Array+2),*(&Array[2]));
    printf("%c,%c\n",*(Array+3),*(&Array[3]));
    

在这里插入图片描述

练习2:定义一个整型数组,存储从键盘上获取的多个整型数据(比如输入 10 40 23 13),输入-1输入完成,并打印出这一排数据的最小值和最大值

#include <stdio.h>

int main(int argc, char const *argv[])
{
    int array[100] = {0};
    int i = 0;
    while(1)
    {
        scanf("%d",&array[i]);
        if(array[i] == -1)
            break;

        i++;
    }
    // 将-1剔除并且缩小数组空间
    array[i--] = 0;

    // 假设array[0]为最小值
    int min_value = array[0];
    // 假设array[0]为最大值
    int max_value = array[0];

    for(int j = 1; j <= i; j++)
    {
        // 如果最小值大于数组的值则更新最小值
        if(min_value > array[j])
            min_value = array[j];
        // 如果最大值大于数组的值则更新最大值
        if(max_value < array[j])
            max_value = array[j];
    }

    printf("min value : %d, max value : %d\n",min_value, max_value);

    return 0;
}

字符串常量

  • 字符串常量在内存中的存储,实质是一个匿名数组
  • 匿名数组,同样满足数组两种涵义的规定
  • 示例:
printf("%s\n","hello");
printf("'h' addr:%p\n","hello");
printf("'e' addr:%p\n","hello" + 1); //地址+1

printf("'o' addr:%p value:%c\n","hello" + 4,*("hello" + 4)); //地址+1
printf("%d\n", sizeof("abcd")); // 此处 "abcd" 代表整个数组
printf("%p\n", &"abcd");        // 此处 "abcd" 代表整个数组

printf("%c\n", "abcd"[1]); // 此处 "abcd" 代表匿名数组的首元素地址

在这里插入图片描述

在这里插入图片描述

#include <stdio.h>

int main(int argc, char const *argv[])
{
    printf("%s\n","abc");
    printf("%c,%c\n","abc"[0],*("abc"+0));
    printf("%c,%c\n","abc"[1],*("abc"+1));
    printf("%c,%c\n","abc"[2],*("abc"+2));
    printf("%d,%d\n","abc"[3],*("abc"+3)); // '\0'
    return 0;
}

多维数组

  • 概念:若数组元素类型也是数组,则该数组称为多维数组,就是一维数组的集合
  • 示例:
  • 在这里插入图片描述
int a1[3];
int a2[3];

int a[2][3];//a[0]-->a1 a[1]-->a2

第一种解释:
	定义一个二维数组。该数组是由2个一维数组组成,分别是a[0] a[1]
	每个一维数组由3个元素组成,所以二维数组有6个元素
   数据类型 二维数组的名字[有多少个一维数组][每个一维数组有多少个元素]
	// 代码释义:
	// 1, a[2]   是数组的定义,表示该数组拥有两个元素
	// 2, int [3]是元素的类型,表示该数组元素是一个具有三个元素的整型数组

第二种解释:
	该数组一共有2行,每行由3个元素组成(23)
    数据类型 二维数组名[][]
    所谓的行:表示这个二维数组一共有多少个一维数组
    所谓的列:表示这个二维数组每个一维数组有多少个元素

在这里插入图片描述

  • 二维数组在内存中的存放

    • 按行存放,先放第一行,再放第二行…

    在这里插入图片描述

  • 多维数组的语法跟普通的一维数组语法完全一致

  • 初始化:

int a[2][3] = {{1,2,3}, {4,5,6}}; // 数组的元素是另一个数组

int a[2][3] = {{1,2,3}, {4,5,6}, {7,8,9}}; // 错误,越界了
int a[2][3] = {{1,2,3}, {4,5,6,7}};        // 错误,越界了

int a[ ][3] = {{1,2,3}, {4,5,6}}; // OK,自动根据初始化列表分配数组元素个数
int a[2][3] = {{1,2,3}};          // OK,只初始化数组元素的一部分
  • 元素引用:
// a[0] 代表第一个元素,这个元素是一个具有 3 个元素的数组:{1,2,3}
// a[1] 代表第二个元素,这个元素也是一个具有 3 个元素的数组:{4,5,6}

printf("%d", a[0][0]); // 输出第一个数组的第一个元素,即1
printf("%d", a[1][2]); // 输出第二个数组的第三个元素,即6

在这里插入图片描述

//1、定义
//int arr[3][4];
//2、定义的时候初始化
//1)分行给二维数组初始化
/* int arr[3][4] = { {10,20,30,33},
	             {40,50,60,66},
	             {70,80,90,99}}; */
//2)将所有的数据全部写在一个大括号里面,按照数组的排列顺序进行赋值
//int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};	
//int arr[3][4] = {{1,2,3,4},{5},6,7};	

//3)清空数组
//int arr[3][4] = {0};
//4)如果对全部元素进行赋值,则定义数组的时候可以省略第一维的长度
//int arr[][4] = {1,2,3,4,5,6,7,8,9,10,11,12};	
//int arr[][] = {1,2,3,4,5,6,7,8,9,10,11,12}; 错误
//int arr[3][] = {1,2,3,4,5,6,7,8,9,10,11,12};	 错误
二维数组解引用
// 二维数组初始化字符串
char buf1[2][5] = {"jack","rose"};
printf("%s,%s\n",&buf1[0][0],buf[0]);
printf("%s,%s\n",&buf1[1][0],buf[1]);

// 取二维数组中的某个字符
printf("%c,%c,%c,%c\n",buf1[0][1],*(&buf1[0][1]),*(buf1[0]+1),*(*(buf1+0)+1));
printf("%c,%c,%c,%c\n",buf1[1][2],*(&buf1[1][2]),*(buf1[1]+2),*(*(buf1+1)+2));

在这里插入图片描述

数组地址偏移

练习1 : 定义二维数组,int buf[2] [3];将二维数组中每个元素的值和地址都打印出来

#include <stdio.h>

int main(int argc, char const *argv[])
{
    int buf[2][3] = {{1,2,3},{7,8,9}};
    printf("%d\n",buf[0][0]);
    printf("%d\n",buf[0][1]);
    printf("%d\n",buf[0][2]);
    printf("%d\n",buf[1][0]);
    printf("%d\n",buf[1][1]);
    printf("%d\n",buf[1][2]);

    // {1,2,3}的数组整体地址,和{7,8,9}数组的整体地址
    printf("%p,%p\n",buf,buf+1);
    
    // 1的地址和2的地址
    printf("%p,%p\n",*(buf+0),((*(buf+0))+1));
    printf("%d,%d\n",*(*(buf+0)),*((*(buf+0))+1));
    // 7的地址和8的地址
    printf("%p,%p\n",*(buf+1),((*(buf+1))+1));
    printf("%d,%d\n",*(*(buf+1)),*((*(buf+1))+1));
    return 0;
}

在这里插入图片描述

练习2 : 定义一个3行3列的二维数组,并初始化,输出该二维数组的和、最小值的下标(该最小值在第几行第几列)

#include <stdio.h>

int main(int argc, char const *argv[])
{
    int array[3][3] = {{10,20,30},{5,8,3},{6,15,28}};
    int sum = 0; // 和
    int min_row = 0; // 行
    int min_col = 0; // 列
    // 假设首元素为最小值
    int min_value = array[0][0]; // 最小值

    for(int i = 0; i < 3; i++) // 行
    {
        for(int j = 0; j < 3; j++) // 列
        {
            // 计算总数
            sum += array[i][j];
            // 找最小值
            if(min_value > array[i][j])
            {
                min_value = array[i][j];
                min_row = i;
                min_col = j;
            }
        }
    }
    printf("sum : %d, min_value[%d][%d]=%d\n",
            sum,min_row,min_col,min_value);
    return 0;
}

  • 二维数组的名字其实也是地址
#include <stdio.h>

int main(int argc, char const *argv[])
{
    int arr[2][3] = {{10,20,30},{40,50,60}};
   
    for(int i = 0; i < 2; i++)// 表示有多少个一维数组
    {
        for(int j = 0; j < 3; j++)// 表示每个一维数组有多少个元素
        {
            printf("arr[%d][%d] arr:%p \t",i,j,&arr[i][j]);
        }
        printf("\n");
    }
    printf("\n");

    printf("arr数组名: %p\n",arr);
    printf("arr+1: %p\n",arr+1);

    printf("&arr[0][0]+4:%p\n",&arr[0][0]+4);
	printf("arr[0]+1:%p\n",arr[0]+1);
    printf("&arr[0]+1:%p\n",&arr[0]+1);
	printf("arr[0]+5:%p\n",arr[0]+5);
	
	/**************************/
	printf("**************************\n");
	
	printf("&arr:%p\n",&arr);
	printf("&arr+1:%p\n",&arr+1);
    
    return 0;
}

练习3:

char str[3][5] = {  {'h','e','a','l','o'},
                    {'w','o','r','c','d'},
                    {'h','d','g','b','k'}};

画出 该二维数组的内存图 , 数组的名字代表的地址范围 是哪个,还有 &str[1] [2] +2 代表的地址范围

str[2] +2 代表的地址范围 &str代表的地址范围 &str + 1代表的地址范围

#include <stdio.h>

int main(int argc, char const *argv[])
{
    char str[3][5] = {  {'h','e','a','l','o'},
                    {'w','o','r','c','d'},
                    {'h','d','g','b','k'}};

    // d的地址
    printf("%c\n",*(&str[1][2]+2));
    // g的地址
    printf("%c\n",*(str[2]+2));

    // 越界了,段错误
    // &str 是整个二维数组的地址,+ 1是整个数组偏移一个单位,越界了
    printf("%c\n",*(*(*(&str + 1)));

    return 0;
}

数组万能拆解法

  • 任意的数组,不管有多复杂,其定义都由两部分组成。
    • 第1部分:说明元素的类型,可以是任意的类型(除了函数)
    • 第2部分:说明数组名和元素个数

在这里插入图片描述

  • 示例:
int   a[4];       // 第2部分:a[4]; 第1部分:int
int   b[3][4];    // 第2部分:b[3]; 第1部分:int [4]
int   c[2][3][4]; // 第2部分:c[2]; 第1部分:int [3][4]
int  *d[6];       // 第2部分:d[6]; 第1部分:int *
int (*e[7])(int, float); // 第2部分:e[7]; 第1部分:int (*)(int, float)
  • 注解:
    • 上述示例中,a[4]、b[3]、c[2]、d[6]、e[7]本质上并无区别,它们均是数组
    • 上述示例中,a[4]、b[3]、c[2]、d[6]、e[7]唯一的不同,是它们所存放的元素的不同
    • 第1部分的声明语句,如果由多个单词组成,C语言规定需要将其拆散写到第2部分的两边

搞个题目同志们可以练练手:**

1、定义一个二维数组,存入 5 个学生的数学、语文、英语、物理、化学 5 门课程的成绩,计算并输出每一门课程的平均成绩和每一位学生的平均成绩。

在这里插入图片描述

同志们可以挑战一些自己 扩展:

2、设计一个程序,实现 学生信息录入系统(姓名、年龄、分数)

​ 1、可以制作一个简单的登录系统

​ 账号、密码的验证 (其实就是字符串的比较 strcmp)

​ 2、录入信息之后,可以查询

#include <stdio.h>

int main(int argc, char const *argv[])
{
    float student_score[5][5] = {{60,70,80,90,100},
                               {60,70,80,90,100},
                               {60,70,80,90,100},
                               {60,70,80,90,100},    
                               {60,70,80,90,100}};  
    
    // 学生总成绩
    float sum = 0.0;
    // 平均分
    float ave = 0.0; 
    printf("序号\t数学\t语文\t英语\t物理\t化学\t平均分\n");

    for(int i = 0; i < 5; i++)
    {
        // 先输出序号
        printf("%d\t",i+1);
        // 每位学生学科计数完成重置
        sum = 0.0;
        for(int j = 0; j < 5; j++)
        {
            printf("%.2f\t",student_score[i][j]);
            sum += student_score[i][j];
        }
        // 平均分
        ave = sum/5;
        printf("%.2f",ave);
        printf("\n");
    }

    // 单科平均分
    printf("平均分: ");
    ave = 0;
    for(int i = 0; i < 5; i++)
    {   
        sum = 0;
        for(int j = 0; j < 5; j++)
        {
            sum += student_score[j][i];
        }
        ave = sum / 5;
        printf("%.2f\t",ave);
    }                             
    printf("\n");

    return 0;
}

在这里插入图片描述

拓展:


  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值