嵌入式基础 C语言篇 数组.初阶
一、基本概念
- 逻辑:一次性定义多个相同类型的变量,并存储到一片连续的内存中
- 语法释义:
int buf[5];
- buf是数组名,即这片连续内存的名称
- [5] 代表这片连续内存总共分成5个相等的格子,每个格子称为数组的元素
- int 代表每个元素的类型,可以是任意基本类型,也可以是组合类型,甚至可以是数组
- 初始化:在定义的时候赋值,称为初始化
int buf[5] = {100,200,300,400,500}; // ok,正常初始化
- 图解:
通过buf+1得出的的地址,可以知晓buf的作用范围是多少
数组里面的元素的内存都是相邻的(也就是数组是一片连续的内存构成的)
#include <stdio.h>
int main(int argc, char const *argv[])
{
// (1)、一次性定义多个相同类型的变量 --- 语法释义(数组演化史 --- 带入创作者的角度去看看)
// 1、为什么要像下面的这种写法呢??(a、名字要有意义; b、名字会冲突; c、需要的名字越来越多)
// a、以往的写法:
int num1 = 100;
int num2 = 200;
int num3 = 300;
// b、现有的写法:
int num[100]; // 想象中的:100 int num;
num[0]; // 如何表示数组的元素(从0开始到100-1)
// ...
num[99];
// (2)、 存储到一片连续的内存中
// 1、num1、num2、num3其实他们三个变量的内存也是连续存储的(如何证明:打印其地址即可)
printf("num1的地址 == %p\n", &num1); // &符号是取变量的地址的意思
printf("num2的地址 == %p\n", &num2);
printf("num3的地址 == %p\n", &num3);
/*
地址分析:
num1的地址 == 0x7ffc85bc4294
num2的地址 == 0x7ffc85bc4298
num3的地址 == 0x7ffc85bc429c
说明:1个地址 == 1个字节,所以以上三个变量是相邻的
*/
// 2、数组在内存中的说明
int buf[5] = {100, 200, 300, 400, 500};
// A、数组的元素在一片连续的内存中
printf("buf[0]内存的值 == %d, buf[0]的地址 == %p\n", buf[0], &buf[0]);
// printf("buf[0]+1的地址 == %p\n", &buf[0]+1);
printf("buf[1]内存的值 == %d, buf[1]的地址 == %p\n", buf[1], &buf[1]);
printf("buf[2]内存的值 == %d, buf[2]的地址 == %p\n", buf[2], &buf[2]);
printf("buf[3]内存的值 == %d, buf[3]的地址 == %p\n", buf[3], &buf[3]);
printf("buf[4]内存的值 == %d, buf[4]的地址 == %p\n", buf[4], &buf[4]);
/*
内存值、地址解析:
buf[0]内存的值 == 100, buf[0]的地址 == 0x7fff52e71d80
buf[1]内存的值 == 200, buf[1]的地址 == 0x7fff52e71d84
buf[2]内存的值 == 300, buf[2]的地址 == 0x7fff52e71d88
buf[3]内存的值 == 400, buf[3]的地址 == 0x7fff52e71d8c
buf[4]内存的值 == 500, buf[4]的地址 == 0x7fff52e71d90
每个数组元素(int型),相隔4个地址,也就是4个字节,所以可以证明
数组里面的元素的内存都是相邻的(也就是数组是一片连续的内存构成的)
*/
// B、buf是数组名,即这片连续内存的名称(也表示数组首元素的地址)
// a、buf代表的地址
printf("buf代表的地址 == %p\n", buf);
// b、buf的作用范围(如何求一个地址的作用范围???让其+1,并将其和原先的地址进行比较即可)
printf("buf+1代表的地址 == %p\n", buf+1);
/*
解析:
buf代表的地址: 0x7fff52e71d80
buf+1代表的地址: 0x7fff52e71d84
所以可以得出buf的作用范围是4个字节,它和&buf[0]是相等的
*/
// c、&buf的作用范围
printf("&buf代表的地址 == %p\n", &buf);
printf("&buf+1代表的地址 == %p\n", &buf+1);
/*
解析:
buf代表的地址: 0x7fff52e71d80
buf+1代表的地址: 0x7fff52e71d94
所以可以得出&buf代表的作用范围是20个字节(刚好就是整个数组的大小)
*/
// (3)、数组的初始化:
int buf1[5] = {100, 200, 300, 400, 500}; // ok,正常初始化
// int buf2[5] = {100, 200, 300, 400, 500, 600}; // 不ok,越界了(会报一个警告: warning: excess elements in array initializer)
int buf3[5] = {100,200,300}; // ok,只初始化数组元素的一部分
int buf4[] = {100,200,300}; // ok,自动根据初始化列表分配数组元素个数
return 0;
}
二、数组元素的赋值和引用
- 存储模式:一片连续的内存,按数据类型分割成若干相同大小的格子
- 元素下标:数组开头位置的偏移量,a[0]引用第1个格子,a[1]引用第2个格子,以此类推
- 元素下标偏移量
- 示例代码:
#include <stdio.h> // 计算数组元素的个数 #define CAL_ARR_NUM(A) (sizeof(A)/sizeof(A[0])) // 整个数组的大小 / 数组的首元素的大小 == 数组元素个数 // 主函数 int main(int argc, char const *argv[]) { // 初始化 int buf1[5] = {1,2,3,4,5}; // int型数组的12345,和char型数组(字符串)的"12345"是不一样的 char buf2[5] = {'1','2','3','4','\0'}; // 等价于 char buf2[5] = "1234"; // (1)、单个赋值 buf1[0] = 100; buf1[1] = 200; buf1[2] = 300; buf1[3] = 400; buf1[4] = 500; // buf1[5] = 600; // 到此为止,后面的内存是非法区域(也就是你没有权限对其进行操作) printf("buf[5] == %d\n", buf1[5]); // (2)、循环赋值 unsigned long int ret = CAL_ARR_NUM(buf2); printf("数组buf2的元素个数 == %lu\n", ret); for (int i = 0; i < ret; i++) { buf2[i] = 'A'+i; printf("buf2[%d] == %c\n", i, buf2[i]); } return 0; }
三、其它类型的数组
(1)、多维数组
- 概念:若数组元素类型也是数组,则该数组称为多维数组
- 语法:
int buf[2][3]; // 相当于int [3] buf[2];
- 图解:
- 示例代码:
#include <stdio.h> int main(int argc, char const *argv[]) { // 一、二维数组 int buf[2][3] = {{1,2,3},{4,5,6}}; // (1)、二维数组各个地址的表示说明(推导) // 1、&buf的作用范围 printf("\n"); printf("&buf代表的地址 == %p\n", &buf); printf("&buf+1代表的地址 == %p\n", &buf+1); /* 解析(本地址和后面的地址不相干,是之前编译分配的地址): &buf代表的地址 == 0x7fff037540c0 &buf+1代表的地址 == 0x7fff037540d8 说明:c到d == 16, 0到8 == 8, 总:24字节 */ // 2、buf的作用范围 printf("\n"); printf("buf代表的地址 == %p\n", buf); printf("buf+1代表的地址 == %p\n", buf+1); printf("\n"); printf("&buf[0]代表的地址 == %p\n", &buf[0]); printf("&buf[0]+1代表的地址 == %p\n", &buf[0]+1); /* 解析(本地址和后面的地址不相干,是之前编译分配的地址): buf代表的地址 == 0x7fff24d28480 buf+1代表的地址 == 0x7fff24d2848c &buf[0]代表的地址 == 0x7fff24d28480 &buf[0]+1代表的地址 == 0x7fff24d2848c 说明:0到c == 12, 总:12字节 */ // 3、buf[0]的作用范围 printf("\n"); printf("buf[0]代表的地址 == %p\n", buf[0]); printf("buf[0]+1代表的地址 == %p\n", buf[0]+1); printf("\n"); printf("&buf[0][0]代表的地址 == %p\n", &buf[0][0]); printf("&buf[0][0]+1代表的地址 == %p\n", &buf[0][0]+1); /* 解析(本地址和后面的地址不相干,是之前编译分配的地址): buf[0]代表的地址 == 0x7ffc5d50d2f0 buf[0]+1代表的地址 == 0x7ffc5d50d2f4 &buf[0][0]代表的地址 == 0x7ffc5d50d2f0 &buf[0][0]+1代表的地址 == 0x7ffc5d50d2f4 说明:0到4 == 4, 总:4字节 */ // 4、buf[1]的作用范围 printf("\n"); printf("buf[1]代表的地址 == %p\n", buf[1]); printf("buf[1]+1代表的地址 == %p\n", buf[1]+1); /* 解析(本地址和后面的地址不相干,是之前编译分配的地址): buf[1]代表的地址 == 0x7ffc8c006c2c buf[1]+1代表的地址 == 0x7ffc8c006c30 说明:2c到30 == 4, 总:4字节 */ return 0; }
(2)、字符数组
- 概念:专门存放字符的数组,称为字符数组
- 示例代码:
#include <stdio.h> int main(int argc, char const *argv[]) { // 二、字符数组 // (1)、字符串数组的初始化 char s1[5] = {'a', 'b', 'c', 'd', 'e'}; // 字符数组,不是字符串(末尾没有'\0')(字符串是一个特殊的字符数组) char s2[5] = {'a', 'b', 'c', 'd', '\0'}; // 它既是字符数组,又是字符串 char s3[5] = {"nihao"}; // 字符数组(末尾没有'\0') // printf("%s\n", s3); // 此处因为没有'\0',有可能打印不出数据来 char s4[5] = {"niha"}; // 字符串(末尾有'\0') char s5[5] = "niha"; // 字符串的简写 // (2)、字符串数组的赋值和引用 // a、字符数组的赋值和引用和前面的普通数组的赋值一样(为什么?因为char型本质上就是单字节的int整型) // b、字符串的赋值,可以使用字符串处理函数(str开头的函数) // 栈区(可读可写) 常量区(可读不可写) char s6[256] = "吾爱有三,日月与卿,日为朝,月为暮,卿为朝朝暮暮"; // 初始化 printf("s6 == %s\n", s6); // 使用strcpy函数对字符串数组进行赋值 bzero(s6, sizeof(s6)); // 将s6数组清零(清sizeof(s6)这么大的空间) // 将常量区的数据,复制到栈区中(s6),后面修改s6的值,修改是栈区s6的值 strcpy(s6, "世界那么大,我想带你去看看"); printf("s6 == %s\n", s6); // 题: // 栈区(可读可写) 常量区(可读不可写) char *s7 = "nihao,shijie"; // 将常量区("世界...")的数据,通过s7指针复制到另一个常量区""nihao.." // strcpy(s7, "世界那么大,我想带你去看看"); // Segmentation fault (core dumped) return 0; }
四、数组语法解析
- 任意的数组,不管有多复杂,其定义都由两部分组成。
- 第1部分:说明元素的类型(type),可以是任意的类型(除了函数)
- 第2部分:说明数组名(x)和元素个数(N)
- 示例代码:
-
int a[4]; // 第一部分:int(元素的类型), 第二部分:a[4](数组名+元素个数) int b[3][4]: // 第一部分:int [4](元素的类型), 第二部分:b[3](数组名+元素个数) int c[2][3][4]; // 第一部分:int [3][4](元素的类型), 第二部分:c[2](数组名+元素个数) int *d[6]; // 第一部分:int *(元素的类型), 第二部分:d[6](数组名+元素个数) int (*e[7])(int,char); // 第一部分:int (*)(int,char)(元素的类型), 第二部分:e[7](数组名+元素个数)
- 图解:
@FeZit