当前位置: 首页 > news >正文

C++第五次:malloc /calloc/new /free/delete/auto/decltype /范围for循环/编译过程/typedef/using/字符串/转义字符总结

一、malloc /calloc/new /free/delete 完整总结

函数 / 关键字所属语言依赖头文件用途定位
mallocC 语言<stdlib.h>堆内存原始分配,只分配裸内存,不初始化
callocC 语言<stdlib.h>堆内存分配,分配后自动清零整块内存
freeC 语言<stdlib.h>释放malloc/calloc分配的堆内存
newC++ 关键字无需头文件分配内存 +调用构造函数初始化对象,支持基础类型 / 自定义类
deleteC++ 关键字无需头文件释放new内存 +调用析构函数清理对象资源

1. malloc

int* p = (int*)malloc(n * sizeof(int)); if (p == NULL) { // 错误处理 } // 自己赋值 // 用完释放 free(p); p = NULL;
  1. 参数:需要分配的总字节数
  2. 行为:
    • 在堆上开辟size字节连续内存;
    • 不会初始化内存,内存里是随机脏值;
    • 分配失败返回NULL,必须判空;

为什么 malloc 不初始化?

为了性能。初始化需要额外遍历内存,很多场景不需要初始值,因此不做。

2. calloc

void* calloc(size_t num, size_t size);
  1. 参数:num元素个数,size单个元素字节大小,总内存 =num * size
  2. 行为:
    • 分配完成后,整块内存全部置 0
    • 分配失败返回NULL
  3. 等价效果:malloc分配后手动memset清零
// 10个int,全部初始化为 0 int* p = (int*)calloc(10, sizeof(int)); int* p = (int*)calloc(n, sizeof(int)); // 自动清0

calloc 为什么适合数组?

因为数组默认需要 0 初始化,calloc 一步到位,不用手动 memset。

补充:memset

memset(指针, 0, 大小); // 99% 的用途就是清0 // 把 p 指向的内存全部设为 0 memset(p, 0, 10 * sizeof(int));

memset(p,0,count*size):把p指向的地址清零

memset(&p,0,count*size):把p清零

free(p):释放p指向地址的空间

free(&p):释放p指针

函数作用初始化底层性能
malloc分配指定字节内存不初始化(脏数据)空闲链表 + sbrk/mmap最快
calloc分配数组内存自动全部清零malloc + memset较慢
memset填充内存手动设置每个字节逐字节 / 批量赋值

3. free

void free(void* ptr);
  1. 只用于释放malloc / calloc开辟的堆内存;
  2. 不能释放栈内存、new分配的内存;
  3. 释放后建议手动置空ptr = NULL,避免野指针;
  4. 重复 free、free 栈变量会触发程序崩溃。

内存释放(free)底层逻辑

free 不会立即把内存还给操作系统,而是:

  1. 标记内存块为空闲
  2. 合并相邻空闲块(防止内存碎片)
  3. 放入空闲链表,供下次 malloc/calloc 复用

4. new(C++)

分两种用法:单对象、数组

// 单个对象 int* p1 = new int; // 分配内存,不初始化 int* p2 = new int(10); // 分配 + 初始值10 // 数组 int* arr = new int[10]; // 分配10个int,随机脏值 int* arr2 = new int[10]{}; // C++11起,数组全部清零 // 自定义类,自动调用构造函数 class Student{}; Student* s = new Student();

核心特性:

  1. 自动计算类型字节,不用手动算sizeof
  2. 自动强类型转换,不用强制(int*)
  3. 分配失败默认抛std::bad_alloc异常(可捕获);
  4. 自动调用类的构造函数,完成对象初始化(C 函数做不到)。

5. delete(C++)

配套new释放,区分单个对象数组,必须配对使用:

int* p = new int(5); delete p; // 释放单个对象 int* arr = new int[10]; delete[] arr; // 数组必须加[],否则内存泄漏/崩溃

核心特性:

  1. 释放前自动调用类的析构函数,释放类内部资源(文件、堆内存等);
  2. 不能用来释放malloc/calloc的内存,底层堆管理不兼容;
  3. 释放后置空防止野指针。

失败处理

  • C:判断返回指针是否为 NULL;
  • C++:默认抛出异常,可 try-catch 捕获。

二、C++ 动态内存:new /delete(3 种使用形式)

2.1.运算符 new(分配内存+ 自动初始化)

底层执行四步:

  1. 自动算好要开辟的内存大小(不用自己写 sizeof)
  2. 底层调用类似 malloc 去堆区申请内存
  3. 给这块内存做初始化(括号 / 大括号赋值)
  4. 返回对应类型指针;内存不够申请失败直接抛std::bad_alloc异常

不想抛异常:std::nothrow写法

如果内存不够不抛异常,直接返回空指针 NULL,方便后续判断指针是否有效

int* p = new(std::nothrow) int(10); if (p == nullptr) { // 内存开辟失败处理逻辑 }

new两种开辟形式

  1. 单个变量空间()
    int* p = new int(10); // 堆上开1个int,直接赋值10 // 等价简写 int* p = new int{10};

2.数组空间[]

int* s = new int[10]{1,2,3,4,5,6,7,8,9,10}; // 堆上连续开10个int,批量初始化数组

():申请一个int空间

【】:申请一组int空间

释放空间:

释放之后一定要制空

2.2. operator new 函数用法(只能申请空间无法对空间初始化)

长得像函数,只负责分配原始堆内存,不会初始化内存内容,和 malloc 行为几乎一样,只有一点区别:

  • operator new:内存不足抛std::bad_alloc异常
  • malloc:内存不足直接返回 NULL
// 手动自己算sizeof,强制类型转换,和malloc格式几乎一致 int* p = (int*)::operator new(sizeof(int)); // 释放对应用 operator delete operator delete(p); p = nullptr; // 释放后置空

函数用法和malloc用法一致。唯一区别在于new失败抛出异常malloc返回空

2.3. 定位 new/构建new

核心特点:不向堆申请新内存,直接在一块已经存在的内存(栈 / 堆都行)上调用构造函数做初始化 适用场景:提前预留一块内存,后续再按需创建对象 示例:

int main() { char buf[20]; // 栈上提前开好20字节内存 new (buf) int(10); // 在buf起始位置放int 10 new (buf + 4) int(20); // 偏移4字节(一个int大小)再放int 20 return 0; }

注意:定位 new 开辟的对象,不能直接用 delete 释放,需要手动调用析构函数再回收底层内存

2.4.new和malloc的区别

2.5.free和delect的区别

1. delete 配套 new(C++ 专用)

  • 单个对象用delete 指针;
  • new [] 开辟的数组,必须用delete[] 指针;写错会内存错乱 / 崩溃
int* p = new int(10); delete p; p = nullptr; // 释放后一定要置空,防止野指针 int* s = new int[10]{1,2,3}; delete[] s; s = nullptr;

规范:内存释放后立刻把指针赋值 nullptr,杜绝野指针问题

2. free 配套 malloc(C 语言风格)

int* tmp = (int*)malloc(sizeof(int)*5); free(tmp); tmp = nullptr;

三、C++11 auto 自动类型推导

3.1. 基础语法规则

硬性要求:auto 定义变量必须初始化,编译器靠等号右边的值推导出变量真实类型,无初始值直接编译报错

auto a = 10; // a → int auto dx = 12.25; // dx → double auto ft = 12.25f;// ft → float auto pa = &a; // pa → int* auto pd = &dx; // pd → double*

补充拓展基础写法:

auto b = 3.14; // double auto c = 'x'; // char auto str = "abc"; // const char*
auto 简化复杂类型(核心优势,迭代器场景)

容器迭代器原生类型极长,auto 直接省略:

#include <vector> #include <iostream> using namespace std; int main() { vector<int> vec = {1,2,3}; // 原生超长写法:vector<int>::iterator it = vec.begin(); auto it = vec.begin(); // 编译器自动推导迭代器类型 cout << *it << endl; return 0; }

3.2. 同一行定义多个 auto 变量 考题

int main() { auto x = 10; auto p = &x, z = 10; // 写法1:编译报错 auto *p = &x, z = 10; // 写法2:编译通过 }

原理:同一行 auto 推导的基础类型必须统一; 写法 1 中p推导为int*z推导为int,两种基础类型冲突报错; 写法 2 显式标注auto *p,auto 只推导基类型intz同样推导为 int,类型统一合法。

3.3. auto 推导完整核心规则(必记,包含 const 常量推导规则)

规则 1:裸 auto 默认忽略顶层 const、引用

// 丢弃顶层const const int x = 10; auto a = x; // a 的类型是 int,不是 const int;顶层const被丢弃,a可修改 // 丢弃引用属性 int num = 20; int& ref = num; auto b = ref; // b 的类型是 int,不是 int&;引用属性直接丢掉

const 常量拓展示例:

const int a = 10; auto x = a; // x是普通int,顶层const丢失,x可修改 auto& ra = a; // ra是const int&,绑定常量自动带上const,ra不可修改 auto p = &a; // p是const int*,指针指向常量,*p不可修改

但对于引用和指针auto推演出来的类型就是带const,因为它们是a的别名和指向,这是不可以修改的

规则 2:想要保留引用 → auto&

int num = 20; auto& ref_num = num; // ref_num 类型 int&,和num绑定,修改ref_num会直接修改原变量 ref_num = 99;

规则 3:想要保留 const 常量 → const auto

const int x = 100; const auto val = x; // val 是 const int,不能修改

规则 4:同时保留 const + 引用 → const auto&(最常用,只读不拷贝)

string s = "hello"; // 推导为 const string&,不拷贝字符串、只读,运行效率高 const auto& temp = s;

规则 5:auto 配合指针两种写法,等价

int m = 5; auto p = &m; // p 是 int* auto* p2 = &m; // 和上面等价,显式写*可读性更好

4. auto 不能使用的场景(坑点,编译全报错)

  1. 变量不初始化
    auto x; // 错误!无初始值,无法推导类型

    2.C++11 函数形参不能直接写 auto(C++14 才支持泛型 auto 参数)

  2. // C++11直接报错 void func(auto x) {}
  3. C++11 类内成员变量不允许 auto(C++17 放开限制)

    class Test { auto a = 10; // C++11编译报错 };
  4. 不能直接定义 auto 数组

    auto arr[5] = {1,2,3}; // 语法错误

四、decltype 关键字(弥补 auto 必须初始化的缺陷)

4.1. decltype 基础概念

auto 修饰的变量必须初始化,编译器依靠初始值确定类型; 如果只需要提取表达式类型、不需要立刻定义变量,使用decltype; C++11 新增decltype(exp),编译期推导表达式类型,不会执行表达式计算,仅提取类型

基础语法示例:

int a = 10, b = 20; double dx = 12.23; decltype(a + b) x = 0; // 仅推导a+b的类型int,不会实际执行a+b的加法运算

但针对(++a)和(a++)又有了不一样的情况,这个需要记死

4.2. decltype 推导引用特殊规则

  1. decltype(++a):前置自增返回变量左值本体(a本身),推导为int&,引用必须初始化

    decltype(++a) y; // 报错:int&引用无初始值

    当为(++a)的时候我们认为他返回的是a本身,即y不是一个整形而是一个整形引用类型(引用是必须要初始化的)

2.decltype(a++):后置自增返回临时右值,推导普通int,可无初始值定义

decltype(a++) z; // 合法,普通int类型

当为(a++)的时候z是一个整形不是一个引用

3.decltype((变量)):变量外层包裹双层括号,强制推导为引用类型

int x = 5; decltype((x)) ra = x; // ra是int&引用

4.3. decltype (auto) 拓展区分普通 auto

普通 auto 会丢弃 const、引用属性;decltype(auto)会完整保留表达式原本的 const、引用类型:

int n = 10; int& r = n; auto a = r; // int,丢失引用 decltype(auto) b = r; // int&,完整保留引用属性

五、C++11 基于范围的 for 循环(范围 for)

死循环的三种写法:for的效率最高

1. 在c++中遍历一个数组(容器)的方法一般如下

int main() { int ar[] = {1,2,3,4,5,6,7,8,9}; int n = sizeof(ar) / sizeof(ar[0]); // 下标遍历 for(int i = 0; i < n; ++i) { cout << ar[i] << " "; } cout<<endl; // 指针遍历 int* ip = NULL; for(ip = ar; ip != ar + n; ++ip) { cout << *ip << " "; } cout<<endl; return 0; }

2. 范围 for 核心语法限制

格式:for(类型 临时变量 : 数组/容器)限制:冒号后只能填写完整数组、容器对象不支持裸指针

考题示例:(谁可以运行?)

int main() { int ar[10]={1,2,3,4,5,6,7,8,9,10}; int *ip = ar; int (&br)[10] = ar; // br是数组引用,保留完整数组类型信息 for(auto x : ip)// ❌ 编译报错,ip是int*裸指针(只保存地址,忽略了本身的长度) { } for(auto x : br) // ✅ 正常运行,br是合法数组引用 { } return 0; }
  1. 范围 for 搭配 auto 三种写法(完整区分)
    int() { int ar[] = {12,23,34,45,56,67}; // 1. auto x : 数组:值拷贝,操作副本,修改x不会影响原数组 for(auto x : ar) { cout << x << " "; } cout<<endl; // 2. auto& x : 数组:绑定原元素引用,修改x会直接改动原数组内容 for(auto& x : ar) { cout << x << " "; } cout<<x<<endl; // 3. const auto& x : 数组:只读引用,无拷贝开销,遍历性能最优(只读场景首选) for(const auto& x : ar) { cout << x << " "; } cout<<endl; return 0; }

    六、编译阶段

    1. C++ 程序编译四阶段流程:预处理 → 编译 → 汇编 → 链接 文件转换链路:yhp.cpp→预处理→yhp.i→编译→yhp.s→汇编→yhp.o(yhp.obj)→链接→yhp.exe

2.用nullptr代替NULL

七、类型重命名:typedefusing

1. 基础等价写法(数组类型别名)

// C语言老式typedef写法 typedef int Array[10]; // C++11 using新式写法(更直观,推荐) using Array = int[10];

两者完全等价,都表示Array长度为 10 的 int 数组类型。

核心区别:using支持模板别名(typedef做不到)

typedef无法结合模板,using可以定义模板类型别名,多线程 / 泛型高频使用:

// 模板数组别名:任意类型T、任意长度N的数组 template<class T, size_t N> using Array = T[N]; // 使用:创建长度5的double数组 Array<double,5> d_arr = {1.1,2.2,3.3,4.4,5.5};

八、字符串相关知识

include<string>和<string.h>有巨大的差异

<string.h>和<cstring>是一个东西

1. 头文件区分

  1. <string.h>:C 语言原生字符串头文件,提供strlen/strcpy等 C 风格字符数组函数;
  2. <cstring>:C++ 对<string.h>的封装,功能完全一致,命名空间为std
  3. <string>C++ 标准字符串容器头文件,专属std::string,和上面两个完全无关。

2.sizeofvsstrlen

  • sizeof(变量):运算符,计算整个变量占用内存字节数,包含末尾\0
    • 数组:char c[] = "abc"; sizeof(c)=4(3 字符 + 1 结束符)
    • 指针:char* p = "abc"; sizeof(p)=8(x64)/4(x86),只算指针本身大小,和字符串长度无关,指针在x86下占4字节在x64下占8个字节
  • strlen(字符指针):C 库函数,只计算有效字符长度,遇到\0立刻停止,不计入\0
    • strlen("abc") = 3

3.std::string:C++ 第一个标准容器

底层封装字符数组,自带长度管理、内存自动扩容,不用手动处理\0

四种初始化写法
#include <string> // 1. 空字符串,长度0 std::string stra; // 2. 等号列表初始化 std::string strb = {"yhpinghello"}; // 3. 构造函数初始化 std::string strc("yhpinghello"); // 4. C++11列表初始化(推荐) std::string strd{"yhpinghello"};

4. string 两种下标遍历方式([]vs.at()

std::string strd{"yhpinghello"}; // 方式1:[]下标访问 for (int i = 0; i < strd.size(); ++i) { std::cout << strd[i] << std::endl; } // 方式2:.at()访问 for (int i = 0; i < strd.size(); ++i) { std::cout << strd.at(i) << std::endl; }
核心差异(笔记补充解释)
  1. str[i]:不做越界检查。如果i >= str.size()访问非法内存,输出乱码 / 程序崩溃,速度更快;
  2. .at(i):内置越界校验。下标超出范围会抛出std::out_of_range异常,方便调试,安全性更高。

5. 字符串合并(+运算符重载)

std::string重载了+,可以直接拼接两个字符串对象:

std::string stra = "yhping"; std::string strb = "hello"; std::string strd = stra + strb; std::cout << strd << std::endl; // 输出 yhpinghello

注意:不能直接写"a"+"b",两个字面量是const char*,不支持+拼接;至少一侧是std::string才行。

九、编译器内置宏(调试打印专用)

无需定义,编译器自动提供,快速获取代码位置信息:

#include <iostream> int main() { // 当前代码文件完整路径字符串 std::cout << __FILE__ << std::endl; // 当前所在函数名 std::cout << __func__ << std::endl; // 当前代码行号(数字) std::cout << __LINE__ << std::endl; // 程序编译时间 std::cout << __TIME__ << std::endl; // 程序编译日期 std::cout << __DATE__ << std::endl; return 0; }

实用场景:日志打印,快速定位报错代码位置。


十、转义字符(单引号 / 双引号 / 反斜杠打印)

基础规则

字符串 / 字符字面量中,' " \拥有特殊语法含义,想要原样打印,必须加反斜杠\转义。

  1. 单引号':字符字面量' '的边界符号,内部写\'代表单引号字符;
  2. 双引号":字符串字面量" "的边界符号,内部写\"代表双引号字符;
  3. 反斜杠\:转义标记,想要输出\,必须写两个\\

/:转义字符

‘:/’(才能被识别为单引号字符)如果不带/,会被认为是一个字符的开始

“:/”(双引号字符)如果不带/会被认为是字符串的开始

代码示例

// 存储字符:单引号 ' char chb = '\''; // 存储字符:双引号 " char chc = '\"'; // 存储字符:反斜杠 \ char chd = '\\';

拓展打印演示

std::cout << chb << chc << chd; // 输出:'" \
  1. int*裸指针无法使用范围 for,std::string是容器,支持for(auto c : strd)简洁遍历;
  2. typedef只支持基础类型别名,模板别名一律用using
  3. 区分 C 风格char[]和 C++std::string:前者需要手动strlen、防越界,后者自带.size()和安全at()
  4. 空指针统一用nullptr,抛弃NULL避免重载 bug;
  5. 转义字符只在引号包裹的字面量里生效,变量输出不需要转义。
http://www.jsqmd.com/news/1204651/

相关文章:

  • Prompt 工程
  • 深入解析文件系统接口:从基础操作到性能优化
  • 纯C++信号-槽机制实现:从设计到实战的轻量级解耦方案
  • JUnit 5 单元测试实战指南:从架构到Mockito集成
  • Windows上运行Ubuntu虚拟机:开发环境配置与优化指南
  • 美度中国官方售后服务中心|服务热线及全部官方地址权威信息公示(2026年7月更新) - 亨得利官方服务中心
  • YOLOv8目标检测模型解析与实战部署指南
  • 2026 年新发布:木垒哈萨克自治专业的水闸 品牌怎么联系,水闸背后:你从未知道的隐藏秘密 - 行业严选官
  • AI时代开发者成长指南:从技术转型到工程实践全解析
  • 若依框架密码加密算法升级实战:从MD5到BCrypt的安全迁移
  • 小程序商城开发报价多少?订单、会员和营销功能费用对比
  • GM/T 0054标准下的密钥生命周期管理:从生成到销毁的八个核心环节与实践指南
  • AI全栈工程师实战成长路径与关键技术解析
  • 老旧电脑安装Win7全攻略:镜像选择、BIOS设置与性能优化
  • C++性能监控黄金标准:三大工业级工具选型与实战指南
  • Linux系统基础16:环境变量与路径 新手超详细教程
  • 欧米茄官方售后服务中心电话和详细地址实地考察报告_多信源验证(2026年7月更新) - 欧米茄服务中心
  • LLM安全全生命周期防御:从数据投毒到推理攻击的10项关键技术
  • 2026年7月最新宝玑天津西青大悦春风里维修保养服务电话 - 亨得利钟表维修中心
  • CentOS7手动编译GCC7.5指南与C++17支持
  • C++ WebRTC开发实战:从零构建点对点音视频通信示例
  • WSL2虚拟机配置与开发环境搭建指南
  • 大屏适配:CSS3视窗单位vm/vh实战指南
  • VLA四模型工程落地实战:RT-1/RT-2/Octo/OpenVLA关键断面解析
  • 基金周复盘工作流
  • AI算力解析:GPU、FPGA、ASIC三大硬件支柱与应用指南
  • Codex Cli 安装教程(Windows)
  • Havenlon|历史中的执行控制(十二):核潜艇危机 Arkhipov——为什么单点权力不能决定灾难性执行
  • 营销型官网怎么搭建?首页结构、表单和转化路径对比
  • CH32V208开发板TCP通信环境搭建与优化指南