当前位置:主页 > c/c++教程 > C++动态内存管理 泛型编程

详解C++中动态内存管理和泛型编程

发布:2023-03-05 09:30:01 59


为网友们分享了相关的编程文章,网友饶成周根据主题投稿了本篇教程内容,涉及到C++动态内存管理、C++、内存管理、C++、泛型编程、C++动态内存管理 泛型编程相关内容,已被553网友关注,相关难点技巧可以阅读下方的电子资料。

C++动态内存管理 泛型编程

一、C/C++内存区域划分

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

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

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

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

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

二、常见变量存储区域

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";//栈区,*char2在栈区
    const char* pChar3 = "abcd";//指针在栈区,*pchar3在常量区
    int* ptr1 = (int*)malloc(sizeof(int) * 4);//指针在栈区,*ptr1在堆区
    int* ptr2 = (int*)calloc(4, sizeof(int));///栈区
    int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);//栈区
    free(ptr1);
    free(ptr3);
}

三、new和delete

1、new和delete的使用方式

int main()
{
    int* p1 = new int;//在堆区申请一个int大小的空间,不会初始化
    int* p2 = new int(0);//申请并初始化为0
    delete p1;
    delete p2;
 
    int* p3 = new int[10];//在堆区申请一块10个int大小的空间,未初始化
    int* p4 = new int[10]{ 1,2,3,4 };//初始化为{1,2,3,4,0,0,0,0,0,0}
    delete[] p3;
    delete[] p4;
    return 0;
}

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],一定要匹配起来使用。

2、new、delete和malloc、free的区别

1、对于内置类型,没有区别。

2、new和delete是C++的关键字/操作符,而malloc和free是C语言的库函数。

3、对于自定义类型,相比于malloc和free,new和delete会额外调用类中的构造函数和析构函数。

4、malloc的返回值是void*,使用时需要强转,new后边跟的是空间的类型,所以new不需要强转。

5、malloc失败返回空指针,需要判空;new失败抛异常,需要捕获异常。

3、new的原理

new等于operator new()+构造函数。operator new()不是new运算符的重载,因为参数没有自定义类型。它是一个库里的全局函数。

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) 
{
// try to allocate size bytes
    void *p;
    while ((p = malloc(size)) == 0)
         if (_callnewh(size) == 0)
         {
             // report no memory
             // 如果申请内存失败了,这里会抛出bad_alloc 类型异常
             static const std::bad_alloc nomem;
             _RAISE(nomem);
         }
    return (p);
}

从底层代码可以看出operator new()是对malloc的封装,如果malloc失败,将会抛出异常。

4、delete的原理

delete等于operator delete()+析构函数

//operator delete: 该函数最终是通过free来释放空间的
void operator delete(void *pUserData) {
     _CrtMemBlockHeader * pHead;
     RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
     if (pUserData == NULL)
         return;
     _mlock(_HEAP_LOCK);  /* block other threads */
     __TRY
         /* get a pointer to memory block header */
         pHead = pHdr(pUserData);
          /* verify block type */
         _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
         _free_dbg( pUserData, pHead->nBlockUse );//调用free()
     __FINALLY
         _munlock(_HEAP_LOCK);  /* release other threads */
     __END_TRY_FINALLY
     return; }
//free的实现
#define   free(p)               _free_dbg(p, _NORMAL_BLOCK)

从底层代码可以看出operator delete()调用了free。

所以针对内置类型或无资源的类对象delete时,使用delete和free效果相同。但对于有资源需要释放的对象时,直接使用free虽然释放了对象的空间,但对象内部的资源还未被清理,导致内存泄漏!这种情况必须使用delete。

5、new T[N]原理

1、new T[N]调用operator new[]

2、operator new[]调用operator new完成N个对象空间的开辟。

3、调用N次构造函数完成N个对象的初始化。

6、delete[]原理

1、调用N次析构函数完成N个对象资源的清理工作。

2、调用operator delete[]

3、operator delete[]调用operator delete完成整段空间的释放。

四、定位new

1、定位new的概念

对于一个类,我们可以显式的去调用类的析构函数,但是不能显式调用构造函数,那么使用定位new,就可以显式调用类的构造函数,对一块空间重新初始化。

2、定位new的使用格式

new (指针)类名或者new (指针) type(初始化列表)

int main()
{
    Date d1;
    new(&d1)Date;//new (指针)类名
    Date* p = new Date[4]{ {2022,10,15},{2023,11,8} };
    new(p)Date[4];//new (指针) type(初始化列表)
    delete[] p;
    return 0;
}

上述代码一共调用了10次构造函数,经过定位new的处理,d1和p所代表的空间已经被重新初始化了。

3、定位new的使用场景

一般不会像上边代码一样,对一块已有对象数据的空间重新初始化。定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,对于自定义类型的对象,可以使用定位new对这些没有被初始化的内存显式调用类的构造函数初始化。

五、泛型编程

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

模板分为函数模板和类模板

六、函数模板

1、函数模板的使用

template
void Swap(T& a, T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
}
int main()
{
    int a = 10, b = 5;
    double m = 2.3, n = 4.9;
    Swap(a, b);
    Swap(m, n);
    return 0;
}

两个Swap调用的不是模板,而是模板生成的实例化函数,像上述代码中,模板会生成int和double类型的两种实例化函数。

2、不同类型形参传参时的处理

2.1传参时强转(对应形参需要const修饰)

template
T Add(const T& a,const T& b)//const接收常性实参
{
    return a + b;
}
int main()
{
    int a = 10, b = 5;
    double m = 2.3, n = 4.9;
    Add(a, (int)m);//强转,临时变量传参,具有常性
    return 0;
}

使用强制类型转换在推演的时候将形参转换成同一类型。

2.2显式实例化(传参时隐式类型转,对应形参需要const修饰)

template
T Add(const T& a, const T& b)//需要使用const接收
{
    return a + b;
}
int main()
{
    int a = 10, b = 5;
    double m = 2.3, n = 4.9;
    Add(a, m);//显式实例化,m发生隐式类型转换
    return 0;
}

显式实例化编译器不再去推演T的类型,而是直接使用尖括号内的类型实例化对应函数。

2.3使用多个模板

template//可以写typename也可以写class
T1 Add(const T1& a, const T2& b)
{
    return a + b;
}
int main()
{
    int a = 10, b = 5;
    double m = 2.3, n = 4.9;
    Add(a, m);//Add(a,m);多个模板的手动推演
    return 0;
}

3、模板和实例可以同时存在,编译器会优先调用实例 

template//可以写typename也可以写class
T Add(const T& a, const T& b)
{
    return a + b;
}
int Add(const int& a, const int& b)
{
    return a + b;
}
int main()
{
    int a = 10, b = 5;
    double m = 2.3, n = 4.9;
    Add(a, m);//调用已有实例
    Add(a, m);//调用模板生成的实例
    return 0;
}

1、模板和普通函数的函数名修饰规则是不一样的。

2、模板和实例可以同时存在,编译器会优先调用实例。如果想使用模板生成的实例,必须使用尖括号指定类型。

3、如果模板可以生成更加匹配的版本,编译器将会生成这个匹配版本而不是使用那个已有但不太匹配的实例。

六、类模板

1、对象定义时需要显式实例化

int main()
{
    Stack st1; // double
    st1.Push(1.1);
    Stack st2; // int
    st2.Push(1);
    return 0;
}

函数模板可以通过传参确定T的类型,但是类模板编译器无法推演,必须要在对象定义时显式实例化类型。

模板参数不同,他们就是不同的类型。st1和st2属于不同的类定义出的两个对象。所以不能有st1=st2,因为他们不是同一个类,除非针对这种赋值,自己写一个赋值重载。

2、为什么stl被称为模板

类模板和函数模板不同,需要在实例化的时候在类名后加上<类型>。

类模板不是真正的类,而实例化出来的才是真正的类。

// Vector是类模板,Vector才是类型
Vector s1;
Vector s2;

以上就是详解C++中动态内存管理和泛型编程的详细内容,更多关于C++动态内存管理 泛型编程的资料请关注码农之家其它相关文章!


参考资料

相关文章

  • Java C++题解leetcode816模糊坐标示例

    发布:2023-03-13

    这篇文章主要为大家介绍了Java C++题解leetcode816模糊坐标示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪


  • C++标准模板库函数sort的那些事儿

    发布:2022-07-11

    为网友们分享了关于C++的教程,sort函数是标准模板库的函数,已知开始和结束的地址即可进行排序,可以用于比较任何容器(必须满足随机迭代器),任何元素,任何条件,执行速度一般比qsort要快


  • C++11 成员函数作为回调函数的使用方式

    发布:2023-03-10

    这篇文章主要介绍了C++11 成员函数作为回调函数的使用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教


  • Inline Hook(ring3)的简单C++实现方法

    发布:2022-09-23

    给大家整理了关于C++的教程,这篇文章主要介绍了Inline Hook(ring3)的简单C++实现方法,需要的朋友可以参考下


  • C++中构造函数与析构函数的调用顺序详解

    发布:2022-10-21

    为网友们分享了关于C++的教程,C++ 语言一直被批评太复杂了,很多细节的地方需要仔细推敲,甚至其构造函数和析构的调用顺序也成为了一个让人迷惑的问题,在此我做了简单的总结。这篇文章主要介绍了C++中构造函数与析构函数的调用顺序,需要的朋友可以参考借鉴。


  • Windows下sentry接入C/C++程序的详细过程

    发布:2023-03-02

    sentry作为一个开源的软件,发展至今,已经非常成熟。它支持的平台众多,甚至于针对不同的工作者(后台、前端、客户端)都有相应的内容,这篇文章主要介绍了Windows下sentry接入C/C++程序,需要的朋友可以参考下


  • 基于C++实现的哈夫曼编码解码操作示例

    基于C++实现的哈夫曼编码解码操作示例

    发布:2022-10-09

    给网友朋友们带来一篇关于C++的教程,这篇文章主要介绍了基于C++实现的哈夫曼编码解码操作,结合实例形式分析了C++实现的哈夫曼编码解码相关定义与使用技巧,需要的朋友可以参考下


  • C++实现优酷土豆去视频广告的方法

    发布:2022-09-06

    给网友们整理关于C++的教程,这篇文章主要介绍了C++实现优酷土豆去视频广告的方法,实例分析了C++实现屏蔽功能的相关技巧,需要的朋友可以参考下


网友讨论