欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

c++ fill 二维数组初始化_C/C++二维数组浅谈

程序员文章站 2024-02-14 17:06:10
...

C/C++ 二维数组

二维数组本来是一个很简单的知识点,但是因为C++的灵活度很高,导致不同的人,有不同的使用方法,并且在各自的语境下可能都能运行,但是换一个语境,可能就运行不了了。这里总结下二维数组的知识点,并且给出一个较为通用的一个使用方法。

二维数组构造方式:

首先给出二维数组的几种构造方式:

//自动分配,这种方式定义在栈内存上,不用手动销毁内存,缺点在于数组大小不能定义过大(栈内存)。
const int rows = 4;
const int cols = 5;
int p[rows][cols]; //编译器识别的类型为 int(*)[5], 
​//评论指出rows和cols可能不是在栈区。查了一下如果是被const修饰的全局变量,存储在常量区,其它情况有可能实在栈区,补充一下
(https://www.cnblogs.com/yanqi0124/p/3795547.html)
​
//手动分配,这种方式定义在堆内存上,需要手动销毁内存,否则会造成内存泄漏,可以定义变长
//c方式的分配
int **p = (int**) malloc(sizeof(int*)*rows);
for(int i=0;i<rows;i++){
    p[i] = (int*)malloc(sizeof(int)*cols);
}
for(int i=0;i<rows;i++){
    free(p[i]);
}
free(p) //之前漏了,现在加上
//c++方式的分配
int ** p = new int* [rows];
for(int i=0;i<rows;i++){
        p[i] = new int[cols];
}
for(int i=0;i<rows;i++){
        delete [] p[i];
}
delete [] p;
​
//也有部分的人,会直接用一维的指针,按照行排列,然后当作二维数组使用
int *p = new int [rows*cols];
//p[i][j]
cout<<p[i*cols+j];

总结一下总共三种的使用方式:

  1. 自动分配,定义在栈内存上,类型是(int*)[5]
  2. 手动分配,定义在堆内存上,需要手动释放,类型是 int **, 每一维度可以变长
  3. 使用一维数组进行定义,需要手动进行寻址,类型int*

二维数组作为函数参数:

将二维数组作为函数参数的时候,本质上,只要保证实参和形参的数据类型一致那就没啥毛病,因此相应的有这么几种函数形参定义方式。

双指针形式:

void TestFunc(int **p,int rows,int cols){
    //二维数组可以通过一个指针的指针进行传入,这种方式列可以是变长
    //int rows = 4;
    //int cols = 5;
    cout<<"test "int **p" "<<endl;
    for(int i=0;i<rows;i++){
        for(int j=0;j<cols+i;j++){
            cout<<p[i][j]<<" ";
        }
        cout<<endl;
    }
}

这种函数传入形式,当然要求我们定义的实参也就是双指针形式,也就是第二种手动分配方式。

单指针形式:

void TestFunc3(int *p,int rows,int cols){
    cout<<"test " int *p" "<<endl;
    for(int i=0;i<rows;i++){
        for(int j=0;j<cols+i;j++){
            cout<<*(p+i*cols+j)<<" ";
        }
        // 这里还有一个地方就是,即便数组超限,它没有报错,只是输出了一堆的无效值。这个他妈的是最恼火的地方了啊。
        cout<<endl;
    }
}

这种形式的话,要求传入的实参是单指针

(int *)[5] 形式:

void TestFunc2(int (*p)[5],int rows,int cols){
    cout<<"test "int (*p)[rows]" "<<endl;
    for(int i=0;i<rows;i++){
        for(int j=0;j<cols+i;j++){
            cout<<p[i][j]<<" ";
        }
        cout<<endl;
    }
}

这种形式还有几种变种,比如 "int p[] [5]", 但是这种方式都会要求指定第二维度的长度,这是因为二维数组在内存的存储方式是行存储的,对于一个p[i] [j]的数组,实参仅仅传递了一个起始地址,要获取任意位置的地址,寻址方式为:p+i*cols+j。站在编译器的角度,你必须告诉它列长,才能够访问任意位置的地址。

c++ fill 二维数组初始化_C/C++二维数组浅谈

实参形参之间的相互转化:

本来吧,各自按照实参形参的定义,好好的传就可以了,但是呢,总有一些大神,会不按照常理出牌,定义了int[rows] [cols]格式的,但是却喜欢传入一个*p或者**p的形参函数。导致明明简单的问题变得复杂起来了。实际上我们可以把事情看得简单一点,函数的定义和函数的构造仍然只有三种,只不过多加了几种转换:

1. p[][5] -> int* p
   *(p+i * cols+j)
2. int p[][5] -> int** p
   *((int * )p+i * cols+j)”
3. int **p -> int *p
   *((int*)p+i*cols+j)
4. int *p -> int **p

上述左边是实参类型,右边是形参类型。经过实验,1,2, 3是可行的,但是转换后不能使用p[i] [j]访问,需要手动的进行寻址,具体的寻址方式已经写上去了,4的转换是不可行的,是因为一维数组已经缺少了列信息,不可能恢复到二维数组。