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; // 置空指针
// 删除节点后,父节点的指针未置空会导致成为悬空指针。后续遍历时访问了已释放的内存,输出残留数据。
}
}