一、数组的定义
1、定义
数组:指针的特殊化。
它也是内存分配(空间申请)的一种形式,而不是一种全新的数据结构。
其目的就是为了定义一个连续的空间。
2、2个属性:
大小
-
读取方式
数据类型 数组名[m]: []:把数组名升级为一个连续空间 m:连续空间的个数。 数据类型:表示内存以何方式切割。 int a[100]; // 100个int这么大的连续空间 a[10] // 使用方式与指针是一样的
(1)m 就是一个建议符,只是在申请的时候用一下。后面使用的时候,编译器无法知道[]
里面的数是否越界。
int a[100]; // 理论上,a的空间只有100个,但是只是建议;还是可以越界的。
(2)经典错误1:数组名是一个常量符号,只是一个标签常量,是无法改变的,一定不要放到=
的左边
char buf[100];
buf = "hello world"; // 错误!
buf++; // 错误!
数组名是一个常量,指针是一个变量。
二、数组空间的初始化(数组空间的赋值问题)
原理:按照标签逐一赋值。(空间的赋值没有投机取巧的办法,只能这样做)
int a[10];
a[0] = x;
a[1] = y;
...
问题:
但如果这样赋值,程序员的工作量很大。
偷懒办法:让编译器进行一些自动处理,帮助程序员写如上的逐一赋值的程序。
方法:空间在定义时,就告知编译器初始化情况,这叫空间的第一次赋值。
int a[10] = 空间;
C语言本身/CPU内部本身,一般不支持空间和空间的拷贝,只支持int、int(4B)等之间小的拷贝。
int a[10] = {10, 20, 30}; -- > 学习arm汇编后,可以进行反汇编。
其内部相当于执行一个拷贝的过程。只是不同编译器调用不同的函数来做拷贝。
这句定义,其实CPU还是做了:
a[0] = 10;
a[1] = 20;
a[2] = 30;
a[3] = 0 / 随机值; (不同编译器可能有所不同)
...
所以,一定不要这样认为:数组初始化一步就可以完成了!
数组空间的初始化和变量的初始化,本质上是不同的。
尤其在嵌入式的裸机开发中,数组空间的初始化往往需要一些库函数的辅助,或者程序员的人为设计。
1、字符空间(软件的最小空间:char)
char buf[10] = {'a','b','c'};
// 1. buf如果当成普通内存来看,这样写没有问题
// 2. buf如果当成一个字符串来看,最后一定加上一个'\0'
字符串的重要属性:结尾一定有个'\0'
(1)buf当成字符串的写法:
char buf[10] = {'a','b','c', '\0'};
char buf[10] = {"abc"};
// buf当成字符串最合理的写法
// C语言编译器看到双引号,就会自动在末尾加上'\0'
char buf[10] = "abc";
char buf[] = "abc";
(2)经典错误2:
char buf[10] = "abc";
// buf有空间,再把abc逐一地拷贝到buf中
// 通过初始化方法完成了常量区向变量区的拷贝。
// 因此,对buf变量区的值进行修改是可以的;但无法对“abc”常量区进行改动。
buf[2] = 'e'; //对变量区进行修改,没有问题
char *p = "abc";
// p直接指向abc的地址
p[2] = 'e'; // 错误!不能对常量区进行修改
(3)经典错误1:数组名是一个常量符号,只是一个标签常量,是无法改变的,一定不要放到=
的左边。
char buf[100] = "abc"; // 数组空间的初始化,是空间的第一次赋值,可以直接这样写
buf = "hello world"; // 错误!
// 想把空间的值变为其它,这是进行空间的第二次赋值,就不能直接这样写了,只能逐一处理:
buf[0] = 'h';
buf[1] = 'e';
...
buf[n] = 'd';
buf[n+1] = 0;
C语言编译器只支持第一次空间赋值(初始化)时,直接=
来一次赋值;第二次开始,必须进行逐一赋值。
问题:
第二次赋值,是很多人都有的需求,而逐一赋值工作量太大啦!
鉴于此,C语言提供了一套字符拷贝函数。
2、字符拷贝函数(strcpy、strncpy)
字符拷贝函数:内存空间和内存空间的逐一赋值功能的一个封装体。
注意:一旦空间中出现了0这个特殊值,字符拷贝函数就将结束。
也就是说,只有遇到\0
,strcpy和strncpy才会结束拷贝;否则会一直拷贝。
(1)strcpy
char *strcpy(char *dest, const char *src);
使用strcpy实现第二次空间赋值:
char buf[10] = "abc";
// buf = "hello world";
strcpy(buf, "hello world"); // 使用strcpy实现了第二次空间赋值!
该函数存在严重的内存泄漏问题,在工程中绝对不能使用!
char buf[10] = "abc";
strcpy(buf, "hello world, i am lixingzhi. How are you ? ...");
// buf只有10个连续空间,但是strcpy时字符串的字符多于10个,导致内存泄漏
(2)strncpy
char *strncpy(char *dest, const char *src, size_t n);
3、非字符空间
字符空间:可以使用ASCII码来解码的空间,其目的是让人看的(%s
看字符空间的内容,人可以看懂)
字符空间有字符串和结束标志(\0)。
非字符空间:
数据采集,是8bit或其倍数为单位来进行采集的。
所以需要开辟一个存储这些数据的空间。
char buf[10]; // 看到char,第一反映就是:一定是个string(字符串)
unsigned char buf[10]; // 看到unsigned char,一定是个data(普通数据,非字符串)
注意:非字符空间的第二次赋值,不能用strcpy和strncpy:
unsigned char *p = sensor_base; // sensor的数据
strcpy(buf, p);
// strcpy遇到\0才会结束。sensor_base里很可能没有\0,或者刚开始就是低电平(0)
问题:
字符空间的拷贝,结束标志为\0
;那么非字符空间拷贝的结束标志是什么?
非字符空间没有结束标志,只能定义拷贝的个数。
因此其拷贝的三要素:
- src
- dest
- 拷贝的个数
4、memcpy
// man memcpy:
#include <string.h>
void *memcpy(void *dest, const void *src, size_t n);
// void *:非字符空间标识符
// n:非字符空间的大小
例1:
int buf[10];
int sensor_buf[100];
// memcpy(buf, sensor_buf, 10); 错误!! 10是10个字节(char)
memcpy(buf, sensor_buf, 10 * sizeof(int)); // 拷贝的是10个int大小
例2:
unsigned char buf[10];
unsigned char sensor_buf[100];
memcpy(buf, sensor_buf, 10 * sizeof(unsigned char));
三、用指针表示数组
1、一维数组与一级指针
int b[100];
int *p1 = b; // 数组名就是数组首地址,可以赋给一个指针
2、指针数组与多级指针(二级指针)
int b[100]; // 100个空间都存的是int整型值
// 指针数组
char *a[100]; // 100个空间都存的是*(指针),指针对取的方式是char(一个字节一个字节地操作)
sizeof(a) = 100 * 4; // 1个地址4个字节
// 二级指针
char **a ;
// 指针数组和二级指针,都通过a[0]、a[1]这样的方式访问
3、多维数组(二维数组)
(1)Q:二维数组与二级指针有关吗?
// 定义一个指针,指向int b[5][6]的首地址
// 错误!:
int **p2 = b;
例1:
// 001.c
#include <stdio.h>
int main()
{
int a[5][6]; // 二维数组
int **p2 = a; // 二级指针
return 0;
}
二维数组与二级指针没有一点关系,完全不一样。
二级指针:只是描述地址的线性关系,是地址的一个存储器。
二维数组:一块一块地读内存。
(2)Q:二维数组该如何用指针表示?
int a; // 一个int型变量a
int *p; // 一个int型指针p
int a[5];
int *p[5];
int (*p)[5];
例2:
// 002.c
#include <stdio.h>
int main()
{
int a[5][6];
int (*p2)[6] = a; // 指针表示二维数组
return 0;
}
(3)多维数组
int b[2][3][4]; // 多维数组
int (*p)[3][4] = b; // 指针表示多维数组