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

CPP之动态内存管理以及模板初阶

1.动态内存管理:

(1)一个小问题回顾内存分布:

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));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}
1. 选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?____
staticGlobalVar在哪里?____
staticVar在哪里?____
localVar在哪里?____
num1 在哪里?____
char2在哪里?____
*char2在哪里?___
pChar3在哪里?____
*pChar3在哪里?____
ptr1在哪里?____
*ptr1在哪里?____

用以下的图片来反映上图的答案:

注意:

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

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

      3,数据段(静态区)存储全局变量和静态变量;

      4,代码段--可执行的代码/只读常量。

(2)cpp中内存管理的方法:

 在cpp中内存管理是通过new和delete来实现的。

下面具体举例:

//动态申请一个int大小的空间
int* ptr1 = new int;//动态申请一个int的空间并初始化为0
int* ptr2 = new int(0);//动态申请一个有10个int的数组
int* ptr3 = new int[10];//分别释放动态申请的三个
delete ptr1;
delete ptr2;
delete[] ptr3;

上述代码用图片来解析:

注意:申请和销毁一个元素的空间用new和delete,申请和销毁连续的空间用new[ ]和delete[ ],注意,配套起来用。

(3)特点:

对于malloc和free,new和delete在创建自定义类型的时候有很大的区别,对于malloc函数来说,它会给自定义类型申请一块空间,并不会给这个自定义内型初始化,但是new不仅创建空间,而且还要去调用自定义类型的构造函数对其进行初始化。对于free函数,他仅仅是对自定义类型的空间进行释放,但是delete会先调用自定义类型的析构函数然后再将这块内存释放。

(他们在对内置类型进行创建的时候基本上没有区别)。

(4)operator new和operator delete函数

说明:

new delete 是用户进行 动态内存申请和释放的操作符 operator new operator delete
系统提供的 全局函数 new 在底层调用 operator new 全局函数来申请空间, delete 在底层通过
operator delete 全局函数来释放空间。

-->operator new函数:

下面是operator函数的原理:
void*__CRTDECLoperatornew(size_tsize)_THROW1(_STDbad_alloc)
{//trytoallocatesizebytesvoid*p;while((p=malloc(size))==0)if(_callnewh(size)==0){//reportnomemory//如果申请内存失败了,这里会抛出bad_alloc类型异常staticconststd::bad_allocnomem;_RAISE(nomem);} return(p);
}

可以看到,起始operator new 函数里面还是会去使用malloc,那为什么还要有new的存在呢,这和cpp以后的抛异常的知识有关。

(operatornew :该函数实际通过 malloc 来申请空间,当 malloc 申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。)

  -->operator delete函数:

下面是operator delete函数的实现:

voidoperatordelete(void*pUserData)
{_CrtMemBlockHeader*pHead;RTCCALLBACK(_RTC_Free_hook,(pUserData,0));if(pUserData==NULL)  return;_mlock(_HEAP_LOCK); /*blockotherthreads*/__TRY/*getapointertomemoryblockheader*/pHead=pHdr(pUserData);/*verifyblocktype*/_SSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));_free_dbg(pUserData,pHead->nBlockUse);__FINALLY_munlock(_HEAP_LOCK); /*releaseotherthreads*/__ENDTRYFINALLYreturn;
}
起始operator delete底层是通过free实现的。

小结:

通过上述两个全局函数的实现知道, operator new 实际也是通过 malloc 来申请空间 ,如果
malloc 申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施
就继续申请,否则就抛异常。 operator delete 最终是通过 free 来释放空间的

(2)定位new:(定位new表达式(placement-new) )--->了解程度

   定位 new 表达式是在 已分配的原始内存空间中调用构造函数初始化一个对象
使用格式:
  new (place_address) type 或者 new (place_address) type(initializer-list)
  place_address 必须是一个指针, initializer-list 是类型的初始化列表
使用场景:
  定位 new 表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如
果是自定义类型的对象,需要使用 new 的定义表达式进行显示调构造函数进行初始化。

2.模板:

(1)泛型编程:

     就一个简单的交换函数,是否有一个通用的方法去解决不同类型的交换呢,虽然函数重载可以完成这件事,但是它存在这样两个问题:

(1)改变类型就要重新写函数,效率不高;

(2)代码的可维护性低,一个重载出错,则可能全部都出错了。

模板就是用来解决上述问题的:告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码。

泛型编程:编写于类型无关的通用代码,是代码复用的一种手段,模板是泛型编程的基础。

(2)函数模板:

          (1)模板的概念:函数模板是一类函数,该函数模型与类型无关,在使用时被参数,根实参类型产生函数的特定类型版本。

          (2)函数模板格式:template<typename T1,typename T2,......typename Tn>;

下面用交换函数来举例:

templete<typename T>
void swap(T& a T& b)
{T tmp = b;b = a;a = tmp;
}

不管T是什么类型,在调用函数的时候,编译器都会根据模板生成相应的代码。

注意:typename是用来定义模板参数的关键字,也可以用class来代替,但是切记不可以用struct来代替class。

       (3)函数模板的原理:

函数模板就相当于一张蓝图,并不是函数,是告诉编译器生成特定类型函数的模具。

在编译器编译阶段 ,对于模板函数的使用, 编译器需要根据传入的实参类型来推演生成对应 类型的函数 以供调用。比如: 当用 double 类型使用函数模板时,编译器通过对实参类型的推演, T 确定为 double 类型,然后产生一份专门处理 double 类型的代码 ,对于字符类型也是如此。
      (4)函数模板的实例化:
用不同类型的参数使用函数模板时 ,称为函数模板的 实例化 。模板参数实例化分为: 隐式实例化
和显式实例化
        ---->隐式实例化:让编译器根据实参推演模板类型的实际类型。
temlate<typename T>
T add(T left,T right)
{return left+right;
}
int main()
{int a1 = 1;double b1 = 1.1;add(a1,b1);return 0;
}
上述代码中,语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型通过实参a1T推演为int,通过实参d1T推演为double类型,但模板参数列表中只有一个T。编译器无法确定此处到底该将T确定为int或者double类型而报错。
这时候就有两种解决方法:1,用户自己强制类型转换。2,使用显示实例化。
add(a1,(int)b1);
        ---->显式实例化:在函数名后的<>中指定模板参数的实际类型:
 
add<int>(a1,b1);
如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

    (3)模板参数的匹配原则:

1.一个非模板函数可以与一个同名的模板函数同时存在,而且模板函数还可以通过实例化转化为该非模板函数。

//专用于int类型的add
int add(int a,int b)
{return a+b;
}
//通用的add函数
temlate<typename T>
T add(T left,T right)
{return left+right;
}
2.对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而 不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模 板.
int add(itn& a,int& b)
{return a+b;
}
template<typename T1,typename T2>
T add(T1 a, T2 b)
{return a+b;
}
int main()
{add(1,1);//调用第一个函数add(1,1.1);//调用第二个函数
}
模板函数不允许自动类型转换,但普通函数可以进行自动类型转换.

(4)类模板:

        (1)类模板的定义格式:

template<classT1,classT2,...,classTn>
class 类模板名
{//类内成员定义
}

       (2)写一个stack的模板:

#include<iostream>
using namespace std;namespace zmj
{template<typename T>class stack{public:stack(int n = 4){_array = new T[n];_size = 0;_capacity = n;}void push(const T& data){_array[_size] = data;++_size;}private:T* _array;int _size;int _capacity;};
}// 模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误。
int main()
{Stack<int> st1;   //intStack<double> st2; //doublereturn 0;
}

若想把push的内容写在外面:

需要特定的格式:

#include<iostream>
using namespace std;namespace zmj
{template<typename T>class stack{public:stack(int n = 4){_array = new T[n];_size = 0;_capacity = n;}void push(const T& data);private:T* _array;int _size;int _capacity;};
}template<typename T>
void zmj::stack<T>::push(const T& x)
{_array[_size] = x;++_size;
}

需要重新写一遍template<typename T>,以及还需要在stack后面加<T>。   

(3)类模板的实例化:

类模板实例化与函数模板实例化不同, 类模板实例化需要在类模板名字后跟 <> ,然后将实例化的
类型放在 <> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类
(类模板只能显示实例化)

相关文章:

  • 第三十九节:视频处理-光流法 (Lucas-Kanade, Dense)
  • 计算机存储与数据单位的核心定义及换算逻辑
  • 深度解析 MCP:重新定义 API 的开发范式
  • CSS attr() 函数详解
  • Srinath多元假设检验 (Multiple-hypothesis Testing)(To 廖老师)
  • SpringBoot(二)--- SpringBoot基础(http协议、分层解耦)
  • flask蓝图的导入与注册
  • 宇宙漂流的时间胶囊:我用 CodeBuddy 实现了一个「太空感」单页应用
  • 【C语言内存函数】--memcpy和memmove的使用和模拟实现,memset函数的使用,memcmp函数的使用
  • java笔记07
  • SAP系统的委外业务是什么?委外采购(标准委外)与工序外协的区别有哪些?
  • leetcode hot100刷题日记——3.移动零
  • 【Nginx学习笔记】:Fastapi服务部署单机Nginx配置说明
  • laravel 通过Validator::make验证后,如何拿到验证后的值
  • Kali安装配置JAVA环境和切换JDK版本的最详细的过程
  • 自己拥有一台服务器可以做哪些事情
  • AI自媒体封面生成系统
  • 图像分割(2)u-net代码实战——基于视网膜分割
  • ubuntu open shh9.9安装
  • 系统思考:动态性复杂
  • 上海文化馆服务宣传周启动,为市民提供近2000项活动
  • 多名幼师殴打女童被行拘后续,盘锦教育局工作人员:该局将专项整治全市幼儿园
  • 国家外汇管理局:4月货物贸易项下跨境资金净流入649亿美元
  • 探月工程鹊桥二号中继星取得阶段性进展
  • 上影节公布今年IMAX片单:暗涌、重生与感官的史诗
  • 媒体报道一民企投资400万运营出租车4年未获批,广西隆林县回应