C++动态内存分配与指针
C 内存操作
malloc
C语言中动态分配内存指令:malloc
void* malloc(size_t size)
malloc将为用户分配size_t字节个内存,并且返回内存分配地址,如果分配失败,则返回0。
如:
1// pa是分配好的内存首地址,4是分配的内存大小
2int* pa = (int*) malloc(4);
3
4// 如果分配失败则pa=0
1#include<iostream>
2int main()
3{
4 unsigned in;
5 std::cin >> in;
6
7 int* pa = (int*)malloc(in * sizeof(int));
8 if (pa == nullptr)
9 {
10 std::cout << "内存分配失败" << std::endl;
11 }
12 else
13 {
14 pa[0] = 12;
15 pa[1] = 20;
16 pa[2] = pa[0] * pa[1];
17
18 std::cout << pa[0] << " " << pa[1] << " " << pa[2] << std::endl;
19 }
20
21
22 return 0;
23}
calloc
void* calloc(size_t count, size_t size)
为用户分配count * size_t字节个内存,并返回内存分配的首地址,如果分配失败则返回0。
如:
1int* pa = (int*) calloc(1, 4)
其中,pa是分配好的内存首地址,1是要分配的元素个数,4是要分配的每个元素的大小。
如果内存分配失败则返回0。
calloc命令会讲分配好的内存区域设置为0。
realloc
void* realloc(void* _block, size_t size);
为用户重新分配内存,_block是用户已经奉陪好的内存,size是要求重新分配的大小,函数返回重新分配后的内存首地址。
如果分配失败,那么返回0;
如:
1int* pa = (int*)malloc(4);
2 pa = (int*)realloc(pa, 4);
1#include<iostream>
2int main()
3{
4 unsigned in;
5 std::cin >> in;
6
7 int* pa = (int*)malloc(in * sizeof(int));
8 int* copy_pa = pa;
9 if (pa == nullptr)
10 {
11 std::cout << "内存分配失败" << std::endl;
12 }
13 else
14 {
15 pa[0] = 12;
16 pa[1] = 20;
17 pa[2] = pa[0] * pa[1];
18
19 std::cout << pa[0] << " " << pa[1] << " " << pa[2] << std::endl;
20 }
21
22 std::cout << "请输入重新分配内存大小:";
23 std::cin >> in;
24 pa = (int*)realloc(pa, in);
25 std::cout << "realloc之后内存中的值:" << pa[0] << " " << pa[1] << " " << pa[2] << std::endl;
26 std::cout << "未重新分配前的pa地址:"<< copy_pa << "\nrealloc之后pa地址:"<< pa << std::endl;
27 return 0;
28}
29
30/**
31输出:
32100
3312 20 240
34请输入重新分配内存大小:1000
35realloc之后内存中的值:12 20 240
36未重新分配前的pa地址:00E20A50
37realloc之后pa地址:00E21C40
38**/
- realloc函数会保留内存中已有的值,只是内存地址重新分配了
- 如果重新分配的内存小于原来的内存,那么首地址不变。
- 如果重新分配的内存大于原来的内存,那么先判断扩大的内存中是否有已经被分配的,如果没有可以直接重新分配,如果已经有了,那么就需要重新找一段符合需求大小的内存段来重新分配。原内存中的值将会被拷贝过来。
free
void free(void* _block);
_block为需要释放的内存地址;
1int* pa = (int*)malloc(4);
2free(pa);
pa所占用的内存被释放
C++内存操作
分配
数据类型* 指针变量名称 = new 数据类型;
1int* pa = new int;
数据类型* 指针变量名称 = new 数据类型[数量]
1int* pa = new int[5];
分配失败pa返回0;
底层还是使用malloc来实现的。
释放
delete 指针
释放new分配的内存
delete[] 指针
释放new 数据类型[]分配的内存
悬挂指针
内存使用使用完毕之后,使用free或者delete释放之后,原指向该内存区域的指针就称之为悬挂指针,若此时再次使用该指针,可能就会出现问题。
不要使用已被释放的指针
不要重复释放内存
内存碎片
频繁的申请和释放小快内存会造成内存随便,虽然原则上还有内存可以使用,但是实际上由于剩余内存碎片化的存在,使得我们无法分配新的内存,当然,现在也不必担心这样的情况,因为new和delete背后的算法会尽力规避这个风险。
memcpy
void* memcpy(void* _dst, const void* _src, size_t size);
memcpy可以将_src区域的内存复制到_dst区域,复制长度为_size
如:
1int a[5]{1001, 1002, 1003, 1004};
2int *p = new int[5];
3memcpy(p, a, 5*sizeof(int));
1#include<iostream>
2int main()
3{
4 int a[5]{ 1001, 1002, 1003, 1004 };
5 int* copy_a = new int[5];
6
7 memcpy(copy_a, a, 5 * sizeof(int));
8 for (int i = 0; i < 5; i++) std::cout << copy_a[i] << std::endl;
9
10 return 0;
11}
12
13/**
14输出:
151001
161002
171003
181004
190
20**/
如果拷贝的空间大于_src的空间,则会将_src后面的的空间也拷贝到_dst,且会将_dst后面的空间覆盖掉。
memset
void* memset(void* _dst, int value, size_t _size)
value的值实际上是0~0xFF。
将指定内存区域的每一个自己的值都设置为val,_size
1int* p = new int[100];
2memset(p, 0, 100 * sizeof(int));
1#include<iostream>
2int main()
3{
4 int a[5]{ 1001, 1002, 1003, 1004 };
5 int* copy_a = new int[5];
6
7 // 尝试设置四个字节,实际上只接受了最低的两个字节(34)
8 memset(a, 0x1234, 5 * 4);
9 memcpy(copy_a, a, 5 * sizeof(int));
10 for (int i = 0; i < 5; i++) std::cout << std::hex << copy_a[i] << std::endl;
11
12 return 0;
13}
14
15/**
16输出:
1734343434
1834343434
1934343434
2034343434
2134343434
22**/
指针的本质是一种特殊的变量类型
声明
1int* a;
2int *a;
3
4// 多个指针变量定义不可以如下定义
5int *a, b;
取址运算
1int a = 200;
2int* pA = &a;
间接运算符
1// 将pA地址里的值设置为400
2*pA = 400;
指针数组
指针也可以通过数组的方式声明
1#include<iostream>
2int main() {
3 int a[5]{ 3,6,8,3,5 };
4 int* pA[5]{};
5
6 for (int i = 0; i < 5; i++)
7 {
8 pA[i] = &a[i];
9 std::cout << "a[" << i << "] 的地址为:" << pA[i] << std::endl;
10 }
11 return 0;
12}
13
14打印内容:
15a[0] 的地址为:001EF864
16a[1] 的地址为:001EF868
17a[2] 的地址为:001EF86C
18a[3] 的地址为:001EF870
19a[4] 的地址为:001EF874
指针大小
使用sizeof()方法来获得指针的大小
1#include<iostream>
2int main() {
3 int a{ 111 };
4 int* pA{ &a };
5 std::cout << sizeof(pA) << std::endl;
6}
7
8打印结果:
94
指针类型转换
1#include<iostream>
2int main() {
3 unsigned ua{ 222 };
4
5 int* pA = (int*)&ua;
6 *pA = -1;
7
8 std::cout << pA << std::endl;
9 std::cout << ua << std::endl;
10 return 0;
11}
12
13输出:
14004FF968
154294967295
场景
*ptr++
地址增加。指针加1= 该指针指向地址 + 1 * 改指针类型的大小
1#include<iostream>
2int main()
3{
4 int a[]{10001, 20001, 30001, 40001};
5 int* pA{ &a[0] };
6
7 std::cout << "地址为:" << pA << ",值为:" << *pA << std::endl;
8 std::cout <<*pA++<< std::endl;
9 std::cout << "地址为:" <<pA << ",值为:" << *pA << std::endl;
10 return 0;
11}
12
13输出:
14地址为:0073FCC8,值为:10001
1510001
16地址为:0073FCCC,值为:20001
(*ptr)++
ptr指针指向地址中的值增加
1#include<iostream>
2int main()
3{
4 int a[]{ 10001, 20001, 30001, 40001 };
5 int* pA{ &a[0] };
6
7 std::cout << "地址为:" << pA << ",值为:" << *pA << std::endl;
8 std::cout << (*pA)++ << std::endl;
9 std::cout << "地址为:" << pA << ",值为:" << *pA << std::endl;
10 return 0;
11}
12
13输出:
14地址为:0077FB40,值为:10001
1510001
16地址为:0077FB40,值为:10002
指针的指针
指针变量也是一个变量,在内容空间中也有具体的地址
1#include<iostream>
2int main()
3{
4 int a[]{ 10001, 20001, 30001, 40001 };
5 int* pA{ &a[0] };
6
7 int** ppA{&pA};
8 std::cout << "地址为:" << ppA << ",值为:" << *ppA << std::endl;
9 std::cout << "地址为:" << *ppA << ",值为:" << **ppA << std::endl;
10 return 0;
11}
12
13输出:
14地址为:00EFF984,值为:00EFF990
15地址为:00EFF990,值为:10001
修改指针的指向
1#include<iostream>
2int main()
3{
4 int a[]{ 10001, 20001, 30001, 40001 };
5 int* pA{ &a[0] };
6
7 int** ppA{ &pA };
8 std::cout << "地址为:" << ppA << ",值为:" << *ppA << std::endl;
9 std::cout << "地址为:" << *ppA << ",值为:" << **ppA << std::endl;
10 std::cout << "地址为:" << pA << ",值为:" << *pA << std::endl;
11
12 // *ppA 即 pA
13 *ppA = &a[1];
14 std::cout << "地址为:" << ppA << ",值为:" << *ppA << std::endl;
15 std::cout << "地址为:" << *ppA << ",值为:" << **ppA << std::endl;
16 std::cout << "地址为:" << pA << ",值为:" << *pA << std::endl;
17 return 0;
18}
19
20
21输出:
22地址为:005CF82C,值为:005CF838
23地址为:005CF838,值为:10001
24地址为:005CF82C,值为:005CF83C
25地址为:005CF83C,值为:20001
常量指针
定义:
const 变量类型*
所谓常量指针,即这个指针指向的是一个常量的地址,常量指针中,不能对其指向的内存地址进行改变,但是指针指向的这个地址可以改变
1#include<iostream>
2int main()
3{
4 const int a{ 100 };
5 const int b{ 2500 };
6 int c{ 3000 };
7
8 const int* ptr{ &a };
9 ptr = &b;
10
11 // 不能进行如下操作
12 //*ptr = 500;
13
14 ptr = &c;
15 // 虽然c不是常量,但是依然不能进行如下操作,因为ptr是const修饰的
16 //*ptr = 500;
17}
指针常量
定义:
变量类型* const
所谓的指针常量,即这个指针变量是一个常量,一旦初始化就不可以再指向其他内存地址,但是内存地址中的数据可以读写
1#include<iostream>
2int main()
3{
4 int a{ 100 };
5 int b{ 2500 };
6 int c{ 3000 };
7
8 int* const ptr{ &a };
9 // 虽然指针的地址不可以改变,但是指针指向地址中的值可以改变
10 *ptr = 500;
11
12 // 不能进行如下操作
13 // ptr = &b;
14}
指向常量的常量指针
定义:
const 变量类型* const
指向常量的常量指针:即这个指针变量是一个常量,一旦初始化就不可以再指向其他内存地址,因为其本身就是一个指向常量的指针,所以其指向的内存区域也不可以修改。
1#include<iostream>
2int main()
3{
4 int a{ 100 };
5 int b{ 2500 };
6 int c{ 3000 };
7
8 const int* const ptr{ &a };
9 // 不能进行 如下操作
10 // *ptr = 500;
11
12 // 也不能进行如下操作
13 // ptr = &b;
14}
可以通过如下方式修改
1#include<iostream>
2int main()
3{
4 const int a{ 1000 };
5 const int b{ 2500 };
6 const int c{ 3000 };
7
8 const int* const ptr{ &a };
9 // 不能进行 如下操作
10 // *ptr = 500;
11
12 // 也不能进行如下操作
13 // ptr = &b;
14
15 // 去掉a变量的const之后 *ptr 和 *ptrA的值都改变了
16 int* ptrA{ (int*)&a };
17 *ptrA = 9500;
18 std::cout << "地址:" << &a << ",值为:" << a << std::endl;
19 std::cout << "地址:" << ptrA << ",值为:" << *ptrA << std::endl;
20 return 0;
21}
指针和数组
相同
数组的底层实现是利用了指针
从原来上将,指针和数组是同一个方法的不同表达方式,而数组名本身就是一个指针,数组元素只是这个指针按照一定量偏移后对应的内存区域里的内容。
指针同样可以使用[]符号来操作内存
1#include<iostream>
2int main()
3{
4 int a[]{ 1000, 2000, 3000 };
5 int* ptrA{ a };
6
7 a[0] = 1001;
8 a[2] = 3003;
9
10 // 指针同样可以使用[]符号来操作内存
11 ptrA[1] = 2002;
12
13 return 0;
14}
对应的汇编代码
1 ;int a[]{ 1000, 2000, 3000 };
2007F34E2 mov dword ptr [a],3E8h
3007F34E9 mov dword ptr [ebp-10h],7D0h
4007F34F0 mov dword ptr [ebp-0Ch],0BB8h
5 ;int* ptrA{ a };
6007F34F7 lea eax,[a]
7007F34FA mov dword ptr [ptrA],eax
8
9 ;a[0] = 1001;
10007F34FD mov eax,4
11007F3502 imul ecx,eax,0
12007F3505 mov dword ptr a[ecx],3E9h
13 ;a[2] = 3003;
14007F350D mov eax,4
15007F3512 shl eax,1 ; eax左移1位,等于将eax中的值(4)乘以2
16007F3514 mov dword ptr a[eax],0BBBh
不同
在使用`size函数来获取数组大小的时候,返回是整个数组的大小
而指针返回的则是指针类型的大小。
1#include<iostream>
2int main()
3{
4 int a[]{ 1000, 2000, 3000 };
5 int* ptrA{ a };
6
7 a[0] = 1001;
8 a[2] = 3003;
9
10 // 指针同样可以使用[]符号来操作内存
11 ptrA[1] = 2002;
12
13 std::cout << "数组a大小" <<sizeof(a) << std::endl;
14 std::cout << "指针ptrA的大小" <<sizeof(ptrA) << std::endl;
15
16 return 0;
17}
18
19输出:
20数组a大小12
21指针ptrA的大小4
多维数组
数组的本质是连续的内存区域,所以所谓的多维数组其实是不存在的,多维只是方便理解而创造出来的逻辑方法。
1#include<iostream>
2int main()
3{
4 int a[2][5]
5 {
6 {1001, 1002, 1003, 1004, 1005},
7 {2001, 2002, 2003, 2004, 2005}
8 };
9
10 int b[2][6]
11 {
12 {0, 1001, 1002, 1003, 1004, 1005},
13 {0, 2001, 2002, 2003, 2004, 2005}
14 };
15
16 int* pA{ a[0] };
17 std::cout << a[1][4] << std::endl;
18 // 因为是连续的内存空间
19 std::cout << pA[9] << std::endl;
20 return 0;
21}
数组指针
注意和指针数组的区别
1#include<iostream>
2int main()
3{
4 int a[2][5]
5 {
6 {1001, 1002, 1003, 1004, 1005},
7 {2001, 2002, 2003, 2004, 2005}
8 };
9
10 int b[2][6]
11 {
12 {0, 1001, 1002, 1003, 1004, 1005},
13 {0, 2001, 2002, 2003, 2004, 2005}
14 };
15
16
17 // 指针数组
18 //int* pArr[5];
19
20 // 数组指针, 每一行中有5个数据,本质上是一个指针
21 int(*pArr)[5]{a};
22 // 声明了数组指针之后可以按照二维数组的形式获取地址中的数据
23 std::cout << "数组指针:" << pArr[1][4] << std::endl;
24 std::cout << "数组指针大小:" << sizeof(pArr) << std::endl;
25
26 // 数组指针加1的时候,相当于维度*数据类型的大小,这里是6(维度)*4(int的大小)
27 std::cout << "数组指针大小:" << pArr << std::endl;
28 pArr = pArr + 1;
29 std::cout << "偏移之后数组指针大小:" << pArr << std::endl;
30
31 return 0;
32}
33
34输出:
35数组指针:2005
36数组指针大小:4
37数组指针大小:006FF91C
38偏移之后数组指针大小:006FF930
数组指针加1的时候,相当于维度*数据类型的大小
sizeof()
sizeof()方法在编译期就已经知道了大小。
堆栈
堆的本质是空闲内存,C++中把堆称之为自由存储区,只要是程序加载后没有占用的空闲内存,都是自由存储区域,使用new和malloc申请一块新的内存区域,都是由操作系统从对上操作的。
栈是程序在编译时就确定了大小的一段内存区域,主要是用于里临时变量的存储,栈的效率要高于堆,但是容量有限。
定义
引用是创建一个变量的引用名称
语法:
数据类型& 变量名称{引用对象名称}
如:
1int a{500};
2int& ra{a};
ra = 5200时,相当于a=5200
1#include<iostream>
2int main()
3{
4 int a{ 1500 };
5 int& ref_a{ a };
6 std::cout << "a=" << a << " ref_a=" << ref_a << std::endl;
7
8 ref_a = 2400;
9 std::cout << "a=" << a << " ref_a=" << ref_a << std::endl;
10 return 0;
11}
12
13输出:
14/**
15a=1500 ref_a=1500
16a=2400 ref_a=2400
17**/
引用一旦赋值就无法改变
1#include<iostream>
2int main()
3{
4 int a{ 1500 };
5 int b{ 2000 };
6 int& ref_a{ a };
7 std::cout << "a=" << a << " ref_a=" << ref_a << std::endl;
8
9 ref_a = 2400;
10 std::cout << "a=" << a << " ref_a=" << ref_a << std::endl;
11
12 // 这里并不是将ref_a设置成了b的引用,而是将a的值修改成了b
13 ref_a = b;
14
15 return 0;
16}
引用取址
同一个变量的引用指向同一个地址
1#include<iostream>
2int main()
3{
4 int a{ 1500 };
5 int b{ 2000 };
6 int& ref_a{ a };
7 int& ref_b{ a };
8 int& ref_c{ a };
9 int& ref_d{ a };
10 std::cout << "a=" << a << " ref_a=" << ref_a << std::endl;
11
12 ref_a = 2400;
13 std::cout << "a=" << a << " ref_a=" << ref_a << "地址="<< &ref_a <<std::endl;
14 std::cout << "a=" << a << " ref_b=" << ref_b << "地址="<< &ref_b << std::endl;
15 std::cout << "a=" << a << " ref_c=" << ref_c << "地址=" << &ref_c << std::endl;
16 std::cout << "a=" << a << " ref_d=" << ref_d << "地址=" << &ref_d <<std::endl;
17
18 // 这里并不是将ref_a设置成了b的引用,而是将a的值修改成了b
19 ref_a = b;
20
21 return 0;
22}
23
24输出:
25a=1500 ref_a=1500
26a=2400 ref_a=2400 地址=0107F750
27a=2400 ref_b=2400 地址=0107F750
28a=2400 ref_c=2400 地址=0107F750
29a=2400 ref_d=2400 地址=0107F750
常量引用
引用的值无法修改
const int& a{b}
则无法修改a的值。
定义
std::unique_ptr是所谓智能指针中的一种,主要目的是为了解决原生指针安全性不足的弊端
声明:
std::unique_ptr<类型> 变量名称{};
1// 这里的150表示数组的size为150
2std::unique<int> ptrA{std::make_unique<int>(150)};
3
4// 这里的10表示初始化智能指针所指地址中的值初始化为10
5std::unique<int> ptrA{std::make_unique<int[]>(10)};
常用方法:
reset()
reset()方法会释放掉std::unique_ptr的内存空间,并且将std::unique_ptr设置为nullptr;
1#include<iostream>
2int main()
3{
4 int* a = new int[5];
5 // 这里的5表示数组的size为5
6 std::unique_ptr<int[]> ptr_arr{ std::make_unique<int[]>(5) };
7 // 这里的5表示初始化智能指针所指地址中的值初始化为5
8 std::unique_ptr<int> ptr{ std::make_unique<int>(5) };
9
10 std::cout << "地址为:" << ptr << "值为:" << *ptr << std::endl;
11
12 ptr.reset();
13 std::cout << "地址为:" << ptr << "值为:" << *ptr << std::endl;
14 return 0;
15}
get()
会返回一个std::unique_ptr的指针
用法:
1std::unique_ptr<int> ptrA{std::make_unique<int>(150)};
2int* p = ptrA.get();
get()等于ptrA申请内存时的地址。
release()
会返std::unique_ptr的指针,并将std::unique_ptr设置为nullptr,但是注意release并不会释放器占用的内存空间。
用法:
1std::unique_ptr<int> ptrA{std::make_unique<int>(150)};
2int *ptr = ptrA.release();
此时ptrA所占用的内存空间没有被释放,只是将ptrA的值设置成了nullptr
ptr等于ptrA申请内存时的地址。
std::move
std::unique_ptr指针具有唯一性,因此不能被复制,但是可以转移。
转移语法:
转以后的指针变量 = std::move(转移前的指针变量);
如:
1std::unique_ptr<int> ptrA {std::make_unique<int>(150)};
2std::unique_ptr<int> ptrB {};
3
4ptrB = std::move(ptrA);
转移后ptrA被设置为nullptr。
std::shared_ptr
可以有多个std::shared_ptr指向同一个地址,同一个地址下只有当最后一个std::shared_ptr释放的时候,才会释放其所占用的内存空间,std::shared_ptr会记录当前地址有多少个智能指针调用。
语法:
std::shared_ptr<类型> 变量名称
例如:
1std::shared_ptr<int> ptrA{};
2std::shared_ptr<int> ptrB {std::make_shared<int>(5)};
std::make_shared不支持数组
std::shared_ptr<int[]> ptrC{new int[5]{1,2,3,4,5}}
1#include<iostream>
2int main()
3{
4 int* a{};
5 std::shared_ptr<int> ptrA{ new int{5} };
6 std::shared_ptr<int> ptrB{ptrA};
7
8 std::cout << ptrA << " " << *ptrA << std::endl;
9 std::cout << ptrB << " " << *ptrB << std::endl;
10 return 0;
11}
12
13输出:
1400BB6120 5
1500BB6120 5
use_count()
获取一个有多少个指针来调用当前对象:
long std::shared_ptr.use_count();
会返回当前指针共有多少对象调用。
1#include<iostream>
2int main()
3{
4 int* a{};
5 std::shared_ptr<int> ptrA{ new int{5} };
6 std::shared_ptr<int> ptrB{ptrA};
7 std::shared_ptr<int> ptrC{ptrA};
8 std::shared_ptr<int> ptrD{ptrA};
9 std::shared_ptr<int> ptrE{ptrA};
10
11 // 获取当前有多少指针指向ptrA
12 long use_count{ ptrA.use_count() };
13
14 std::cout << ptrA << " " << *ptrA << std::endl;
15 std::cout << ptrB << " " << *ptrB << std::endl;
16 std::cout << use_count << std::endl;
17 return 0;
18}
19
20输出:
2100D89E48 5
2200D89E48 5
235
unique()
将会返回一个bool值,如果当前智能指针是唯一拥有该指针的,那么返回true,否则返回false;
1#include<iostream>
2int main()
3{
4 int* a{};
5 std::shared_ptr<int> ptrA{ new int{5} };
6 // 获取当前有多少指针指向ptrA
7 int use_count{ ptrA.unique() };
8 std::cout << use_count << std::endl;
9
10 std::shared_ptr<int> ptrB{ptrA};
11 std::shared_ptr<int> ptrC{ptrA};
12 std::shared_ptr<int> ptrD{ptrA};
13 std::shared_ptr<int> ptrE{ptrA};
14
15 // 获取当前有多少指针指向ptrA
16 use_count = ptrA.unique();
17
18 std::cout << ptrA << " " << *ptrA << std::endl;
19 std::cout << ptrB << " " << *ptrB << std::endl;
20 std::cout << use_count << std::endl;
21 return 0;
22}
23
24输出:
251
2600EEF6C8 5
2700EEF6C8 5
280
reset()
std::shared_ptr.reset()
reset()会将当前共享指针设置为nullptr,同事如果当前智能指针是最后一个拥有该指针的对象,那么将会释放内存。