目录
【数组的定义】:一组相同类型元素的集合。
【数组的下标】:数组每个元素都有对应下标,下标从0开始。
1 一维数组
1.1 一维数组的创建与初始化
1.2.1 一维数组的创建
type_t arr_name [const_n]
//即:元素类型 数组名 [数组大小]
//示例:
int arr[10];
char ch[20];
【注意点】
const_n通常是常量表达式,即:
int n = 10;
int arr[n]; //这种方式不能正常创建
但C99标准中引入了变长数组(数组长度可以通过变量确定)的概念,创建时可以使用变量,但这样的数组不能初始化。
这种情况也可以使用宏定义的方式:
#define N 10;
int arr[N];
1.2.2 一维数组的初始化
(1)完全初始化:数组中的所有元素在定义时都被指定了具体的值。
① 元素个数确定,元素值确定
int arr1[5] = {1, 2, 3, 4, 5};
char ch1[5] = {'a', 'b', 'c', 'd', 'e'};
② 只指定元素值
int arr[] = {1, 2, 3};
//数组大小为3
char ch1[] = {'a', 'b', 'c', 'd', 'e'};
//数组大小为5
char ch2[] = "abcde"
//数组大小为6,包含:abcde和\0
此时:arr为一个包含3个整形元素的数组,数组元素分别是:1,2,3。
因此,省略数组大小时必须初始化数组内容,因为这种情况下数组大小根据初始化内容确定!!!
关于字符数组详细内容请看第3章~~~
③ 只指定元素个数
int arr[10] = {0};
此时:arr为一个包含10个整形元素的数组,数组元素都被初始化为0。
(2)不完全初始化
① 只确定部分数组内容
int arr[5] = {1, 2, 3};
此时: arr前3个元素分别是:1,2,3,后面的2个元素为0。
【注意区分 int arr[5] = {1} 和 int arr[5] = {0}】
int arr[5] = {1}; 此时arr为一个包含5个整形元素,第一个元素为1,其余为0;
int arr[5] = {0}; 此时arr为一个包含5个整形元素,元素值全为0;
char ch1[5] = {'a', 'b', 'c'};
char ch2[5] = "abc";
此时:ch1前3个元素分别是:a b c,后面2个元素为'\0';ch2前3个元素分别是a b c,后面2个元素为'\0'。
关于字符数组详细内容请看第3章~~~
小结:整型数组不完全初始化会将未定义位置值初始化为0;字符数组不完全初始化会将未定义位置值初始化为\0。
1.2 一维数组的访问
数组通过 下标引用操作符[] 来访问内容,注意下标从0开始!
int arr[5] = {1, 2, 3, 4, 5};
printf("%d", arr[2]); //打印3
1.3 一维数组在内存中的存储
一维数组在内存中是连续存放的,地址由低到高变化。
【数组越界访问】
数组下标有范围限制,如果有那个元素,那么最后一个元素对应下表(n-1),如果访问下标超出0~(n-1)就产生越界访问。这种情况编译器不一定报错,但不意味着程序是对的。
2 二维数组
2.1 二维数组的创建与初始化
2.1.1 二维数组的创建
int arr[3][4]; //创建3行4列的整型数组
char arr[3][4];
double arr[3][4];
2.2.2 二维数组的初始化
(1)完全初始化:数组大小确定,元素值确定
int arr[4][5] = { {1, 2, 3, 4, 5}, {2, 3, 4, 5, 6}, {3, 4, 5, 6, 7}, {4, 5, 6, 7, 8} };
//创建4行5列整型数组
(2)不完全初始化
int arr[4][5] = { {1, 2}, {2, 3, 4}, {3, 4, 5, 6, 7}, {4, 5, 6, 7, 8} };
int arr[][5] = { {1}, {2, 3, 4}, {3, 4}, {4, 5, 6, 7, 8} };
二维数组不完全初始化时:未定义位置会初始化为0。
注意:二维数组可以省略行数,不能省略列数!!!
(3)其他初始化方式
int arr[4][5] = {1,2,3,4,5,6,7,8,9,10};
编译器会按照指定数组大小将已定义的内容填充进去,剩余位置填充为0;
所以此时arr,第一行元素为:1 2 3 4 5,第二行元素为:6 7 8 9 10,剩余两行元素全为0。
注意:这种初始化也是“可以省略行,不可以省略列”!
2.2 二维数组的访问
二维数组也通过 操作符[] 访问元素内容,注意:行列下标都从0开始!
2.3 二维数组在内存中的存储
二维数组在内存中也是连续存放的,地址由低到高变化。
二维数组可以理解为:存放 “一维数组” 的数组。
3 字符数组
3.1 字符数组的创建与初始化
- 字符数组的创建
char ch[10]; //创建一个可以存储10个字符的字符数组
- 字符数组的初始化
(1)字符串值直接初始化
char ch1[] = "abc";
//ch1中存放元素:a b c \0;
char ch2[4] = "abc";
//ch2中存放元素:a b c \0;
char ch3[3] = "abc";
//ch3中存放元素:a b c ;
在长度定义准确或未定义长度时,字符串末尾会加上'\0'。
(2) 逐个字符初始化
char ch1[] = {'a', 'b', 'c'};
//ch1中存放元素:a b c;
char ch2[] = {'a', 98, 'c'};
//ch2中存放元素:a b c;
//(ch3中将整数98用于初始化字符数组,此时98会被解释为字符值,而98对应ASCII为b,因此此处ch3[1]初始化为b。)
//这种情况下虽然会发生转换,但不建议这么定义!
char ch3[] = {'a', 'b', 'c', '\0'}
小结:字符串值直接初始化,通常末尾会有'\0';逐个字符初始化,如果不特意添加则不会有'\0'。
【字符串的结尾】:C语言中,字符串以 '\0'(空字符)作为结束标志。
C语言中很多库函数(如:printf、strlen......)在处理字符串时依赖 '\0' 来确定字符串长度。如果字符串缺少这个结束标志,使用这类函数会导致未定义行为!如下图所示
3.2 字符数组在内存中的存储
3.3 补充:字符串的两种定义方式
(1)使用字符数组:直接定义字符数组储存字符,这种情况编译器会自动加上结束符'\0'
int main() {
// 定义字符数组,自动添加结束符 '\0'
char str1[] = "Hello, world!";
printf("%s\n", str1); // 输出 "Hello, world!"
str1[0] = 'Y';
printf("%s\n", str1); //输出 "Yello, world!"
return 0;
}
- 实际数组包含13个字符和1个结束符'\0';
- 通过下标可以修改字符串内容。
(2)使用字符指针:通过字符指针指向一个字符串,字符串会在内存“代码段”存储,以'\0'结束
int main() {
// 使用字符指针指向字符串字面量
char *str2 = "Hello, world!";
printf("%s\n", str2); // 输出 "Hello, world!"
return 0;
}
- 字符指针指向的字符串存储在静态区中,不能被修改,并且空间在程序结束才会被操作系统回收。
4 数组大小的计算
4.1 sizeof操作符
sizeof用于获取数据类型(如:int、char、float、double、结构体......)or对象所占用的内存大小(以字节为单位)。
int main() {
printf("Size of int: %zu bytes\n", sizeof(int)); // 4
printf("Size of char: %zu bytes\n", sizeof(char)); // 1
printf("Size of float: %zu bytes\n", sizeof(float)); // 4
printf("Size of double: %zu bytes\n", sizeof(double)); //8
return 0;
}
【关于sizeof需要注意的点】
- sizeof是操作符,不是函数!
- sizeof计算字符串大小时,不依赖于字符串的结束符'\0'。(有'\0'则计入,没有也不影响)
- sizeof的计算发生在编译阶段
int main()
{
int a = 5;
short s = 11;
printf("%d\n", sizeof(s = a + 2)); //2 【sizeof通过类型确定s大小是short类型,大小为2】
//从数学角度看,a+2的值为整型,但一定要赋值给short类型的s,会发生截断,并且short类型占2字节
//截断后s的值应该是7
printf("%d\n", s); //11 【sizeof在编译期间就完成处理,而s的运算结果在可运行程序中计算】
return 0;
}
- sizeof并不会去访问变量的实际内容,而是根据类型信息确定变量在内存中需要占用的空间大小。
4.2 数组大小 & 元素个数 计算
int main() {
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
printf("%d\n", sz); //输出:10
}
sizeof(arr) :整个数组大小;
sizeof(arr[0]) :数组首元素大小,即每个元素的大小。
5 指针和数组 & 数组名代表的意义
- 通常情况下,数组名表示首元素地址。
数组名各种操作访问到的内容:
因此,*(arr+i) 和 arr[i] 等价 。
- 但是有以下两种特殊情况:
(1)sizeof(数组名)
数组名单独放在sizeof内部,此时 数组名 表示整个数组,计算出的是整个数组的大小。
(2)&数组名
此时数组名也表示整个数组,取出的是整个数组的地址。
这也代表着 &数组名 结合+-等操作访问到的是整个数组:
上面提到:二维数组可以理解为 存放一维数组 的数组 。结合这个知识点加深理解:
6 数组传参
数组作为参数传递给函数时,实际上是将数组的指针传递给函数,即传递的时数组的地址。
数组名在参数列表中会被隐式转换为指向其第一个元素的指针。
(1)一维数组传参
//方式一
void printArray(int arr[], int size) {
//函数内容略
}
//方式二
void printArray(int* arr, int size) {
//函数内容略
}
int main() {
int arr[] = { 1, 2, 3, 4, 5 };
int sz = sizeof(arr) / sizeof(arr[0]); // 计算数组的元素个数
printArray(arr, sz); // 传递数组和大小
return 0;
}
(2)二维数组传参
//方式一
void printArray(int arr[3][4]) {
//函数内容略
}
//方式二
void printArray(int arr[][4]) {
//函数内容略
}
int main() {
int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };
printArray(arr);
}
注意:二维数组传参时,形参列表中也是数组行信息可以省略,列信息不能省略!
在传参过程中,如果粗要用到数组的元素个数,要在传参前计算出结果,将结果作为参数传递给函数。
如果在被调用函数内部计算元素个数,会出现错误。如下图所示
7 指针数组
即:存放指针的数组
char* arr[5];
int* arr2[6];
示例:
8 数组指针
即:指向数组的指针
int (*p)[10];
- p是一个数组指针,指向一个大小为10的整型数组
- * :说明p是一个指针变量
- [10] : 说明数组有10个元素
- int :说明数组每个元素类型为int