Pointer

1. You need to know.

We use type *variables to declare a pointer.

Type of pointer is important, it not only hint the data- type it point, it also means how long of one step if you use pointer to operate .

For example, memory address between char *p1 = sth and p1 + 1 is 1 byte.

But, memory address between int *p1 = sth and p1 + 1 is 4 bytes.

Just because int takes 4bytes, char takes 1byte.

* 解引用操作符,是指针操作中非常重要的工具。相对于上面声明指针时的 int *里的*用作声明,

运算符中的 * 将指定内存地址中所存储的值,取出来。

2. 指针操作实例

这里有一个一维数组

x[0]x[1]x[2]x[3]x[4]
12345

我们让
int *ptr1 = x;
那么编译器便在stack区中画出了一些地址,用于存储ptr所指向的内存地址,并且指出这是一个int型的指针。

显然,打印 ptr1 的结果,将会是数组的首地址,即 &x[0]

而如果你打印 ptr+1 则会是 &x[1], 很简单,因为你是int*型指针,一步走4bytes。举个例子

#include <cstdio>
int main() {
    char a[] = "abcdefgh";
    char *p1 = a;
    int *p2 = (int*)a;
    printf("%c\n", *(p1+1));
    printf("%c", *(p2+1));
    return 0;
}

Results:

b
e

是不是char类型的走了一步读到b,int类型走了一步读到e?

如果打印 *(ptr+1) 结果是 2 ,这也是显然的因为 取出 &x[1] 里存放的值,便是2

We assume there is an int array of two dimension.

x[0][0]x[0][1]x[0][2]x[0][3]x[0][4]
12345
x[1][0]x[1][1]x[1][2]x[1][3]x[1][4]
678910

要定义一个多维数组的指针,我们首先要指出这个指针是什么类型的,或者说一步走多远:

int (*p)[5] = x 这个的意思是,声明一个指针p,但类型却是 int[5],或者说,这是一个 一步走5*4bytes的int指针

你想想,既然 &a &a[0] &a[0][0] 他们都是 一个地址,为什么打印他们的时候前两个出来的是地址,而最后一个才是值?因为类型不同。

    std::cout << "a: type is " << typeid(a).name() << std::endl;
    std::cout << "a[0]: type is " << typeid(a[0]).name() << std::endl;
    std::cout << "a[0][0]: type is " << typeid(a[0][0]).name() << std::endl;
    std::cout << "p: type is " << typeid(p).name() << std::endl;
    std::cout << "*p: type is " << typeid(*p).name() << std::endl;
    std::cout << "**p: type is " << typeid(*(*p)).name() << std::endl;

results:

a: type is A2_A5_i
a[0]: type is A5_i
a[0][0]: type is i
p: type is PA5_i
*p: type is A5_i
**p: type is i

我们先别纠结这些类型是啥 因为我也不知道,你看 int (*p)[5] = x是不是declare了p是一个int[5]*类型,而不是int*,结果也是如此吧!

*p取出了 p中的 内存地址 中的 值。显然 p中的内存地址是 &a[0], 而*(&a[0])便是 &a[0][0]

正同上面的结果,a[0] 和 *p 的type是一样的,他们打印出来也是一样的东西。

而只有取到最后的 *(*p) and a[0][0] 才是不属于&p 占位符的数据类型,got it ?

以下等号寓意他们等效
p = a[0]
*p = &a[0][0]
*(*p) = a[0][0]
p+1 = a[1]
*p + 1 = &a[0][1]
*(p+1) = &a[1][0]
*(*(p+1)+3) = a[1][3] 

所以要怎么才能声明一个指向多维数组的指针呢?

在声明时,指出其低维的长度,或者说是,进行指针运算时的步长

你想,对于一个指向高维数组的指针 *ptr2 ,如果我们进行运算 ptr2 += 1 那么这个内存地址的跨度是多少字节呢?

只有神知道 绝大多数情况下,内存中数组都是顺序排序的,不存在所谓 高维数组,只有所谓 数组的数组

x[0][0]x[0][1]x[1][0]x[1][1]x[2][0]x[2][1]...etc

です,你需要告诉指针,它加一的时候,需要跨越, 什么数据类型的,多少个内存地址。比如下面的

int a[10][10];
int (*ptr1)[10] = a
// 那么 ptr1+1 的结果就是 a[1]
int (*ptr2) = a[0]
// 那么ptr2+1 的结果就是 &a[0][1]

为什么要用 () 把指针包起来呢?因为这样的话,声明指针这一操作的优先度会比较高,如果不包起来的话,就先运行后面的[10],显然不能这样操作,会编译错误。

来康康更高维的。

int a[2][5][8][9];
int (*ptr1)[5][8][9] = a;
// ptr1 + 1 结果是 a[1]
int (*ptr2)[8][9] = a[1];
// ptr2 + 2 结果是 a[1][2]

3. 向函数中传入高维数组的指针。

实际上,对于一维数组而言

现在有 int a[10];

那么:
void func(int *b)
func(a)
和
void func(int b[]) // 这其实是语法糖,可以更清晰的告诉我们,传进去的是一个数组的指针。
func(a)
是等价的

数组在被传入函数以后,会退化为指针

实际上对于数组 int a[10] 而言, a其实是 int* const a,但传这个const a进函数后,接受值的b会退化成普通指针,既可改值,又可以改地址。

那么高维数组的呢?

int a[10][10][10];
那么
void func(int (*b)[10][10])
和
void func(int b[][10][10])  //语法糖,指出这是一个三维数组
也是等价的。
两者都可以直接
func(a)

4. String

我刚刚知道 众所周知,C语言中其实没有String字符串这种结构,但是可以用字符数组来模拟。

这里面有三种类型: 字符串(fake) and 字符数组 and 字符常量

前两者的区别就是结尾有没有'\0' 作为字符串的结尾。在一些比较老的或者我没用过的IDE上(现在只用Clion)

你用 char a[5] = {'a', 'b', 'c', 'd', 'e'}这种全填满数组,不留地方给 '\0' 的方法可以整出字符数组。

不过不知道为什么我在Clion里,不管怎么写都会补个 '\0' 在最后,放弃了。。。

字符常量

那就比较有意思了,字符常量在编译时,不会被放在stack或者heap中,而是会被放在静态层,是不能被修改的。

比如 printf("hello world") 里面的hello world就是字符常量,不可以被修改。

char *ptr = "hello world";
char b[] = "hello world";

这两个整出来的字符常量,我也不知道指针为什么可以这么赋值为字符串,历史遗留问题?

5. 动态分配内存 malloc calloc realloc

一定要记得free喔,动态分配的内存均在heap区域,不会自己释放的,只能通过free(接受内存地址的那个指针)

malloc()

就是在heap里开一块内存,然后返回 所开辟区域的内存首地址,这个首地址是 void*的指针。因为编译器不知道你(赛博精神病)要怎么对待这个数据

malloc(size_t*size) // 括号里面是,要返回多少byte的heap内存
int *a = (int*)malloc(4*sizeof(int)); //放回 4*4 bytes

当然你也可以在括号里面直接放多少bytes就是了。

calloc()

malloc()的区别就是,会初始化heap里面分到的区域。但是据说性能不如malloc,所以还是用malloc()吧

realloc()

重新划分已经被分配的heap内存区域

最后修改:2023 年 01 月 26 日
如果觉得我的文章对你有用,请随意赞赏