目录
1. 一维数组的创建和初始化
1.2 一维数组的使用
1.3 一维数组在内存中的存储
2. 二维数组的创建和初始化
2.2 二维数组的使用
2.3 二维数组在内存中的存储
3. 数组越界
4. 数组作为函数参数
4.1 冒泡排序
4.2 数组名是什么?
1. 一维数组的创建
1.1 数组的创建
数组是一组相同数据类型元素的集合。
数组的创建方式:
type_t arr_name[const_n];
//type_t:是指数组的元素类型(可以是基本数据类型的任意一种)
//arr_name:数组名(与变量相同,遵循标识符的命名规则)
//const_n:常量表达式(用来表示数组中元素个数,即数组的大小(长度))
注:常量表达式是用[]方括号括起来的,不是()圆括号。
数组创建的实例
代码1:
int arr1[10];//定义一个有10个整形的一维数组
char arr2[5];
float arr3[6];
...
代码2:
int n=10;
int arr[n];//这样可以吗?
注:变长数组(不定长的数组)
数组创建,在C99标准之前,[]中要给一个常量才可以,不能使用变量。在C99标准支持了变长数组的概念,数组的大小可以使用变量指定,但是数组不能初始化。
理解:
①能否用变长数组:先看环境支不支持C99的新特性
如:VS IDE 不支持C99中的变长数组
Linux gcc 支持C99中的变长数组
②注意:变长数组不能初始化
1.2 数组的初始化
数组的初始化是指,在创建数组的同时给数组的内容一些(合理)初始值。
即:创建的同时给数组一些值,这就叫初始化(类似变量)
一维数组初始化的一般形式如下:
1、完全初始化:(元素个数=数组长度)
①指定数组长度:
int arr1[6]={0,1,2,3,4,5};//元素个数=数组长度
②不指定数组长度:(没有指定数组长度,编译会根据初始化的内容来确定数组长度)
int arr2[]={0,1,2,3,4};//数组长度=5
2、不完全初始化:(数组长度!=元素个数)
如:
①int型:剩余没赋初值的元素默认初始化为0
②char型:剩余的元素默认初始化为‘\0’
注:数组元素之间用,逗号隔开。
注意区分以下代码:
代码1:
#include<stdio.h>
int main()
{char arr1[] = { 'a','b','c'};char arr2[] = "abc";printf("%s\n", arr1);printf("%s\n", arr2);return 0;
}
运行结果:
%s——打印字符串,直到遇到‘\0’才停止。
每一个字符串末尾隐藏放了个'\0'。
代码2:
#include<stdio.h>int main()
{char arr1[4] = { 'a','b','c' };char arr2[] = { 'a','b','c' };printf("%s\n", arr1);printf("%s\n", arr1);return 0;
}
运行结果:
注:char未初始化的元素默认'\0'
1.3 一维数组的使用
知识点:
1、数组是使用下标来访问的,下标是从0开始的(下标标明了元素在数组中的位置)
2、[]在这是下标访问操作符(所以里面可以是变量)
3、数组的大小可以通过计算得到
如:
int arr[10];
sz=sizeof(arr)/sizeof(arr[0]);
代码实例如下:
#include<stdio.h>int main()
{int arr[] = { 0,1,2,3,4,5 };int sz = sizeof(arr) / sizeof(arr[0]);//计算元素个数//1、顺序打印int i = 0;//下标for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");//逆序打印for (i = sz - 1; i >= 0; i--){printf("%d ", arr[i]);}printf("\n");return 0;
}
运行结果:
说明:
数组的使用与变量相似,但也有区别!
1、数组必须先定义,后使用。
2、数组元素只能逐个引用,而不能一次引用整个数组(字符串除外)。
3、 当逐个使用数组中的每一个元素时,通常借助for循环语句。
1.4 一维数组在内存中的存储
探讨数组在内存中的存储:我们打印每个元素的地址看看
#include<stdio.h>
int main()
{int arr[6] = { 0,1,2,3,4,5 };//打印每一个元素的地址int i = 0;for (i = 0; i < 6; i++){printf("%p\n", &arr[i]);//%p-专门用来打印地址的}return 0;
}
运行结果:
由于数组在内存连续存放,所以我们知道数组的一个地址就可以顺藤摸瓜找到其他元素。
代码如下:
#include<stdio.h>
int main()
{int arr[6] = { 0,1,2,3,4,5 };int * p = &arr[0];int i = 0;for(i=0;i<6;i++){printf("%d ", *(p+i));}return 0;
}
运行结果:
2. 二维数组的创建和初始化
2.1 二维数组的创建
//二维数组创建
int arr[3][4];//定义一个3行4列的整形数组
char arr1[2][3];
说明:
①二维数组可以看成一个矩阵(先行后列)
②元素个数=行数*列数
③存储一个二维数组所需的内存字节数:
总字节数=sizeof(类型)*行数*列数
2.2 二维数组的初始化
1、按数组的排列顺序对各数组元素赋初值:
int arr1[2][3]={1,2,3,4,5,6};
2、分行给二维数组赋初值(一个花括号{}表示一行)
int arr2[2][3]={{1,2},{4}};
3、注意:二维数组如果初始化,行是可以省略的,但列不能省略!
int arr3[][2]={1,2,3,4,5,6};//元素个数=行数*列数—》行数=3
2.3 二维数组的使用
二维数组的使用也是通过下标的方式(访问)。
说明:
1、行和列的下标也是从0开始的。
2、确定行和列的下标就能找到元素了。
3、注:数组元素只能逐个引用,而不能一次引用整个数组。(for嵌套)
代码实例:
#include<stdio.h>int main()
{int arr[3][4] = { 0,1,2,3,4,5,6,7,8,9,10,11 };//打印3行4列的数组int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 4; j++){printf("%d ", arr[i][j]);}printf("\n");}return 0;
}
2.4 二维数组在内存中的存储
像一维数组一样,这里我们打印二维数组的每一个元素的地址看看:
#include<stdio.h>int main()
{int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };//打印二维数组每一个元素的地址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;
}
结论:二维数组在内存也是连续存储的。
理解:二维数组是连续的。
1、看成连续的一维数组。
#include<stdio.h>int main()
{int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };//1、下标法打印int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 4; j++){printf("%d ", arr[i][j]);}}printf("\n");//2、指针法int* p = &arr[0][0];int k = 0;for (k = 0; k < 12; k++){printf("%d ", *(p + k));}printf("\n");return 0;
}
运行结果:
2、 把每一行看成一维数组(即:二维数组也是一维数组的数组(集合))
应用:
计算行数:sizeof(arr)/sizeof(arr[0])
计算列数:sizeof(arr[0])/sizeof(arr[0][0])
总结:知道数组在内存中是连续存放的,我们就可以用指针访问数组元素。
3. 数组的越界
数组的下标是有范围限制的。
数组的下标规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1.
所以数组的下标小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。
C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味程序就是正确的。
所以程序员写代码时,最好自己做越界的检查。
代码实例:
#include<stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };//下标的范围0-9int i = 0;for (i = 0; i <= 10; i++)//下标大于9了{printf("%d ", arr[i]);}printf("\n");return 0;
}
这个时候我们发现当i=10时,数组越界了,但是编译器不报错!
运行结果:
当我们打印数组元素,发现有不是数组范围的值,经验告诉我们很可能数组越界了。
当出现以下错误,一定是数组越界了。
注:二维数组的行和列也可能存在越界。(越界就如:生活中没抓到的小偷,就不是罪犯吗?)
4. 数组作为函数参数
我们在写代码的时候,会将数组作为参数传给函数。
将一个整形数组排序。
排序:冒泡、选择、插入、快速...
在这我先讲个简单的冒泡排序
冒泡排序:
4.1 冒泡排序函数的错误设计
#include<stdio.h>void 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 - 1 - i; j++){if (arr[j] > arr[j + 1])//是否升序{int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}}int main()
{int arr[10] = { 3,6,9,0,8,5,2,1,4,7 };//写一个函数对数组进行排序Sort(arr);//能否正确排序?int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr[i]);}printf("\n");return 0;
}
运行结果:
我们发现数组并未排序成功,这是为什么呢?
通过调试,我们发现在函数内部sz=1。为什么呢——难道数组作为函数参数的时候,不是把整个数组传递过去?
接下来理解数组名。
4.2 数组名是什么?
#include<stdio.h>
int main()
{int arr[10] = { 0 };printf("%p\n", arr);printf("%p\n", &arr[0]);return 0;
}
运行结果:
结论:数组名是数组首元素的地址。
但是有两个例外:
1、sizeof(数组名),这里的数组名是表示整个数组,计算的是整个数组的大小,单位是字节。
2、&数组名,这里的数组名是表示整个数组,&数组名取出的是数组的地址。
#include<stdio.h>
int main()
{int arr[10] = { 0 };printf("%p\n", arr);printf("%p\n", arr+1);printf("%p\n", &arr);printf("%p\n", &arr + 1);return 0;
}
运行结果:
虽然arr和&arr值一样,但是含义是不同的!
4.3 冒泡排序函数的正确设计
当数组传参的时候,实际上只是把数组的首元素的地址传递过去了。所以即使在函数参数部分写成数组的形式:int arr[]的本质是一个指针:int * arr。(所以在sort函数内部sz=1,因为在函数内部的sizeof(arr)的结果是4)
改正:
#include<stdio.h>void Sort(int arr[], int sz)
{int i = 0;for (i = 0; i < sz - 1; i++){int j = 0;for (j = 0; j < sz - 1 - i; j++){if (arr[j] > arr[j + 1]){int t = arr[j];arr[j] = arr[j + 1];arr[j + 1] = t;}}}
}int main()
{int arr[] = { 3,6,9,0,7,2,1,5,8,4 };int sz = sizeof(arr) / sizeof(arr[0]);Sort(arr, sz);int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");return 0;
}
运行结果:
补充:
arr[i] <---->*(arr+i)
&arr[i] <----->arr+i