二维数组的使用

目录

一、数组相关知识

二、二维数组传参

1、静态数组

2、动态数组

三、数组相关题目整理

1、第一题

2、第二题

3、第三题

4、第四题

5、第五题

6、第六题

7、第七题

四、参考

1、段错误

五、二维数组创建方式

1、malloc


一、数组相关知识

1、不能在方括号中用变量来表示元素的个数, 但是可以是符号常数或常量表达式

(针对这个还有需要解释的,符号常量就是用#define和const定义的常数,例如#define DATA_PI 3.1415926。常量表达式是例如array[2+1])。

2、数组名可以加+1,但不可以++。因为++就是给数组名赋值了,数组名不能赋值。

3、不可以在赋值语句中通过赋值运算符"="对字符数组整体赋值;

二、二维数组传参

二维数组传参的示例代码

【正例】

#include <stdio.h>
#include <stdlib.h>

bool findNumberIn2DArray(int** matrix, int matrixSize, int* matrixColSize, int target){
    for(int i = 0; i < matrixSize; i++)
    {
        for(int j = 0; j < *matrixColSize; j++)
        {
            int num = *((int*)matrix + i * 5 + j);
            if(*((int*)matrix + i * 5 + j) == target)
            {
                printf("true");
                return true;

            }
        }
    }
    printf("false");
    return false;
}

int main()
{
    int row = 5;
    int colum = 5;

    int target = 20;

    int array[row][colum] = {
        {1,   4,  7, 11, 15},
        {2,   5,  8, 12, 19},
        {3,   6,  9, 16, 22},
        {10, 13, 14, 17, 24},
        {18, 21, 23, 26, 30}
    };

    bool value = findNumberIn2DArray((int**)array, row, &colum, target);

    return 0;
}

2023年7月21日13:59:41更新,最近在做牛客网的题目时,经常遇到二维数组的问题,有时候不太懂的时候会百度一下,有了一些理解。就比如这边;

首先最近理解的一个知识点就是:二维数组不是二级指针,二维数组在内存中还是以一维数组的形式存放的。

那么对于这个强制转换成二级指针,百度了,有的说是不能强制转换的。 https://www.cnblogs.com/rainySue/p/er-wei-shu-zu-he-er-ji-zhi-zhen.html

改文章指出的一点:

不管是一维还是多维数组,都是内存中一块线性连续空间,因此在内存级别上,其实都只是一维。但是不同的定义使得表现形式不一样,从而有多维数组的概念。

 数组名总是代表数组首元素的地址,因此,二维数组名是一个数组指针,而非二级指针。数组指针形式:T (*p)[num];  相对的是指针数组形式: T *p[num];

对于为什么二维数组不能强制转换成二级指针的原因,上述文章说的原因是因为会引起段错误,至于什么是段错误?进入了知识盲区,虽然之前有点印象,但是不深了,百度一下。参考段错误。

他认为不能转的原因如下:

#include <iostream>

#include <stdio.h>
#include <stdlib.h>

using namespace std;

void fun(int **data,int row,int column)
{
    for(int i = 0;i < row; ++ i)
    {
        for(int j = 0;j < column;++ j)
            cout << data[i][j] << "\t";
        cout << endl;
    }
}
int main()
{
    int data[2][3] = {{3,8,4},{4,5,6}};
    int** ptr = (int **)data;
    printf("%p,%p\n",ptr,data);
    printf("%p\n",ptr[0]);
    printf("%d\n",*(int*)ptr);          //能读到是3
    //printf("%d\n",**ptr);             //异常
    //printf("%d\n",ptr[0][0]);         //异常
    return 0;
}


//0061FF04,0061FF04
//00000003
//3


测试时增加以下三行代码
printf("%p\n",*ptr);
printf("%p\n",(int*)ptr);
printf("%p, %p, %p\n",ptr,&data[0][0], &data[0][1]);


//0000000C
//0061FF04
//0061FF04,0061FF04, 0061FF08


仔细观察发现3就是二维数组第一个元素3,而二维指针一次解引用*ptr就能娶到数组元素,这时候才恍然大悟,原来那个(int*)是强制转换不是解引用,随意二级指针只做一次解引用就行。
所以得出
int num = *((int*)matrix + i * 5 + j);
是可以遍历二维数组元素的。试了这样也可以
int num = (int)(*(matrix + i * 5 + j));

 

 64位机器中,int占4个字节,int *占8个字节,因此ptr[0]就是data[0][0]和data[0][1]拼接起来的结果,故ptr[0]的值为0x800000003。可以看到,这个值并非data数组首元素的地址。因此当进行如下调用fun(ptr,2,3)时,试图访问的是0x800000003的未知空间,因此发生段错误。


综上,上述正例其实是可以的,至于为什么,还需要研究一下,毕竟有人认为不能转换。

这里其实就是找到数组首地址再偏移遍历的,毕竟是一维数组嘛。 

【反例】

#include <stdio.h>
#include <stdlib.h>

bool findNumberIn2DArray(int** matrix, int matrixSize, int* matrixColSize, int target){
    for(int i = 0; i < matrixSize; i++)
    {
        for(int j = 0; j < *matrixColSize; j++)
        {
            if(matrix[i][j] == target)        //这边使用array[i][j]方式不可行
            {
                printf("true");
                return true;
            }
        }
    }
    printf("false");
    return false;
}

int main()
{
    int row = 5;
    int colum = 5;

    int target = 20;

    int array[row][colum] = {
        {1,   4,  7, 11, 15},
        {2,   5,  8, 12, 19},
        {3,   6,  9, 16, 22},
        {10, 13, 14, 17, 24},
        {18, 21, 23, 26, 30}
    };

    bool value = findNumberIn2DArray((int**)array, row, &colum, target);

    return 0;
}

2023年7月21日15:16:32更新,这样确实不可以。这个跟数组寻址有关系吧,微机原理。


2023年5月13日16:02:31

二维数组传参分为两种情况,一种是静态数组传参,另一种是动态数组传参

1、静态数组

#include <stdlib.h>
#include <stdio.h>

#define PRINTF(x,y,z)   printf(#x":%d\n",x)


//适用于数组传参, 可行
void diliverArrayWayOne(int array[5][5])
{
    for(int i = 0; i < 5; i++)
    {
        for(int j = 0; j < 5; j++)
        {
            PRINTF(array[i][j], i, j);
        }
    }
}

// 可行
void diliverArrayWayTwo(int array[][5], int row, int col)
{
    for(int i = 0; i < row; i++)
    {
        for(int j = 0; j < col; j++)
        {
            PRINTF(array[i][j], i, j);
        }
    }
}

// 引用传递, 可行,2023年7月21日15:17:54更新,引用就是取个别名
//2023年9月26日10:06:54更新,写成diliverArrayWayThree(int (&array))这样不行
void diliverArrayWayThree(int (&array)[5][5])
{
    for(int i = 0; i < 5; i++)
    {
        for(int j = 0; j < 5; j++)
        {
            PRINTF(array[i][j], i, j);
        }
    }
}

//指针传递, 可行,2023年7月21日15:18:25更新,数组指针传递
void diliverArrayWayFour(int (*p)[5], int row, int col)
{
    for(int i = 0; i < row; i++)
    {
        for(int j = 0; j < col; j++)
        {
            //均可  p[i] 等同于 *p + i    p[i][j] 等同于 *( (*p + i) + j )
//            PRINTF(p[i][j], i, j);
//            PRINTF( (*p + i)[j], i, j);
            PRINTF( *((*p + i) + j), i, j);
        }
    }
}

// 二维数组不能作为二级指针参数进行传递,不可用
void diliverArrayWayFive(int **p, int row, int col)
{
    for(int i = 0; i < row; i++)
    {
        for(int j = 0; j < col; j++)
        {
//            PRINTF(p[i][j], i, j);
//            PRINTF((*p + i)[j], i, j);
//            PRINTF( *((*p + i) + j), i, j);           //怎么都访问不了数据  二维数组不能作为二级指针参数进行传递,无法强制转换。
        }
    }
}


int main()
{
    int row = 5, col = 5;

    int array[5][5] = {                     //二维数组传参需要确定一维数组长度
        {1,   4,  7, 11, 15},
        {2,   5,  8, 12, 19},
        {3,   6,  9, 16, 22},
        {10, 13, 14, 17, 24},
        {18, 21, 23, 26, 30}
    };

    diliverArrayWayOne(array);
    diliverArrayWayTwo(array, col, row);
    diliverArrayWayThree(array);
    diliverArrayWayFour(array, col, row);
    diliverArrayWayFive((int**)array, 5, 5);

    return 0;
}

通过上面的例子可以看出,二维数组可以转化为数组指针,指针数组可以转化为二级指针【2023年7月21日15:20:27,上面例子怎么看出来的。。。。】。二维数组名其实就是一个数组指针,指向的是数组,因此二维数组可以转化为数组指针。指针数组,是一个数组,数组元素的类型是指针,一个是指向指针,一直是指向数组,因为基本数据类型不是数组指针,因此不可以进行转化。而二级指针,是指向指针的指针,数组指针名是指针,指向数组元素,数组元素也是指针,因此数组指针是指向指针的指针,因此可以和二级指针进行转化。

后面补充:PRINTF( *( (int*)p + i * col + j ), i, j);可以访问


2、动态数组

#include <iostream>

#include <stdlib.h>
#include <stdio.h>

#define PRINTF(x,y,z)   printf(#x":%d\n",x)

using namespace std;

void diliverArrayWaySix(int **p, int row, int col)
{

    for(int i = 0; i < row; i++)
    {
        for(int j = 0; j < col; j++)
        {
            PRINTF(p[i][j], i, j);              //可行,其他都不可行
//            PRINTF((*p + i)[j], i, j);
//            PRINTF( *((*p + i) + j), i, j);
//           PRINTF( *( (int *)p + i * col + j ), i, j);
        }
    }

}


int main()
{
    // 创建动态二维数组
    int row = 3, col = 4;
    int **p = (int**)malloc(row * sizeof(int*));
    for(int i = 0; i < row; i++)
    {
        p[i] = (int*)malloc(sizeof(int) * col);
    }

    int count = 0;

    for(int i = 0; i < row; i++)
    {
        for(int j = 0; j < col; j++)
        {
            p[i][j] = 2 * count++;              //查资料也是用这种方式赋值,其他不可行
//            *(((int*)p + i) + j) = 2 * count;
//            *(*p + i * col + j) = 2 * count;        //2023年7月8日18:02:54试了确实不行
//            PRINTF(p[i][j], i, j);
        }
    }


    diliverArrayWaySix(p, row, col);


    cout << "Hello world!" << endl;
    return 0;
}

暂时先这样,动态数组传参后访问数据搞的头疼,后面再研究。

2023年7月21日15:25:43

哈哈,不用头疼啦,所谓静态数组其实就是二维数组,不能用二级指针传递。

动态数组就是二级指针数组,可以用二级指针传参。

突然觉得二级指针创建数组自己又快忘了。。。


2023年5月13日16:35:33

经过查书,一番捣鼓,发现又可以了,查下书很快就搞定了,还有很多知识不通的点啊

void diliverArrayWaySix(int **p, int row, int col)
{
    printf("six:%p\n",p);
    printf("six:%p\n",p + 1);
    printf("six:%p\n",(int*)p + 1);

    for(int i = 0; i < row; i++)
    {
        for(int j = 0; j < col; j++)
        {
//            PRINTF(p[i][j], i, j);                   // 可行
//            PRINTF((*p + i)[j], i, j);               // 不可行
//            PRINTF( *((*p + i) + j), i, j);          // 不可行,但是上面是怎么可以的
//           PRINTF( *( *p + i * col + j ), i, j);     // 不可行
           PRINTF( *( *(p + i) + j ), i, j);           // 可行
           printf("1address:%p\n",( *p + i * col + j ));
           printf("22address:%p\n",( *(p + i) + j ));
        }
    }

}


int main()
{
    // 创建动态二维数组
    int row = 3, col = 4;
    int **p = (int**)malloc(row * sizeof(int*));
    for(int i = 0; i < row; i++)
    {
        p[i] = (int*)malloc(sizeof(int) * col);
    }

    int count = 0;

    for(int i = 0; i < row; i++)
    {
        for(int j = 0; j < col; j++)
        {
            p[i][j] = 2 * count++;              //采用这种方式赋值
//            *(((int*)p + i) + j) = 2 * count;
//            *(*p + i * col + j) = 2 * count;
//            PRINTF(p[i][j], i, j);
        }
    }

    printf("%p\n",p);
    printf("%d\n",*((*p + 4) + 1) );

    for(int i = 0; i < row; i++)
    {
        printf("address:%p\n",p[i]);
    }

    for(int i = 0; i < row; i++)
    {
        for(int j = 0; j < col; j++)
        {
            printf("0address:%p\n",&p[i][j]);
        }
    }

    diliverArrayWaySix(p, row, col);


    cout << "Hello world!" << endl;
    return 0;
}


//打印信息
//00791828
//134247102
//address:00791650
//address:00791668
//address:00791680
//0address:00791650
//0address:00791654
//0address:00791658
//0address:0079165C
//0address:00791668
//0address:0079166C
//0address:00791670
//0address:00791674
//0address:00791680
//0address:00791684
//0address:00791688
//0address:0079168C
//six:00791828
//six:0079182C
//six:0079182C
//*( *(p + i) + j ):0
//1address:00791650
//22address:00791650
//*( *(p + i) + j ):2
//1address:00791654
//22address:00791654
//*( *(p + i) + j ):4
//1address:00791658
//22address:00791658
//*( *(p + i) + j ):6
//1address:0079165C
//22address:0079165C
//*( *(p + i) + j ):8
//1address:00791660
//22address:00791668
//*( *(p + i) + j ):10
//1address:00791664
//22address:0079166C
//*( *(p + i) + j ):12
//1address:00791668
//22address:00791670
//*( *(p + i) + j ):14
//1address:0079166C
//22address:00791674
//*( *(p + i) + j ):16
//1address:00791670
//22address:00791680
//*( *(p + i) + j ):18
//1address:00791674
//22address:00791684
//*( *(p + i) + j ):20
//1address:00791678
//22address:00791688
//*( *(p + i) + j ):22
//1address:0079167C
//22address:0079168C
//Hello world!

真是无语啊,搞了那么久,还不如查书一下子就搞定了,路不对,就很费劲啊

从书上查到其实就一个点: a[i] = *(a + i)。

2023年7月21日15:24:19更新

是的,printf("%d ", *(*(p + i) + j));这样也可以访问到二级指针指向的二维数组的元素。对应的就是p[i][j];


2023年6月9日18:03:15更新

关于数组形参

1、一维数组

int        vector[10];

...

func1(vector);

参数vector的类型是指向整型的指针,所以func1的原型可以是下面两种中的任何一种:

void func1(int *vec)

void func1(int vec[])

2、二维数组

int        matrix[3][10];

...

func2(matrix);

这里,参数matrix的类型是指向包含10个整型元素的数组的指针。fiunc2的原型应该是怎样的?可以使用下面两种中的任何一种:

void func2(int  (*mat)[10]);

void func1(int mat[][10]);

来自于“C和指针”。


2023年6月29日10:49:52  更新

做的牛客网上的题目,题目做对了,看别人的解释挺好的,所以记录一下

题目是这样的

17.

若有以下定义和赋值语句,则与&s[i][j]等价的是(C)

1

2

3

int s[2][3] = {0}, (*p)[3], i, j;

p = s;

i = j = 1;

A、*(*(p+i)+j)

B、*(p[i]+j)

C、*(p+i)+j

D、(*(p+i))[j]

正确答案:C

参考答案:int (*p)[3]可以看出p是数组的指针,p=*s说明p指向的是s[0]数组。[]符号的优先级高于&符号,&s[i][j]是s[i][j]的地址。*(p + i) + j与&s[i][j]等价。

别人的总结如下

int (*p)[3] 表示一个指针,指向一个含有三个元素的数组;

p=s,表示p指向了数组s的第一行,

p+1 表示现在指针指向了数组s的第二行;

*(p+1)表示数组s第二行第一个元素的地址;

*(p+1)+1 表示数组s第二行第二个元素的地址;

感觉总结的还行,记录一下,之前这种题目都是晕晕的。

2023年7月8日17:35:44在看这个题目,如果想选择D选项,D选项应该改成&(*(p+i))[j]。


2023年7月8日17:36:31更新

做牛客网题目时遇到的题目,

题目如下:

4.若已定义:int a[4][3]={1,2,3,4,5,6,7,8,9,10,11,12},(*prt)[3]=a,*p=a[0];则能够正确表示数组元素a[1][2]的表达式是(   B错, 正确答案是D    )

A、*((*prt+1)[2])                        //语法错误

B、*(*p+5))                                //*p指向a[0][0]的地址,6

C、(*prt+1)+2                            //指向4的地址 * ((*prt+1)+2))等于4    

D、*(*(a+1)+2)                        //6

做题目时被难住了,在codeblock上自己试验整理如下

int main(){
    int atest[4][3]={1,2,3,4,5,6,7,8,9,10,11,12},(*ptr)[3]=atest,*p=atest[0];
    printf("%d\n", *(*(ptr+1)) );           //4
    printf("%d\n", *(*(ptr+1)+2) );         //6  ptr+1指向a[1][3]数组地址,解引用指向a[1][3]首地址,+2指向第二排第【2】个元素,就是6
    printf("%d\n", *((*ptr+1)+2) );         //4  ptr解引用指向元素1的地址,+1后再+2,指向元素4的地址,解引用即4
    printf("%d\n", (*(ptr+1)[2]) );         //10 为什么?        //2023年8月31日17:47:55 猜测[]优先级大于*,所以先执行(ptr+1)[2],又因为(ptr+1)[2] = (ptr+1) + 2,,所以指向10的地址
    printf("%d\n", (*(ptr+1))[2] );         //6  *(p + i) = a[i]
    printf("%d\n", ((*ptr+1)[2]) );         //4  *ptr指向a[0][3]首地址,+1指向2的地址,[2]向右偏移2位,就是4


    printf("%d\n", *(*ptr + 1*3 + 2) );     //打印的是6
    printf("%d\n", *(ptr + 1*3 + 2) );      //打印地址

    int col = 3;
    for(int i= 0; i < 4; i++)
    {
        for(int j = 0; j < 3; j++)
        {
            printf("%d ", *(*ptr + i*col + j) );        //另外一种方法,基于百度得到的二维数组不是二级指针。
        }
    }
    printf("\n");

    return 0;
}

再看这个题目,A选项是错的,如果改成(*(prt+1)[2])可以。B选项一开始做的时候觉得肯定是错的,现在竟然发现是对。C是地址,如果加上*,那么*((*prt+1)+2)是4;D选项*(*(a+1)+2)改成*(*(prt+1)+2)就行了。

搞定!

害!这个题目有反转,答案竟然是D!!!

2023年7月24日10:42:20更新:

现在在看B答案,p = a[0],*(*p+5))对p做了一次解引用,打印*p的值是1,+5之后是6,解引用之后出错,如果改成*(p+5)应该可以。codeblock验证可以。

2023年8月31日19:34:03更新,这边记住a[num]就是num行首地址,*p=a[0]就是将p指向第一行数组首地址,一次解引用*p为1,*(p+5)=6。


2023年8月31日19:33:39  更新
#include <stdlib.h>
#include <stdio.h>

int main(){
    int atest[4][3]={1,2,3,4,5,6,7,8,9,10,11,12},(*ptr)[3]=atest,*p=atest[0];
    printf("%d\n", *(*(ptr+1)) );           //4
    printf("%d\n", *(*(ptr+1)+2) );         //6  ptr+1指向a[1][3]数组地址,解引用指向a[1][3]首地址,+2指向第二排第【2】个元素,就是6
    printf("%d\n", *((*ptr+1)+2) );         //4  ptr解引用指向元素1的地址,+1后再+2,指向元素4的地址,解引用即4
    printf("%d\n", (*(ptr+1)[2]) );         //10 为什么?        //2023年8月31日17:47:55 猜测[]优先级大于*,所以先执行(ptr+1)[2],又因为(ptr+1)[2] = (ptr+1) + 2,,所以指向10的地址
    printf("%d\n", (*(ptr+1))[2] );         //6  *(p + i) = a[i]
    printf("%d\n", ((*ptr+1)[2]) );         //4  *ptr指向a[0][3]首地址,+1指向2的地址,[2]向右偏移2位,就是4


    printf("%d\n", *(*ptr + 1*3 + 2) );     //打印的是6
    printf("%d\n", *(ptr + 1*3 + 2) );      //打印地址

    printf("%d\n", ptr );
    printf("%d\n", ptr + 1 );

    printf("p:%d\n",p);                     //第一行首地址
    printf("*p:%d\n",*p);                   //1

    printf("atest[0]:%d\n",atest[0]);       //第一行首地址
    printf("atest[0][0]:%d\n",atest[0][0]); //1
    printf("*p:%d\n",*p);                   //1
    printf("atest[1]:%d\n",atest[1]);       //第二行首地址
    printf("atest[1][0]:%d\n",atest[1][0]); //4
    printf("*p:%d\n",*(p+1));               //2
    printf("*p:%d\n",*(&p+1));              // -2,这样写也不等于4,是因为*p=atest[0]已经把p指向第一排数组了
    // 这边其实记住就好了,二维数组的a[num]就时第几排的首地址

    int col = 3;
    for(int i= 0; i < 4; i++)
    {
        for(int j = 0; j < 3; j++)
        {
            printf("%d ", *(*ptr + i*col + j) );        //另外一种方法,基于百度得到的二维数组不是二级指针。
        }
    }
    printf("\n");

    for(int i= 0; i < 4; i++)
    {
        for(int j = 0; j < 3; j++)
        {
            printf("%d ", *(ptr + i*col + j) );        //这边打印的就是数组每一行的首地址,然后越界
        }
    }
    printf("\n");

    for(int i= 0; i < 4; i++)
    {
        for(int j = 0; j < 3; j++)
        {
            printf("%d ", *(&atest[0][0] + i*col + j) );        //另外一种方法,基于百度得到的二维数组不是二级指针。
        }
    }
    printf("\n");

    for(int i= 0; i < 4; i++)
    {
        for(int j = 0; j < 3; j++)
        {
            printf("%d ", *((int*)atest + i*col + j) );        //另外一种方法,基于百度得到的二维数组不是二级指针。
        }
    }
    printf("\n");

    return 0;
}

//打印
//1 2 3 4 5 6 7 8 9 10 11 12
//6422212 6422224 6422236 6422248 6422260 6422272 6422284 6422296 6422308 6422320 6422332 6422344
//1 2 3 4 5 6 7 8 9 10 11 12
//1 2 3 4 5 6 7 8 9 10 11 12
2023年11月14日10:58:10更新

A、*((*prt+1)[2])                        //语法错误

B、*(*p+5))                                //*p指向a[0][0]的地址,6                错误 p指向a[0][0]地址  *(p+5)值为6

C、(*prt+1)+2                            //指向4的地址 * ((*prt+1)+2))等于4    验证正确

D、*(*(a+1)+2)                        //6

关于数组指针,int array[3][4]; int (*p)[4] = array; 数组指针名p[0]就是array[0][0]地址,*p就是array[0][0]地址。验证如下:

int main()
{
    int a[4][3]={1,2,3,4,5,6,7,8,9,10,11,12},(*prt)[3]=a,*p=a[0];
    //验证*prt等价于a[0]、等价于a[0][0]
    int d = (int)((*prt+1)+2);
    printf("%d\n", d);
    int e = (int)(&a[1][0]);
    printf("%d\n", e);

    printf("%d\n", a[1][0]);
    printf("%d\n", *((*prt+1)+2));
    
    //验证*prt等价于a[0]、等价于a[0][0]
    printf("%d\n", &a[0][0]);
    printf("%d\n", (int)prt[0]);
    printf("%d\n", (int)*prt);
    
    //验证题目里的选项A写法错误
    // printf("%d",*((*prt+1)[2]) );
    getchar();
}

//6422252
//6422252
//4
//4
//6422240
//6422240
//6422240

这边再补充一个别人总结的关于数组传参的总结

总结
一维数组传参的几种正确形式:

<1>  test(int arr[5]) —— 直接传入数组并给出数组中含有几个元素

<2>  test(int arr[ ]) —— 直接传入数组,省略数组中含有的元素个数

<3>  test(int* arr) —— 形参写为指针形式,传入数组首元素地址

二维数组传参的几种正确形式:

<1> test(int arr[3][5]) —— 直接传入二维数组,行数和列数均不省略

<2> test(int arr[ ][5]) —— 直接传入二维数组,省略行数,不省略列数

<3> test(int (*p)[5]) —— 以数组指针的形式传参

二维数组传参的几种典型错误形式:

<1> test(int arr[ ][ ]) —— 将二维数组的列数省略,二维数组列数不可省略

<2> test(int* arr[5]) —— 传入指针数组

<3> test(int **arr) —— 传入二级指针
————————————————
原文链接:https://blog.csdn.net/weixin_43908419/article/details/127714436

这边涉及到一个二维数组不能以二级指针方式传参的点。那么什么时候可以传二级指针呢?

参考以下文档

指针数组参数传递
二维数组不能作为二级指针参数进行传递,无法强制转换。即:

void fun(char **p);
char str[2][6] = {"hello", "world"};

fun(str);

这样的调用会出错。

但是指针可以转化为二级指针。如:

void fun(char **p);
char *str[6] = {"hello", "world"};

fun(str);

这样调用就OK。

数组指针参数传递
下面这种情况,调用会出错。

void func(int **p);
int ary[2][6];

func(ary);

这种情况下,二维数组不能转化为二级指针。进行如下修改。

void func(int (*p)[6]);
int ary[2][6];

func(ary);

这样调用正确。

通过上面的例子可以看出,二维数组可以转化为数组指针,指针数组可以转化为二级指针。二维数组名其实就是一个数组指针,指向的是数组,因此二维数组可以转化为数组指针。指针数组,是一个数组,数组元素的类型是指针,一个是指向指针,一直是指向数组,因为基本数据类型不是数组指针,因此不可以进行转化。而二级指针,是指向指针的指针,数组指针名是指针,指向数组元素,数组元素也是指针,因此数组指针是指向指针的指针,因此可以和二级指针进行转化。

上面的转化是针对默认转化来说的。

数组指针和指针数组对应的关系应该如下:

int ary[2][6];
int (*ary1)[6];
int *ary2[2];

原文链接:https://blog.csdn.net/duapple/article/details/108193065

三、数组相关题目整理

1、第一题

单选以下代码

void example(char acHello[])
  {
      printf("%d", sizeof(acHello));
      return;
  }
  void main()
  {
      char acHello[] = "hello";
      example(acHello);
      return;
  }

的输出是( )                A

A 4     B 5         C 6      D不确定

解释:在函数参数列表里char acHello[]可以等价于char *acHello,(这和数组的初始化有点像但又不能等同,数组初始化中声明的acHello有些特别,他是“数组名”其中数组名又是一个地址)

所以到了example函数里后acHello不是代表了数组名,只是一个指向数组的指针,这样指针用下面的语句查看下所占用的大小就是4了

2、第二题

首先是针对两个题目

① 以下代码的输出为:D

A 0     B 10         C 11      D不确定

void example()
  {
      int i;
      char  acNew[20];
      for(i = 0; i < 10; i++)
      {
          acNew[i] = '0';
      }
      printf("%d\n", strlen(acNew));
      return;
  }

① 以下代码的输出为:B

A 0     B 10         C 11      D不确定

void Example()
  {
      int i;
      char  acNew[20] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
      
      for(i = 0; i < 10; i++)
      {
          acNew[i] = '0';
      }
      printf("%d\n", strlen(acNew));
      return;
  }

② 以下代码的输出为:B

针对这两个题目,在网上查了下,下面的代码可以解决问题

/*
	2022.06.02
	目的:
		strlen函数求字符串长度和字符数组的长度
	理论:
		1.计算字符串的库函数;
		2.strlen函数遇见空字符’\0’时就立即结束返回;
		3.ASCII的值是'0'表示空字符,即'\0';
	功能:
		
*/

#include<stdio.h>
#include<string.h>

int main(void)
{
	char cCh1 = 0x00; /* 16进制0x00翻译字符为’\0’*/
	char cCh2 = '\0'; /* \0 的ASCII码值是0,表示一个字符串结束的标志空字符,表示的是转义字符 */
	char cCh3 = '0';  /* 字符0的ASCII码值是48,不是空字符 */
	int iInt4 = 0;    /* 整数0,ASCII码的值是0,字符表示空字符 */
	
	char* str1 = "abc";   /* 系统自动默认会在后面给字符串加上空字符\0,是将其当成了一个整体,相当于字符串为“abc\0” */
	char* str2 = "ab\0c"; /* 因为中间有\0,所以提前结束了计数 */
	char* str3 = "ab\\0c";/* 第二个斜杠被第一个斜杠转义,“\\0”表示的“\\”和“0”两个字符 */
	char* str4 = "abc0d"; /* 字符串中的0代表的是字符0,而不是数字0 */
	char* str5 = "0";     /* 这个表示的是字符串,由'0'和'\0'两个字符组成 */

	char arry1[5] = {'1','2','3','4','5'};  /* 这是一个字符数组,他里面存放的并不是字符串(无‘\0’),只是一系列字符,因为该数组中没有遇到空字符,所以会一直寻找,直到找到空字符,所以才会有随机长度 */
	char arry2[5] = {'1','2','3','4','\0'}; /* 因为第五个元素是‘\0’*/
	char arry3[5] = {'1','2','3','4',0};    /* 因为数字‘0’等价于‘\0’*/
	char arry4[5] = {'0','0','0'};          /* 因为该数组是部分初始化,所以除了前三个元素是字符‘0’外面,其余都是自动填充的数字‘0’,数字‘0’等价于‘\0’,所以计数结束于第四个元素*/
	char arry5[5] = {'1','2','3','4','0x00'}; /* 一个16进制的字符是用4位二进制来表示的,1个字节是8位,那么4位就是半个字节,所以16进制的字符是半个字节大小 */
	char arry6[5] = { '1','2','3','4',0x00};  /* 因为16进制的‘0x00’翻译字符是‘\0’*/

	printf("Line = %d, ASCII = %d\n", __LINE__, cCh1); 
	printf("Line = %d, ASCII = %d\n", __LINE__, cCh2);
	printf("Line = %d, ASCII = %d\n", __LINE__, cCh3);
	printf("Line = %d, ASCII = %d\n", __LINE__, iInt4);

	printf("Line = %d, strlen(str1) = %d\n", __LINE__, strlen(str1)); 
	printf("Line = %d, strlen(str2) = %d\n", __LINE__, strlen(str2));
	printf("Line = %d, strlen(str3) = %d\n", __LINE__, strlen(str3));
	printf("Line = %d, strlen(str4) = %d\n", __LINE__, strlen(str4));
	printf("Line = %d, strlen(str5) = %d\n", __LINE__, strlen(str5));

	printf("Line = %d, strlen(arry1) = %d\n", __LINE__, strlen(arry1));
	printf("Line = %d, strlen(arry2) = %d\n", __LINE__, strlen(arry2));
	printf("Line = %d, strlen(arry3) = %d\n", __LINE__, strlen(arry3));
	printf("Line = %d, strlen(arry4) = %d\n", __LINE__, strlen(arry4));
	printf("Line = %d, strlen(arry5) = %d\n", __LINE__, strlen(arry5));
	printf("Line = %d, strlen(arry6) = %d\n", __LINE__, strlen(arry6));


	return 0;
}

/*
	程序运行结果:

	Line = 21, ASCII = 0
	Line = 22, ASCII = 0
	Line = 23, ASCII = 48
	Line = 24, ASCII = 0

	Line = 29, strlen(str1) = 3
	Line = 30, strlen(str2) = 2
	Line = 32, strlen(str3) = 5
	Line = 34, strlen(str4) = 5

	Line = 38, strlen(arry1) = 19
	Line = 40, strlen(arry1) = 4
	Line = 42, strlen(arry3) = 4
	Line = 44, strlen(arry4) = 3
	Line = 45, strlen(str5) = 1
	Line = 46, strlen(arry2) = 4
	Line = 47, strlen(arry3) = 4
	Line = 48, strlen(arry4) = 3
	Line = 49, strlen(arry5) = 19
    Line = 50, strlen(arry6) = 4
*/

以上就是关于char型数组以及字符串结束符'\0'的知识点。

再增加一个例题

下列程序段的输出结果是___Hello__

main()

        char  b[]=”Hello,you”;

        b[5]=0;

        printf(“%s\n”, b );

}   

3、第三题

下述对C语言字符数组的描述中正确的是( )        ABD

A、字符数组可以存放字符串;

B、字符数组的字符串可以整体输入、输出;

C、可以在赋值语句中通过赋值运算符"="对字符数组整体赋值;

D、不可以用关系运算符对字符数组中的字符串进行比较

至于为什么选项c错误,是因为我一开始选的c是觉得char str[] = “hello world”是=赋值,查了百度其实这是一个初始化列表,即 char str[] = {‘h’,'e','l','l','0','\0'};不是赋值操作。而 char str2;   str2 = str1赋值操作是不对的,数组名是指针常量,数组名不能赋值。

13.声明语句为int a[3][4];,下列表达式中与数组元素a[1][2]等价的是(B、C)

A、*(&a[0][0]+7)

B、*(a[1]+2)

C、*(a[0]+6)

D、a[7]

自己选的错误答案是B、C、D

对于二维数组a[3][4],a[0]和a,都表示首元素地址,a[1]表示第二行首元素地址,a[2]表示第三行首元素地址。

&a、a、a[0]、&a[0],四者的值表现为一样的,

但是&a+1表示跳过整个二维数组,此处跳过12个int的大小

a+1和a[0]+1都表示跳过一个元素类型的大小,此处跳过一个int的大小

&a[0]+1表示跳过一个一维数组的大小,此处跳过4个int的大小

4、第四题

假设数原型和变量说明如下:

void f4(int **p);
int a[4]={1,2,3,4};
int b[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int *q[3]={b[0],b[1],b[2]};

下面调用合法的是(C)

A、f4(a);

B、f4(b);

C、f4(q);

D、f4(&a);

解释:①

f(a). a 被传入函数形参时, 会退化成指向其首元素的指针, 类型是 int*, 不符.

f(&a). &a 是数组 a 的地址, 其类型是 int(*)[4], 不符.

f(b). b 是数组的数组. b 被传入函数形参时, 会退化成指向其首元素的指针, 也就是 b[0] 的地址, b[0] 的类型是 int[4], 故 &b[0] 类型是 int(*)[4], 不符.

f(q). q 是一个指针数组, 在初始化时, 用 b[0], b[1], b[2] 初始化了, 此时 b[0], b[1], b[2] 会退化成指向各首元素的指针(int* 类型, 因此类型符合, 可以用它们初始化). q 被传入函数形参时, 退化成指向其首元素的指针, 即 int**. 符合

数组名a相当于指向数组第一个元素的指针&a【0】,但是数组名是一个指针常量,也就是说他和常量一样,不能++或—(变量的性质),整形常量和字符常量不占据内存,就更不能取地址了。因此a和&a指向是相同的!

这边的解释有几个没理解的,按下。

2024年1月17日09:53:35

这个题目的解释看懂了。其精华就是数组名作为参数时,会退化成这个数组的首元素的指针。对于一维数组 int array1[2]; 传参为array,接收形参就是int* ;对于二维数组,int array2[2][3] = {{1,2,3}, {4, 5, 6}};传参为array2,其首元素是array[0][]为{1,2,3}。首元素的地址是int (*)[3]。所以形参就是fun(int (*p)[3]);

这边还要补充一下C和指针对于数组传参的整理。

一维数组int array1[5] = {1,2,3,4,5};

传参方式:

1、func(int *p)

2、func(int p[])

二维数组int array2[4][5];

传参方式

1、func(int p[][5])

2、func(int (*p)[5])

对比了一下文中其他地方的描述,是这样的!现在数组传参这边应该清晰多了。

35、第五题

在 C/C++ 中,有字符数组 a[80] 和 b[80],则正确的输出语句是()

A、puts(a); puts(b);

B、printf("%s,%s", a[], b[]);

C、putchar(a, b);

D、puts(a, b);

我选了b,错误,然后有人说应该是printf("%s,%s", a, b);,但是没人解释为什么是a[]不行,我自己在codeblock上敲了一下,发现printf("%s,%s", a[], b[]);这样写报错,所以有语法错误,这边原来还是我一个盲点。a[]可能只能出现在定义的时候。

三种情况可以省略数组括号中数字

①在定义数组时同时初始化

二维数组,可以省略行数,但不可以省略列数

③形参传递,可以省略一维数组的长度或者二维数组的行数

6、第六题

有以下语句定义

1

2

3

int a[2][3];

int (*p)[3]=a; 

int *q=*a;

则能输出a[1][2]的值的语句是(A、B、C)

A、cout<<*(*(a+1)+2)

B、cout<<p[1][2];

C、cout<<*(q+5);

D、cout<<q[1][2];

7、第七题

假设函数原型和变量说明如下:

1

2

3

void f3(int(*p)[4]);

int a[4]={1,2,3,4},

int b[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};

下面调用非法的是(B)

A、f3(&a);

B、f3(b[1]);

C、f3(&b[1]);

D、f3(b);

解释:

参考答案:选B。根据题目结合选项来看考察的是对函数的传参调用,其中参数涉及到数组指针 。 void f3(int(*p)[4]);    其参数是数组指针 ,指向数组p的指针。 选项A:f3(&a);  参数为一个地址,符合指针定义。 选项B:f3(b[1]); 参数为一个数组的具体元素,不符合指针定义。所以B是非法的调用。 选项C:f3(&b[1]); 参数为一个数组元素的地址,符合指针定义。 选项D:f3(b);  参数为数组名,表示该数组的首地址,符合指针定义。

按照答案的意思,b[1]是元素。

下面是别人评论的答案,感觉更靠谱

先给出结论,便于以后查阅:

首先给出几个定义:

typedef int (*p1x4)[4];//定义数据类型,p1x4这种类型为指向含4个int元素的1维数组的指针

typedef int (*p3x4)[3][4];//定义数据类型,p3x4这种类型为指向含3x4个int元素的2维数组的指针

下面从一维数组说起:(定义:int a[4])

(1)一维数组名a是个地址,地址类型为:int *

(2)一维数组名取地址&a是个地址,地址类型同:int (*p)[4], 也即&a指向含4个int元素的一维数组

再看二维数组b[3][4],这个二维数组也可以可以看成一个含3个成员的一维数组,每一个成员含有4个int元素,依次,仿照一维数组的结论,有:

(1)b[0]是个一维数组名,也是个地址,地址类型为:int *

(2)&b[0]是个地址,地址类型同:int (*p)[4], 也即&b[0]指向含4个int元素的一维数组

更进一步:

(3)b是个地址,地址类型同:int (*p)[4],也即b指向含4个int元素的一维数组

(4)&b是个地址,地址类型同:int (*p)[3][4],也即&b指向含3x4个int元素的2维数组

注意:尤其注意上面的(3),这条结论指出了:

二维数组名实际上是一维数组的地址!b==&b[0]

b[0]又是b[0][0]的地址(类比a是a[0]的地址), b= &(&b[0][0])

总结:

数组名,是指向它的第一个一级成员的指针

② 数组名取地址,是指向整个数组的指针

PS:所谓一级成员,举个例子,int a[5],那么数组a的一级成员就是int型变量;int b[10][5],数组b的一级成员是int [5]的一维数组

****************************************************************************************************

b[0]是个一维数组名,也是个地址,地址类型为:int *

&b[0]是个地址,地址类型同:int (*p)[4], 也即&b[0]指向含4个int元素的一维数组

b[0]相当于一维数组中的数组名a

int(*p)[4]指向含4个int元素的1维数组的指针 ,相当于二级指针(*p)[4]=&a,就是说&b[0]==(*p)[4]

例题:

int a[3];
int *b=a;
int *c=&a; //错误
int (*c)[3]=&a; //正确
int *d=&a[0];
int *e=&a[1];
int *f=&a[2];

例子解析:

1. 定义一维数组a[3],在例子中,a、&a、&a[0]的值虽然都是一样,但是意义不一样。a代表a[3]这个数组,也是a[3]的起始地址。&a就是取a[3]的起始地址。&a[0]就是取数组第一个元素的地址。

2. 例子中,使用int *c=&a是错误的,因为a的数据类型是int (*)[3],而不是int *,所以必须以int (*c)[3]=&a。

3. 定义了int (*c)[3]=&a,可以使用(*c)[0]取得a[0],(*c)[1]取得a[1],(*c)[2]取得a[2]。

8、第八题:

先看题目

若有定义:char s[3][4];

则下列对数组元素s[i][j]的各种引用形式中,正确的是(B)

A、* (s+i)[j]

B、* (&s[0][0]+4 * i+j)

C、* ((s+i)+j)

D、* ( * (s+i)[j])

然后这个题目我是做对的,当时当时没出答案的时候很不确定,我在考虑的点是,&s[0][0]指向的不是二维数组的地址吗,然后执行加法运算的时候,会出问题。但是我印象中好像二维数组是这样用的,还是选了B,虽然作对了但是有疑虑。

然后缪可下一个试卷出现这题:

下面程序的输出是什么?选择B。

1

2

3

4

5

6

int main(void) {

     int a[5] = {1, 2, 3, 4, 5};

     int *ptr = (int *)(&a + 1);

     printf("%d %d", *(a + 1), *(ptr - 1));

     return 0;

 }

A、2 1

B、2 5

C、1 2

D、5 2

然后我突然意识到,&a才是真正的数组的地址。那么我想起来二维数组的&s[0][0],是取s[0][0]的地址,而不是二维数组的地址,而&s才是二维数组的地址。

所以我现在预测* (&s[0][0]+4 * i+j)改成* ((char*)&s+4 * i+j)也是可以的。验证一下哦。

#include <stdio.h>
#include <stdlib.h>


int main(){
    int atest[4][3]={1,2,3,4,5,6,7,8,9,10,11,12},(*ptr)[3]=atest,*p=atest[0];

    for(int i = 0; i < 4; i++)
    {
        for(int j = 0; j < 3; j++)
        {
            printf("%d ", *((int*)&atest + 3 * i + j) );        //验证自己的猜测
        }
    }
    printf("\nfinish\n");

    return 0;
}


//1 2 3 4 5 6 7 8 9 10 11 12
//finish

猜测正确,感觉二维数组理解的更顺滑了!

9、第九题:

先看牛客题,则程序的输出是()

#include <stdio.h>

void foo(int b[][3]){ 

    ++b;   

    b[1][1] = 9;

}

int main(){

      int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

      foo(a);   

    printf("%d", a[2][1]);

}

A、8

B、9

C、7

D、以上均不对

 然后我有点蒙,蒙在b++到底地址偏移了多少。然后我又在思考形参b到底是什么?这些我都不清楚,然后我用了下面的代码验证,打印结果显示++B偏移了12个字节,所以b其实是指向二维数组第一行的地址。

其实也就是我之前整理的别人的知识点。【二维数组名就是以及成员的指针】。具体见题目七。

#include <stdio.h>
void foo(int b[][3]){
    printf("%p\n",b);
    ++b;
    printf("%p\n",b);
    b[1][1] = 9;
}
int main(){
      int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
      printf("%p\n",a);
      foo(a);
    //printf("%d", a[2][1]);
}


//0061FEFC
//0061FEFC
//0061FF08

预测答案是9。验证一下。预测正确!yes!

10、第十题

若有以下程序段

1

2

3

4

char str[4][12] = {"aa""bbb""cccc""d"}, *strp[4];

int i;

for (i = 0; i < 4; i++)

    strp[i] = str[i];

不能正确引用字符串的选项是  B

A、str[0]

B、strp

C、strp[3]

D、* strp

正确答案:B

你的答案:D

官方解析:本题考查字符串指针作为函数参数,本题中p作为字符串指针传入fun中,p指向的内容并没有发生变化,所以选项B正确。

11、第十一题

对于以下变量定义,正确的赋值是(D)

int *p[3], a[3];

A、p=a

B、*p=a [0]

C、p=&a [0]

D、p[0]=&a[0]

注意:数组名不能当左值

12、第十二题

在做题目时,它提供了一个很好的思路,即从键盘获取一对对数字组再存到数组中的办法思路。其实是可以用结构体来处理的,但是题目是用二维数组处理的,实际上处理的也不错。所以记一下。另外,还出现两个问题,一个是我突然不知道二维数组即int **p怎么解析了。还看了这个帖子。

二是,我在解析二级指针的时候越界了。这个就很尴尬,以后for循环解析数据的时候一定要注意不能数组越界!

例题如下:

//题目描述
//绘图机器的绘图笔初始位置在原点(0, 0),机器启动后其绘图笔按下面规则绘制直线:
//1)尝试沿着横向坐标轴正向绘制直线,直到给定的终点值E。
//2)期间可通过指令在纵坐标轴方向进行偏移,并同时绘制直线,偏移后按规则1 绘制直线;指令的格式为X offsetY,表示在横坐标X 沿纵坐标方向偏移,offsetY为正数表示正向偏移,为负数表示负向偏移。
//
//给定了横坐标终点值E、以及若干条绘制指令,请计算绘制的直线和横坐标轴、以及 X=E 的直线组成图形的面积。
//
//解答要求
//时间限制:1000ms, 内存限制:256MB
//输入
//首行为两个整数 N E,表示有N条指令,机器运行的横坐标终点值E。
//接下来N行,每行两个整数表示一条绘制指令X offsetY,用例保证横坐标 X 以递增排序方式出现,且不会出现相同横坐标 X 。
//取值范围:0 < N <= 10000, 0 <= X <= E <= 20000, -10000 <= offsetY <= 10000。
//
//输出
//一个整数,表示计算得到的面积,用例保证,结果范围在 0~4294967295 内
//
//样例
//输入样例 1 复制
//
//4 10
//1 1
//2 1
//3 1
//4 -2
//输出样例 1
//
//12

/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved.
 * Description: 考生实现代码
 * Note: 缺省代码仅供参考,可自行决定使用、修改或删除
 */
#include "securec.h"

#define MAXN 10000

unsigned int GetMinArea(int **opers, int operCnt, int stopPoint)
{
    int m_x = 0; int m_y = 0;
    int j = 0;
    int s = 0;
    // 在此添加你的代码
    for(int i = 0; i < stopPoint; i++)
    {
        if( j < operCnt && opers[j][0] == i )
        {

            s += (opers[j][0] - m_x) * m_y;
            m_y += opers[j][1];
            m_x = opers[j][0];
            j++;
        }

        if(i + 1 == stopPoint)
        {
            s += (stopPoint - m_x) * m_y;
        }
    }
    return s;
}

/* 代码仅供参考,注意避免圈复杂度过大 */
int main(void)
{
    int n, stopPoint;
    if (scanf_s("%d %d\n", &n, &stopPoint) != 2) { return -1; }

    static int buf[MAXN][2];
    static int *opers[MAXN];
    int i;
    for (i = 0; i < n; i++) {
        if (scanf_s("%d %d", &buf[i][0], &buf[i][1]) != 2) {
            return -1;
        }
        opers[i] = buf[i];
    }

    unsigned int ret = GetMinArea(opers, n, stopPoint);
    (void)printf("%u", ret);
    return 0;
}

作为对比,这个1ms就执行完了,我的代码是2.8ms。我的代码最大圈复杂度是5,最大代码嵌套深度是3。

1ms执行用时
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved.
 * Description: 考生实现代码
 * Note: 缺省代码仅供参考,可自行决定使用、修改或删除
 */
#include "securec.h"

#define MAXN 10000

unsigned int GetMinArea(int **opers, int operCnt, int stopPoint)
{
    // 在此添加你的代码
    int area = 0;
    int idx;
    int high = opers[0][1];
    for (idx = 1; idx < operCnt; idx++) {
        area += (opers[idx][0] - opers[idx - 1][0]) * abs(high);
        high += opers[idx][1];
    }
    area += (stopPoint - opers[operCnt - 1][0]) * abs(high);
    return (unsigned int)area;
}

/* 代码仅供参考,注意避免圈复杂度过大 */
int main(void)
{
    int n, stopPoint;
    if (scanf_s("%d %d\n", &n, &stopPoint) != 2) { return -1; }

    static int buf[MAXN][2];
    static int *opers[MAXN];
    int i;
    for (i = 0; i < n; i++) {
        if (scanf_s("%d %d", &buf[i][0], &buf[i][1]) != 2) {
            return -1;
        }
        opers[i] = buf[i];
    }

    unsigned int ret = GetMinArea(opers, n, stopPoint);
    (void)printf("%u", ret);
    return 0;
}

四、参考

1、段错误

针对段错误,本来想一句话就总结的,百度了一下,竟然还有好几点,就单独列出一个章节吧。

①段错误定义:段错误究根到底就是访问了非法内存。

②产生段错误的四种情况

一、段错误究根到底就是访问了非法内存:
这个内存区要么是不存在的,要么 是受到系统保护的,还有可能是缺少文件或者文件损坏。可能的原因包括:
访问代码段(原因经常是指针未初始化指向了错误的位置或解引用空指针)、访问寄存器
例子1:解引用空指针 int *p=NULL; printf("%d\n",*p); //因为内存低地址为代码段,不可访问?
例子2:访问含有非法值得内存 register int p =10; printf("%d\n",*p); //由于register关键字使变量存储到内核寄存器中,因此不能访问。
野指针:即定义指针时并未对其初始化,其指向的的位置式未知的。对野指针解引用可能造成段错误或者导致程序崩溃。
防止方案:1;定义时初始化为NULL 2;解引用前赋值 3;使用完后指向NULL 每次使用指针之前记得赋值就好了

二、数组越界
如:当使用malloc申请了一页内存,但使用却超出了。 越过数组边界写入数据,在动态分配的内存两端之外写入数据,或改写一些堆管 理数据结构(在动态分配的内存之前的区域写入数据)

堆中:p = malloc(256); p[-1] = 0; p[256] = 0; //访问了未知空间的内存
栈中:int *p=NULL; int a[6]; p=a; for(int i=0;i<10;i++){*p++=i;} //stack smashing detected 访问了未知空间的内存

三、scanf错误使用:
int b; scanf("%d",b);//应为scanf("%d",&b);

四、指针访问只读内存区:
如:char *p=“abcddf”; *p=‘A’; //其实本质上错误原因和解引用空指针类似,“abcddf”在被定义时放在了代码段或常量区。
//解决方法是将字符串存到数组中,再将指针指向数组头

这里补充一下程序运行时内存分配:
1 栈区:存放函数运行时产生的临时变量,局部变量、函数的入口参数,返回值和const定义的局部变量,函数结束后由编译器释放。
2 堆区:用于存放程序运行时被动态分配的内存段,一般由程序员手动申请释放malloc申请,free释放。
3 全局区(静态区):全局区用来存储全局变量,主要分为两个段:1. .bss段:该段用于存放未初始化或者初始化为0的全局变量和静态变量(static)
2. .data段:又叫数据段,用于存储初始化不为0的全局变量和静态变量、const定义的全局变量(在.rodata段)
该段在程序结束后由系统释放。
4 常量区:常量字符串就是放在这里的。 程序结束后由系统释放
5 代码区:又叫.text段用于存放函数的代码,部分字符串常量也存在代码段

三个申请内存的函数:
1. void *malloc(unsigned int size); //单位为字节 如要申请200个字节的空间存储int型数据 int *p=(int *)malloc(50 *sizeof(int))
2. void *calloc(unsigned n,unsigned size); //long *buffer; buffer =(long *)calloc(20,sizeof(long)); 获得一块长整型数组空间
3. void *realloc(void *mem_address,unsigned int newsize);//重新分配内存
三种方式,申请成功返回(void *)类型的指针,失败返回NULL;使用完之后切记要free释放

来源:常见段错误原因总结_bty156的博客-CSDN博客


这边段错误因为时间原因,就直接赋值粘贴的,可能会印象不深。

五、二维数组创建方式

1、malloc

2023年9月8日14:42:53更新

这是练习牛客编程题时,写的malloc动态创建二维数组,编译发现还是不对。其中还存在另外几个问题:

1、memcpy头文件不知道是什么

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    int num = 10;
    char **p = (char**)malloc(sizeof(char*) * num);
    for(int i = 0; i < 10; i++)
    {
        *p = (char*)malloc(sizeof(char) * 101);
    }

    memcpy(p[0], "hello", 101);
    memcpy(p[1], "world", 101);

    for(int i = 0; i < 2; i++)
    {
        printf("%s", p[i]);
    }
}

改成这样就行了

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    int num = 10;
    char **p = (char**)malloc(sizeof(char*) * num);
    for(int i = 0; i < 10; i++)
    {
        p[i] = (char*)malloc(sizeof(char) * 101);
    }

    memcpy(p[0], "hello\n", 101);
    memcpy(p[1], "world\n", 101);

    for(int i = 0; i < 2; i++)
    {
        printf("%s", p[i]);
    }
}

时间久了就忘了,总结是学习过程中的而重要一部分。。

另外有个差点忘了的是qsort

原型是这样

int comp(const void* a, const void* b)
{
    return *(int*)a - *(int*)b;
}

2、数组指针和指针数组申请内存 

 //针对oj浏览历史的题目,网页长度最大为32
    int num = 10;
    int a[num];
    //指针数组申请内存
    char *p[num];
    for(int i = 0; i < num; i++)
    {
        p[i] = (char*)malloc(sizeof(char) * 32);
    }

    //数组指针
    char (*p)[32] = (char(*)[32])malloc(sizeof(char) * 32 * 10);

 这边的数组指针直接malloc一段内存,然后指针赋值。指针数组这边如果不满足C99编译器没办法创建变长数组,不好实现。目前的办法就是写一个较大的常量值。

六、C99变长数组

C99之前,数组的创建只能用常量或者常量表达式,如果初始化数据的话也可以省略数组的大小。

c++都支持。

七、数组传参再次整理

2024年3月28日17:07:00

今天更新一下数组传参的相关知识点。起因是今天看了公司相关培训,包括前两天写的一个数组传参的代码。代码要实现的功能是数组传进函数,然后在函数里整理好再取回。我使用了&array写法。实现了功能。但是今天一想,不对啊这个知识点怎么平时没注意到啊。然后自己有梳理一下,写了下面的代码,发现也不需要使用&array这种写法。

梳理代码如下:

#include <stdio.h>
#include <stdlib.h>

void modifyArray1(int array[], int len)
{
    int size = sizeof(array);       //预测,array传参弱化为指针,size的值是4
    printf("size:%d",size);
    for(int i = 0; i < len; i++)    //c语言中不能这样写
    {
        printf("%d ",array[i]);
    }
    printf("\n");

    for(int i = 0; i < len; i++)    //c语言中不能这样写
    {
        array[i] = i;
    }

    for(int i = 0; i < len; i++)
    {
        printf("%d ",array[i]);
    }
    printf("\n");
}

void modifyArray2(int* array, int len)
{
    for(int i = 0; i < len; i++)
    {
        printf("%d ",array[i]);
    }
    printf("\n");

    for(int i = 0; i < len; i++)
    {
        array[i] = i;
    }

    for(int i = 0; i < len; i++)
    {
        printf("%d ",array[i]);
    }
    printf("\n");
}

void modifyArray3(int (*array)[10], int len)
{
    for(int i = 0; i < 4; i++)
    {
        for(int j = 0; j < len; j++)
        {
            printf("%d ",array[i][j]);
        }
        printf("\n");
    }
    printf("\n");

    for(int i = 0; i < 4; i++)
    {
        for(int j = 0; j < len; j++)
        {
            array[i][j] = i + j;
        }
    }

    for(int i = 0; i < 4; i++)
    {
        for(int j = 0; j < len; j++)
        {
            printf("%d ",array[i][j]);
        }
        printf("\n");
    }
    printf("\n");
}

void modifyArray4(int (*array)[10], int len)
{
    for(int i = 0; i < 4; i++)
    {
        for(int j = 0; j < len; j++)
        {
            printf("%d ", *((*array + i) + j));
        }
        printf("\n");
    }
    printf("\n");

    for(int i = 0; i < 4; i++)
    {
        for(int j = 0; j < len; j++)
        {
            *((*array + i) + j) = i + j;
        }
    }

    for(int i = 0; i < 4; i++)
    {
        for(int j = 0; j < len; j++)
        {
            printf("%d ",*((*array + i) + j));
        }
        printf("\n");
    }
    printf("\n");
}

void modifyArray5(int (*array)[10], int len)
{
    for(int i = 0; i < 4; i++)
    {
        for(int j = 0; j < len; j++)
        {
            printf("%d ", array[i][j]);
        }
        printf("\n");
    }
    printf("\n");

    for(int i = 0; i < 4; i++)
    {
        for(int j = 0; j < len; j++)
        {
            printf("%p ", &array[i][j]);
        }
        printf("\n");
    }
    printf("\n");

    for(int i = 0; i < 4; i++)
    {
        for(int j = 0; j < len; j++)
        {
            array[i][j] = i + j;
        }
    }

    for(int i = 0; i < 4; i++)
    {
        for(int j = 0; j < len; j++)
        {
            printf("%d ",array[i][j]);
        }
        printf("\n");
    }
    printf("\n");
}

void modifyArray6(int **array, int len)
{
    for(int i = 0; i < 4; i++)
    {
        for(int j = 0; j < len; j++)
        {
            printf("%d ", *((*array + i) + j));
        }
        printf("\n");
    }
    printf("\n");

    for(int i = 0; i < 4; i++)
    {
        for(int j = 0; j < len; j++)
        {
            *((*array + i) + j) = i + j;
        }
    }

    for(int i = 0; i < 4; i++)
    {
        for(int j = 0; j < len; j++)
        {
            printf("%d ",*((*array + i) + j));
        }
        printf("\n");
    }
    printf("\n");
}

int main()
{
    int array1[10] = {13,8,11,5,1,7,9,23,2,8};
    printf("array1:\n");
    modifyArray1(array1, 10);

    printf("array2:\n");
    int array2[10] = {13,8,11,5,1,7,9,23,2,8};
    modifyArray2(array2, 10);

    int array3[5][10] =
    {
        {13, 8,11, 5, 1, 7, 9,23, 2, 8},
        {23,28,21,25,21,27,29,23,22,28},
        {33,38,31,35,31,37,39,33,32,38},
        {43,48,41,45,41,47,49,43,42,48},
    };
    printf("array3:\n");
    modifyArray3(array3, 10);

    int array4[5][10] =
    {
        {13, 8,11, 5, 1, 7, 9,23, 2, 8},
        {23,28,21,25,21,27,29,23,22,28},
        {33,38,31,35,31,37,39,33,32,38},
        {43,48,41,45,41,47,49,43,42,48},
    };
    printf("array4:\n");
    modifyArray4(array4, 10);

    //申请动态数组
//    int** array5 = (int**)malloc(sizeof(int*) * 4);
//    for(int i = 0; i < 4; i++)
//    {
//        array5[i] = (int*)malloc(sizeof(int) * 10);
//    }
//    printf("array5:\n");
//    modifyArray5((int(*)[10])array5, 10);       //int (*)[10],这边竟然还要写成这样吗   //动态二维数组不能强制转换数组指针,地址不连续
//
//    for(int i =0; i < 4; i++)
//    {
//        free(array5[i]);
//        array5[i] = NULL;
//    }
//
//    free(array5);
//    array5 = NULL;
//执行结果Process returned -1073741819 (0xC0000005)   execution time : 6.893 s

    int** array6 = (int**)malloc(sizeof(int*) * 4);
    for(int i = 0; i < 4; i++)
    {
        array6[i] = (int*)malloc(sizeof(int) * 10);
    }
    printf("array6:\n");
    modifyArray6(array6, 10);               //动态数组二级指针传参

    for(int i = 0; i < 4; i++)
    {
        free(array6[i]);
        array6[i] = NULL;
    }
    free(array6);
    array6 = NULL;

    return 0;
}

//array1:
//size:413 8 11 5 1 7 9 23 2 8
//0 1 2 3 4 5 6 7 8 9
//array2:
//13 8 11 5 1 7 9 23 2 8
//0 1 2 3 4 5 6 7 8 9
//array3:
//13 8 11 5 1 7 9 23 2 8
//23 28 21 25 21 27 29 23 22 28
//33 38 31 35 31 37 39 33 32 38
//43 48 41 45 41 47 49 43 42 48
//
//0 1 2 3 4 5 6 7 8 9
//1 2 3 4 5 6 7 8 9 10
//2 3 4 5 6 7 8 9 10 11
//3 4 5 6 7 8 9 10 11 12
//
//array4:
//13 8 11 5 1 7 9 23 2 8
//8 11 5 1 7 9 23 2 8 23
//11 5 1 7 9 23 2 8 23 28
//5 1 7 9 23 2 8 23 28 21
//
//0061FE40 0061FE44 0061FE48 0061FE4C 0061FE50 0061FE54 0061FE58 0061FE5C 0061FE60 0061FE64
//0061FE44 0061FE48 0061FE4C 0061FE50 0061FE54 0061FE58 0061FE5C 0061FE60 0061FE64 0061FE68
//0061FE48 0061FE4C 0061FE50 0061FE54 0061FE58 0061FE5C 0061FE60 0061FE64 0061FE68 0061FE6C
//0061FE4C 0061FE50 0061FE54 0061FE58 0061FE5C 0061FE60 0061FE64 0061FE68 0061FE6C 0061FE70
//
//0 1 2 3 4 5 6 7 8 9
//1 2 3 4 5 6 7 8 9 10
//2 3 4 5 6 7 8 9 10 11
//3 4 5 6 7 8 9 10 11 12
//
//array5:
//16784448 16783224 16783272 16783320 -1529729396 40765 16783368 16784496 1936028764 1818575988
//1768053810 1698978926 6780258 959656243 -1462555007 251699003 1096563268 1666994256 1113941103 1801678700
//1936028764 1818575988 1768053810 1698978926 1550284130 1953719668 845964612 1702389038 942682112 1836008284
//-1496109425 134258486 16783224 16784344 16784384 1635212346 100663302 40765 16783224 16777408
//
//01001BC0 01001BC4 01001BC8 01001BCC 01001BD0 01001BD4 01001BD8 01001BDC 01001BE0 01001BE4
//01001BE8 01001BEC 01001BF0 01001BF4 01001BF8 01001BFC 01001C00 01001C04 01001C08 01001C0C
//01001C10 01001C14 01001C18 01001C1C 01001C20 01001C24 01001C28 01001C2C 01001C30 01001C34
//01001C38 01001C3C 01001C40 01001C44 01001C48 01001C4C 01001C50 01001C54 01001C58 01001C5C
//
//0 1 2 3 4 5 6 7 8 9
//1 2 3 4 5 6 7 8 9 10
//2 3 4 5 6 7 8 9 10 11
//3 4 5 6 7 8 9 10 11 12
//
//
//Process returned -1073741819 (0xC0000005)   execution time : 2.389 s
//Press any key to continue.
//
//
//Process returned -1073741819 (0xC0000005)   execution time : 2.094 s
//Press any key to continue.
//第一次打印结果array5那边已经报错,returned -1073741819 (0xC0000005)是因为访问到非法内存
//array6已经打印不出来了
//打印信息中有地址,可以看到静态二维数组地址连续。动态二维数组地址不连续


//下面是注释掉array5后的打印结果,正常打印
//array1:
//size:413 8 11 5 1 7 9 23 2 8
//0 1 2 3 4 5 6 7 8 9
//array2:
//13 8 11 5 1 7 9 23 2 8
//0 1 2 3 4 5 6 7 8 9
//array3:
//13 8 11 5 1 7 9 23 2 8
//23 28 21 25 21 27 29 23 22 28
//33 38 31 35 31 37 39 33 32 38
//43 48 41 45 41 47 49 43 42 48
//
//0 1 2 3 4 5 6 7 8 9
//1 2 3 4 5 6 7 8 9 10
//2 3 4 5 6 7 8 9 10 11
//3 4 5 6 7 8 9 10 11 12
//
//array4:
//13 8 11 5 1 7 9 23 2 8
//8 11 5 1 7 9 23 2 8 23
//11 5 1 7 9 23 2 8 23 28
//5 1 7 9 23 2 8 23 28 21
//
//0 1 2 3 4 5 6 7 8 9
//1 2 3 4 5 6 7 8 9 10
//2 3 4 5 6 7 8 9 10 11
//3 4 5 6 7 8 9 10 11 12
//
//array6:
//7280504 7281624 7281664 1635212346 100663302 33405 7280504 7274688 1329876553 1509970775
//7281624 7281664 1635212346 100663302 33405 7280504 7274688 1329876553 1509970775 1551352187
//7281664 1635212346 100663302 33405 7280504 7274688 1329876553 1509970775 1551352187 33400
//1635212346 100663302 33405 7280504 7274688 1329876553 1509970775 1551352187 33400 7281624
//
//0 1 2 3 4 5 6 7 8 9
//1 2 3 4 5 6 7 8 9 10
//2 3 4 5 6 7 8 9 10 11
//3 4 5 6 7 8 9 10 11 12
//
//
//Process returned 0 (0x0)   execution time : 0.062 s
//Press any key to continue.

这次也是对数组传参重新梳理了一下吧。

2024年4月9日11:23:06更新

感觉这边的代码还是有问题。原由今天做的牛课题HJ35蛇形数组。我想创建一个动态二维数组,但是实际上我写的代码有问题。在将二位数组传参之后,形参是二级指针,在子函数中解引用二级指针。我没有使用array\[x][y]形式,而是使用\*((*array + i) + j)形式,问题就在这边,我现在不确定这样写是否是对的。我百度基本看到的都是array\[x][y]形式。

我看了**二维数组**自己的文档,对于实参是二维数组,形参用数组指针的,解引用方式是\*((*array + i) + j)。这边应该没有问题。关于这点我还查了资料,描述如下:

> [数组指针](https://m.baidu.com/s?word=数组指针&sa=re_dqa_zy)解引用指的是通过指针访问数组中的元素。具体来说,对于一维数组,数组指针的类型是int *,解引用操作后得到的是整个数组的地址,即指向数组的第一个元素。对于二维或多维数组,数组指针的类型是int (*)[n],其中n是数组的深度,解引用操作后得到的是指向数组的第一个元素的指针,即指向数组的深度为1的子数组。例如,对于二维数组int arr,arr是一个指向二维数组的指针,解引用操作后得到的是一个指向二维数组首元素的指针,即指向元素值为1的子数组。

由上述文字,可以得,数组指针先解引用得到一个指向二维数组首元素的指针。

对于二级指针,是要以下面这种形式

第一种:\*(*a+1)--------等价于a\[0][1]

第二种:\*(*(a+1) + 1)------等价于a\[1][1]

其实这个很久以前就知道了,但是还是忘了啊或者迷糊了

回到题目中,所以二级指针解引用要写成\*(*(a+1) + 1)形式。

但是为什么二级指针也可以使用形如a\[x][y]形式呢?这还是个问题。


以上代码注意事项:

①打印地址的时候代码没写对,写成了

printf("%p ", array[i][j]);

 应该是

printf("%p ", &array[i][j]);

然后上面多次malloc打印地址信息看起来地址是连续的,但是实际上物理地址是不连续的。具体见帖子:C中关于malloc 内存地址是如何分配的?_memalign申请得内存地址物理上连续吗-CSDN博客


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值