数组

目录

1. 数组的概念

2. 一维数组的创建和初始化 

2.1 数组创建

2.2 数组的初始化

2.3 数组的类型

3. 一维数组的使用

3.1 数组下标

3.2 数组元素的打印

3.3 数组的输入

4. 一维数组在内存中的存储 

5. sizeof计算数组元素个数

6. 二维数组的创建

6.1 二维数组的概念

6.2 二维数组的创建

7. 二维数组的初始化

7.1 不完全初始化

7.2 完全初始化

7.3 按照行初始化

7.4 初始化时省略行,但不能省略列 

 8. 二维数组的使用

8.1 二维数组的下标

8.2 二维数组的输入和输出

9. 二维数组在内存中的存储

10. C99中的变长数组

11. 数组练习

11.1 多个字符从两端移动,向中间汇聚。

12.2 二分查找


1. 数组的概念

假设我要存放100个整型类型的值,那么我就要创建100个整型变量来存放100个整型类型的值,显然是比较麻烦的,为了能够存放多个相同类型的值,就有了数组。

数组是一组相同类型元素的集合,从这个概念中我们就可以发现两个有价值的信息。

  • 数组中存放的是1个或多个数据,但是数组元素个数不能为0.
  • 数组中存放的是多个数据,类型是相同的。

数组分为一维数组和多维数组,多维数组一般比较多见的是二维数组。

2. 一维数组的创建和初始化 

2.1 数组创建

一维数组创建的基本语法:

type arr_name[常量值];

存放在数组的值被称为数组的元素,数组在创建的时候可以指定数组的大小和数组的元素类型。

  • type指定的是数组中存放数据的类型,可以是:char、short、int、float等,也可也自定义类型。
  • arr_name指的是数组名的名字,这个名字根据实际情况,起的有意义就行。
  • [ ]中的常量值是哟过来指定数组的大小的,这个数组大小根据实际的需求指定就行。

比如:我们现在想存储某个班级的20人的数学成绩,那我们就可以创建一个数组。

//一维数组的创建
int math[20];

 当然我们也可以根据需要创建其他类型和大小的数组:

//一维数组的创建
char ch[10];
double score[10];

2.2 数组的初始化

数组在创建的时候,我们需要给定一些初始值,这种就被称为初始化。

数组的初始化一般使用大括号,将数据放在大括号中。

int main()
{
	//完全初始化
	int math[10] = {0,1,2,3,4,5,6,7,8,9};
	//不完全初始化
	char ch[5] = { 'a'};//第一个元素初始化为1,剩余的默认为0
	char ch2[5] = "abc";//也可以拿字符串初始化
	//错误的初始化
	double score[3] = { 1.0,2.0,3.0,4.0 };//初始化项太多 - err
	return 0;
}

2.3 数组的类型

数组也是有类型的,数组算是一种自定义类型,去掉数组名留下的就是数组的类型。

#include <stdio.h>
int main()
{
	int a = 100;
	char b = 'a';
	double d = 0.0;
	//上面这些变量的类型就是int,char,double。
	int arr[10] = { 0 };
	//数组的元素类型是int
	//数组的类型就是去掉数组名就是类型,int[10]
    int arr2[5];//int[5]
	return 0;
}

数组的类型跟数组元素的类型和数组元素的个数是有关系的。

3. 一维数组的使用

知道了一维数组的基本语法,一维数组是可以存放数据的,存放数据的目的就是对数据进行操作,那么我们就应该了解了解一维数组的使用。

3.1 数组下标

C语言规定数组是有下标的,下标是从0开始的,假设数组有n个元素,最后一个元素的下标是n-1,下标就相当于数组元素的编号。

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };

在C语言中数组的访问提供了一个操作符[ ],这个操作符叫:下标引用操作符。

有了下标访问操作符,我们就可以轻松的访问到数组的元素了,比如我们访问下标为5的元素,我们就可以使用arr[7],想要访问下标是3的元素,就可以使用arr[3]。 

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//数组名后面的10是指定数组元素个数的
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	//在数组创建的时候,数组元素个数也可以不指定
	//但前提是需要初始化数组
	//当对数组初始化的时候,数组的大小可以省略的
	//编译器会根据数组的初始化内容自动计算数组的元素个数

	printf("%d\n", arr[5]);//[ ] -- 下标引用操作符
	//这里的5是数组元素的下标

	printf("%d\n", arr[9]);

	return 0;
}

 输出结果:

3.2 数组元素的打印

如果想要访问整个数组的内容,只要我们产生数组所有元素的下标就可以了,那我们使用for循环产生0~9的下标,接下来使用下标访问就行了。

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

输出结果: 

3.3 数组的输入

我们可以打印数组的值,那么肯定也可以根据需求,自己给数组输入想要的数据。

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

输入输出: 

4. 一维数组在内存中的存储 

依次打印数组元素的地址:

//一维数组在内存中的存储
#include <stdio.h>
//%d - 整型
//%c - 字符
//%s - 字符串
//%p = 地址
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	for (int i = 0; i < 10; i++)
	{
		printf("&arr[%d] = %p\n",i, &arr[i]);//& - 取地址操作符
	}
	return 0;
}

 输出:

如果你打印的地址比我打印的地址长的话那你可以把你的vs环境修改为x86环境。如果你的环境是x64的话表示64位环境,所有地址打印出来就是64个比特位,就会比较长,为了观察方便我们就修改为x86,32位的环境,其实64位和32位地址都是一样的。

从输出的结果我们分析,数组随着下标的增长,地址是由小到大变化的,并且我们发现两个相邻的元素之间相差4(因为一个整型是4个字节)。所有我们得出结论:数组在内存中是连续存放的。 

5. sizeof计算数组元素个数

在遍历数组的时候,我们经常想知道元素的个数,那C语言中就可以使用sizeof来计算元素的个数。

sizeof是C语言的一个关键字,是可以计算类型或者变量大小的。

sizeof计算的是变量或者类型的长度,单位是字节。 

其实sizeof也是可以计算数组的大小。

#include <stdio.h>
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%zd\n", sizeof(arr));
	return 0;
}

这里计算的40是因为素组有10个元素,每个元素是4个字节,所有是40个字节。 所以sizeof中放数组名的时候,计算的是数组的大小,40个字节。

数组中所有元素的类型都是相同的,那么只要计算出一个元素所占的字节个数,数组的元素个数就可以计算出来。

#include <stdio.h>
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%zd\n", sizeof(arr)/sizeof(arr[0]));
	return 0;
}

这⾥的结果是:10,表⽰数组有10个元素。 

以后在代码中需要数组元素个数的地⽅就不⽤固定写死了,使⽤上⾯的计算,不管数组怎么变化,计算出的大小也就随着变化了。

那么我们打印数组的时候for循环的条件我就可以不用写死了。

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

运行结果:

现在就算我的数组增加元素,下面的代码也不用改。 

6. 二维数组的创建

6.1 二维数组的概念

前面了解的数组是一维数组,数组的元素都是内置类型的,如果我们把一维数组作为 数组的元素,这时候就是二维数数组,二维数组作为数组元素的数组被称为三维数组,二维数组以上的数组统称为多维数组。

6.2 二维数组的创建

定义二维数组的语法: 

type arr_name[常量值1][常量值2];

例如:
int arr[3][5];
double data[2][8];
  • 3表示数组有三行
  • 5表示每一行有5个元素
  • int表示数组的每个元素是整型类型
  • arr是数组名,可以根据自己的需要指定名字

data数组基本一致

假设我现在要创建一个二维数组,来保存每个班的成绩。

#include <stdio.h>
int main()
{
	float score[5][30];
	//一共5个班,每个班最多保存30个成绩,而且
	//成绩有可能有小数,所有是float类型
	return 0;
}

7. 二维数组的初始化

在创建数组的时候,给定一些初始值,被称为初始化,那么二维数组和一维数组一样,也是使用大括号初始化。

7.1 不完全初始化

#include <stdio.h>
int main()
{
	int arr1[3][5] = { 1,2 };
	int arr2[3][5] = { 0 };
	return 0;
}

7.2 完全初始化

#include <stdio.h>
int main()
{
	//int arr3[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
	int arr3[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15} };
	//这两种初始化都可以
	return 0;
}

 7.3 按照行初始化

#include <stdio.h>
int main()
{
	int arr4[3][5] = { {1,2},{6,7,8},{11,12,13,14} };
	return 0;
}

7.4 初始化时省略行,但不能省略列 

#include <stdio.h>
int main()
{
	int arr5[][5] = { 1,2,3 };
	int arr6[][5] = { 1,2,3,4,5,6,7 };
	int arr7[][5] = { {1,2}, {3,4}, {5,6} };
	return 0;
}

 8. 二维数组的使用

8.1 二维数组的下标

当我们掌握了二维数组的创建和初始化,那么也应该掌握二维数组的使用。

其实二维数组访问也是使用下标的形式,二维数组也是有行和列的,只要锁定行和列就能唯一锁定数组中的一个元素。

C语言规定,二维数组的行是从0开始,列也是从0开始。

#include <stdio.h>
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15} };
	return 0;
}

图中最左侧绿色的数字表示行号,第一行蓝色的数字表示列号,都是从0开始的,比如,第一行第一列,我们就可以找到7。

#include <stdio.h>
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15} };
	printf("%d\n", arr[1][1]);
	return 0;
}

运行结果: 

8.2 二维数组的输入和输出

访问整个二维数组的话,其实我们只要能够按照一定的规律产生所有的行和列的数字就行,上面代码中的数组行的范围是0~2,列的范围是0~4,所以我们可以借助循环实现生成所有的下标。 

#include <stdio.h>
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15} };
	int hang = sizeof(arr) / sizeof(arr[0]);//计算行
	int lie = sizeof(arr[0]) / sizeof(arr[0][0]);//计算列
	//输入
	for (int i = 0; i < hang; i++)
	{
		for (int j = 0; j < lie; j++)
		{
			scanf("%d", &arr[i][j]);
		}
	}
	//输出
	for (int i = 0; i < hang; i++)
	{
		for (int j = 0; j < lie; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

输入和输出

9. 二维数组在内存中的存储

和一维数组一样,如果想要研究二维数组在内存中的存储方式的,我们也是可以打印出数组所有元素的地址。

#include <stdio.h>
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15} };
	int hang = sizeof(arr) / sizeof(arr[0]);//计算行
	int lie = sizeof(arr[0]) / sizeof(arr[0][0]);//计算列
	for (int i = 0; i < hang; i++)
	{
		for (int j = 0; j < lie; j++)
		{
			printf("arr[%d][%d] = %p\n",i,j, &arr[i][j]);
		}
	}
	return 0;
}

运行结果:

使用32位环境打印。

从输出的结果来看,每一行内部的元素都是相邻的,地址之间相差4个字节,跨行位置处的两个元素之间也是差4个字节,所以二维数组中的每个元素在内存中也是连续存放的。 

二维数组中的每个元素就是一维数组。

前面的都是一行一行打印,其实也可以一列一列的打印。 

#include <stdio.h>
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15} };
	int hang = sizeof(arr) / sizeof(arr[0]);//计算行
	int lie = sizeof(arr[0]) / sizeof(arr[0][0]);//计算列
	//1 2 3 4 5
	//6 7 8 9 10
	//11 12 13 14 15
	for (int i = 0; i < lie ; i++)
	{
		for (int j = 0; j < hang; j++)
		{
			printf("%d ", arr[j][i]);
		}
		printf("\n");
	}
	return 0;
}

10. C99中的变长数组

在C99标准之前,C语言在创建数组的时候,数组大小的指定只能使用常量、常量表达式、或者我们初始化数组的时候可以省略大小。

#include <stdio.h>
int main()
{
	int arr1[10];
	int arr2[2 + 2];
	int arr3[] = { 1,2,3 };
	return 0;
}

正因为有这样的语法限制,让我们创建数组就不够灵活,有时候数组大了浪费空间,有时候数组小了不够用。

但是C99中给了一个变长数组(variable-length array,简称VLA)的新特性,允许我们使用变量指定数组的大小。

#include <stdio.h>
int main()
{
	int n = 10;
	int arr[n];
	return 0;
}

上⾯示例中,数组 arr 就是变⻓数组,因为它的长度取决于变量 n 的值,编译器没法事先确定,只有运行时才能知道n是多少。

变长数组的根本特性,就是数组长度只有运行时才能确定,所以变长数组不能初始化。它的好处是程序员不必在开发时随意为数组指定一个估计的长度,程序可以在运行时为数组分配精确的长度。变长数组的意思时数组的大小是可以使用变量来指定的,在程序运行的时候,根据变量的大小来指定数组的元素个数,而不是说数组的大小是可变的。数组的大小一旦确定就不能在变化了。

但是在VS2019上,虽然支持大部分C99的语法,但是并没有C99中的变长数组,没法测试,下面是在gcc编译器上测试。

#include <stdio.h>
int main()
{
    int n = 0;
    scanf("%d",&n);
    int arr[n];//边长数组不能初始化
    for(int i = 0;i < n;i++)
    {
        scanf("%d",&arr[i]);
    }
    for(int i = 0;i < n;i++)
    {
        printf("%d ",arr[i]);
    }
    return 0;
}

两次测试,第一次输入10,第二次输入5,并正常输出。

11. 数组练习

11.1 多个字符从两端移动,向中间汇聚。

编写代码,演示多个字符从两端移动,向中间汇聚。

#include <stdio.h>
#include <string.h>
#include <Windows.h>
/*
hello wang!!!
#############
h###########!

*/
int main()
{
	char ch1[] = "hello wang!!!";
	char ch2[] = "#############";
	int sz = strlen(ch1);
	printf("%s\n", ch2);
	for (int i = 0; i <= sz/2; i++)
	{
		ch2[i] = ch1[i];
		ch2[sz - i-1] = ch1[sz - i-1];
		Sleep(1000);//睡眠函数 - 睡眠1s
		system("cls");//清屏函数
		printf("%s\n", ch2);
	}
	return 0;
}

12.2 二分查找

在一个升序的数组中查找指定的数字n,很容易想到的方法就是遍历数组,但是这种方法的效率比较低。

比如我买了一双鞋,你好奇问我多少钱,我说不超过300元。你还是好奇,你想知道到底多少,我就让你猜,你会怎么猜?你会1,2,3,4....这样猜吗?显然很慢,一般你就会猜中间数字,比如:150,然后看大了还是小了,这就是二分查找,也叫折半查找。

#include <stdio.h>
int main()
{
	int k = 9;
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int left = 0;
	int right = sz - 1;
	while (left <= right)
	{
		int mid = left + (right - left) / 2;//这样写为了防止溢出
		if (arr[mid] == k)
		{
			printf("找到了,下标是:%d\n", mid);
			break;
		}
		else if (arr[mid] < k)
		{
			left = mid + 1;
		}
		else if (arr[mid] > k)
		{
			right = mid - 1;
		}
	}
	if (left > right)
		printf("没找到!!\n");
	return 0;
}

建议在计算中间下标的时候不要使用int mid = (left + right)/2来计算,因为当left是最大值,right是最大值的时候,相加除2就不合适了,会溢出。 

#include <stdio.h>
int main()
{
	int a = 2147483646;//_CRT_INT_MAX - 1
	int b = 2147483646;
	int avg = (a + b) / 2;
	printf("%d\n", avg);

	return 0;
}

两个相同的值相加再除2不应该还是原来的值吗,可这里偏偏就是-2,这就不是平均值,所以这里存在潜在问题。所以我们在求平均值的时候可以用下面的思路。

#include <stdio.h>
int main()
{
	int a = 2147483646;//_CRT_INT_MAX - 1
	int b = 2147483646;
	int avg = a + (b - a) / 2;//小的加上大的和小的差的一半
	printf("%d\n", avg);

	return 0;
}

那如果a大的话就小的减大的,成负的了,然后除2,然后再给a减去m,也是一样的。

所以求中间下标的时候就可以用上面的思路。

int mid = left + (right - left) / 2;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值