C语言数组超详细讲解上

目录
  • 前言
  • 1、一维数组的创建和初始化
    • 1.1 一维数组的创建
    • 1.2 一维数组的初始化
    • 1.3 一维数组的使用
    • 1.4 一维数组在内存中的存储
  • 2、二维数组的创建和初始化
    • 2.1 二维数组的创建
    • 2.2 二维数组的初始化
    • 2.3 二维数组的使用
    • 2.4 二维数组在内存中的存储
  • 3、数组越界
  • 4、数组作为函数参数
    • 4.1 冒泡排序函数的错误设计
    • 4.2 数组名是什么?
    • 4.3 对数组名的用法进行总结
    • 4.4 冒泡排序函数的正确设计
  • 总结

前言

本文主要介绍数组相关的内容,主要内容包括:

  • 一维数组的创建和初始化
  • 一维数组的使用
  • 一维数组在内存中的存储
  • 二维数组的创建和初始化
  • 二维数组的使用
  • 二维数组在内存中的存储
  • 数组越界
  • 数组作为函数参数

1、一维数组的创建和初始化

1.1 一维数组的创建

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

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

//代码1
int arr1[10];
//代码2
int count = 10;
int arr2[count];//数组不要放变量
//代码3
char arr3[10];
float arr4[1];
double arr5[20];

注:数组创建,在C99标准之前, [ ] 中要给一个常量才可以,不能使用变量。在C99标准支持了变长数组的概念。

1.2 一维数组的初始化

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

int arr[10] = {0};
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 arr6[] = "abcdef";//字符串也是通过数组定义的

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

仔细对比下面5个数组:

	char arr1[] = "abc";
	char arr2[3] = { 'a','b','c' };
	char arr3[] = { 'a','b','c' };
	char arr4[4] = { 'a','b','c' };
	char arr5[] = { 'a','b','c','\0' };

	printf("%s\n", arr1);
	printf("%s\n", arr2);
	printf("%s\n", arr3);
	printf("%s\n", arr4);
	printf("%s\n", arr5);
	printf("\n");
	printf("%d\n", sizeof(arr1));
	printf("%d\n", sizeof(arr2));
	printf("%d\n", sizeof(arr3));
	printf("%d\n", sizeof(arr4));
	printf("%d\n", sizeof(arr5));

对比下面两图可知,字符串以字符 ‘\0’ 为结尾:

  • arr1 用双引号存储字符串时,末尾有隐藏的 ‘\0’ ,字符串长度为4
  • arr4 规定了字符串的长度,末尾也有隐藏的 ‘\0’ ,字符串长度为4
  • arr5 规直接末尾添加了 ‘\0’ ,字符串长度为4
  • arr3 和 arr4都是只有三个字符,字符串长度为3,但是末尾没有 ‘\0’ ,字符串没有结束,打印出来后面是乱码的

下面两图的结果能清楚的表示,上面5中定义的区别,因此,推荐使用数组arr1的方式定义字符串。

1.3 一维数组的使用

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

int main()
{
	int arr[10] = {0};//数组的不完全初始化
	//计算数组的元素个数= 整个数组的大小/数组首元素的大小
	int sz = sizeof(arr)/sizeof(arr[0]);
	//对数组内容赋值,数组是使用下标来访问的,下标从0开始。所以:
	int i = 0;//做下标
	//for(i=0; i<sz; i++)//这样也行
	for(i=0; i<10; i++)
	{
		arr[i] = i;//给数组元素初始化
	}
	//输出数组的内容
	for(i=0; i<10; ++i)
	{
		printf("%d ", arr[i]);//打印数组
	}
	return 0;
}

数组是使用下标来访问的,下标是从0开始。 数组的大小可以通过计算得到的。

1.4 一维数组在内存中的存储

接下来探讨数组在内存中的存储

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//打印数组中的每个元素的地址
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	int* p = &arr[0];//元素首地址
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", *(p++));//地址加1
	}
	return 0;
}

两种打印数组的方式都可以,结果相同:

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//打印数组中的每个元素的地址
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = &arr[0];//元素首地址
	for (int  i = 0; i < sz; i++)
	{//打印地址
		printf("&arr[%d]=%p <==> %p\n", i, &arr[i], p++);
	}
	return 0;
}

输出结果见下图,随着数组下标的增长,元素的地址,也在有规律的递增。整形数组每个元素占4个字节,共10个元素,总共40个字节。所以相邻元素的地址依次递增4个字节。

由此可以得出结论:数组在内存中是连续存放的。

2、二维数组的创建和初始化

2.1 二维数组的创建

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

2.2 二维数组的初始化

//数组初始化
int arr[3][4] = {1,2,3,4};
int arr[3][4] = {{1,2},{4,5}};
int arr[][4] = {{2,3},{4,5}};//二维数组如果有初始化,行可以省略,列不能省略
int arr1[][5] = { 1,2,3,4,5,6 };//不完全初始化
int arr2[][5] = { {1,2},{3,4},{5,6} };
char ch[5][7];
int arr[4][5] = { 0 };

2.3 二维数组的使用

二维数组的使用也是通过下标的方式

int main()
{
	int arr2[][5] = { {1,2},{3,4},{5,6} };

	for (int i = 0; i < sizeof(arr2) / sizeof(arr2[0]); i++)//打印二维数组
	{
		//int j = 0;
		for (int j = 0; j < sizeof(arr2[0]) / sizeof(arr2[0][0]); j++)
		{
			printf("%d ", arr2[i][j]);
		}
		printf("\n");
	}
	return 0;
}

2.4 二维数组在内存中的存储

像一维数组一样,打印二维数组的每个元素

int main()
{
	int arr[3][5] = { {1,2},{3,4},{5,6} };
	for (int i = 0; i < 3; i++)
	{
		int j = 0;
		for ( j = 0; j < 5; j++)
		{
			printf("&arr[%d]{%d]=%p\n", i, j, &arr[i][j]);//地址是连续的
		}
	}
	return 0;
}

二维数组的地址在内存中也是连续的,相邻元素依次相差4个字节,&arr[0][3]与&arr[1][0]是相连的。

3、数组越界

  • 数组的下标是有范围限制的
  • 数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问
  • C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的,所以程序员写代码时,最好自己做越界的检查
  • 二维数组的行和列也可能存在越界。
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
for(i=0; i<=10; i++)
{
printf("%d\n", arr[i]);//当i等于10的时候,越界访问了
}
return 0;
}

4、数组作为函数参数

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

4.1 冒泡排序函数的错误设计

//方法1:
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;
}

结果见下图,没有达到预期的排序效果。

按下F10进入调试界面,按F11进行单步调试,会发现主函数中的数组是10个元素,这是自己定义的,没有问题。

但是将数组当作参数传递给函数bubble_sort会发现,函数接受的数组只包含首元素3。

因此计算数组长度sizeof(arr)时,结果长度是4,不再是原来主函数里的40了。

调试之后可以看到 bubble_sort 函数内部的 sz ,是1。

所以数组作为函数参数的时候,不是把整个数组的传递过去。

这种情况之前在【C语言基础3——函数(1)】中 4.3.3 二分查找中就具体分析过。

4.2 数组名是什么?

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%p\n", arr);//首元素的地址
	printf("%p\n", arr+1);//+1指向第二个元素

	printf("%p\n", &arr[0]);//首元素的地址
	printf("%p\n", &arr[0]+1);+1//指向第二个元素

	printf("%p\n", &arr);//整个数组的地址
	printf("%p\n", &arr+1);

	return 0;
}

运行结果见下图:

  • 数组名就是首元素的地址,用指针接收
  • 对数组名求地址,是整个数组的地址
  • &arr的地址和 &arr+1 的地址相差28,这是16进制,转化为2进制就是 40了,这正是数组的长度,包含10个元素

4.3 对数组名的用法进行总结

  • sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。
  • &数组名,取出的是数组的地址。&数组名,数组名表示整个数组。
  • 除此1,2两种情况之外,所有的数组名都表示数组首元素的地址。

4.4 冒泡排序函数的正确设计

上述代码当数组传参的时候,实际上只是把数组的首元素的地址传递过去了。

所以即使在函数参数部分写成数组的形式: int arr[] 表示的依然是一个指针: int *arr 。因为地址可以用指针就收。

那么,函数内部的 sizeof(arr) 结果是4。数组长度应该放在主函数中进行计算。

下面对4.2 进行改进,有时候数组元素本身就是按一定顺序排好的,只需第一轮判断即可:

void bubble_sort(int* arr, int sz)
{
	//排序坐外面的大循环次数
	int i = 0;
	for ( i = 0; i < sz-1; i++)
	{
		int flag = 1;//状态机标志位,代表数组本身元素就是从小到大排序的
		int j = 0;
		for ( j = 0; j < sz-1-i; j++)
		{
			if (arr[j]>arr[j+1])
			{
				flag = 0;//只要有一个地方需要排序,就置零
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
		if (1==flag)
		{//第一轮排序结果都是1,说明没有地方需要排序
			break;//直接跳出后面的循环,不需要再排序了
		}
	}
}
int main()
{
	//int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr[] = { 3,1,2,4,5,6,8,9,7,10 };
	//写一个冒泡排序的函数,
	//arr表示首元素的地址, &arr[0],也是首元素的地址
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	int i = 0;
	for ( i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

总结

本文是对数组相关知识点的学习,下一篇将通过完成三子棋游戏巩固前面所学的知识点。

到此这篇关于C语言数组超详细讲解上的文章就介绍到这了,更多相关C语言 数组内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言 指针数组进阶详解

    目录 指针与数组中的sizeof与strlen sizeof strlen 数组名 1.一维数组 整型数组 字符数组 指针数组 2.二维数组 指针笔试题 笔试题1 笔试题2 笔试题3 笔试题4 笔试题5 前言:指针与数组的知识往往让我们无法给自己定位,似乎是懂了,但真的碰上了又一言难尽.接下来有一些关于指针与数组的知识和例题讲解,来看看你对指针和数组到底有多了解吧! 指针与数组中的sizeof与strlen sizeof sizeof值关注占用空间的大小,单位是字节,不关注元素的类型,是一个操作

  • C语言 柔性数组的使用详解

    目录 一.柔性数组的特点 二.柔性数组的使用 1.如何使用柔性数组 2.不用柔性数组的话有什么代替 三.柔性数组的优势 1.方便内存释放 2.提高访问速度 一.柔性数组的特点 struct S { int x; int a[]; }; int main() { printf("%d", sizeof(S)); } 这段代码的输出是什么? 我们打印结构体S所占空间的大小,这个a[]占多少字节呢? 输出结果是4,可一个int类型的x就是4了,a[]去哪了?好奇怪哦. 原来,这是一种柔性数组

  • C语言数组全面总结梳理

    目录 一,一维数组 1.创建和初始化 2.使用下标访问 3.在内存中的存储 二,二维数组 1.创建和初始化 2.使用下标访问 3.在内存中的存储 三,越界问题 数组(array)是由一系列类型相同的元素构成. 一般形式: 类型     数组名  [常量表达式] 一,一维数组 1.创建和初始化 创建一堆相同元素的集合,以整型为例: //创建大小为8的整型数组 int arr1[8]; // []里面应放常量,因此若: int num=8; //则有: arr2[num]; num为变量,创建失败

  • C语言超详细讲解轮转数组

    目录 题目描述 实例 解题思路 1. 先整体逆转 2.逆转子数组[0, k - 1] 3.逆转子数组[k, numsSize - 1] 易错点 代码 题目描述 给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数.OJ链接 实例 1.实例1 输入: nums = [1,2,3,4,5,6,7], k = 3输出: [5,6,7,1,2,3,4]解释:向右轮转 1 步: [7,1,2,3,4,5,6]向右轮转 2 步: [6,7,1,2,3,4,5]向右轮转 3 步: [5,6,7

  • C语言详细讲解树状数组与线段树

    目录 树状数组 动态求连续区间和 数星星 线段树 动态求连续区间和 数列区间最大值 树状数组 动态求连续区间和 给定 n 个数组成的一个数列,规定有两种操作,一是修改某个元素,二是求子数列 [a,b] 的连续和. 输入格式第一行包含两个整数 n 和 m,分别表示数的个数和操作次数. 第二行包含 n 个整数,表示完整数列. 接下来 m 行,每行包含三个整数 k,a,b (k=0,表示求子数列[a,b]的和:k=1,表示第 a 个数加 b). 数列从 1 开始计数. 输出格式输出若干行数字,表示 k

  • C语言全方位讲解数组的使用

    目录 一维数组的创建和初始化 1.数组的创建 2.数组创建方式 3.数组的初始化 一维数组的使用 一维数组的存储 二维数组的创建与初始化 1.二维数组的创建 2.二维数组的初始化 二维数组的存储 数组的越界 总结 接着上次的操作符的详解,让我们来简单了解C语言里的数组. 一维数组的创建和初始化 1.数组的创建 数组是一组相同类型的元素的集合. 2.数组创建方式 type_t(数组类型) arr_name(数组名) [const_n](用来指定数组大小) 3.数组的初始化 数组的初始化是在其定义的

  • C语言数组超详细讲解上

    目录 前言 1.一维数组的创建和初始化 1.1 一维数组的创建 1.2 一维数组的初始化 1.3 一维数组的使用 1.4 一维数组在内存中的存储 2.二维数组的创建和初始化 2.1 二维数组的创建 2.2 二维数组的初始化 2.3 二维数组的使用 2.4 二维数组在内存中的存储 3.数组越界 4.数组作为函数参数 4.1 冒泡排序函数的错误设计 4.2 数组名是什么? 4.3 对数组名的用法进行总结 4.4 冒泡排序函数的正确设计 总结 前言 本文主要介绍数组相关的内容,主要内容包括: 一维数组

  • C语言数组超详细讲解下篇扫雷

    目录 前言 1.扫雷是什么? 2.程序框架 2.1 主函数 2.2 函数menu 2.3 函数game 2.3.1 函数init_board 2.3.2 函数show_board 2.3.3 函数set_mine 2.3.4 函数find_mine 2.3.5 函数get_mine_count 3.头文件.h 4.游戏试玩 总结 前言 本文接着复习前面所学知识,以扫雷游戏为例. 1.扫雷是什么? 百度百科:<扫雷>是一款大众类的益智小游戏,于1992年发行.游戏目标是在最短的时间内根据点击格子

  • C语言数组超详细讲解中篇三子棋

    目录 前言 1.三子棋是什么? 1.1 百度百科 1.2 游戏编程准备工作 2. 程序实现 2.1 搭建程序框架 2.2 模块化编程 2.2.1 源文件test.c 2.2.2 源文件play.c 2.2.3 头文件play.h 2.3 程序实现—拓展play函数 2.3.1 棋盘初始化与打印函数 2.3.2 玩家下棋函数 PlayMover 2.3.3 电脑下棋函数 ComputerMove 2.2.4 判断赢家函数 WhoIsWin 总结 前言 本文主要是对前面所学内容进行复习和练习,学习内

  • C语言指针超详细讲解上篇

    目录 前言 1.指针是什么 1.1 指针变量 1.2 指针是内存中一个最小单元的编号 2.指针和指针类型 2.1 指针±类型 2.2 指针的解引用 2.2.1 int* 类型的解引用 2.2.2 char* 类型的解引用 3.野指针 3.1 野指针成因 3.1.1 指针未初始化 3.1.2 指针越界访问 3.1.3 指针指向的空间释放 3.2 如何规避野指针 总结 前言 本文开始指针相关内容的学习,主要内容包括: 指针是什么 指针和指针类型 野指针 指针运算 指针和数组 二级指针 指针数组 1.

  • C语言操作符超详细讲解下篇

    目录 前言 赋值操作符 单目操作符 单目操作符介绍 sizeof 和 数组 关系操作符 逻辑操作符 条件操作符 逗号表达式 下标引用与函数调用和结构成员 [ ] 下标引用操作符 ( ) 函数调用操作符 访问一个结构的成员 表达式求值 隐式类型转换-整形提升 算术转换 操作符的属性 总结 前言 本文接着学习操作符的内容. 赋值操作符 赋值操作符就是能够重新赋值 int weight = 120;//体重 weight = 89;//不满意就赋值 double salary = 10000.0; s

  • C语言指针超详细讲解下篇

    目录 前言 指针运算 指针±整数 4.1 指针±整数 4.2 指针-指针 4.3 指针的关系运算 5.指针和数组 6.二级指针 7.指针数组 7.1 举例 1 7.2 举例 2 总结 前言 本文接着上一篇内容,继续学习指针相关知识点. 指针运算 指针±整数 指针-指针 指针的关系运算 4.1 指针±整数 #define VALUE 5 int main() { float values[VALUE]; float *vp; //指针+-指针,关系运算 for (vp = &values[0];

  • C语言函数超详细讲解下篇

    目录 前言 函数的声明和定义 函数声明 函数定义 举例 简单的求和函数 把加法单独改写成函数 添加函数声明 带头文件和函数声明 静态库(.lib)的生成 静态库文件的使用方法 函数递归 什么是递归? 递归的两个必要条件 练习1 一般方法 递归的方法 练习2 一般方法 递归方法 练习3 一般方法 递归方法 练习4 一般方法 递归方法 递归与迭代 递归隐藏的问题 如何改进 选递归还是迭代 总结 前言 紧接上文,继续学习函数相关内容. 函数的声明和定义 函数声明 告诉编译器有一个函数叫什么,参数是什么

  • C语言函数超详细讲解上篇

    目录 前言 1.函数是什么? 2.C语言中函数的分类 2.1 库函数 2.1.1 如何学会使用库函数 2.1.2 自定义函数 3.函数的参数 3.1 实际参数(实参) 3.2 形式参数(形参) 4.函数的调用 4.1 传值调用 4.2 传址调用 4.3 练习 4.3.1 判断一个数是不是素数 4.3.2 判断一年是不是闰年 4.3.3 二分查找 4.3.4 数值自增增加1 5.函数的嵌套调用和链式访问 5.1 嵌套调用 5.2 链式访问 总结 前言 本文主要学习函数的相关内容. 1.函数是什么?

  • C语言数据结构超详细讲解单向链表

    目录 1.链表概况 1.1 链表的概念及结构 1.2 链表的分类 2. 单向链表的实现 2.1 SList.h(头文件的汇总,函数的声明) 2.2 SList.c(函数的具体实现逻辑) 2.2.1 打印链表 2.2.2 搞出一个新节点(为其他函数服务) 2.2.3 链表尾插 2.2.4 链表头插 2.2.5 链表尾删 2.2.6 链表头删 2.2.7 查找节点 2.2.8 在pos位置之前插入 2.2.9 在pos位置之后插入 2.2.10 删除pos位置 2.2.11 删除pos之后位置 2.

  • C语言数组全面详细讲解

    目录 1.基础知识 2.数组的分类 2.1按元素类型分类 2.2按维数分类 3.数组定义和初始化 3.1 一维数组 3.2 二维数组 4.数组元素的引用方法 5.字符数组的定义 1.基础知识 C语言中使用数组表示多个连续的同类型的存储位置 使用数组表示多个连续存储位置的时候只需要一个名字,这个名字代表所有这些存储位置的整体 每一个存储位置有一个自己的编号,最前边的存储位置的编号是0,向后依次递增,最后一个存储位置的编号是个数减一,这个编号叫做下标 决不可以使用超过范围的下标 使用名称和下标就可以

随机推荐