C++

C++一览

C++一览

阿Q觉得可能会遗忘些什么,而且笔记记得比较基础而且还算全。

阿Q比较模糊的(数据结构中使用)

引用传递 &

使用‘&’进行引用传递是C++中的语法习惯,事实上C语言中‘&’一般用作取地址符,不支持引用传递。


头指针 与 头结点指针

头指针 静态分配,头指针结点是动态分配的(先动态分配空间,然后头指针指向该动态空间的首地址)


结构体构造函数

使用初始化列表的简洁写法

typedef struct BtNode
{
    /* data */
    Elemtype data;
    struct BtNode *lchild, *rchild;
    BtNode(Elemtype x): data(x), lchild(nullptr), rchild(nullptr){}   
}BtNode,*Btree;

头文件被多次包含

使用 #ifndef

#ifndef __FILENAME_H__
#define __FILENAME_H__

// 声明和定义

#endif

使用 #pragma once

#pragma once
// 声明和定义

malloc/free 与 new/delete区别

阿Q先开始大量误用free (基本都是new创建的堆区数据)

语言归属及类型安全、内存大小计算

malloc/free

  • 属于 C 标准库函数,需要包含 <stdlib.h>
  • 返回 void* 指针,需手动强制类型转换,类型不安全
  • 需手动计算内存大小(单位:字节)。
int* arr = (int*)malloc(10 * sizeof(int)); // 需要显式转换,且需要计算总字节数

new/delete

  • 属于 C++ 运算符,是语言的一部分。
  • 自动计算内存大小,返回具体类型指针,类型安全
int* arr = new int[10]; // 自动推导类型,无需转换,且自动计算内存

构造函数和析构函数

malloc/free

  • 仅分配和释放原始内存不会调用构造函数或析构函数
  • 若对象包含动态资源(如打开的文件、其他堆内存),用 free 释放会导致资源泄漏
  • 自动计算所需内存大小,简化代码。
class MyClass {
public:
    MyClass()  { /* 初始化资源 */ }
    ~MyClass() { /* 清理资源 */ }
};

MyClass* obj = (MyClass*)malloc(sizeof(MyClass));
free(obj); // 析构函数不会被调用,资源泄漏!

new/delete

  • new 会调用对象的构造函数delete 会调用析构函数,确保资源正确管理。
MyClass* obj = new MyClass(); // 调用构造函数
delete obj;                   // 调用析构函数,资源正确释放

内存分配处理失败

malloc

  • 内存不足时返回 NULL,需手动检查。
int* ptr = (int*)malloc(10000000000 * sizeof(int));
if (ptr == NULL) {
    // 处理分配失败
}

new

  • 内存不足时抛出 std::bad_alloc 异常,可用 nothrow 版本返回 nullptr
int* ptr = new(std::nothrow) int[10000000000];
if (ptr == nullptr) {
    // 处理分配失败
}

重载

new/delete

  • 支持运算符重载,可自定义内存管理策略(如内存池)。
// new
void* operator new(size_t size) {
    // 自定义内存分配逻辑
    return custom_alloc(size);
}

例如:

void* operator new(size_t size) {
    void* ptr = malloc(size);  // 调用 malloc 分配内存
    if (!ptr) {
        throw std::bad_alloc(); // 内存不足时抛出异常(与默认行为一致)
    }
    return ptr;
}

malloc/free

  • 不可重载,行为固定。

阿Q建议 直接使用 new/delete 就行了。


智能指针 – 了解

C++  std::unique_ptr 或 std::shared_ptr,自动管理内存生命周期。

std::unique_ptr

详情看大佬的文章C++11 unique_ptr智能指针详解

阿Q的理解为 unique_ptr指针获得了对其所指堆内存空间的所有权。(两unique_ptr不能共享)

#include <iostream>
#include <memory>

int main()
{
    std::unique_ptr<int> p1(new int);

    *p1 = 10;

    // p 接收 p1 释放的堆内存
    int * p = p1.release();

    // 检查p所指空间的内容
    std::cout << *p << std::endl;

    //判断 p1 是否为空指针
    if (p1) {
        std::cout << "p1 is not nullptr" << std::endl;
    }
    else {
        std::cout << "p1 is nullptr" << std::endl;
    }

    std::unique_ptr<int> p2;

    //p2 获取 p 的所有权
    p2.reset(p);
    
    // 判断 p是否为空指针
    if(p){
        std::cout << "p is not nullptr" << std::endl;
    }else{
        std::cout << "p is nullptr" << std::endl;
    } //p2获取p所指空间的所有权不会释放 p

    // 检查p2所知空间的内容
    std::cout << *p2 << std::endl;;

    //  C++11 标准中的 unique_ptr 模板类没有提供拷贝构造函数,只提供了移动构造函数
    // 调用移动构造函数的 p3 和 p2 来说,p3 将获取 p2 所指堆空间的所有权,而 p2 将变成空指针(nullptr)。
    std::unique_ptr p3(move(p2));

    // 判断 p2 是否为空
    if (p2) {
        std::cout << "p2 is not nullptr" << std::endl;
    }
    else {
        std::cout << "p2 is nullptr" << std::endl;
    }

    // 检查 p3 所指空间的内容
    std::cout << *p3 << std::endl;

    return 0;
}

/*
输出结果:
10
p1 is nullptr
p is not nullptr
10
p2 is nullptr
10
*/

std::shared_ptr

引用计数机制,std::shared_ptr 确保多个智能指针可以安全地共享对同一个对象的所有权 (std::unique_ptr 计数只能为1)

创建
#include <memory>

// 使用 make_shared 创建 shared_ptr
auto sp = std::make_shared<int>(5);

支持自定义删除器,管理一个动态分配的数组时,可以指定一个删除器来确保使用 delete[] 而不是 delete

// 使用自定义删除器
std::shared_ptr<int> sp(new int[10], [](int* p){ delete[] p; });
引用计数

std::shared_ptr 内部维护了一个引用计数器,每当有一个新的 std::shared_ptr 拷贝或赋值给另一个 std::shared_ptr 时,引用计数会增加。当 std::shared_ptr 被销毁或重新赋值时,引用计数会减少。当引用计数降至零时,管理的对象会被自动销毁。

......
{
  std::shared_ptr<int> sp1 = std::make_shared<int>(10);
  std::shared_ptr<int> sp2 = sp1; // 引用计数增加
  // sp1 和 sp2 离开作用域时,引用计数减少
}
// 离开作用域后,引用计数为零,对象被销毁

悬空指针

阿Q在做二叉树遍历算法题过程中遇到的一个问题,请看下面代码

void DeleteTree(Btree &T){
    if(T){
        DeleteTree(T->lchild);
        DeleteTree(T->rchild);
        delete T;
    }
}

void LevelSearch(Btree &T, BtElemtype x){
    if(T){
        // 根节点的值为待删元素情况
        if(T->data == x){
            DeleteTree(T);
            return;
        }
    .....
}

int main(){
    Btree Root = new BtNode('X');
    Root->lchild = new BtNode('B');
    Root->lchild->lchild = new BtNode('C');
    Root->lchild->rchild = new BtNode('X');
    Root->lchild->lchild->lchild = new BtNode('D');
    Root->lchild->rchild->lchild = new BtNode('E');
    Root->lchild->rchild->rchild = new BtNode('X');
    Root->lchild->rchild->lchild->lchild = new BtNode('F');
    Root->lchild->rchild->rchild->rchild = new BtNode('G');
    Root->rchild = new BtNode('H');
    Root->rchild->lchild = new BtNode('X');
    Root->rchild->rchild = new BtNode('I');
    LevelSearch(Root,'X');
    LevelOrder(Root);
    return 0;
}

按道理来说,根结点为待删元素‘X’,再层次遍历的时候应该没有任何输出结果阿。

但是终端显示结果为:X B H C X X I D E X F G (咦,为什么还能显示删之前的结果呢?)

阿Q想了很久,在查阅文章和咨询DS小姐后找到了原因:

虽然结点的空间是被释放了(注意:释放空间,但空间里面的值在没有被再次利用前还是原有值哦),但是指向该空间的指针没有被置为nullptr,仍然指向被释放的空间区域,出现了悬空指针的现象。

因此上述代码修改如下(只修改DeleteTree函数即可):

void DeleteTree(Btree &T){
    if(T){
        DeleteTree(T->lchild);
        DeleteTree(T->rchild);
        delete T;
        T = nullptr;  // 置空指针
        // 删除节点后,父节点的指针未置空会导致成为悬空指针。后续遍历时访问了已释放的内存,输出残留数据。
    }
}

暂无评论

发送评论 编辑评论


				
上一篇
下一篇