• C++学习 day--20 new和delete关键字


    1、new delete 基本语法

    1 )在软件项目开发过程中,我们经常需要动态地分配和撤销内存空间,特别是数据结构中结点的插入与删除。在 C 语言中是利用库函数 malloc free 来分配和撤销内存空间的。C++ 提供了较简便而功能较强的运算符 new delete 来取代 malloc free 函数。
    ( 注意: new delete 是运算符,但也是C++里边的关键字,但不是函数,因此执行效率高。 )
    2 )虽然为了与 C 语言兼容, C++ 仍保留 malloc free 函数,但建议用户不用 malloc free 函数,而用 new delete 运算符。 new 运算符的例子
    new int; // 开辟一个存放整数的存储空间,返回一个指向该存储空间的地址 ( 即指针 )
    new int(10); // 开辟一个存放整数的空间,并指定该整数的初值为 10 ,返回一个指向该存储空间的地址
    new char[100]; // 开辟一个存放字符数组 ( 包括 100 个元素 ) 的空间,返回首元素的地址
    new int[5][4]; // 开辟一个存放二维整型数组 ( 大小为 5*4) 的空间,返回首元素的地址
    float *p=new float (3.14159); // 开辟一个存放单精度数的空间,并指定该实数的初值为3.14159 ,将返回的该空间的地址赋给指针变量 p
    3 new delete 运算符使用的一般格式为:
    new 运算符动态分配堆内存
    使用方法:
    普通变量:
    指针变量 = new 类型 ( 常量) ;
    指针变量 = new 类型 [ 表达式 ];
    数组:
    指针变量 = new 类型 [ 表达式 ][ 表达式 ] // 二维数组
    作用:从堆上分配一块“类型”指定大小的存储空间,返回首地址
    其中:“常量”是初始化值,可缺省
    创建数组对象时,不能为对象指定初始值
    delete 运算符释放已分配的内存空间
    使用方式:
    普通类型(非数组)使用 : delete 指针变量
    数组使用 :
    delete[] 指针变量
    其中“ 指针变量 ” 必须是一个 new 返回的指针或者malloc返回的指针!
    我们几个代码:
    1. #include <stdlib.h>
    2. #include <iostream>
    3. using namespace std;
    4. //分配基础类型
    5. int main(void) {
    6. int* p1 = new int;//第一种分配动态内存不执行初始化
    7. *p1 = 100;
    8. int* p2 = new int(100);//第二种分配动态内存同时执行初始化
    9. int* p3 = (int*)malloc(sizeof(int));// 第三种 malloc 返回值是 void *
    10. free(p1); //基础类型可以 new h和 free 可以混搭
    11. delete p3; //基础类型可以 malloc delete 可以混搭
    12. delete p2; //free(p2); 同样效果
    13. system("pause");
    14. return 0;
    15. }

    正常编译正常运行,就是对于申请普通变量时,free和new可以混搭,及new申请的普通变量可以通过free释放。delete和malloc也可以混搭。

    1. #include <stdlib.h>
    2. #include <iostream>
    3. //分配数组变量
    4. int main(void) {
    5. int* p1 = (int*)malloc(sizeof(int) * 10);
    6. int* p2 = new int[10];
    7. delete p1; // 等价于free(p1); 可以混搭
    8. free(p2);//等价于delete p2; //可以混搭
    9. system("pause");
    10. return 0;
    11. }

    正常运行正常编译。申请连续空间时(就是数组),也能有free和new混搭使用,delete和malloc函数混搭使用。

    2、 C++程序员的噩梦-内存泄漏

    内存泄漏 Memory Leak - 是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
    在这里说一下,内存泄漏是真的噩梦,我当时用64G 的内存的高配置电脑,但是就是用代码执行不断申请内存但不释放,结果最后进程卡死,而且整个系统都卡死了,连关机什么的都不能操作了,最后只能强行断电才让电脑重获新生。感兴趣的小伙伴可以试一下。
    来看一个只申请不释放内存的例子:
    1. #include <stdlib.h>
    2. #include <iostream>
    3. #include <stdio.h>
    4. #include <Windows.h>
    5. using namespace std;
    6. void A_live() {
    7. int* p = new int[1024];
    8. p[0] = 0;
    9. //挥霍、没有释放申请的内存,申请的内存必须要“还”给系统
    10. }
    11. int main(void) {
    12. for(int i=0; i<100000; i++){
    13. A_live();
    14. Sleep(50);
    15. }
    16. return 0;
    17. }

     感兴趣的可以运行试一下,运行一段时间,内存申请完以后,电脑没有内存,会卡死。只能强行断电再重新启动,不要轻易尝试。

    正确的做法是申请用完以后记得释放,还给系统。

    1. #include <stdlib.h>
    2. #include <iostream>
    3. #include <stdio.h>
    4. #include <Windows.h>
    5. void B_live() {
    6. int* p = new int[1024];//正常的开支
    7. p[0] = 0;
    8. delete[] p;//用完就还给系统
    9. }
    10. int main() {
    11. for (int i = 0; i < 100000; i++) {
    12. B_live();
    13. }
    14. system("pause");
    15. return 0;
    16. }

     3、 变量的 4 种存储类型

    所有的数据都有两种类型
    数据类型: 如 int,float 等
    存储类型: 总共有四种存储类型的变量,分别为自动变量(auto)、静态变量 static)、外部变量(extern)以及寄存器变量(register)。
    auto - 函数中所有的非静态局部变量。
    register - 一般经常被使用的的变量(如某一变量需要计算几千次)可以设置成寄存器变量,register 变量会被存储在寄存器中,计算速度远快于存在内存中的非 register 变量。
    static - 在变量前加上 static 关键字的变量。之前已经讲过这个关键字了
    extern - 把全局变量在其他源文件中声明成 extern 变量,可以扩展该全局变量的作用域至声明的那个文件,其本质作用就是对全局变量作用域的扩展

     注意:局部变量的作用域受块{ }的限制。但是最新的C++auto关键字进行了更新,不再表示原来的意思了,在C语言里还是表示局部变量,后面C++11会补充auto关键字!!

    注意:register变量不允许取地址

    1. #include <stdlib.h>
    2. #include <iostream>
    3. using namespace std;
    4. static int yahuan_xiaoli = 24;//全局静态变量
    5. void register_demo() {
    6. register int j = 0;//寄存器变量
    7. printf("j: %d\n", j);
    8. //C++ 的 register 关键字已经优化,如果我们打印它的地址,它就变成了普通的 auto 变量
    9. for (register int i = 0; i < 1000; i++) {
    10. //....
    11. }
    12. printf("&j : 0x%p\n", &j);
    13. {
    14. int k = 100;
    15. k += j;
    16. }
    17. printf("yahuan_xiaoli: %d\n", yahuan_xiaoli);
    18. }
    19. int main(void) {
    20. int i = 0; //C 语言的 auto 不会报错,C++ auto 已经升级啦
    21. register_demo();
    22. return 0;
    23. }

     

     虽然register规定不允许取地址,但是通过程序我们发现,其实也是可以取地址的。

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. //局部静态变量
    6. void static_demo() {
    7. static int girl = 18;
    8. int yahuan = 17;
    9. ++girl;
    10. ++yahuan;
    11. printf("girl: %d yahuan: %d\n", girl, yahuan);
    12. }
    13. int main() {
    14. static_demo();
    15. static_demo();
    16. static_demo();
    17. }

    运行结果:

    其实这个static关键字我们讲过很多遍了。它修饰的变量只有在程序结束后才释放它的内存空间。当它在子函数里时,当多次调用该子函数时,它只赋值一次!!

    extern就是要用其他文件.c文件的变量,我们要通过extern关键字来声明,这里就不再代码演示了。

    4、 变量的作用域和生存周期

    块在代码中体现就是就是{ }, auto register 在实际开发中用的不多,或者说基本没有。

    5、函数返回值使用指针

    可以返回函数内部:动态分配内存地址、局部静态变量地址以及全局静态变量和外部变量地址
    1. #include <iostream>
    2. #include <stdlib.h>
    3. using namespace std;
    4. int* add(int x, int y)
    5. {
    6. int sum = x + y;
    7. return &sum;
    8. }
    9. int main()
    10. {
    11. int a = 3, b = 5;
    12. int* sum = NULL;
    13. cout << add(a, b) << endl;
    14. sum = add(a, b);//不能使用外部函数局部变量的地址 bad
    15. cout << *sum << endl;
    16. }

     运行结果:

    觉不觉得奇怪,因为我们知道子函数的临时变量的内存空间在子函数调用结束后就被释放了,我们虽然把这块内存的地址空间返回了,但是这块空间其实已经给系统不能被用了,但是程序却正常运行了,其实我们不要看运行结果,我们要看逻辑,这种做法显然不对。其实编译是出现了警告的

    下面的做法是可以的: 

    1. #include <iostream>
    2. #include <stdlib.h>
    3. using namespace std;
    4. //返回动态内存分配地址
    5. int* add1(int x, int y)
    6. {
    7. int* sum = NULL;
    8. sum = new int;
    9. *sum = x + y;
    10. return sum;
    11. }
    12. int main() {
    13. int a = 3, b = 5;
    14. int* sum = NULL;
    15. sum = add1(a, b);//接收外部函数动态内存分配的地址 ok
    16. cout << *sum << endl;
    17. delete sum;//用完要释放
    18. }

    运行结果:

    因为堆区要手动 释放,因此这种返回方式是可以的,但是别望了在外部释放,否则就会产生内存泄漏了。

    下面这种方式也是可以的:

    1. #include <iostream>
    2. #include <stdlib.h>
    3. using namespace std;
    4. //返回局部静态变量的地址
    5. int* add2(int x, int y)
    6. {
    7. static int sum = 0;
    8. printf("sum: %d\n", sum);
    9. sum = x + y;
    10. return &sum;
    11. }
    12. int main() {
    13. int a = 3, b = 5;
    14. int* sum = NULL;
    15. sum = add2(a, b);//接收外部函数局部静态变量的地址
    16. cout << *sum << endl;
    17. *sum = 88888;
    18. cout << *sum << endl;
    19. }

    运行结果:

     

     6、常见的错误总结:

    1. 申请的内存 多次释放 ,这是容易犯错的一个问题, 多次释放程序会出现异常,会挂起
    2. 内存泄漏, 只申请,不释放
    3. 释放的内存不是申请时的地址 ,这个问题最大,我们释放的地址只能是从堆区申请的空间,如果释放栈区或其他区的地址,会产生一些列意想不到的问题。
    4. 释放空指针
    5. 释放一个内存块,但继续引用其中的内容 ,释放后的空间是不能再次访问的!!
    6. 越界访问
    我们通过代码来看看这几种错误:

    第一种:多次释放

    1. #include <iostream>
    2. #include <stdlib.h>
    3. using namespace std;
    4. int main()
    5. {
    6. int* p = new int[18];
    7. p[0] = 0;
    8. delete[] p;//只允许一次释放
    9. delete[] p;//1.申请的内存多次释放, 程序出现异常
    10. }

     这是一种典型错误,虽然编译能通过,但是是违法的,我们以后在判断程序对不对时不能仅仅只看是否通过编译和运行,因为编译器各有差别,我们要从根源上找原因,因此我们需要扎实的基础功底。

    第二种错误:

    1. #include <iostream>
    2. #include <stdlib.h>
    3. using namespace std;
    4. int main() {
    5. char* p1 = NULL;
    6. do{
    7. p1 = new char[10]; //2. 忘记 delete,内存泄漏
    8. }while(1==1);
    9. }

    这种也是典型的错误,特别是在子函数里申请内存时,在外部函数最容易忘记释放,从而导致内存泄漏。 

    第三种错误:释放的内存不是自己手动申请的内存

    1. #include <iostream>
    2. #include <stdlib.h>
    3. using namespace std;
    4. int main() {
    5. //3.释放的内存不是申请时的地址
    6. char a[10] = "1234567";
    7. char* p = a;
    8. for(int i=0; i<sizeof(a)/sizeof(a[0]); i++){
    9. cout<<*(p++)<<endl;
    10. }
    11. delete [] p;
    12. }

    运行结果:直接终止程序了。所以释放只能释放自己手动申请的内存。

     第四种错误:释放空指针

    1. #include <iostream>
    2. #include <stdlib.h>
    3. using namespace std;
    4. int main() {
    5. //4.释放空指针
    6. char* p1 = NULL;
    7. if (1 == 0) { //比如文件能打开的情况
    8. p1 = new char[2048];
    9. }
    10. delete p1;
    11. }

    这种错误能通过编译也能通过运行,但是空指针是指向值为0(NULL)的指针,这个指针不需要释放!!

    第五种错误:释放后再次引用该内存的内容

    1. #include <iostream>
    2. #include <stdlib.h>
    3. using namespace std;
    4. int main() {
    5. //5.释放一个内存块,但继续引用其中的内容
    6. char a[] = "1234";
    7. char* p = a;
    8. delete[] p;
    9. p[0]= '\0';//绝对禁止
    10. }

    运行结果:直接给你终止程序。

    第六种错误:

    1. #include <iostream>
    2. #include <stdlib.h>
    3. using namespace std;
    4. int main() {
    5. //6.越界访问
    6. int* p = new int[3];
    7. memset(p, 0, 3 * sizeof(int));
    8. for (int i = 0; i < 3; i++) {
    9. cout << *(p++) << endl;
    10. }
    11. //误判
    12. for (int i = 0; i < 3; i++) {
    13. cout << *(p++) << endl;
    14. }
    15. cout << "come here!" << endl;
    16. }

     运行结果:

    运行结果:

    因为第一个循环指针已经指向最后一个元素了,此时在往后移动,那么就越界访问了。这种错误也是常见错误,而且还不容易被发现。

  • 相关阅读:
    聊一下C#中的lock
    【电源专题】LDO基础——热性能2
    C专家编程 第2章 这不是Bug,而是语言特性 2.2 多做之过
    DDD.从引进到落地
    全球133种语言自动翻译mishop大米外贸商城系统
    【VeighNa】开始量化交易——第二章:PostgreSQL数据库配置
    DB2 SQL Error: SQLCODE=-911, SQLSTATE=40001, SQLERRMC=2
    JavaScript简介
    企业中WEB前端项目开发流程
    生活随笔-吐槽篇
  • 原文地址:https://blog.csdn.net/qq_51956388/article/details/134078256