谈一谈内存规划与C++的指针

指针

指针是一种底层的数据类型,可以实现对各种内存的操作,无论内存是位于栈上还是堆上,有权限就可以操作。如果说变量是盒子,那指针就是盒子的编号或者标签,*就是一种表示指针的操作符。

指针可以根据描述对象分为很多种,但是其本质就是一种表达内存的数据。一个非野指针或非空指针变量会拥有两个内存地址,一个是本身位于栈上的内存地址,另一个是其指向的内存地址。

为了解释的更具体,Minloha采购了C Primer Plus并且深入的和书籍交流了一番,做出以下解释

结构体指针

结构体指针就是指向一个结构体变量的指针,其调用都可以像正常结构体对象一样。先写出一个结构体并实现结构体指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

struct A
{
int number;
} T;
int main()
{
T.number = 123123;
//使用已有的结构对象作为指针的指向
A* p = &T;
//从栈中分配出新的区域作为指针指向
A* p2 = new A;
//分别修改各自的内容
p->number = 114514;
p2->number = 123;
//T中原本保留的内容[123123]被修改为[114514]
cout << T.number << endl;
//p2内容正常输出:123
cout << p2->number << endl;
}

对结构体A,在它声明的同时也创建了一个对象T,这个对象可以被一个同为A的指针指向,这个指针就叫结构体指针,也就是代码中的*p,指针的初始化可以通过取值符&取已有对象的地址。

底层的说是不存在变量什么的,都是内存地址的移动,所以当指针被一个对象地址初始化之后,指针就是对象了,之后对指针的增删改都会直接影响到对象,因为它们两个是在一个内存上生长的。

野指针&空指针&无类型指针

野指针是一种指向地址不确定的指针,空指针就是指向地址为空的指针,无类型指针就是用void定义的指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

int main()
{
int num = 114514;
//三种声明指针的方法
void* a = &num;
int* b = &num;
int* c = (int *)malloc(2);//
//void*是无类型指针,可以指向任何数据类型,输出时要进行强制转换
cout << *(int*) a << endl;
//正常指针类型
cout << *b << endl;
//*c的定义是从栈中随机取出的,所以输出内容就非常不可懂了
cout << *c << endl;
//释放内存空间,*c变成了野指针
//释放内存空间也可以用 delete c;
free(c);
cout << *c << endl;
}

在内存管理部分中要尽可能避免这小部分提到的指针,主要是为了保证内存的安全性。

数组指针

数组的定义是int array[],而这个array就是一种数组指针,指向了数组头

1
2
3
4
5
6
7
8
9
10
11
[[include]] <iostream>
using namespace std;

int main()
{
int list[] = { 1,2,3,4,9 };
int* p = list;
for (int i = 0; i < 5;i++) {
cout << p[i] << endl;
}
}

函数指针

函数指针与指针函数一直被区分着,其实可以用偏意词的角度理解,函数指针即指向函数的指针,指针函数就是返回值为指针的函数。

1
2
3
4
5
6
7
8
9
10
11
[[include]] <iostream>
using namespace std;

//这是一个指针函数,返回值是int*类型
int* func(int n) {
return (int*)n;
}
int main()
{
cout << *func(9) << endl;
}

函数指针的写法是类型 (*指针名)(参数表)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[[include]] <iostream>
using namespace std;

int add(int a,int b){
return a+b;
}

int sub(int a,int b) {
return a-b;
}
//这是函数指针
int (*func)(int a,int b);

int main()
{
//函数指针可以被函数名初始化(换个角度,函数名就是函数指针)
func = add;
cout << func(2,1) << endl;//输出:3
func = sub;
cout << func(2,1) << endl;//输出:1
}

多级指针

指针也是一种数据类型,也会在栈中留下自己的地址,而指向指针地址的指针就叫二级指针,无限套娃下去就会出现各种等级的指针,这里说说二级指针。

在出现函数传参为指针的时候需要注意,函数所传的指针是以引用的形式传递,意味着离开函数后不会对原有指针产生任何影响,这时就需要二级指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//无法修改案例
[[include]] <iostream>
using namespace std;

int gb = 4;

void getAddress(int *q) {
cout << "函数内:q=" << q << "|" << "&q=" << &q << endl;
q = &gb;
cout << "函数内:q=" << q << "|" << "&q=" << &q << endl;
}

int main()
{
int num = 3;
cout << "num=" << num << "|" << "&num=" << &num << endl;
int* p = &num;
getAddress(p);
cout << "p=" << p << "|" << "&p=" << &p << endl;
}
//----------------------------------------------------------
控制台输出:
num=3|&num=00000045030FFB34
函数内:q=00000045030FFB34|&q=00000045030FFB10
函数内:q=00007FF75DD5D000|&q=00000045030FFB10
p=00000045030FFB34|&p=00000045030FFB58

此时的p指向的地址并未被修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//使用二级指针成功案例
[[include]] <iostream>
using namespace std;

int gb = 4;

void getAddress(int **q) {
cout << "函数内:q=" << q << "|" << "&q=" << &q << endl;
*q = &gb;
cout << "函数内:q=" << q << "|" << "&q=" << &q << endl;
}

int main()
{
int num = 3;
cout << "&gb=" << &gb << "|" << "&num=" << &num << endl;
int* p = &num;
getAddress(&p);
cout << "p=" << p << "|" << "&p=" << &p << endl;
}
//-------------------------------------------------------------
控制台输出:
&gb=00007FF6F5CBD000|&num=00000051BA2FF9C4
函数内:q=00000051BA2FF9E8|&q=00000051BA2FF9A0
函数内:q=00000051BA2FF9E8|&q=00000051BA2FF9A0
p=00007FF6F5CBD000|&p=00000051BA2FF9E8

可以明显看到经过函数操作后传递的参数&p指向被修改为gb的地址了,在函数内直接通过指针的地址实现的二级指针修改了参数指针指向地址……(嗯)

总结

挺复杂的,但是理解之后很爽!非常建议收藏保存。


谈一谈内存规划与C++的指针
https://blog.minloha.cn/posts/170757773c09562022020757.html
作者
Minloha
发布于
2022年2月7日
更新于
2024年9月16日
许可协议