C++学习笔记总结练习:动态内存

动态内存

存在的问题:栈空间和堆空间的分配也是运行时内存分配,即动态内存分配。文字常量区、全局变量和静态变量区是在编译时内存分配,即静态内存分配。

栈空间的动态内存分配由操作系统管理。堆空间的动态内存分配由用户自身管理。二者可以交叉使用。

例如:使用栈空间无法返回局部对象,只能返回局部对象的复制(重写复制构造函数),比较麻烦,可以返回堆空间申请一个对象,并返回指向该对象的指针。

例如:使用堆空间,对象的释放需要自己管理,可以在栈空间创建一个局部对象,在其内部封装申请和释放堆空间的方法。当栈空间的局部对象被销毁时,调用析构函数,清除堆空间的对象。这也是allocator和智能指针的实现方案。(allocator一般使用栈对象的析构函数,销毁堆对象的空间。智能指针一般通过引用计数的方法销毁堆对象。)

关于汇编语言程序有两种方法进行动态分配:
方法一:通过系统调用从操作系统获得内存块。(linux/windows操作系统提供了alloc方法和heap方法。在当前的进程中,后者用来申请新的私有堆,前者用来在已有的堆空间上申请内存。)
方法二:实现自己的堆管理器来服务更小的对象提出的请求。

0 C++直接管理内存

概念

  • C++使用new分配内存,使用delete释放new分配的内存。
  • 动态对象的声明周期从创建到被释放时为止。

使用动态内存的原因

既然这么多风险为什么要使用动态内存

  1. 程序不知道自己需要使用给多少对象。动态循环创建多个对象。(容器,包括字符串string,都是使用动态内存管理自身的存储。)
  2. 程序不知道所需对象的准确类型。运行过程中创建对象。
  3. 程序需要在多个对象间共享数据。多个对象共享相同的状态。

申请内存

  • new无法为其分配的对象命名,而是返回一个指向该对象的指针。
int * p = new int;
  • 动态分配的对象执行默认初始化操作。内置类型和组合类型的对象是未定义的。类类型的对象使用默认构造函数进行初始化。

  • 可以动态分配const对象

const int * pci = new const int(1024);
  • 内存耗尽后,new表达式会失败。抛出bad_alloc异常。

释放内存

  • 使用delete来释放内存。将动态内存归还给系统。
  • delete指针必须指向动态分配的内存,或一个空指针。
  • 释放一块非new分配的内存,或者将相同的指针释放多次,行为是未定义。产生错误。
  • delete对象之后,指针指向的地址被释放了,指针无效。但是指针依旧保存着原先的地址。编程悬空指针

所有可变长度的容器,都有自己的allcator实现动态内存管理,不需要自己手动申请和释放内存。

new和delete的工作机理

  • 当我们使用new表达式分配内存时,实际上执行了三个步骤。
    1. 是调用名为operator new(或者 operator new[])的标准库函数。该函数分配一块原始的足够大的未构造内存
    2. 编译器运行构造函数构造对象
    3. 返回一个指向该对象的指针
  • 同样的,delete则执行两个步骤:
    1. 倒序执行对象的析构函数
    2. 调用 operator delete或(operator delete[])标准库函数释放内存空间

1 动态内存与智能指针

所以智能指针使用过来做动态内存管理的。普通的局部变量及其指针,不需要智能指针。智能指针是辅助new delete来管理动态内存的。智能指针就是为了解决一下问题。

两个问题

指针直接管理内存存在两个问题:

  1. 没有释放内存,导致内存泄露。
  2. 访问已经释放的内存,引起引用非法内存的指针。

智能指针

  • share_ptr 允许多个指针指向同一对象

  • unique_ptr 独占所指向的对象

  • weak_ptr伴随类。弱引用。指向shared_ptr所管理的对象。

头文件

#include<memory>

智能指针的基础操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tj9VdNAU-1691409480326)(image/2021-03-06-17-05-19.png)]

智能指针陷阱

智能指针本质上不是个指针,是对动态分配的内存的指针的管理,可以通过get函数得到指向动态内存的普通指针。

  • 不能使用相同的内置指针初始化多个智能指针
  • 不能delete get()返回的普通指针
  • 不能用get()返回的指针初始化或reset另一个智能指针。
  • 如果使用智能指针管理的资源不是new分配的内存,传递给他一个删除器。(new分配的动态内存对应的删除器是delete)
  • 当两个智能指针指向的对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。例如他们同时超出作用域,但是因为相互之间还保留一次引用计数,而没有办法销毁任何一个。

智能指针概述

  • 智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。
  • C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并可以通过get函数获得普通指针。

2 动态内存管理shared_ptr

shared_ptr的操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9FYeKMGe-1691409480327)(image/2021-03-06-17-06-13.png)]

make_shared申请内存

  • 最安全的使用动态内存的方法,调用make_shared标准库函数。在动态内存中分配一个对象,并初始化。返回shared_ptr.
    shared_ptr<int> pn = make_shared<int>(42);auto pn = make_shared<int>(42);

shared_ptr拷贝和引用计数

  • shared_ptr 都有一个关联的计数器。引用计数。每次copy一个shared_ptr,计数器都会递增。例如将它作为参数传递给一个函数,或者作为函数的返回值。
  • 一旦一个shared_ptr的计数器变为0.它就会自动释放自己所管理的内存。使用析构函数,销毁自身。
  • 所以当它为局部变量,并且退出局部作用域后,所有的指针变量自动销毁,其所对应的动态分配的内存对象的引用计数就回变为零,此时动态内存会自动销毁。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JaNHRU08-1691409480327)(image/2021-03-06-17-26-40.png)]

shared_ptr和new申请内存

  • shared_ptr可以使用make_shared创建对象。也可以使用new返回的指针来初始化智能指针。此时不需要delete来释放。
shared_ptr<int> p2(new int(42));
shared_ptr<int> p3 = new int{42};

shared_ptr的其他方法

  • shared_ptr的赋值、权限转移和清空。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GotonC7f-1691409480327)(image/2021-03-06-17-43-51.png)]

  • 不能试用get初始化另外一个智能指针,也不能用get为智能指针赋值。

  • 我们智能使用reset将智能指针指向其他的对象,不能将一个新的对象直接赋值给已经初始化的智能指针。reset函数会更新对象的引用计数,使其指向新的动态内存。

shared_ptr<int> = new int;
p = new int(23);//错误,不能直接赋值。
p.reset(new int(1024));//p指向一个新的对象。

智能指针和异常

  • 当触发异常后,局部变量在销毁的时候也会正常出发智能指针的销毁。

3 动态内存管理 unique_ptr

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yD0HMpKc-1691409480327)(image/2021-03-06-17-58-42.png)]

  • unique_ptr。某个时刻只能有一个unique_ptr只有一个给定的对象。当unique_ptr被销毁时,它所指向的对象也被销毁。
  • unique_ptr需要绑定到一个new返回的指针上。直接将指针置为空,指针指向的对象就会被释放。可以使用delete释放unique_ptr
  • unique_ptr不支持普通的拷贝和赋值操作。但是可以拷贝或赋值一个将要被销毁的unique_ptr.例如return unique_ptr。实现控制权转移
unique_ptr<int> clone(int p){return unique_ptr<int>(new int(p));
}//返回了一个动态内存的unique_ptr
  • 可以release放弃控制权,把控制权移交给新的智能指针。实现动态内存的控制权转移。
unique_ptr<int> m=new int;
unique_ptr<int> n = m.release();//m放弃所有权转移给n

4 动态内存管理weak_ptr

  • 不控制所指向对象生存期的智能指针。它指向一个由shared_ptr管理的对象。
  • 将weak_ptr绑定到shared_ptr上不会改变shared_ptr的引用计数。当shared_ptr被销毁,对象就回被释放。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mcpy6fHW-1691409480328)(image/2021-03-06-18-09-20.png)]

  • 使用shared_ptr初始化weak_ptr
shared_ptr<int> p = make_shared<int>(42);
weak_ptr<int>wp(p);
  • 由于weak_ptr指向的对象可能不存在,使用lock检查weak_ptr指向的对象是否存在。
if(shared_ptr<int>np = wp.lock()){}

总结

智能指针的操作包括:初始化、解引用、赋值,reset,get。其中赋值操作是一个特例,其他操作都一致:

  • shared_ptr能在两个智能指针之间赋值。reset可以将智能指针指向新的动态内存对象。不能直接将新的动态内存对象赋值给智能指针。
  • unique_ptr不能再两个智能指针之间赋值,但可以在即将销毁的智能指针之间赋值。不能直接将新的动态内存对象赋值给智能指针。reset可以将智能指针指向新的动态内存对象。
  • weak_ptr能将shared_ptr和weak_ptr赋值给weak_ptr;不能将新的动态内存对象赋值给智能指针。reset会将指针置为空。

5 动态数组

初始化动态内存数组

int * pia = new int[10];//默认初始化
int * pia2= new int[10]();//值初始化
int * pia3 = new int[10]{1,2,3,4,54,6,7,8,6,5};
  • 申请一个大小为0的动态数组是合法的。直接定义一个大小为0的数组是不合法的。

释放动态数组

delete [] pa;

智能指针和动态数组

  • unique_ptr动态数组版本
unique_ptr<int[]> up(new int[10]);
up.release();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jd3LdgOn-1691409480328)(image/2021-03-06-22-29-22.png)]

5 allocator动态内存

简介

  • new将动态内存分配和对象构造组合在了一起。也就是说,分配动态内存的时候,必须确定这块内存上的对象。
  • allocator能够将内存分配和对象构造分离。
  • 在头文件<memory>

操作

  • 主要操作如下
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DNwuhGXA-1691409480328)(image/2021-03-06-22-34-11.png)]
allcator<string> alloc;
auto const p = alloc.allocate(n);//分配n个未初始化的string
auto q = p;
alloc.construct(q++);//*q为空字符串
alloc.construct(q++,10,'c');//*q为cccc
alloc.deallocate(p,n)

allocator算法

  • 标准库为allocator定义了两个伴随算法。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NENTBvoT-1691409480329)(image/2021-03-06-22-42-35.png)]

allocator 原理

  • STL的分配器用于封装STL容器在内存管理上的底层细节。在C++中,其内存配置和释放如下:

  • new运算分两个阶段:

    1. 调用::operator new配置内存;
    2. 调用对象构造函数构造对象内容
  • delete运算分两个阶段:

    1. 调用对象析构函数;
    2. 调用::operator delete释放内存
  • 为了精密分工,STL allocator将两个阶段操作区分开来:内存配置有alloc::allocate()负责,内存释放由alloc::deallocate()负责;对象构造由::construct()负责,对象析构由::destroy()负责。

  • 同时为了提升内存管理的效率,减少申请小内存造成的内存碎片问题,SGI STL采用了两级配置器,当分配的空间大小超过128B时,会使用第一级空间配置器;当分配的空间大小小于128B时,将使用第二级空间配置器。第一级空间配置器直接使用malloc()、realloc()、free()函数进行内存空间的分配和释放,而第二级空间配置器采用了内存池技术,通过空闲链表来管理内存。

6 实例——allocator的简单实现

#ifndef __JJALLOC__
#define __JJALLOC__
#endif
#include<new> // for placement new
#include<iostream> //for cerr
#include<cstddef>  //for ptrdiff_t
#include<cstdlib> // for exit()
#include<climits> // for UINT_MAX
namespace my{// 申请内存空间。调用operator new 。// T*参数是为了注册模板类型Ttemplate<class T>inline T* _allocate(ptrdiff_t size, T*){//set_new_handler(0);T* tmp = (T*)(::operator new)((size_t)(size * sizeof(T)));if (tmp == 0){std::cerr << "out of memory" << std::endl;}return tmp;}// 释放内存空间。调用operator deletetemplate<class T>inline void _deallocate(T* buffer){::operator delete(buffer);}// 创建内存对象。调用placement newtemplate<class T1,class T2>inline void _construct(T1 *p, const T2 &value){new (p)T1(value);}// 通过查询了解到这个操作叫做placement new,就是在指针p所指向的内存空间创建一个T1类型的对象,但是对象的内容是从T2类型的对象转换过来的(调用了T1的构造函数,T1::T1(value))。// 就是在已有空间的基础上重新调整分配的空间,类似于realloc函数。这个操作就是把已有的空间当成一个缓冲区来使用,这样子就减少了分配空间所耗费的时间,因为直接用new操作符分配内存的话,在堆中查找足够大的剩余空间速度是比较慢的。// 释放内存对象。调用析构函数。template<class T>inline void _destroy(T* ptr){ptr->~T();}template <class T>class allocate{public:typedef T value_type;typedef T* pointer;typedef const T* const_pointer;typedef T& reference;typedef const T& const_reference;typedef size_t size_type;typedef ptrdiff_t difference_type;// template<class U>// struct rebind{//     typedef allocator<U> other;// };pointer alloc(size_type n, const void * hint = 0){return _allocate((difference_type)n, (pointer)0);}void deallocate(pointer p, size_type n){_deallocate(p);}void construct(pointer p, const_reference value){return _construct(p, value);}void destroy(pointer p){_destroy(p);}pointer address(reference x){return (pointer)&x;}pointer const_address(const_reference x){return (const_pointer)&x;}size_type max_size()const{return (size_type)(UINT_MAX / sizeof(T));}};
}
#include<iostream>
using namespace std;
int main(){my::allocate<int> al;int * ptr = al.alloc(10);cout<<"alloc:"<<*ptr<<"\t"<<*(ptr+1)<<endl;al.construct(ptr,123);cout<<"construct:"<<*ptr<<"\t"<<*(ptr+1)<<endl;al.destroy(ptr);cout<<"destroy:"<<*ptr<<"\t"<<*(ptr+1)<<endl;al.deallocate(ptr,100);cout<<"deallocate:"<<*ptr<<"\t"<<*(ptr+1)<<endl;int size = al.max_size();cout<<"size:"<<size<<endl;int* b=new int[3];cout<<*b<<endl;new (b)int(999);cout<<*b<<endl;cout<<*(b+1)<<endl;
}

7 实例——shared_ptr的简单实现

template <typename T>
class SmartPtr
{
private:T *ptr; //底层真实的指针int *use_count; //保存当前对象被多少指针引用计数public:SmartPtr(T *p); //SmartPtr<int>p(new int(2));SmartPtr(const SmartPtr<T> &orig); //SmartPtr<int>q(p);SmartPtr<T> &operator=(const SmartPtr<T> &rhs); //q=p~SmartPtr();T operator*(); //为了能把智能指针当成普通指针操作定义解引用操作T *operator->(); //定义取成员操作T *operator+(int i); //定义指针加一个常数int operator-(SmartPtr<T> &t1, SmartPtr<T> &t2); //定义两个指针相减void getcount() { return *use_count }
};int SmartPtr<T>::operator-(SmartPtr<T> &t1, SmartPtr<T> &t2) { return t1.ptr - t2.ptr; }template <typename T>
SmartPtr<T>::SmartPtr(T *p)
{ptr = p;try{use_count = new int(1);}catch (...){delete ptr; //申请失败释放真实指针和引用计数的内存ptr = nullptr;delete use_count;use_count = nullptr;}
}
template <typename T>
SmartPtr<T>::SmartPtr(const SmartPtr<T> &orig) //复制构造函数{use_count = orig.use_count; //引用计数保存在一块内存,所有的SmarPtr对象的引用计数都指向这里this->ptr = orig.ptr;++(*use_count); //当前对象的引用计数加1
}
template <typename T>
SmartPtr<T> &SmartPtr<T>::operator=(const SmartPtr<T> &rhs)
{//重载=运算符,例如SmartPtr<int>p,q; p=q;这个语句中,首先给q指向的对象的引用计数加1,因为p重新指向了q所指的对象,所以p需要先给原来的对象的引用计数减1,如果减一后为0,先释放掉p原来指向的内存,然后讲q指向的对象的引用计数加1后赋值给p++*(rhs.use_count);if ((--*(use_count)) == 0){delete ptr;ptr = nullptr;delete use_count;use_count = nullptr;}ptr = rhs.ptr;*use_count = *(rhs.use_count);return *this;
}
template <typename T>
SmartPtr<T>::~SmartPtr()
{getcount();if (--(*use_count) == 0) //SmartPtr的对象会在其生命周期结束的时候调用其析构函数,在析构函数中检测当前对象的引用计数是不是只有正在结束生命周期的这个SmartPtr引用,如果是,就释放掉,如果不是,就还有其他的SmartPtr引用当前对象,就等待其他的SmartPtr对象在其生命周期结束的时候调用析构函数释放掉{getcount();delete ptr;ptr = nullptr;delete use_count;use_count = nullptr;}
}
template <typename T>
T SmartPtr<T>::operator*()
{return *ptr;
}
template <typename T>
T *SmartPtr<T>::operator->()
{return ptr;
}
template <typename T>
T *SmartPtr<T>::operator+(int i)
{T *temp = ptr + i;return temp;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/31382.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

putty使用记录

在官网下载并安装putty 一、SSH 二、FTP open 192.168.1.118 put -r C:\Users\Administrator\Desktop\test /opt/lanren312/test # 上传&#xff08;文件夹&#xff09; get -r /opt/lanren312/test C:\Users\Administrator\Desktop\test2 # 下载&#xff08;文件夹&#xff…

gray_dilation_rect

灰度图膨胀。图像的宽度和高度不变。 下面创建一个3*3的灰度图&#xff0c;左上角为1&#xff0c;右下角为3&#xff0c;其它为0。 byte[] barr new byte[9]; barr[0] 1; barr[8] 3; var img WHCSHalCon.Base.CreateByteImage(barr,…

Android Studio实现图形验证码

源代码 源代码MainActivity 效果图32行需要修改&#xff0c;不修改会报错&#xff1a;需要常量表达式&#xff0c;我的代码已修改 点击后 MainActivity import static com.example.graphicverificationcode.RxCaptcha.TYPE.NUMBER;import android.annotation.SuppressLint; …

获取 Android 的 SHA1 值

1、调试版&#xff0c;可以直接在 Android studio 中的 gradle 中查看。也可以用下面方法进行 前提要先确定签名文件所在的路径&#xff1a;调试版默认使用的签名文件是debug.keystore&#xff0c;文件处于 C 盘用户目录下的.android文件夹下。打开命令行工具&#xff0c; 1、…

Uniapp使用腾讯地图并进行标点创建和设置保姆教程

使用Uniapp内置地图 首先我们需要创建一个uniapp项目 首先我们需要创建一个uniapp项目 我们在HBuilder左上角点击文件新建创建一个项目 然后下面这张图的话就是uniapp创建项目过程当中需要注意的一些点和具体的操作 然后我们创建完项目之后进入到项目pages文件夹下&#xff…

Android 13 Launcher界面——移除Launcher的删除和卸载功能

目录 一.背景 二.将卸载功能进行屏蔽 三.将移除功能屏蔽 四.将Remove按钮与Uninstall按钮屏蔽

web-csrf

目录 CSRF与XSS的区别&#xff1a; get请求 原理&#xff1a; pikachu为例 post请求 pikachu为例 CSRF与XSS的区别&#xff1a; CSRF是借用户的权限完成攻击&#xff0c;攻击者并没有拿到用户的权限&#xff0c;而XSS是直接盗取到了用户的权限 get请求 原理&#xff1a;…

新法!《个人信息保护合规审计管理办法(征求意见稿)》解读

8月3日&#xff0c;依据《中华人民共和国个人信息保护法》等法律法规&#xff0c;国家互联网信息办公室起草了《个人信息保护合规审计管理办法&#xff08;征求意见稿&#xff09;》&#xff08;下文简称“办法”&#xff09;&#xff0c;并向社会公开征求意见。 据悉&#xff…

交互流程图设计软件都有哪些?

交互流程图是设计行业信息流、观点流或组件流的图形代表。但是市场上应该如何选择各种交互流程图软件呢&#xff1f;如何使用高质量的交互流程图软件来绘制高端氛围的高档流程图&#xff1f;今天&#xff0c;小边给您带来了十个超级实用的交互流程图软件&#xff0c;我希望能帮…

HCIA 路由器工作原理 及其 静态路由配置

目录 1、路由器工作原理 2、获取未知网段的方法&#xff1a; 3、静态路由 1&#xff09;写法&#xff1a; 2&#xff09;扩展配置 a、环回接口 配置命令&#xff1a; 环回接口的作用&#xff1a; b、手工汇总 手工汇总作用&#xff1a; c、路由黑洞 d、缺省路由 配置…

竞赛项目 疫情数据分析与3D可视化 - python 大数据

文章目录 0 前言1 课题背景2 实现效果3 设计原理4 部分代码5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 大数据全国疫情数据分析与3D可视化 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff0…

【位操作符的几种题型】

位操作符的几种题型 目录 题型一&#xff1a;寻找“单身狗”。 题型二&#xff1a;计算一个数在二进制中1的个数 题型三&#xff1a;不允许创建临时变量&#xff0c;交换两个整数的内容 题型一&#xff1a;寻找“单身狗”。 1.1题目解析 在一个整型数组中&#xff0c;只有…

opencv基础57-模板匹配cv2.matchTemplate()->(目标检测、图像识别、特征提取)

OpenCV 提供了模板匹配&#xff08;Template Matching&#xff09;的功能&#xff0c;它允许你在图像中寻找特定模板&#xff08;小图像&#xff09;在目标图像中的匹配位置。模板匹配在计算机视觉中用于目标检测、图像识别、特征提取等领域。 以下是 OpenCV 中使用模板匹配的基…

ClickHouse(十三):Clickhouse MergeTree系列表引擎 - ReplicingMergeTree

进入正文前&#xff0c;感谢宝子们订阅专题、点赞、评论、收藏&#xff01;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; &#x1f3e1;个人主页&#xff1a;含各种IT体系技术&#xff0c;IT贫道_Apache Doris,大数据OLAP体系技术栈,Kerberos安全认证-CSDN博客 &…

【Rust招聘】【理想汽车】rust高级开发工程师(35K-50K)

职位描述&#xff1a; 1、参与设计开发下一代车联网边缘计算引擎的开发。 2、深入发掘和分析业务需求&#xff0c;提出技术及产品改进建议&#xff0c;撰写技术方案和系统设计。 3、把握系统设计复杂度&#xff0c;制定计划&#xff0c;及时沟通、高效执行。 4、勤于梳理工作中…

vb6的dictionary类

vb6的dictionary类 vb6 原生容器, 除了array还有collection, 没有原生的dictionary和set类, 在microsoft script runtime库(scrrun.dll) 中有一个dictionary类, 功能还算强大, 可以较好处理key-value这样的字典, 我甚至觉得用它也完全可以代替原生的collection类. microsoft sc…

深入探索二叉树算法:理解、构建和应用C语言

引言 二叉树是计算机科学中的一种重要数据结构&#xff0c;它在各种算法和应用中都扮演着重要角色。本篇博客将带您深入探索二叉树的世界&#xff0c;从基本概念到高级应用&#xff0c;逐步展开二叉树的奥秘&#xff0c;助您更好地理解、构建和应用二叉树算法。 什么是二叉树…

Nginx反向代理配置+负载均衡集群部署

文章目录 负载均衡反向代理基础环境部署&#xff1a;什么是代理实验环境图流量过程 环境部署准备两台Web服务器安装Nginx准备页面内容添加主机名 代理服务器配置 修改windos hosts文件测试&#xff1a;终端浏览器 负载均衡反向代理基础环境部署&#xff1a; 什么是代理 正向代…

2023-08-09力扣每日一题

链接&#xff1a; 1281. 整数的各位积和之差 题意&#xff1a; 十进制每一位的积减去每一位的和 解&#xff1a; 十进制位处理 实际代码&#xff1a; #include<iostream> using namespace std; int subtractProductAndSum(int n) {int t11,t20;while(n){t1*n%10;t…

爬虫学习记录(持续更新)

一、问题记录 1.使用webdriver报错AttributeError: str object has no attribute capabilities 解决&#xff1a;目前使用的selenium版本是4.11.2&#xff0c;可以不必设置driver.exe的路径&#xff0c;selenium可以自己处理浏览器和驱动程序&#xff0c;因此&#xff0c;使用…