C语言初阶最详细的数组知识总结,超多干货!!(带三大应用实例)

Posted  Do

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言初阶最详细的数组知识总结,超多干货!!(带三大应用实例)相关的知识,希望对你有一定的参考价值。

目录

 

数组

一维数组的创建和初始化

数组的创建

数组的初始化

一维数组的使用

一维数组在内存中的存储

一维数组的指针访问

二维数组的创建和初始化

二维数组的创建

二维数组的初始化

二维数组的使用

二维数组在内存中的存储

二维数组的指针访问

有关数组的运算

数组作为函数参数

数组的应用实例1:冒泡排序

数组的应用实例2:三子棋

数组的应用实例3:扫雷游戏

总结


数组

本篇文章干货满满,细节超多,堪称精华版,博主花了很长时间给大家整理和搜集的,绝对有你想知道又不了解的知识!!赶紧来看看吧,看完别忘记一键三连哦

一维数组的创建和初始化

数组的创建

数组是一组相同类型元素的集合。 数组的创建方式:

type_t   arr_name   [const_n];
//type_t 是指数组的元素类型
//const_n 是一个常量表达式,用来指定数组的大小

数组创建的实例:

//代码1
int arr1[10];
//代码2
int count = 10;
int arr2[count];//数组可以正常创建?
//代码3
char arr3[10];
float arr4[1]

注:上面代码2块是不行的,因为数组中[ ]应是常量,而不是变量。

数组的初始化

数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。 看代码:

int arr1[10] = {1,2,3};
int arr2[] = {1,2,3,4};
int arr3[5] = {1,2,3,4,5};
char arr4[3] = {'a',98, 'c'};
char arr5[] = {'a','b','c'};//等价于char arr5[3]={'a','b','c'}
char arr6[] = "abcdef";

数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。

但是对于下面的代码要区分内存中如何分配,并且要知道字符串长度和数组大小分别是多少:


char arr1[3] = {'a','b','c'};//三个字符,'a','b','c'


char arr2[5]="abc";//5个字符:a b c 0 0

char arr3[] = "abc";//四个字符:a b c \\0
char arr4[]={'a','b','c'};//三个字符:a b c

请看结果:

由此结果我们可以得出一些结论:

  • 没有声明数组大小和声明数组大小,内存存储空间是完全不一样的,打印出来的结果也完全不同,字符串数组本身存在‘\\0’,所以即使没有声明数组大小也可以成功打印出来,但是对于字符数组而言,如果没有声明数组大小,并且字符中没有‘\\0’时,屏幕会回显除字符以外的随机字符,因为'\\0'始终是字符串的结束标志。
  • strlen是求字符串长度的,求长度时'\\0'只是内容,但是不包括在内,所以arr3的有效长度为3,;但是对于arr4字符数组来说,即没有声明数组大小,也没有'\\0'作为结束标志,所以arr4的长度也是随机的。
  • sizeof是求数组大小的,求大小时'\\0'包括在内,所以arr3的大小为4;而arr4的大小就是数组的大小而不是字符串大小,所以为3。

一维数组的使用

对于数组的使用我们之前介绍了一个操作符: [ ] ,下标引用操作符。它其实就数组访问的操作符。 我们来看代码:

#include <stdio.h>
int main()
{
 int arr[10] = {0};//数组的不完全初始化
    //计算数组的元素个数
    int sz = sizeof(arr)/sizeof(arr[0]);
 //对数组内容赋值,数组是使用下标来访问的,下标从0开始。所以:
 int i = 0;//做下标
 for(i=0; i<10; i++)
 {
 arr[i] = i;
 } 
 //输出数组的内容
 for(i=0; i<10; ++i)
 {
 printf("%d ", arr[i]);
 }
 return 0;
}

总结:

1. 数组是使用下标来访问的,下标是从0开始。

2. 数组的大小可以通过计算得到。

一维数组在内存中的存储

对于知道数组在内存中是怎么存储的,这能很好的帮助我们理解数组以及指针,下面通过代码来解释:
 

#include <stdio.h>
int main()
{
 int arr[10] = {0};
 int i = 0;
 for(i=0; i<sizeof(arr)/sizeof(arr[0]); ++i)
 {
 printf("&arr[%d] = %p\\n", i, &arr[i]);
 }
 return 0;
}

揭晓打印结果:

结论:

整型数组在内存中是四个字节。

数组在内存中是连续存放的,随着数组下标的增长,元素的地址由低到高变化。

一维数组的指针访问

代码示例1:

#include<stdio.h>

int main()
{
	int arr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	printf("%p\\n", arr);
	printf("%d\\n", *arr);

	return 0; 
}

结论:

这里的*arr为解引用操作,*arr为首元素。

数组名其实存放的就是首元素的地址

代码示例2:

#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *p);
		p++;
	}
	return 0;


}

结论:

我们定义了一个指针p,指向arr,然后我们通过指针来访问数组。

p++为指针的算法,因为是整型指针,所以p++是跳过四个字节,到下一个数字。


二维数组的创建和初始化

二维数组的创建

//数组创建
int arr[3][4];
char arr[3][5];
double arr[2][4];

二维数组的初始化

//数组初始化
int arr[3][4] = {1,2,3,4};
int arr[3][4] = {{1,2},{4,5}};
int arr[][4] = {{2,3},{4,5}};

注:使用二维数组时,行下标可以省但是列下标不可以省。

二维数组的使用

二维数组的使用和一维数组一样也是通过下标的方式。 看代码:

#include <stdio.h>
int main()
{
	int arr[3][4] = { 0 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 4; j++)
		{
			arr[i][j] = i * 4 + j;
		}
	}
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		
		for (j = 0; j < 4; j++)
			{
				printf("%d ", arr[i][j]);
			}
	}
	return 0;
}

运行结果:

二维数组在内存中的存储

像一维数组一样,这里我们尝试打印二维数组的每个元素:

#include <stdio.h>
int main()
{
 int arr[3][4];
 int i = 0;
 for(i=0; i<3; i++)
 {
 int j = 0;
 for(j=0; j<4; j++)
 {
 printf("&arr[%d][%d] = %p\\n", i, j,&arr[i][j]);
 }
 }
 return 0;
}

内存展示:

结论:

其实二维数组在内存中也是连续存储的。

随着数组下标的增长,元素的地址也是由低到高变化。

二维数组的指针访问

我们知道了一维数组的内存存储模式之后,我们尝试使用指针对一维数组进行访问:


#include<stdio.h>

int main()
{
	int arr[3][5] = { 0 };
	int *p = &arr[0][0];
	int i = 0;
	int j = 0;
	for (i = 0; i < 15; i++)
	{
		*(p + i) = i + 1;
	}
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
	}

	return 0;
}

 

当然指针也是有类型的:

#include<stdio.h>

int main()
{
	int num = 0x11223344;
	int *p = &num;
	*p = 0;

	return 0;
}

这里的指针p是int型,所以我们对它的改变,是改变4个字节的大小,同样的,如果我们对p进行加1操作,指针向后跳4个字节。

#include<stdio.h>

int main()
{
	int num = 0x11223344;
	//int *p = &num;
	//*p = 0;
	char *pc = &num;
	*pc = 0;

	return 0;
}

我们可以从内存中观察到,只改变了1个字节的大小,因为pc指针的类型是char。如果我们对pc进行加1操作,指针向后跳1个字节。

#include<stdio.h>

int main()
{
	int arr[10] = { 0 };
	printf("%p\\n", &arr[0]);
	printf("%p\\n", &arr[0] + 1);
	printf("---------------\\n");
	printf("%p\\n", arr);
	printf("%p\\n", arr + 1);
	printf("---------------\\n");
	printf("%p\\n", &arr);
	printf("%p\\n", &arr + 1);

	return 0;
}

我们可以从输出结果看出&arr[0],arr,&arr它们的地址虽然相同,但它们的意义是不同的,&arr,取的是数组的地址,对它进行加1,就相当于跳过整个数组,又因为数组是int型,总共有10个元素,所以跳过的字节大小是40。

有关数组的运算

关于数组我们必须要学会有关数组的一些运算:

#include<stdio.h>

int main()
{
	//一维数组
	int a[] = { 1, 2, 3, 4 };
	printf("%d\\n", sizeof(a));//16  
	//1.数组名单独放在sizeof内部,数组名表示整个数组,所以sizeof(数组名)计算的是是数组总大小,单位是字节
	//2.&数组名,数组名表示整个数组,所以&数组名取出的是整个数组的地址
	//3.除此之外,所有的数组名都表示首元素的地址
	printf("%d\\n", sizeof(a + 0));//4    a代表首元素地址,a+i代表第i个元素的地址,在32位平台下所有的地址的大小都是4个字节
	printf("%d\\n", sizeof(*a));//4       a是首元素地址,*a是首元素--1,int型占4个字节大小
	printf("%d\\n", sizeof(a + 1));//4    a是首元素地址,a+1是第二个元素的地址,它还是一个地址
	printf("%d\\n", sizeof(a[1]));//4     a[1]--第二个元素
	printf("%d\\n", sizeof(&a));//4       &a虽然取出的是整个数组的地址,但它还是一个地址
	printf("%d\\n", sizeof(*&a));//16     &a取出的是整个数组的地址,对它进行解引用,就是这个数组,这个数字的大小就是16
	printf("%d\\n", sizeof(&a + 1));//4   &a取出的是整个数组的地址,加1跳过了整个数组(16个字节),但它还是一个地址
	printf("%d\\n", sizeof(&a[0]));//4    &a[0]取的是第一个元素的地址
	printf("%d\\n", sizeof(&a[0] + 1));//4   &a[0] + 1取的是第二个元素的地址

	return 0;
}


 

#include<stdio.h>

int main()
{
	//字符数组
	char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' };
	printf("%d\\n", sizeof(arr));//6
	printf("%d\\n", sizeof(arr + 0));//4        首元素地址
	printf("%d\\n", sizeof(*arr));//1           首元素地址解引用是首元素(a),char类型占1个字节
	printf("%d\\n", sizeof(arr[1]));//1         首元素
	printf("%d\\n", sizeof(&arr));//4           数组的地址
	printf("%d\\n", sizeof(&arr + 1));//4       下一个数组的地址,跳过了f
	printf("%d\\n", sizeof(&arr[0] + 1));//4    第二个元素的地址

    printf("%d\\n", strlen(arr));//随机值       strlen()求的是字符串长度,以'\\0'为结束标志,这里并没有'\\0',所以会一直往后数
	printf("%d\\n", strlen(arr + 0));//随机值   还是从'a'开始数,但没有'\\0',所以停不下来
	printf("%d\\n", strlen(*arr));//程序会崩掉  strlen()接收的是一个地址,*arr是字符'a',这里把'a'的ASCII码值(97)作为一个地址访问,这一块的地址是不能被访问的
	printf("%d\\n", strlen(arr[1]));//错误      传的是'b',和传的是'a'效果一样
	printf("%d\\n", strlen(&arr));//随机值      &arr虽然取的是数组的地址,但数组的地址和数组首元素的地址是一样的,也是从‘a'开始数,但并没有'\\0'
	printf("%d\\n", strlen(&arr + 1));//随机值  但这个随机值和前边的随机值意义不同,它是把'a','b','c','d','e','f'跳过去了,从f后边开始数
	printf("%d\\n", strlen(&arr[0] +1));//随机值   这个是从'b'开始往后数的


	return 0;
}


#include<stdio.h>

int main()
{
    char arr[] = "abcdef";
	printf("%d\\n", sizeof(arr));//7   里边还有'\\0',只不过我们看不到而已
	printf("%d\\n", sizeof(arr + 0));//4     arr+0---首元素地址
	printf("%d\\n", sizeof(*arr));//1   对首元素地址解引用是首元素
	printf("%d\\n", sizeof(arr[1]));//1 第二个元素
	printf("%d\\n", sizeof(&arr));//4    数组的地址也是地址
	printf("%d\\n", sizeof(&arr + 1));//4  也是一个地址,不过这个地址在'\\0'后边,跳过了整个数组
	printf("%d\\n", sizeof(&arr[0] + 1));//4   从b开始的一个地址



	printf("%d\\n", strlen(arr));//6   strlen()以'\\0'为结束标志,但不算'\\0'
	printf("%d\\n", strlen(arr + 0));//6  arr+0与arr都代表首元素地址
	printf("%d\\n", strlen(*arr));//错误   这传进来的不是一个地址,而是一个字符
	printf("%d\\n", strlen(arr[1]));//错误
	printf("%d\\n", strlen(&arr));//6    数组的地址也是首元素地址,地址的位置是一样的
	printf("%d\\n", strlen(&arr + 1));//随机值   跳过了'\\0',从'\\0'往后数,不知道会数到哪里去
	printf("%d\\n", strlen(&arr[0] + 1));//5    从第二个元素(b)开始往后数,遇到'\\0'结束


	return 0;
}


#include<stdio.h>

int main()
{
    char *p = "abcdef";
	printf("%d\\n", sizeof(p));//4   p是指针变量,里边存的是a的地址
	printf("%d\\n", sizeof(p + 1));//4   还是一个地址,不过是指向了b的地址
	printf("%d\\n", sizeof(*p));//1      对a的地址解引用就是a
	printf("%d\\n", sizeof(p[0]));//1    第一个元素(a)
	printf("%d\\n", sizeof(&p));//4      &p取的是p的地址,p是一个指针,指向a的地址,但p的地址是什么并不知道
	printf("%d\\n", sizeof(&p + 1));//4  &p+1--跳过了p的一个地址
	printf("%d\\n", sizeof(&p[0] + 1));//4   还是一个地址,这个地址指向了b的地址


    printf("%d\\n", strlen(p));//6        从a开始向后数
	printf("%d\\n", strlen(p + 1));//5    从b开始向后数
	printf("%d\\n", strlen(*p));//错误    *p就是a,strlen()要的是一个地址,而不是a的ASCII码值(97)
	printf("%d\\n", strlen(p[0]));//错误
	printf("%d\\n", strlen(&p));//随机值
	printf("%d\\n", strlen(&p + 1));//随机值
	printf("%d\\n", strlen(&p[0] + 1));//5    从b开始往后数

	return 0;
}


#include<stdio.h>

//二维数组
int main()
{    
	int a[3][4] = { 0 };
	printf("%d\\n", sizeof(a));//48    整个数组有12个元素,每个元素都是int型
	printf("%d\\n", sizeof(a[0][0]));//4   代表的是第一行第一列那个元素
	printf("%d\\n", sizeof(a[0]));//16   a[0]--第一行数组名,第一行总共有4个元素
	printf("%d\\n", sizeof(a[0] + 1));//4   a[0]降级变为a[0][0]的地址,a[0]+1是a[0][1]的地址
	printf("%d\\n", sizeof(a + 1));//4    a--首元素(第一行)地址,a+1--第二行地址
	printf("%d\\n", sizeof(&a[0] + 1));//4    第二行地址
	printf("%d\\n", sizeof(*a));//16   对第一行地址解引用就是第一行元素
	printf("%d\\n", sizeof(a[3]));//16    这里有好多人会出错,认为这个数组并没有这么大,只有3行,不能访问第4行,其实这里并没有访问第4行,它只是一个类型(1行的大小)
	return 0;
}

这些运算都有很详细的解析,大家仔细看,会有很大的收获。


数组作为函数参数

往往我们在写代码的时候,会将数组作为参数传个函数,比如:我要实现一个冒泡排序函数将一个整形数组排序。 那我们将会这样使用该函数

数组的应用实例1:冒泡排序

冒泡排序函数的错误设计

#include <stdio.h>
void bubble_sort(int arr[])
{
 int sz = sizeof(arr)/sizeof(arr[0]);//这样对吗?
    int i = 0;
 for(i=0; i<sz-1; i++)
   {
        int j = 0;
        for(j=0; j<sz-i-1; j++)
       {
            if(arr[j] > arr[j+1])
           {
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
           }
       }
   }
}
int main()
{
    int arr[] = {3,1,7,5,8,9,0,2,4,6};
    bubble_sort(arr);
    for(i=0; i<sizeof(arr)/sizeof(arr[0]); i++)

   {
        printf("%d ", arr[i]);
   }
    return 0;
}

很显然这样是不对的,sz代表的是数组元素的个数,应该是10,但是这里求出来会是1,那么为什么呢?因为sz应该在函数外部求,再通过调用函数传参过去,那么正确的冒泡排序应该是怎么样的呢?

首先我们要搞明白数组作为参数传递的时候,传递的是首元素地址还是整个数组的地址?

数组名是什么?

#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5};
 printf("%p\\n", arr);
    printf("%p\\n", &arr[0]);
    printf("%d\\n", *arr);
   
    return 0;
}

结论:

数组名是数组首元素的地址。

但有两个例外:

1. sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。

2. &数组名,取出的是数组的地址。&数组名,数组名表示整个数组

除此1,2两种情况之外,所有的数组名都表示数组首元素的地址。


所以冒泡函数的正确设计应该是这样的

#include<stdio.h>
void bubble_sort(int arr[], int sz)  //编写函数冒泡排序数组
{
	int i = 0;
	int temp = 0;
	for (i = 0; i < sz - 1; i++)	//确定排序的趟数
	{
		int j = 0;
		int flag = 1;		//假设这一趟排序的数列已经有序

		for (j = 0; j < sz - 1 - i; j++)	//每一趟冒泡排序两两交换的次数
		{
			if (arr[j] > arr[j + 1])
			{
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
				flag = 0;	//本趟排序其实不完全有序


			}
		}
		if (flag == 1)  //不用排序了
		{
			break;
		}
	}


}
int main()
{
	int i = 0;
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);  //计算数组大小;
	bubble_sort(arr, sz);                   //这里的arr指的是首元素地址;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

数组的应用实例2:三子棋

已经为大家准备好了,参考:https://blog.csdn.net/weixin_55124128/article/details/116195494

数组的应用实例3:扫雷游戏

这个也给大家准备好了,见参考:https://blog.csdn.net/weixin_55124128/article/details/116356115

总结

数组和指针紧密联系,所以学好数组对大家学指针有很好的帮助,本篇涉及了一维数组二维数组的创建和初始化,运用,存储,指针访问,以及数组的运算和三个应用实例,我从中受益匪浅,相信对大家有不少帮助。还请大家多多支持我,给我个一键三连,谢谢大家!!

 

 

以上是关于C语言初阶最详细的数组知识总结,超多干货!!(带三大应用实例)的主要内容,如果未能解决你的问题,请参考以下文章

C初阶-遗忘知识点

C初阶-遗忘知识点

C语言初阶笔记重点初识指针,详解!!

C语言初阶笔记重点初识指针,详解!!

C语言初阶笔记重点初识指针,详解!!

C/C++ 语言 零碎知识点的总结干货