指针、引用与数组的粗浅理解

引用

引用和指针

以我的粗浅且可能有很大错误的理解,引用就是有着一些限制、有一些封装的指针,这些限制在编译器层面解决,底层就是以指针形式实现。

限制在如下几点:

  • 引用创建时需要初始化
  • 引用在初始化后无法再改变指向
    • 所以不存在 const int&

引用带来的方便:

  • 引用的元素可以直接按名访问,不需要解指针

指针

指针是指向一块内存首地址的变量。指针的类型提供了对于这个首地址该如何解释的信息。

智能指针

TODO:目前是从 YDJSIR 笔记中复制粘贴的 智能指针内容繁杂,需要开一篇专门文章

  1. 通过将一些需要的信息进行封装的方法,来保证不管出现什么异常,在退出相应操作部分时,自动调用对象的析构函数来保证不会出现内存泄漏的问题。

  2. 同样的还有句柄类(C++ 异常中有)

template <class T>
class auto_ptr{
    public:
        auto_ptr(T *p=0):ptr(p) {}
        ~auto_ptr() { delete ptr; }
        T* operator->()  const { return ptr;}
	    T& operator *()  const { return *ptr; }
    private:
        T*  ptr;
};
//结合智慧指针使用
void processAdoptions(istream& dataSource){
    while (dataSource){
        auto_ptr<ALA> pa(readALA(dataSource));
        pa->processAdoption();//只要对象结束,就会自动delete
    }
}

函数指针

函数指针能够像传递参数一样传递函数。并且也便于我们实现多态。

对于函数指针的简单使用,我还是持开放态度的。但是众所周知,函数指针是能够复杂到令人呕吐的程度的。这种情况还是尽可能不要用函数指针了,要不就用 typedef 定义得好看点。不要恶心自己的同时还恶心他人……

定义函数指针

int (*fp)(int);

fp 就可以存放返回值为 int,参数为一个 int 的函数了。

可以这样理解:fp 是标识符,*fp 中的 * 在说这个变量是个指针,最前面的 int 和后面括号中的 (int) 说明了这是函数指针,并指明了参数和返回值。

int func(int a);

// 写法1:直接赋值
fp = func;
// 写法2:取地址再赋值
fp = &func;

对 fp 的赋值,两种写法均可。写法1 是写法2 的语法糖。

调用

// 写法1:直接调用
fp(1);

// 写法2:解引用后再调用
(*fp)(1);

既可以直接调用,也可以解引用后再调用。

ChatGPT 说直接调用的写法实际上是个 Syntax sugar,编译器会将直接调用改成解引用后再调用的写法。也就是写法1 编译后和写法2 无异。这个说法令人信服,毕竟直接调用的行为本质上是对一个指针变量也就是一个地址进行调用,这是完全没有意义的。

作为参数传递

int foo(int arg1, int (*fp)(int)) {
	result = fp(arg1);
}

写法和定义类似。这里函数指针作为参数传递的一大重要作用是可以实现多态。

使用 typedef 简化函数指针

typedef int (*FP)(int)

FP fp = func
fp(1);

typedef 之后, FP 就是这类函数指针的类型别名了。

这 typedef 的写法,评价为纯纯的答辩。但是这样简化之后,再使用这个函数指针类型就方便了。

深入赤石

以下内容相对复杂,建议浅尝辄止。实际生产中最好不要使用太多复杂的函数指针。

  • 函数指针数组

其实,函数指针数组还不错。初见函数指针数组还是操作系统实验中,使用函数指针存放信号处理函数。

void (*sigaction[SIGNAL_NUM](int, struct proc *));

// typedef 防赤石
typedef void (*sigaction_ptr)(int, struct proc *);
sigaction_ptr[SIGNAL_NUM];

写法还是相对清晰的。可以类比指针数组的写法:

char *str_list[STR_NUM];
  • 函数指针作为返回值
void (*(*f(int)))(int, int)

数组

只要时刻记住,数组是存放在连续空间上的一系列数据。我们想以几维访问都无所谓,只要保证不越界就行。

一维数组

没什么好说的,搬一些数组访问的等价式。说白了就是一些语法糖。

a[i] == *(a + i)

&a[0] = a

二维数组

同样搬等价:

int b[MAXN][10];

int *q;

b[i][j] == *(b + i * 10 + j)  // 第二维大小在这起作用
		== q[i * 10 + j]      // 降维操作,按一维访问

升维降维

我重新理解了这部分。升维降维,归根结底还是逃不出数组的本质:一块连续的内存。

对一块长度为 LEN 字节的数组 a

我们如何去解释这块内存中的每个元素,决定了我们遍历的总次数,也就是元素个数。

byte_t *a = malloc(LEN);

// 解释为 char,就遍历 LEN 次
char *p_char = (char *) a;
for(int i = 0; i < LEN; i++) char element = p_char[i];

// 解释为 int,就只要遍历 LEN / 4 次
int *p_int = (int *) a;
for(int i = 0; i < LEN / 4; i++) int element = p_int[i];

// 解释为 char[10],就要遍历 LEN / 10 次
char (*p_char10)[10] = (char (*)[10]) a;
for(int i = 0; i < LEN / 10; i++) char *element = p_char5[i];

我们决定好了元素解释方法后,总次数确定了。假设总次数为 t ,我们想以几维访问就以几维访问,只要和逻辑相符就行。

同时,用 typedef 或者 using 将元素的类型抽象出来会简化很多工作。

参考资料

YDJSIR 的 C++ 笔记

CPL-2023

万字长文系统梳理一下C++函数指针 - 知乎

C++ 函数指针 & 类成员函数指针 | 菜鸟教程