数组(C语言)

一、数组的定义

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]这样的方式访问
char *a[100]首地址被指针指向,就成了char **a

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;
}
无效的指针类型:p2读内存的方式,和a读内存的方式是不一样的
二维数组与二级指针没有一点关系,完全不一样

二维数组与二级指针没有一点关系,完全不一样。
二级指针:只是描述地址的线性关系,是地址的一个存储器。
二维数组:一块一块地读内存。

(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;
}
这次编译没有警告了,说明a[5][6]读内存的方式,和(*p2)[6]读内存的方式是一样的

(3)多维数组

int b[2][3][4];    // 多维数组
int (*p)[3][4] = b;    // 指针表示多维数组
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容