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

从零开始的C++学习生活 5:内存管理和模板初阶

个人主页:Yupureki-CSDN博客

C++专栏:C++_Yupureki的博客-CSDN博客

目录

前言

1. C语言的内存管理

1.1 内存区域分布

1.2 malloc/calloc/realloc对比

1.3 常见错误

2. C++的新内存管理方式

2.1 new和delete

2.2 new和delete操作自定义类型

3. operator new与operator delete

4. malloc/free和new/delete的区别

 对比总结表

5. 泛型编程:为什么需要模板?

6. 函数模板

6.1 函数模板的概念

6.2 函数模板的格式

6.3 函数模板的原理

6.4 函数模板的实例化

隐式实例化

显式实例化

6.5 模板参数的匹配原则

7. 类模板

7.1 类模板的定义

3.2 类模板的实例化


前言

C语言中,malloc开辟空间大小需要我们自己去计算,也不能初始化每一块空间的值,有没有更加方便快捷的方法?

再之,我们常常会遇到需要处理多种数据类型的情况。例如,实现一个交换两个变量的函数,如果对每种类型都写一个重载函数,代码会显得冗余且难以维护。有没有一种方式,可以让我们只写一次代码,就能适用于多种类型呢?

在C++里,这些问题都得到了解决,new/delete和模板都是C++不可或缺,十分重要的部分。

本文将带你初步了解C++中的新的空间开辟机制和模板机制,包括函数模板和类模板的基本概念、使用方法及其原理。

1. C语言的内存管理

1.1 内存区域分布

在C/C++程序中,内存被划分为几个关键区域,每个区域都有其特定的用途和管理方式

【说明】

1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。

2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口 创建共享共享内存,做进程间通信。

3. 堆用于程序运行时动态内存分配,堆是可以上增长的。

4. 数据段--存储全局数据和静态数据。

5. 代码段--可执行的代码/只读常量。

#include <iostream>
#include <cstdlib>
using namespace std;int globalVar = 1;              // 全局变量
static int staticGlobalVar = 1; // 静态全局变量void Test() {static int staticVar = 1;    // 静态局部变量int localVar = 1;           // 局部变量int num1[10] = {1, 2, 3, 4}; // 局部数组char char2[] = "abcd";       // 局部字符数组const char* pChar3 = "abcd"; // 指向常量字符串的指针int* ptr1 = (int*)malloc(sizeof(int) * 4);  // 动态分配int* ptr2 = (int*)calloc(4, sizeof(int));   // 动态分配并初始化为0int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4); // 重新分配free(ptr1);free(ptr3);
}

1.2 malloc/calloc/realloc对比

#include <iostream>
#include <cstdlib>
using namespace std;void TestCMemory() {// malloc - 分配指定字节数的内存,不初始化int* p1 = (int*)malloc(sizeof(int) * 4);// calloc - 分配并初始化为0int* p2 = (int*)calloc(4, sizeof(int));  // 分配4个int,初始化为0// realloc - 重新分配内存大小int* p3 = (int*)realloc(p2, sizeof(int) * 10);// 注意:p2已被realloc处理,不需要再freefree(p1);free(p3);  // 释放重新分配后的内存
}

三种函数的区别:

函数初始化参数形式使用场景
malloc不初始化malloc(size)普通内存分配
calloc初始化为0calloc(count, size)需要零初始化的数组
realloc保持原数据realloc(ptr, new_size)调整已分配内存大小

1.3 常见错误

内存泄漏(未使用free),重复释放(多次使用free),解引用野指针(读取已释放空间的数据)和越界访问都是常见的错误

void MemoryErrors() {// 1. 内存泄漏int* leak = (int*)malloc(sizeof(int) * 10);// 忘记free(leak)// 2. 重复释放int* p = (int*)malloc(sizeof(int));free(p);// free(p);  // 错误:重复释放// 3. 使用已释放内存int* q = (int*)malloc(sizeof(int));free(q);// *q = 10;  // 错误:野指针// 4. 越界访问int* arr = (int*)malloc(sizeof(int) * 5);// arr[5] = 10;  // 错误:越界访问free(arr);
}

2. C++的新内存管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

2.1 new和delete

#include <iostream>
using namespace std;void TestCPPMemory() {// 申请单个int空间int* ptr1 = new int;// 申请单个int空间并初始化为10int* ptr2 = new int(10);// 申请10个int类型的空间(数组)int* ptr3 = new int[10];// 申请数组并初始化(C++11)int* ptr4 = new int[10]{1, 2, 3};  // 前3个初始化,其余为0cout << "*ptr2 = " << *ptr2 << endl;  // 输出:10// 释放内存delete ptr1;delete ptr2;delete[] ptr3;  // 数组要用delete[]delete[] ptr4;
}

new为操作符,和malloc或者calloc相似,后面跟要开辟空间数据的类型和大小。如果要以数组的形式开辟则加[],里面加大小,后面的{}可以初始化前n个数据的大小。

delete为操作符,和free相似。如果是单个数据则直接用delete释放空间;如果是数组,那么最好加上[](这里不加也不会报错,但如果是类型,那么极有可能出错)

2.2 new和delete操作自定义类型

当new和delete为自定义类型开辟空间时,则会自动调用对应的构造函数析构函数

而内置类型不会,毕竟都没有构造函数和析构函数

class MyClass {
public:MyClass(int val = 0) : data(val) {cout << "构造函数: " << this << ", data = " << data << endl;}~MyClass() {cout << "析构函数: " << this << ", data = " << data << endl;}private:int data;
};void TestDifference() {// C++方式:分配内存并调用构造函数MyClass* p2 = new MyClass(42);delete p2;  // 调用析构函数并释放内存cout << "=== 数组操作 ===" << endl;// 对象数组MyClass* p3 = new MyClass[3];  // 调用3次构造函数delete[] p3;                   // 调用3次析构函数
}

如果以数组的形式为自定义类型开辟空间,那么开辟了几个元素的空间,就会调用几次的构造函数和析构函数

3. operator new与operator delete

在C++中,new和delete只是用户进行动态内存申请和释放的操作符

在运用当中会分别调用对应的operator newoperator delete函数

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{while ((p = malloc(size)) == 0)//new的底层其实是调用malloc//.......
}void operator delete(void *pUserData)
{_free_dbg( pUserData, pHead->nBlockUse );//delete的底层是调用free//.......
}#define  free(p)    _free_dbg(p, _NORMAL_BLOCK)

如果观察这两个函数的内容,则会发现其实就是malloc和free套了个马甲,本质还是使用malloc和free。如果 malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施 就继续申请,否则就抛异常。

4. malloc/free和new/delete的区别

malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。

不同的地 方是:

1. malloc和free是函数,new和delete是操作符

2. malloc申请的空间不会初始化,new可以初始化

3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[]中指定对象个数即可

4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型

5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常

6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new 在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成 空间中资源的清理释放

 对比总结表

特性malloc/freenew/delete
语言C函数C++操作符
初始化不初始化可初始化
大小计算手动计算自动计算
类型安全需要强转类型安全
错误处理返回NULL抛出异常
构造/析构不调用自动调用
重载不可重载可重载
数组需要手动处理专用语法

5. 泛型编程:为什么需要模板?

在没有模板的情况下,如果我们想实现一个通用的交换函数,可能会写出如下代码:

void Swap(int& left, int& right) {int temp = left;left = right;right = temp;
}void Swap(double& left, double& right) {double temp = left;left = right;right = temp;
}void Swap(char& left, char& right) {char temp = left;left = right;right = temp;
}

这种方式虽然可行,但存在明显的问题:

  • 代码冗余:每个函数体几乎相同,只是类型不同。

  • 不易维护:新增类型时需要手动添加函数,容易出错。

我们希望能有一个“模具”,让编译器根据我们传入的类型自动生成对应的函数代码。这就是模板的由来。

6. 函数模板

6.1 函数模板的概念

函数模板是一个通用的函数蓝图,它不具体指定类型,而是在使用时根据传入的实参类型自动实例化出具体的函数版本。

6.2 函数模板的格式

template<typename T>
void Swap(T& left, T& right) {T temp = left;left = right;right = temp;
}
  • template<typename T> 表示定义一个模板,T 是一个占位符类型。

  • 你也可以使用 class 代替 typename,但不能用 struct

6.3 函数模板的原理

函数模板本身不是函数,而是编译器生成具体函数的“模具”。当我们调用 Swap(a, b) 时,编译器会根据 a 和 b 的类型推导出 T,然后生成对应的函数代码。

int main() {int i1 = 10, i2 = 20;Swap(i1, i2);  // 生成 void Swap(int&, int&)double d1 = 1.1, d2 = 2.2;Swap(d1, d2);  // 生成 void Swap(double&, double&)
}

6.4 函数模板的实例化

使用函数模板时,编译器会根据传入的实参类型生成具体的函数,这个过程称为实例化。实例化分为两种:

隐式实例化

编译器自动推导类型:

Add(1, 2);     // T 被推导为 int
Add(1.1, 2.2); // T 被推导为 double
显式实例化

手动指定类型:

Add<int>(1, 2.2);  // 将 2.2 强制转换为 int

6.5 模板参数的匹配原则

  • 如果存在非模板函数与模板函数同名,且参数匹配,优先调用非模板函数。

  • 如果模板能生成更匹配的版本,则选择模板。

  • 模板不支持隐式类型转换,而普通函数支持。


7. 类模板

7.1 类模板的定义

类模板允许我们定义一种通用的类,其成员类型可以是参数化的。

template<typename T>
class Stack {
public:Stack(size_t capacity = 4) {_array = new T[capacity];_capacity = capacity;_size = 0;}void Push(const T& data);private:T* _array;size_t _capacity;size_t _size;
};// 类外定义成员函数
template<class T>
void Stack<T>::Push(const T& data) {// 扩容逻辑_array[_size] = data;++_size;
}

7.2 类模板的实例化

类模板的实例化必须在类名后加上 <类型>

Stack<int> st1;        // 存储 int 类型的栈
Stack<double> st2;     // 存储 double 类型的栈

注意:Stack 是模板名,Stack<int> 才是具体的类型名。

http://www.dtcms.com/a/453599.html

相关文章:

  • 黔东南购物网站开发设计canvas网站源码
  • 为网站做IPhone客户端网站建设中 模板
  • 网站备案可以做电影网站吗厦门建筑信息网
  • 浦东做网站公司中国企业500强出炉
  • 白话大模型评估:文本嵌入与文本生成模型评估方法详解
  • 广州网站制作开发公司哪家好高德地图加拿大能用吗
  • 网站自助建设平台百度网页设计论文题目什么样的好写
  • 小米系耳机配对方法
  • 国外的有名的网站百家联盟推广部电话多少
  • C4D R20新功能实战指南:深度解析域、节点材质与OpenVDB,提升你的3D创作效率
  • 免费查找企业信息的网站旅游网站建设策划方案书
  • 【LeetCode】454. 四数相加 II 【分组+哈希表】详解
  • 用word做旅游网站企业网站建设要注意哪方面
  • 怎么做跳转流量网站专业北京seo公司
  • 日本京都与奈良:古刹与神社的对比之旅
  • 算术操作符 自增运算符 逆向汇编三
  • C# 基于DI和JSON序列化的界面配置参数方案
  • 零遁nas做网站微信小程序怎么做店铺免费
  • 2025 AI 伦理治理破局:从制度设计到实践落地的中国探索
  • 堆排序原理与实现详解
  • 网页设计与网站建设过程wordpress淘宝客主题破解版
  • 不关网站备案wordpress安装完成后
  • 分割回文串(dfs)
  • 第二十二章:记忆封存,时光回溯——Memento的备忘录艺术
  • Spring Framework源码解析——ApplicationContextAware
  • 30个做设计的网站wordpress远程图片下载
  • 建网站权威机构西安专业网站建设服务
  • Express+Vue表格数据分页联调:模拟数据与真实接口的无缝切换
  • Qt 多线程与并发编程详解
  • 第五个实验——动态nat地址转换操作