数组、数组指针和函数指针

数组

数组中每个元素都是对象,即占有特定类型的内存空间。
数组名可以转化为这个数组对象的首个元素的地址。
这里我们不去讨论一维数组,直接从二维说起。所谓二维数组也是数组,只不过它的元素也是一个数组。

首先我们写一个二维数组留作使用

#include<iostream>
using namespace std;
int a[][10]={
    {1,2,3,4,5,5,6,7,8,8},
    {10,12,32,42,51,15,16,71,121,18}
};

既然说到数组名是其首个对象的地址那么来验证一下,测试数组名,以及对数组名求地址:


void test01(){
    cout << (long long)a << endl;         // 140273290059808
    cout << (long long)(a+1) << endl;     // 140273290059848
    // 相差40个字节
    cout << (long long)&a << endl;        // 140273290059808
    cout << (long long)(&a+1) << endl;    // 140273290059888
    // 相差80个字节
   }

a 与 a + 1 正好相差40个字节,代表 a 是这个一整行对象的地址,即首行地址。

取址符取得是整个对象的地址,&a 是对二维数组求址,自然偏移一位就变成了整个二维数组的尾地址,c++中的尾地址是对象所在地址的下一位。&a+1 正好比 a 多了 80 个字节。

也就是,数组名 a 自动转化成指向第一个元素的指针, 那么这个指针是什么类型呢?

编译器会把它识别成 int (*) [10]。也即这个指针指向了第一维数组,数组作为对象,占有一块数组类型的内存空间。因为对象是指一块能存储数据并且具有某种类型的内存空间。

我们解引用一下:

void test03(){
    cout << *a << endl;     // 0x7f051ce02020
    //为了验证,我们偏移一下
    cout << *(a + 1) << endl; // 0x7f051ce02048
    // 正好相差40个字节

经过一次偏移正好相差了 40 个字节,变为第二个元素对象

第二层解掉呢?a作为指针,是一个十个元素的对象地址,解一次引用得到这个对象,由于这个对象还是个数组,也会自动转化为指向自己的首个对象(10个元素的第一个元素的位置)的指针。

因此第二层解掉自然就是第一个 int 型的元素:

    cout << *(*a) << endl; //   1 **a 即可

因为指针指向数组对象时,可以用下标访问下一个位置,又 a 是指针指向了数组,下一个偏移为 0,即 * a = * (a + 0) ,可以写成:

    cout << *a[0] << endl; //   1

基于上述, *a 也就是a[0],也会自动转化为指向自己的首个对象(10个元素的第一个元素的位置)的指针。所以 a[0] 可以用下标访问数组对象(10个元素)内其他元素:

    // cout << (a[0])[0] << endl; 
    cout << a[0][0] << endl;//   1

我们多搞几个案例:

    // 要是访问当前行的下一个元素呢?将这个首地址
    cout << *(*a + 1) << endl;// 2 即 *((*a) + 1) 
    // 请注意这里的指针是 (*a),
    cout << (*a)[1] << endl; //    2
    // 同理(*a)相当于*(a + 0) 即a[0] 
    cout << a[0][1] << endl; //2
    
    // 如果访问下一行呢?
    cout << **(a+1) << endl;
    cout << *a[1] << endl; 
    cout << a[1][0] << endl; // *(a[1] + 0)
    
    // 第二行第二个元素呢?
    cout << *(*(a+1) +1 ) << endl;
    cout << *(a[1] +1) << endl;
    cout << a[1][1] << endl;

基于上述推导,我认为,下标是就是为了让数组不在抽象,好表达。
于是a[0] 就表示a指向的第一个元素,a[1]就是下一个。感觉C++中在编程中应该尽量避免用使用指针。但这里作为没怎么用过C++的我来说还是得熟悉一下的。

	cout << *a << endl;     // 0x7f051ce02020
    cout << *(a + 1) << endl; // 0x7f051ce02048
    cout << a[0] <<endl; //  0x7f051ce02020
    cout << a[1]<< endl; //  0x7f051ce02048

为了进一步探讨,我们看一下类型:

	cout << typeid(*a).name()<< endl;  // A10_i
    cout << typeid(&a[0]).name()<< endl; // PA10_i

由此我们可以看出 取址符是会包含对象整体的信息, 是整体的地址,如下也是一样:

    cout << typeid(a).name()<< endl; // A2_A10_i
    cout << typeid(&a).name()<< endl; // PA2_A10_i

A2_A10_i:由多维数组是数组的数组猜有大到小的猜,A: 这个是数组类型,是两个对象组成的数组,每个对象是由有10个对象的数组,这10个对象是int型的

PA10_i :是一个指针类型, 指向一个数组对象,这个数组对象有10个int型的对象

PA10_i 编译器会把它识别为 int (*)[10],翻译成人话应该是一个指向具有10个元素的数组的指针类型,可以通过试错得到哦:
比如:

 	int *p  = a;// cannot convert ‘int (*)[10]’ to ‘int*’ in initialization

由下面也可以看出, 数组名会自动转成首个元素的地址

	int (*p)[10] = a;
    cout << typeid(p).name()<< endl; // PA10_i
    cout << typeid(&a[0]).name()<< endl; // PA10_i
    int (*ptr)[10] = &a[0];
    cout << typeid(ptr).name()<< endl; // PA10_i
}

数组指针

一般声明数组的时候都有:

int arr[10]; //
int * pArr[10]; //数组里面装的指针

如何声明一个指向数组对象的指针呢?其实刚才已经一不小心就把 int (*)[10] 这个类型引入出来了,通过typeid 我们又发现他是PA10_i,照书上说的由内向外,以外国人的思维,先说重点的后说细节的翻译:这是一种指针(P),指向了一个数组对象(A),数组里面有10个小对象,这些小对象是int型。

int (*p)[10];  
int (&ref)[10] = a[1]; //将ref 引用绑定到a[1]这个对象 ,注意了是指第二行整体。

如果换一个想法的话好像更好理解,括号运算符先执行,这个参数解引用后是一个数组对象,这个数组对象有10个元素。

由此看来,对于数组来说,声明都是这个调调,比普通变量声明多了一个维度说明

举例:

void test04(){
    for(int (*p)[10]= a; p!= a + 2; p++){
        for(int *q= *p;  q != *p + 10; q++)
             cout << *q << " ";
        cout << endl;
    }
    // 1 2 3 4 5 5 6 7 8 8
    // 10 12 32 42 51 15 16 71 121 18
}

可以用类型别名来简化

typedef int int_array[10];
using intArray= int[10];

于是:

void test05(){
    for(int_array *p = a; p!= a + 2; p++){
        for(int *q= *p;  q != *p + 10; q++)
             cout << *q << " ";
        cout << endl;
    }
    // 1 2 3 4 5 5 6 7 8 8
    // 10 12 32 42 51 15 16 71 121 18
}
void test06(){
    for(intArray *p = a; p!= a + 2; p++){
        for(int *q= *p;  q != *p + 10; q++)
             cout << *q << " ";
        cout << endl;
    }
    // 1 2 3 4 5 5 6 7 8 8
    // 10 12 32 42 51 15 16 71 121 18
}

用auto类型说明符,更方便点,但是,这里必须明确,row是对a每一行的引用。如果没有&,row被转化为数组名,也就是每一行第一个元素的地址,不具备迭代功能:

void test07(){
    for(const auto &row: a){
        for (const auto col: row)
            cout << col << " ";
        cout << endl;
    }
    // 1 2 3 4 5 5 6 7 8 8
    // 10 12 32 42 51 15 16 71 121 18
}

如何让一个函数返回数组指针:,利用之前的思路: int (*p)[10], 我们很快能理解,让对一个func返回的结果解引用后是一个数组:
Type (*func( paramlist))[dimension];

int (*altArray(int i))[10] // // 这里想用i去替换数组里的数,并且返回一个新的数组,当然也会改变原来的数组,这里只是为了返回一个数组玩玩
{
    for( int(&row)[10] : a)
    {
        for ( int &col: row)
            col=i;
    }
    return a;
}
// 当然可以简化一下
int (*altArrayI(int i))[10]
{
    for( auto &row : a){
        for ( int &col: row)
            col=i;
    }
    return a;
}
// 类型别名:
intArray *altArrayIII(int i){
    for(intArray &row : a){
        for ( int &col: row)
            col=i;
    }
    return a;
}

我们也可以用 decltype 简化类型名, 注意:decltype 并不将数组类型转成对应的指针,但是我们可以用取址符得到这一指针

decltype(&a[0]) altArrayII(int i)
{
    for(auto &row : a){
        for ( int &col: row)
            col=i;
    }
    return a;
}

还有一种就是在leetcode中经常见到的位置返回类型

auto altArrayIV(int i)->int(*)[10]
{
    for(auto &row : a){
        for ( int &col: row)
            col=i;
    }
    return a;
}

我们用数组指针传参:

void printArray1(intArray *p){
    // 利用for读出看看:
    for(int i =0;i<2;++i){
        for(int j=0; j<10;j++){
            // p 此时就是 &a[0]
            // p+i 就是&a[i]
            // 解引用就是 a[0],a[i], 
            // 就是说(*p/a[0])指向了一个数组的首个元素的地址
            // 即 &a[0][0], &a[i][0]
            // 再解一次就是a[0][0]
            // 所以有:
            // cout << *(*(p + i) +j) << " ";
            cout << p[i][j] << " ";
        }
        cout << endl;
    }
    cout << endl;
}

测试:

void test08(){
    intArray * p = altArray(10);
    intArray * p1 = altArrayI(10);
    intArray * p2= altArrayII(10);
    intArray * p3 = altArrayIII(10);
    intArray * p4 = altArrayIV(10);
    printArray1(p);
    printArray1(p1);
    printArray1(p2);
    printArray1(p3);
    printArray1(p4);
}

函数指针

我们声明一般是这样的:

int arr[10]; 
int * pArr[10];
int (*p)[10]; 
int func1(int i);
int *func2(int i);
int &func3(int i);
int (*altArray(int i))[10];

那如何声明一个函数指针呢?

// 书上的例子
bool lengthCompare(const string &, const string &);
bool (*pf)(const string &, const string &);

就是解引用以后是一个bool函数;也就是一个指向bool函数的指针。
如何初始化呢?龟龟,与int (*p)[10]; 好像啊,都是具有自己的特点,函数就是带参数列表的变量,数组就是带维度的变量,这么说应该也没错吧。

void test09(){
    void (*printf)(intArray *) = printArray1;
    (*printf)(a);
    // 1 2 3 4 5 5 6 7 8 8
    // 10 12 32 42 51 15 16 71 121 18
}

函数指针形参

可以用指针表示 当然也可以传递,我们重载一下 altArrayIII , 测试将输出数组的函数printArray1的指针传给它:

// 类型别名:
typedef void (*PrintArray)(intArray *) ;
intArray *altArrayIII(int i, PrintArray pfa){
    cout << "修改前:" << endl;
    pfa(a);
    for(intArray &row : a){
        for ( int &col: row)
            col=i;
    }
    cout<<  "修改后:" << endl;
    pfa(a);
    return a;
}
// 测试:
void test10(){
    // void (*printf)(intArray *) = printArray1;
    // 函数名类似数组名也可以转化为地址:
    altArrayIII(15, printArray1);
}

返回函数的指针

这一块在函数声明了解以后应该不难理解,目的是让一个函数返回一个函数的指针, 用个小栗子:

int myAdd(int a, int b){
    return a + b;
}
int mySub(int a, int b){
    return a - b;
}
using Fp = int(*)(int, int); // 确定一下类型,等会好操作
Fp test(bool isSub) // 要返回一个指针,指针解引用后是一个函数
{
    if(isSub)
        return mySub;// 函数名自动转化为地址
    else
        return myAdd;
}
void test11(){
    bool sub = true;
    Fp p = test(sub);
    int ret = (*p)(10, 15);
    cout<<ret << endl;
}

复杂一下,将今天所有的都写上:

void  altArrayAndPrint(int i, PrintArray pfa)
{
    cout << "修改前" << endl;
    pfa(a);
    for(intArray &row : a){
        for ( int &col: row)
            col=i;
    }
    cout<<  "修改后" << endl;
    pfa(a);
}
auto testReturnFuncPointer3()->void (*)(int , PrintArray )
{
    return altArrayAndPrint;
}
void test12(){
    auto testPtr3 = testReturnFuncPointer3();
    (*testPtr3)(13, printArray1);
    // 修改前
    // 1 2 3 4 5 5 6 7 8 8
    // 10 12 32 42 51 15 16 71 121 18

    // 修改后
    // 13 13 13 13 13 13 13 13 13 13
    // 13 13 13 13 13 13 13 13 13 13
}

下面是回的四种写法,不用看了。

using AltPtr = void (*)(int , PrintArray );
typedef decltype(altArrayAndPrint) *Func;
typedef void (*Func2) (int , PrintArray );

AltPtr  testReturnFuncPointer()
{
    return altArrayAndPrint;
}

Func testReturnFuncPointer1(){
    return altArrayAndPrint;
}
Func2 testReturnFuncPointer2(){
    return altArrayAndPrint;
}

int main(){
    // test01();
    // test02();
    // test03();
    // test04();
    // test05();
    // test06();
    // test07();
    // test08();
    // test09();
    // test10();
    // test11();
    test12();
    // AltPtr testPtr = testReturnFuncPointer();
    // (*testPtr)(16, printArray1);
    // Func testPtr1 = testReturnFuncPointer1();
    // (*testPtr1)(15, printArray1);
    // Func2 testPtr2 = testReturnFuncPointer2();
    // (*testPtr2)(12, printArray1);
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值