C语言:批量初始化二维整型数组及二维整型数组作函数参数的重要知识点[C_006]

目录

概述:二维整形数组和二维字符数组在处理过程中有些微妙的差异值得注意,否则容易出错。

1、二维整形数组的初始化

2、常见场景:把二维整形数组的元素初始化为相同值

3、应用场景,二维数组作函数的参数


概述:二维整形数组和二维字符数组在处理过程中有些微妙的差异值得注意,否则容易出错。

1、二维整形数组的初始化

如果未对数组进行初始化,其内部元素的值是未知的。

可以在定义的时候进行初始化:

int A[3][3] = {0};    // 首行的首元素初始化为0
int A[3][3] = {1};    // 首行的首元素初始化为1

初始化后的结果为:

A = {0 0 0

        0 0 0

        0 0 0}

 也可以指定行进行初始化:

int A[3][3] = {{1}, {0, 2}, {1, 2, 3}};

这样初始化之后的结果为:

A = {1 0 0

        0 2 0

        1 2 3}

 这里有个结论:只要对整形数组进行了初始化,对数组内没有指定值的元素,都会自动补零填充,如果没有进行初始化,编译器一般不会自动进行初始化。

2、常见场景:把二维整形数组的元素初始化为相同值

在处理字符数组(字符串)时,我们常常会用到memset这个函数来把某一维或多维字符数组设为某一字符,常见的就是'\n'。

但是对于二维甚至多维整形数组,是否可以这样操作呢?

// 函数原型
void *memset(void *str, int c, size_t n);

// 参数
// str -- 指向要填充的内存块。
// c -- 要被设置的值。该值以 int 形式传递,但是函数在填充内存块时是使用该值的无符号字符形式。
// n -- 要被设置为该值的字符数。

先回答上面的问题:除了0和-1可以用这个函数设值外,其他的整数都会发生错误。

分析:

原因:memset函数是按字节对内存进行填充的,第二个参数的值最大127(一字节最大的有符号正整数01111111),如果输入的数长度超过了一字节,高位会被截断。

基于以上原因,对诸如32位正整数用0和-1进行设置时:

int Num;
// 用0
memset(&Num, 0, sizeof(Num));

// 用-1
memset(&Num, -1, sizeof(Num));

Num在内存中占4个字节,所以会写4个0的机器码到&Num开始的4个内存单元中,数字在计算机中是以补码的形式存的,一字节有符号整数0的补码为0000 0000,所以在内存中的4字节Num为:

->&Num(起始地址)

0000 0000

0000 0000

0000 0000

0000 0000

该4字节整数依然是0

同理,一字节有符号整数-1的补码为1111 1111,所以调用函数memset后,内存中的4字节Num为:

->&Num(起始地址)

1111 1111

1111 1111

1111 1111

1111 1111

该4字节数恰好也是32位有符号数-1的补码,该数依然是-1

举个反例,如用1来设置,会出现什么情况呢?

int Num;
// 用1
memset(&Num, 1, sizeof(Num));

一字节有符号整数1的补码为0111 1111,所以调用函数memset后,内存中的4字节Num为:

->&Num(起始地址)

0111 1111

0111 1111

0111 1111

0111 1111

该4字节数为0x7f7f 7f7f,十进制为2,139,062,143

 可见,结果已经不是我们期待的。

3、应用场景,二维数组作函数的参数

之所以会提出这一条,是因为我在写程序时遇到了这个问题,平时二维整型数组用得少,也自认为对C语言指针融会贯通了,不曾想到真正遇到的时候就出问题了,可见,指针值得去多品一品!

言归正传,简单举个例子,有这样一个函数,负责打印这个二维数组。

void PrintAll(int ???A???, int len)
{
    for (int i = 0; i < len) {
        for (int j = 0; j < len; j++) {
            printf("%d ", A[i][j]);
        }
        printf("\n");
    }
}

int main()
{
    int A[3][3] = {0};
    int len;

    len = sizeof(A) / sizeof(int);
    PrintAll(A, len);

    return 0;
}

我们需要把二维数组A传给PrintAll这个函数,在main这一侧都没出意外,当时问题出在了函数形参列表中二维数组的表示上。

有如下几种错误写法:

// 1.
void PrintAll(int **A, int len);

//2.
void PrintAll(int A[][], int len);

//3.
void PrintAll(int *A[], int len)

第一种:这是一个变量,一个指针变量,指向的内容也是一个指针,什么指针,int指针;这个A仅仅表示一个指针,A+1对32位操作系统来说是内存地址加4,对64位来说是内存地址加8。

第二种:这样写看似没问题,看似不应该有问题,但是有一点,定义数组时需要明确数组长度,有同学可能要说了,这里不是定义,这只是形参列表,类型对上就行。但它好歹是个数组,在实参过来的时候,就相当于用实参对其进行初始化,既然是初始化,不妨看看二维数组初始化的写法有哪些。很明显:这位同学是看书不仔细。这个是形式错误,语法错误。

// 1.正确
int A[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

// 2.正确
int A[][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

// 3.错误
int A[3][] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

// 4.错误
int A[][] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

第三种:这是对指针变量种常见困惑梳理不清导致的,即老生常谈的指针数组和数组指针问题:

在这里,int *A[],*和int结合,表示指向int类型的指针,而A是和[]结合的,表示这个变量是个数组,合在一起就是,定义了一个数组A,A的元素是什么呢,是指针,说具体点,是指向int类型数据的指针。

究其本质,A在这里就是一个一维的指针数组,A表示这个数组的首地址,同样,由于它的元素是指针,所以和第一种一样,A+1对32位操作系统来说是内存地址加4,对64位来说是内存地址加8。

再看main函数中的实参:

int A[3][3] = {0};
int len;

len = sizeof(A) / sizeof(int);
PrintAll(A, len);

我们期望传入的A是一个二维数组。

其实回归本源,还是看我们要传的是啥,A到底表示个啥。

知识点也正在这里:A是啥?二维数组名。

二维数组名的含义?对于这个问题,先问数组名是啥?首元素的地址。再问二维数组的“元素”是啥?是每一行一维数组。

所以,二维数组名的含义就是首行地址,首行地址就是首行在内存中的起点的地址,它虽然和首行第一个元素的地址及其二维数组首元素的地址是同一个地址,但区分首行和首元素在这里很有必要。

这里扩充一点:指针变量

对所有基本数据类型乃至自定义数据类型,我们都可以定义该类型的指针变量。这里想补充啥呢?——一个指针变量p,它到底包含了哪些信息:

1、变量的值:表示被指向对象在内存中的地址

2、指向对象的类型:决定了p+1时在内存单元的指向情况

回归正题,我们把二维数组A传入到函数中,归根结底,就是想通过对这个指针的递增操作,逐个访问到所指向的数据(注:下标操作p[i]和指针*(p+i)等价)。

回到上述例子,传入的A应该是首行的地址,A+1应该变成下一行:*(A+1) == A[1]

所以,形参应该是一个指向一行数组的指针,即应该是一个指向一维数组的指针,是数组的指针,数组指针的定义:

// 定义数和数组(数)
int a;
int a[3];

// 定义指针和指针数组
int *p;
int *p[];

// 定义数组指针
int (*p)[3];

可能有点绕,对比着数和数组的定义再看就会容易理解有些。为方便理解,不得不提一下数组,得先对数组是什么,组是什么有个清晰的认识,再看复杂的,也就不会头晕了。

数组,数的组合,本质上是组合,是一种东西的组合,里面的东西有相同的类型。

XXX的组

XXX的指针

我TM也晕了。。。。

来来来,这样看,忽略类型int,定义变量时就一个varName:int varName,这是一个整形变量

要它是一个指针,我们需要加上星号,即星号要修饰这个变量,即星号修饰的变量编译器认为是指针:* varName,即要得到指针,星号需要修饰变量名。

然而,对于数组指针,如果不加括号,星号*修饰了这个数组,即所有元素都成了指针,为了让它之修饰数组名,所以加上括号:int (*p)[]。

(*p)[]:这样,p是指针变量,p是数组名,所以这个指针是数组指针

*p[]:这样,p[i]是指针变量,所有p[i]组成一个数组,数组名是p,这个数字是指针数组

此外,由于实参传给函数形参的,归根结底还是一个地址,只是首元素的地址,所以区分这个地址在使用时按首行地址来处理还是按首元素地址来处理,就由形参类型定义,int (*p)[]虽然定义了这个形参是指向一个一维数组的指针,但是这还不够,还需要指明它指向的一维数组的大小:

// 1
void PrintAll(int (*A)[3], int len)
{
    for (int i = 0; i < len) {
        for (int j = 0; j < len; j++) {
            printf("%d ", A[i][j]);
        }
        printf("\n");
    }
}

int main()
{
    int A[3][3] = {0};
    int len;

    len = sizeof(A) / sizeof(int);
    PrintAll(A, len);

    return 0;
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值