数组与指针

目录

一、数组

二、指针

三、指针数组与数组指针

指针数组:

数组指针:

四、一维数组与指针

小例子1:写一个函数接收数组名并遍历输出数组元素

方法1:指针类型做函数形参

方法2:数组类型做函数形参

小例子2:写一个函数接收数组的地址(数组指针)并遍历输出

方法1:数组指针类型做函数形参

方法2:指针类型做函数形参

结论:写一个函数遍历一维数组问题

五、二维数组与指针

小例子1:写一个函数接收二维数组名并遍历输出数组元素

方法一:二维数组/数组指针类型做形参

方法二:一级指针做函数形参

方法三:二级指针类型做函数形参

总结:两种方法

小例子2:写一个函数接收二维数组地址并遍历输出数组元素


一、数组

C语言中的数组是存储在一块连续的内存空间之中的多个相同类型的变量。也就是说,即便是二维数组中的元素其地址也是连续的,而不是分行存储的。这里的地址连续指的是在进程的虚拟地址空间中是连续的,在实际的物理内存上不一定连续。

二、指针

指针是一种特殊的变量,其存放的是某种类型变量的地址。在32位系统中,指针变量的大小是4个字节。这是因为指针用来存放地址,而在32位的系统中,地址总线的位数也是32位,因此指针变量的大小也是32位即4个字节。

       二级指针指的是一个指针变量其存储的内容是另一个指针变量的地址,即指针的指针。

三、指针数组与数组指针

指针数组:

p本质是一个数组,不能对p解引用

int *p[5];

因为[ ]比*的优先级高,因此可以看成 int* p[5],即指针数组的本质是数组,其内部的元素是int*类型的指针,此时p是包含5个int*类型元素的指针

数组指针:

p本质是一个指针,指向一个int [5] 即包含5个int型元素的数组

int (*p)[5]

四、一维数组与指针

对于一个一维数组a[5],数组名a的值的是数组首元素的地址,也就是如下例子中a的类型是int *

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

一维数组名的值是数组首元素的地址,仅此而已,其类型还是数组并不是指针,只不过能够自动类型转换成为指针,而对数组取地址 &a的本质则是一个数组指针

int (*)[5],即&a指向一个包含5个int类型元素的数组。但是实际上&a和a的值是相同的,因为数组的首地址就是数组首元素的地址,只不过类型不同。

小例子1:写一个函数接收数组名并遍历输出数组元素

方法1:指针类型做函数形参

以下这两种方式都是用指向首元素的指针类型来接收数组,因为数组名在极大多数时候跟指针是可以等价使用的,但需要注意的是,可以等价使用,但两者不等价。

void func(int *p, int len){
    for(int i = 0; i < len; ++i){
        cout<<*(p + i)<<endl;
    }

}

void func(int *p, int len){
    for(int i = 0; i < len; ++i){
        cout<<p[i]<<endl;
    }

}

int main(){
    
    int a[5] = {1, 2, 3, 4, 5};
    func(a, 5);
}

首先一个例子是用sizeof函数的时候,sizeof(数组名a)得到的是数组中所有元素占用的空间大小(以字节算),而sizeof(p)得到的是指针本身的大小,比如32位系统中就是4个字节。还有一个例子如下:即指针是可以改变的,而数组名是不可以改变的。

void func(int *p, int len){
     for(int i = 0; i < len; ++i){
        if(i){
             p += 1;
         }
        
        cout<<*p<<endl;
    }

 }

上边函数是正确的,下边代码段是错误的

for(int i = 0; i < 5; ++i){
        if(i){
            a += 1;
        }
        cout<<*a<<endl;
    }

所以,数组名跟指针之间真正的关系是:数组名可以退化成指针(数组类型自动类型转换成指向首元素的指针类型)。这就是数组和指针唯一的关系

当我们将数组名作为实参传递的时候,会自动类型转换成指向首元素的指针类型,这也解释了为什么sizeof作用于数组名和指针的时候不同,以及数组名不能改变,指针可以改变,因为它们本来就是完全不同的两种类型。

方法2:数组类型做函数形参

当用int p[ ]做形参时候,跟用int * 完全一样,即一维数组类型做形参的时候会完全退化成指针类型,所以用sizeof在我的64位机器上的结果是8个字节。故用int p[]来接收指针类型自然是可以的。

即便是在做形参的时候指定数组大小,int p[5], 结果依然是和int *等价的

总结:int p[option]做函数形参与int *p完全等价

void func(int p[], int len){
    cout<<sizeof(p)<<endl;
    for(int i = 0; i < len; ++i){
        if(i){
            *p += 1;
        }
        
        cout<<*p<<endl;

}

int main(){

    int a[5] = {1, 2, 3, 4, 5};
    func(a, 5);
    
    return 0;
}

小例子2:写一个函数接收数组的地址(数组指针)并遍历输出

方法1:数组指针类型做函数形参

需要注意的是这次传递的实参是&a,其类型是int (*)[5]指向数组的指针,这个时候的参数传递不能发生退化或自动类型转换,即函数形参p必须也是int(*)[5]类型的。即p是一个数组指针。

void func(int (*p)[5], int len){
    for(int i = 0; i < len; ++i){
        
        cout<<(*p)[i]<<endl;
        cout<<*((*p) + i)<<endl;
    }

}

int main(){
    
    int a[5] = {1, 2, 3, 4, 5};
    func(&a, 5);
}

对数组指针解引用得到数组类型,因此这个时候对p解引用得到的是数组类型,即*p并没有退化成指向首元素的指针,*p的类型是 int[5] ,而不是int *,因此*p是不能改变的,像下面这样的代码编译不通过。

void func(int (*p)[5], int len){
    for(int i = 0; i < len; ++i){
        if(i){
            *p += 1;
        }
        // cout<<(*p)[i]<<endl;
        // cout<<*((*p) + i)<<endl;
    }

}

 

方法2:指针类型做函数形参

虽然数组指针类型&a没办法自动类型转换成指针类型int *,

void func(int *p, int len){
    for(int i = 0; i < len; ++i){
        cout<<*(p + i)<<endl;
}

}

int main(){
    
    int a[5] = {1, 2, 3, 4, 5};
    func((int *)&a, 5);
    
    return 0;
}

结论:写一个函数遍历一维数组问题

函数形参直接写成指针类型,即int *,这样可以传递数组名也可以传递数组指针,传递数组指针的时候只需要对实参进行强转就行了。

 

五、二维数组与指针

首要问题:二维数组的本质也是内存中一段连续的区域,二维数组名的值是二维数组0行0列即首元素的地址,但二维数组名的类型还是 int [][]。

小例子1:写一个函数接收二维数组名并遍历输出数组元素

方法一:二维数组/数组指针类型做形参

首先,如果要用二维数组做形参,则至少要指定列数,行数可以指定也可以不指定。

如同一维数组做形参会自动类型转换成一维指针,二维数组做形参时会自动转换成为数组指针。

二维数组做右值的时候,会自动转换成数组指针类型,即int a[2][3]会自动转换成

int (*a)[3]。因此用二维数组和用数组指针做形参是等价的。可以看到我们在输出值的时候用了两层解引用。其实对于数组指针来说,直接用a[i][j]也是可以的。用两层解引用的含义是:a是 int (*)[3]类型的数组指针,*a的结果是一个一维数组 int [3]类型(注意是数组而不是一级指针 int *),a + i的意思就是第i行数组,第一层解引用得到了第i行数组,其值就是该行第一个元素的首地址,首地址 + j再进行第二次解应用就得到的具体的值。当然,也可以在第一次解引用后直接用下标,即 cout<<(*(a + i))[j]<<endl;

void func(int a[][3], int row, int col){
    for(int i = 0; i < row; ++i){
        for(int j = 0; j < col; ++j){
            cout<<*(*(a + i) + j)<<endl;
        }
    }
}

void func(int (*a)[3], int row, int col){
    for(int i = 0; i < row; ++i){
        for(int j = 0; j < col; ++j){
            cout<<*(*(a + i) + j)<<endl;
        }
    }
}

int main(){
    
    int a[2][3] = {1,2,3,4,5,6};
    func(a, 2, 3);
    // func((int **)a, 5);

    return 0;
}

方法二:一级指针做函数形参

因为C语言中数组都是连续存储的,因此我们可以用首地址加偏移量的方式来遍历每一个元素。即

cout<<*(a + i * col + j)<<endl;

但是需要注意的是二维数组是没办法自动类型转换成一级指针的,因此在调用的时候需要强转一下。

void func(int *a, int row, int col){
    for(int i = 0; i < row; ++i){
        for(int j = 0; j < col; ++j){
             
            cout<<*(a + i * col + j)<<endl;
        }
    }
}

int main(){
    
    int a[2][3] = {1,2,3,4,5,6};
    func((int *)a, 2, 3);
    // func((int **)a, 5);

    return 0;
}

方法三:二级指针类型做函数形参

首先明确一点,二级指针int **与二维数组int [ ][ ]没有半毛钱关系!因此虽然可以用二级指针来接收二维数组,但最后遍历元素的时候还是相当于用了一级指针中首地址加偏移量的方式。即在func函数中虽然形参是二级指针,但是具体使用的时候还是转成了一级指针来用。

同理,二维数组也是没办法自动转成二级指针的,因此调用函数的时候需要强转。

void func(int **a, int row, int col){
    for(int i = 0; i < row; ++i){
        for(int j = 0; j < col; ++j){
            cout<<*((int *)a + i * col + j)<<endl;
        }
    }
}

int main(){
    
    int a[2][3] = {1,2,3,4,5,6};
    //func(a, 2, 3);
    func((int **)a, 5);

    return 0;
}

总结:两种方法

一种不需要强转,函数形参是数组指针,因为二维数组做右值自动退化成数组指针。

一种需要强转,函数形参可以理解为是一级指针,此时利用了数组的连续存储性质,用首地址加偏移量的方式来遍历输出。

小例子2:写一个函数接收二维数组地址并遍历输出数组元素

因为对二维数组取地址&a类型是 指向二维数组的指针,int (*a)[2][3],唉,没必要考虑那么多,一个强转成一级指针用首地址加偏移量遍历就行,即(int *)&a 。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值