一维数组的创建和初始化
数组的创建
数组是一组相同类型元素的集合。数组的创建方式:
type_t arr_name [const_n]
type_t 是指数组的元素类型
const_n 是一个常量表达式,用来指定数组的大小
数组创建的实例
创建一个数组存放10个整型
正确
int arr[10];
错误
int n = 10;
char ch[n];
数组创建,[ ]中要给一个常量才可以,不能使用变量。
数组的初始化
数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。
type_t arr_name [const_n]={表达式1, 表达式2,...,表达式n}
上面是标准的数组的初始化格式,下面我们看几个例子
int arr1[10] = {1,2,3}; //注释1
int arr2[] = {1,2,3,4};
int arr3[5] = {1, 2, 3, 4, 5};
char arr4[3] = {'a', 98, 'c'}; //注释2
char arr5[] = {'a', 'b' , 'c'};
char arr6[] = "abcdef"; //注释3
注释1:这种初始化叫不完全初始化,剩下的元素会默认初始化为0
注释2:arr4中虽然第二个元素我们写的是98,但存入内存的依然为abc,98被解析为b,因为b的ASCII值是98。
注释3:双引号引起来的叫字符串,字符串是一种以‘\0’结尾的字符数组。arr6中放入数组有7个字符,因为最后还要加上一个’\0’。字符串可以通过字符型数组来存放。
数组在创建的时候如果不指定数组的确定大小就初始化,数组的元素个数会根据初始化的内容来确定。但是对于下面的代码要区分,在内存中如何分配。
char arr1[] = "abc";
char arr2[3] = {'a','b','c'};
像arr1这种将字符串"abc"存放在字符型数组的形式,等价于
char arr2[] = {'a','b','c','\0'};
补充:
sizeof与strlen的区别
char arr6[] = "abcdef";
printf("%d\n", sizeof(arr6));
printf("%d\n", strlen(arr6));
运行结果
解析:
sizeof是计算arr6所占空间的大小,字符串存了7个元素。strlen求字符串长度找到\0就停止,并且\0不算。
- sizeof和strlen没什么关联
- strlen是只能对字符串求长度,只跟字符串有关。
- sizeof计算变量、数组、类型的大小,单位字节。
- strlen是库函数,使用要引用头文件。
- sizeof是操作符,使用不用引用头文件。
不同存储方式sizeof和strlen的值
char arr1[] = "abc";
char arr2[] = {'a' , 'b' , 'c' };
printf("%d\n", sizeof(arr1));//arr1所占空间大小 abc\0
printf("%d\n", sizeof(arr2));//arr2所占空间大小 abc
printf("%d\n", strlen(arr1));//arr1长度
printf("%d\n", strlen(arr2));//输出随机值
运行结果
一维数组的使用
对于数组的使用我们之前介绍了一个操作符:[ ],下标引用操作符。它其实就是数组访问用到的操作符。我们来看代码:
//想要打印arr[3]
int main()
{
char arr[] = "abcdef";//[a][b][c][d][e][f][\0]
printf("%c\n",arr[3]);
return 0;
}
运行结果
//想要打印整个数组
int main()
{
char arr[] = "abcdef";//[a][b][c][d][e][f][\0]
int i = 0;
for (i = 0; i <(int)strlen(arr); i++) //strlen默认返回无符号数
{
printf("%c ", arr[i]);
}
return 0;
}
运行结果
strlen默认返回一个无符号数,如果编译器报警告在strlen前面加上一个强制类型转换(int)。
当然也可以改成下面的形式:
int main()
{
char arr[] = "abcdef";//[a][b][c][d][e][f][\0]
int i = 0;
int len = strlen(arr)
for (i = 0; i < len; i++)
{
printf("%c ", arr[i]);
}
return 0;
}
上面是一个字符串数组打印,那么整型数组如何打印呢?
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,0};
//计算数组的元素个数
int sz = sizeof(arr)/sizeof(arr[0]);
//对数组内容赋值,数组是使用下标来访问的,下标从0开始,所以:
int i = 0; //做下标
//输出数组内容
for(i=0; i<sz; i++)
{
printf("%d",arr[i]);
}
return 0;
}
运行结果
一维数组在内存中的存储
接下来我们探讨数组在内存中的存储。看代码:
int main()
{
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int sz = sizeof(arr)/sizeof(arr[0]);
int i = 0;
for (i=0; i<sz; i++)
{
printf("&arr[%d] = %p\n", i, &arr[i];)
}
return 0 ;
}
运行结果
int型占4个字节,观察地址相邻元素地址相差4。说明数组在内存中是连续存放。
二维数组的创建和初始化
二维数组的创建
//数组创建
int arr[3][4];//整型三行四列
char arr[3][5];//字符串型 三行五列
double arr[2][4];
二维数组的初始化
//数组初始化
int arr1[3][4] = {1,2,3,4,5}; //不完全初始化
int arr2[3][4] = {{1,2,3},{4,5}};
int arr3[][4] = {{2,3},{4,5}};//列绝对不能省略,行可以省略
arr1数组中1,2,3,4存储在第一行,5在第二行。
arr2数组中1,2,3存储在第一行,4 ,5在第二行。
arr3组中2,3存储在第一行,4 ,5在第二行。二维数组列绝对不能省略,行可以省略。
二维数组的使用
二维数组的使用也是通过下标的方式。看代码:
int main()
{
int arr[3][4] = {{1,2,3},{4,5}};
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;
}
运行结果:
二维数组在内存中的存储
像一维数组一样,这样我们尝试打印二维数组的每一个元素地址。
int main()
{
int arr[3][4] = { {1,2,3},{4,5} };
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;
}
运行结果
观察二位数组地址可知在地址二维数组在内存中是连续的。
可以把二位数组想象成由一维数组组成,上图可以把arr[3][4]想象成三个一维数组。
数组作为函数参数
往往我们在写代码的时候,会将数组作为参数传给函数,比如:我要实现一个冒泡排序(这里要讲算法思想)将一个整型数组排序。通过冒泡排序来学习数组作为函数参数是怎样传递的。
冒泡排序的思路
假设数组有n个元素,采用冒泡排序法对该数组元素进行排序。从下标为0的元素开始,比较相邻的元素开始,比较相邻的两个元素大小,每次比较如果前面的元素大于后面的元素,则交换这个两个元素的值。
第一轮,从下标0的元素到下标为n-1元素,依此比较相邻两个数组元素的大小。比较n-1次后,n个数中最大的一个数被交换到最后一个数的位置上,这样大的数“沉底”,小的数“浮起”。
第二轮,仍然从下标为0的元素开始,到下标为n-2的数组元素为止,对余下的n-1个数重复上述过程,比较n-2次后,将n个数中第二大的数交换到下标为n-2的倒数第二个位置上。
依此类推,重复以上过程n-1次,分别将n个数中最大的数到第n-1大的数“沉底”到相应位置上,则n个数全部排序完毕。
下面我们开始写代码
void bubble_sort(int arr[],int sz)
{
int i = 0; //确定冒泡排序的趟数
//int sz = sizeof(arr) / sizeof(arr[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])
{
int tmp = arr[j];
arr[j] =arr[j+1];
arr[j + 1] = tmp;
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
int main()
{
int arr[] = {9,8,7,6,5,4,3,2,1,0};
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);//sz要直接在主函数里求出,传进冒泡函数
//arr是数组,我们对数组arr进行传参,实际上是数组首元素的地址
//计算机对数组传参不是把数组整个传到函数里,如果数组有一万个参数直接传是会造成浪费的
bubble_sort(arr,sz); //调用冒泡排序函数
for (i=0;i<sz ;i++)
{
printf("%d ", arr[i]);
}
return 0;
}
我们之前学过实参传给形参,形参是实参的一份拷贝,在主函数中调用冒泡排序函数将arr数组传给形参,现在arr数组有10个元素,按照我们之前学的,void bubble_sort(int arr[ ])就会复制10个元素,产生一倍多的浪费。如果元素个数是10000个呢?
其实数组传参不会这么傻的,我们对数组arr进行传参,实际上传的是数组首元素的地址。所以sz要在主函数里求出然后传到冒泡排序函数里,不要认为在冒泡函数里用int sz = sizeof(arr) / sizeof(arr[0])这个公式能求出来。
为了使冒泡排序函数减少不必要的运行。最好在每一趟冒泡排序前加一个flag,将flag设为1,假设这一趟要排序的数据已经有序。如果无序flag会被赋值为0,如果有序if (arr[j] > arr[j + 1])这句判断语句一次都不会被执行,flag不会被赋值为0,这一趟for循环结束,函数运行到if (flag == 1)直接运行break,退出大循环。输出结果。
总结:
每一趟冒泡排序都是从数组开头(也就是数组下标为0)开始比较大小。
每一趟比较如果过程中有两个元素恰巧顺序,两者不交换位置。我下面放个动图仔细观察一下。
数组大小一定要在主函数里求出再作为参数传进冒泡排序函数。
数组名是什么?
#include<stdio.h>
int main()
{
int arr[ ] = {1,2,3,4,5,6,7};
printf("%p\n", arr);
printf("%p\n", &arr[0]);
printf("%d\n", *arr);
return 0;
}
运行结果
结论:
数组名使数组首元素的地址。(有两个例外)
如果数组名是元素地址,那么:
int arr[10] = {0};
printf("%d\n",sizeof(arr));
运行结果
为什么输出的结果是:40?
int main()
{
int arr [ ] = { 1,2,3,4,5,6,7 };
printf("%p\n", arr);
printf("%p\n", &arr[0]); //数组首元素地址
printf("%p\n", &arr); //数组的地址
}
运行结果
printf("%p\n", arr);确实取出了第一个元素的地址。
printf("%p\n", &arr[0]);也确实取出第一个元素的地址。
printf("%p\n", &arr);显示是第一个元素的地址,但这个地址代表整个数组地址从这个地址开始。这种我们叫他数组的地址。
第三种和前两种完全不同,我们不妨为arr后面加上一个1。
int main()
{
int arr [ ] = { 1,2,3,4,5,6,7 };
printf("%p\n", arr);
printf("%p\n", arr+1);
printf("%p\n", &arr[0]);
printf("%p\n", &arr[0]+1);
printf("%p\n", &arr);
printf("%p\n", &arr+ 1);
return 0;
}
运行结果
观察结果前两种arr+1地址都加了4,而第三种地址直接增加了28(7个int型元素就是28)。
总结:
- sizeof(数组名)——数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位字节。
- &数组名,数组名代表整个数组,&数组名取出的是整个数组的地址。
除这两种情况外,所有的数组名都表示数组首元素的地址。
下一节操作符详解