【重学C++】03 | 手撸C++智能指针实战教程

文章首发

【重学C++】03 | 手撸C++智能指针实战教程

前言

大家好,今天是【重学C++】的第三讲,书接上回,第二讲《02 脱离指针陷阱:深入浅出 C++ 智能指针》介绍了C++智能指针的一些使用方法和基本原理。今天,我们自己动手,从0到1实现一下自己的unique_ptrshared_ptr

回顾

智能指针的基本原理是基于RAII设计理论,自动回收内存资源,从根本上避免内存泄漏。在第一讲《01 C++ 如何进行内存资源管理?》介绍RAII的时候,就已经给了一个用于封装int类型指针,实现自动回收资源的代码实例:

class AutoIntPtr {
public:
    AutoIntPtr(int* p = nullptr) : ptr(p) {}
    ~AutoIntPtr() { delete ptr; }

    int& operator*() const { return *ptr; }
    int* operator->() const { return ptr; }

private:
    int* ptr;
};

我们从这个示例出发,一步步完善我们自己的智能指针。

模版化

这个类有个明显的问题:只能适用于int类指针。所以我们第一步要做的,就是把它改造成一个类模版,让这个类适用于任何类型的指针资源。
code show time

template <typename T>
class smart_ptr {
public:
	explicit smart_ptr(T* ptr = nullptr): ptr_(ptr) {}
	~smart_ptr() {
		delete ptr_;
	}
	T& operator*() const { return *ptr_; }
	T* operator->() const { return ptr_; }
private:
	T* ptr_;
}

我给我们的智能指针类用了一个更抽象,更切合的类名:smart_ptr

AutoIntPtr相比,我们把smart_ptr设计成一个类模版,原来代码中的int改成模版参数T,非常简单。使用时也只要把AutoIntPtr(new int(9)) 改成smart_ptr<int>(new int(9))即可。

另外,有一点值得注意,smart_ptr的构造函数使用了explicitexplicit关键字主要用于防止隐式的类型转换。代码中,如果原生指针隐式地转换为智能指针类型可能会导致一些潜在的问题。至于会有什么问题,你那聪明的小脑瓜看完下面的代码肯定能理解了:

void foo(smart_ptr<int> int_ptr) {
    // ...
}

int main() {
    int* raw_ptr = new int(42);
    foo(raw_ptr);  // 隐式转换为 smart_ptr<int>
    std::cout << *raw_ptr << std::endl;   // error: raw_ptr已经被回收了
    // ...
}

假设我们没有为smart_ptr构造函数加上explicit,原生指针raw_ptr在传给foo函数后,会被隐形转换为smart_ptr<int>foo函数调用结束后,栖构入参的smart_ptr<int>时会把raw_ptr给回收掉了,所以后续对raw_ptr的调用都会失败。

拷贝还是移动?

当前我们没有为smart_ptr自定义拷贝构造函数/移动构造函数,C++会为smart_ptr生成默认的拷贝/移动构造函数。默认的拷贝/移动构造函数逻辑很简单:把每个成员变量拷贝/移动到目标对象中。

按当前smart_ptr的实现,我们假设有以下代码:

smart_ptr<int> ptr1{new int(10)};
smart_ptr<int> ptr2 = ptr1;

这段代码在编译时不会出错,问题在运行时才会暴露出来:第二行将ptr1管理的指针复制给了ptr2,所以会重复释放内存,导致程序奔溃。

为了避免同一块内存被重复释放。解决办法也很简单:

  1. 独占资源所有权,每时每刻一个内存对象(资源)只能有一个smart_ptr占有它。
  2. 一个内存对象(资源)只有在最后一个拥有它的smart_ptr析构时才会进行资源回收。

独占所有权 - unique_smart_ptr

独占资源的所有权,并不是指禁用掉smart_ptr的拷贝/移动函数(当然这也是一种简单的避免重复释放内存的方法)。而是smart_ptr在拷贝时,代表资源对象的指针不是复制到另外一个smart_ptr,而是"移动"到新smart_ptr。移动后,原来的smart_ptr.ptr_ == nullptr, 这样就完成了资源所有权的转移。
这也是C++ unique_ptr的基本行为。我们在这里先把它命名为unique_smart_ptr,代码完整实现如下:

template <typename T>
class unique_smart_ptr {
public:
	explicit unique_smart_ptr(T* ptr = nullptr): ptr_(ptr) {}

	~unique_smart_ptr() {
		delete ptr_;
	}

	// 1. 自定义移动构造函数
	unique_smart_ptr(unique_smart_ptr&& other) {
		// 1.1 把other.ptr_ 赋值到this->ptr_
		ptr_ = other.ptr_;
		// 1.2 把other.ptr_指为nullptr,other不再拥有资源指针
		other.ptr_ = nullptr;
	}

	// 2. 自定义赋值行为
	unique_smart_ptr& operator = (unique_smart_ptr rhs) {
		// 2.1 交换rhs.ptr_和this->ptr_
		std::swap(rhs.ptr_, this->ptr_);
		return *this;
	}

T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }

private:
	T* ptr_;
};

自定义移动构造函数。在移动构造函数中,我们先是接管了other.ptr_指向的资源对象,然后把otherptr_置为nullptr,这样在other析构时就不会错误释放资源内存。

同时,根据C++的规则,手动提供移动构造函数后,就会自动禁用拷贝构造函数。也就是我们能得到以下效果:

unique_smart_ptr<int> ptr1{new int(10)};
unique_smart_ptr<int> ptr2 = ptr1; // error
unique_smart_ptr<int> ptr3 = std::move(ptr1); // ok

unique_smart_ptr<int> ptr4{ptr1} // error
unique_smart_ptr<int> ptr5{std::move(ptr1)} // ok

自定义赋值函数。在赋值函数中,我们使用std::swap交换了 rhs.ptr_this->ptr_,注意,这里不能简单的将rhs.ptr_设置为nullptr,因为this->ptr_可能有指向一个堆对象,该对象需要转给rhs,在赋值函数调用结束,rhs析构时顺便释放掉。避免内存泄漏。

注意赋值函数的入参rhs的类型是unique_smart_ptr而不是unique_smart_ptr&&,这样创建rhs使用移动构造函数还是拷贝构造函数完全取决于unique_smart_ptr的定义。因为unique_smart_ptr当前只保留了移动构造函数,所以rhs是通过移动构造函数创建的。
Pasted image 20230426083308

多个智能指针共享对象 - shared_smart_ptr

学过第二讲的shared_ptr, 我们知道它是利用计数引用的方式,实现了多个智能指针共享同一个对象。当最后一个持有对象的智能指针析构时,计数器减为0,这个时候才会回收资源对象。
image.png

我们先给出shared_smart_ptr的类定义

template <typename T>
class shared_smart_ptr {
public:
	// 构造函数
	explicit shared_smart_ptr(T* ptr = nullptr)
	// 析构函数
	~shared_smart_ptr()
	// 移动构造函数
	shared_smart_ptr(shared_smart_ptr&& other)
	// 拷贝构造函数
	shared_smart_ptr(const shared_smart_ptr& other)
	// 赋值函数
	shared_smart_ptr& operator = (shared_smart_ptr rhs)
	// 返回当前引用次数
	int use_count() const { return *count_; }

	T& operator*() const { return *ptr_; }
	T* operator->() const { return ptr_; }

private:
	T* ptr_;
	int* count_;
}

暂时不考虑多线程并发安全的问题,我们简单在堆上创建一个int类型的计数器count_。下面详细展开各个函数的实现。

为了避免对count_的重复删除,我们保持:只有当ptr_ != nullptr时,才对count_进行赋值。

构造函数

同样的,使用explicit避免隐式转换。除了赋值ptr_, 还需要在堆上创建一个计数器。

explicit shared_smart_ptr(T* ptr = nullptr){
	ptr_ = ptr;
	if (ptr_) {
		count_ = new int(1);
	}
}

析构函数

在析构函数中,需要根据计数器的引用数判断是否需要回收对象。

~shared_smart_ptr() {
	// ptr_为nullptr,不需要做任何处理
	if (ptr_) {
		return;
	}
	// 计数器减一
	--(*count_);
	// 计数器减为0,回收对象
	if (*count_ == 0) {
		delete ptr_;
		delete count_;
		return;
	}

}

移动构造函数

添加对count_的处理

shared_smart_ptr(shared_smart_ptr&& other) {
	ptr_ = other.ptr_;
	count_ = other.count_;

	other.ptr_ = nullptr;
	other.count_ = nullptr;
}

赋值构造函数

添加交换count_

shared_smart_ptr& operator = (shared_smart_ptr rhs) {
	std::swap(rhs.ptr_, this->ptr_);
	std::swap(rhs.count_, this->count_);
	return *this;
}

拷贝构造函数

对于shared_smart_ptr,我们需要手动支持拷贝构造函数。主要处理逻辑是赋值ptr_和增加计数器的引用数。

shared_smart_ptr(const shared_smart_ptr& other) {
	ptr_ = other.ptr_;
	count_ = other.count_;

	if (ptr_) {
		(*count_)++;
	}
}

这样,我们就实现了一个自己的共享智能指针,贴一下完整代码

template <typename T>
class shared_smart_ptr {
public:
	explicit shared_smart_ptr(T* ptr = nullptr){
		ptr_ = ptr;
		if (ptr_) {
			count_ = new int(1);
		}
	}

	~shared_smart_ptr() {
		// ptr_为nullptr,不需要做任何处理
		if (ptr_ == nullptr) {
			return;
		}

		// 计数器减一
		--(*count_);

		// 计数器减为0,回收对象
		if (*count_ == 0) {
			delete ptr_;
			delete count_;
		}
	}

	shared_smart_ptr(shared_smart_ptr&& other) {
		ptr_ = other.ptr_;
		count_ = other.count_;
		
		other.ptr_ = nullptr;
		other.count_ = nullptr;
	}

	shared_smart_ptr(const shared_smart_ptr& other) {
		ptr_ = other.ptr_;
		count_ = other.count_;
		if (ptr_) {
			(*count_)++;
		}
	}

	shared_smart_ptr& operator = (shared_smart_ptr rhs) {
		std::swap(rhs.ptr_, this->ptr_);
		std::swap(rhs.count_, this->count_);
		return *this;
	}

	int use_count() const { return *count_; };

	T& operator*() const { return *ptr_; };

	T* operator->() const { return ptr_; };

private:
	T* ptr_;
	int* count_;
};

使用下面代码进行验证:

int main(int argc, const char** argv) {
	shared_smart_ptr<int> ptr1(new int(1));
	std::cout << "[初始化ptr1] use count of ptr1: " << ptr1.use_count() << std::endl;
	{
		// 赋值使用拷贝构造函数
		shared_smart_ptr<int> ptr2 = ptr1;
		std::cout << "[使用拷贝构造函数将ptr1赋值给ptr2] use count of ptr1: " << ptr1.use_count() << std::endl;

		// 赋值使用移动构造函数
		shared_smart_ptr<int> ptr3 = std::move(ptr2);
		std::cout << "[使用移动构造函数将ptr2赋值给ptr3] use count of ptr1: " << ptr1.use_count() << std::endl;
	}
	std::cout << "[ptr2和ptr3析构后] use count of ptr1: " << ptr1.use_count() << std::endl;
}

运行结果:

[初始化ptr1] use count of ptr1: 1
[使用拷贝构造函数将ptr1赋值给ptr2] use count of ptr1: 2
[使用移动构造函数将ptr2赋值给ptr3] use count of ptr1: 2
[ptr2和ptr3析构后] use count of ptr1: 1

总结

这一讲我们从AutoIntPtr出发,先是将类进行模版化,使其能够管理任何类型的指针对象,并给该类起了一个更抽象、更贴切的名称——smart_ptr

接着围绕着「如何正确释放资源对象指针」的问题,一步步手撸了两个智能指针 ——unique_smart_ptrshared_smart_ptr。相信大家现在对智能指针有一个较为深入的理解了。

本文转载于网络 如有侵权请联系删除

相关文章

  • 优Tech分享 | 腾讯优图在弱监督目标定位的研究及应用

    计算机视觉技术让AI拥有了“眼睛”,而深度学习的出现让这双“眼睛”的算力增强,能够识别并对它看到的图像特征作出反应并获取对应信息。而其中,目标检测(ObjectDetection)作为图像理解中的重要一环,适用于包含多个对象的图片,需要对图像中的目标/物体进行定位和识别分类,从而确认它们的位置和大小,这也是计算机视觉领域的核心问题之一。全“手工”的强监督目标检测方法费时且需耗费较大的标注成本,遇到任务变化或演变更是十分不友好,而弱监督学习则有望解决这些这些问题。腾讯优图实验室高级研究员noahpan以「弱监督目标定位的研究及应用」为主题,结合腾讯优图实验室在弱监督目标定位的研究进展、成果以及相关思考进行了分享。01从全监督到弱监督目标定位的局限性弱监督目标定位是指仅利用图像层面的类别标签学习图像中目标的位置,相比于全监督来说,弱监督目标定位可以节约很大的标注成本。相比需要标注图像级别的分类标签,标注bounding box level的图像标注需要大概10倍的时间。因此,仅利用图像层面的类别标签去学习目标位置,可以极大程度节约标注成本。目前弱监督目标定位所关注的焦点更多在于一张图片包含

  • 23 个非常实用的 Shell 拿来就用脚本实例

    点击上方“机器学习与生成对抗网络”,关注星标 获取有趣、好玩的前沿干货!文章来自:博智互联编辑杰哥的IT之旅复制shell脚本是帮助程序员和系统管理员完成费时费力的枯燥工作的利器,是与计算机交互并管理文件和系统操作的有效方式。区区几行代码,就可以让计算机接近按照你的意图行事。为大家整理了23个实例,通过23个实战经典脚本实例,展示了shell脚本编程的实用技术和常见工具用法。大家只需根据自己的需求,将文中这些常见任务和可移植自动化脚本推广应用到其他类似问题上,能解决那些三天两头碰上的麻烦事。检测两台服务器指定目录下的文件一致性#!/bin/bash ##################################### #检测两台服务器指定目录下的文件一致性 ##################################### #通过对比两台服务器上文件的md5值,达到检测一致性的目的 dir=/data/web b_ip=192.168.88.10 #将指定目录下的文件全部遍历出来并作为md5sum命令的参数,进而得到所有文件的md5值,并写入到指定文件中 find$dir

  • 圣杯布局的原理

    圣杯布局的起源InSearchoftheHolyGrail圣杯布局解决的问题两边定宽,中间自适应的三栏布局,中间栏要放在文档流前面以优先渲染。 圣杯布局的原理HTML代码<divclass="content"> <divclass="centercol"> </div> <divclass="leftcol"> </div> <divclass="rightcol"> </div> </div>复制CSS代码第一步:定义容器content的样式padding:0100px,以及center,left,right的公共样式,同时定义每个容器的颜色和宽度,左右固定一百,中间100%; .content{padding:0100px;} .col{ float:left; height:200px; position:relative; } .left,.right{ width:100px; } .left{ bac

  • 45 张图深度解析 Netty 架构与原理

    接下来我们会学习一个Netty系列教程,Netty系列由「架构与原理」,「源码」,「架构」三部分组成,今天我们先来看看第一部分:Netty架构与原理初探,大纲如下:前言1.Netty基础1.4.1.缓冲区(Buffer)1.4.2.通道(Channel)1.4.3.选择器(Selector)1.1.Netty是什么1.2.Netty的应用场景1.3.Java中的网络IO模型1.4.JavaNIOAPI简单回顾1.5.零拷贝技术2.Netty的架构与原理2.2.1.单Reactor单线程模式2.2.2.单Reactor多线程模式2.2.3.主从Reactor多线程模式2.1.为什么要制造Netty2.2.几种Reactor线程模式2.3.Netty的模样2.4.基于Netty的TCPServer/Client案例2.5.Netty的Handler组件2.6.Netty的Pipeline组件2.7.Netty的EventLoopGroup组件2.8.Netty的TaskQueue2.9.Netty的Future和Promise3.结束语前言读者在阅读本文前最好有Java的IO编程经验(知道

  • 【80期】说出Java创建线程的三种方式及对比

    阅读本文大概需要4分钟。来自:cnblogs.com/songshu120/p/7966314.html一、Java中创建线程主要有三种方式:1、继承Thread类创建线程类定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。创建Thread子类的实例,即创建了线程对象。调用线程对象的start()方法来启动该线程。示例代码为:packagecom.thread; publicclassFirstThreadTestextendsThread{ inti=0; //重写run方法,run方法的方法体就是现场执行体 publicvoidrun() { for(;i<100;i++){ System.out.println(getName()+""+i); } } publicstaticvoidmain(String[]args) { for(inti=0;i<100;i++) { System.out.println(Thread.currentThread().getName

  • Linux下docker安装教程(设置使用权限)

    Docker的介绍Docker的目标是实现轻量级的操作系统虚拟化的解决方案。Docker的基础是Linux容器(LXC)等技术,(LXC系统提供工具来管理容器,具有先进的网络和存储支持,还有最小容器操作系统模版的广泛选择)。在LXC的基础上Docker进行了进一步的封装,用户不需要去关心容器的管理,操作更简单。就像操作一个快速轻量的虚拟机一样简单。现在docker是云计算计算发展的重要一环了,各大云服务商都提供了Docker镜像帮助大家快速在LinuxCentos环境下安装Docker。例如腾讯云Docker快速安装镜像(省却手工安装的麻烦)目前最新版本的docker19.03支持nvidia显卡与容器的无缝对接,从而摆脱了对nvidia-docker的依赖。因此毫不犹豫安装19.03版本的docker,安装教程可参考官方教程Centos:GetDockerEngine-CommunityforCentOS或Ubuntu:GetDockerEngine-CommunityforUbuntu,安装好之后还要解决一个问题就是如何才能使非root用户拥有docker使用权。用户其实是通过/v

  • asp.net core 系列之允许跨域访问2之测试跨域(Enable Cross-Origin Requests:CORS)

    这一节主要讲如何测试跨域问题你可以直接在官网下载示例代码,也可以自己写,我这里直接使用官网样例进行演示样例代码下载:Cors一.提供服务方,这里使用的是API1.创建一个API项目。或者直接下载样例代码2.像之前讲的那样设置允许CORS,例如:publicvoidConfigure(IApplicationBuilderapp,IHostingEnvironmentenv) { if(env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } //ShowsUseCorswithCorsPolicyBuilder. app.UseCors(builder=> { builder.WithOrigins("http://example.com", "http://www.contoso.com", "https://localhost:44375", "https://localhost:5001"

  • 腾讯云学生机是什么?有什么用?有什么优势?

    现在云服务商对学生都是很优惠的,腾讯云学生服务器腾讯云也推出了9.9元购买云服务器的优惠活动,是一款固定的优惠套餐,包含特价云服务器、域名(加钱可选)、免费对象存储空间(6个月),但是好多用户却不知道在哪里申请,需要什么条件,流程是怎么样的,下面给大家做个介绍blob.jpg学生服务器官网链接腾讯云学生服务器官网:https://cloud.tencent.com/act/campus套餐包含特价云服务器、域名(可选)、50G免费对象存储空间(6个月);每日限量100个,每个用户限购1个,并赠送2次体验价续费机会,优惠续费需在本页面进行。活动链接blob.jpgblob.jpg活动对象面向腾讯云官网通过个人认证的在校大学生。如果你是学生身份的话,速速注册腾讯云领取学生服务器。【注】企业用户、协作者、后付费用户暂不支持参与此活动。活动规则特别注意:符合条件的用户可购买腾讯云服务器校园优惠套餐,套餐内包含云服务器,对象存储,域名(可选),可选产品需加价购买同一个身份证号码、手机号对应的多个账号仅限一个帐号购买特别注意:本套餐每日限量100个,每个用户限购1个,此前购买旧版学生套餐的用户也可

  • 第十三章 支持向量机

    该系列文章为,观看“吴恩达机器学习”系列视频的学习笔记。虽然每个视频都很简单,但不得不说每一句都非常的简洁扼要,浅显易懂。非常适合我这样的小白入门。 本章含盖13.1优化目标13.2直观上对大间隔的理解13.3大间隔分类器的数学原理13.4核函数113.5核函数213.6使用SVM13.2直观上对大间隔的理解有时候,“支持向量机”又被称为“大间距分类器”事实上,如果你有一个正样本y=1,则其实我们仅仅要求θ^T*X大于等于0,就能将该样本恰当分出,这是因为如果θ^T*X>0大的话,我们的模型代价函数值为0,类似地,如果你有一个负样本,则仅需要θ^T*X<=0就会将负例正确分离,但是,支持向量机的要求更高,不仅仅要能正确分开输入的样本,即不仅仅要求θ^T*X>0,我们需要的是比0值大很多,比如大于等于1,我也想这个比0小很多,比如我希望它小于等于-1,这就相当于在支持向量机中嵌入了一个额外的安全因子,或者说安全的间距因子。当然,逻辑回归做了类似的事情。但是让我们看一下,在支持向量机中,这个因子会导致什么结果。具体而言,我接下来会考虑一个特例。我们将这个常数C设置成一个非

  • 我们如何衡量一个微服务实施的成功

    本次介绍的案例来自于我曾经服务过的客户R,到今天已经5年整了。2013年的国庆后,我加入了客户R的其中一个产品团队,这个团队有三个项目:一个项目做日常维护工作(BAU),这是一个长期项目。一个项目开发一些新的功能。另外一个项目就是将现有的Java遗留系统进行改造,把这个Java应用的一部分功能从ESB和内部调用的方式改成用Sinatra(Ruby的一个RestfulAPI框架)做的HTTP外部调用。当时我还不知道我们做的东西就是微服务,只是觉得通过自动化测试和持续交付的方式把应用进行了低风险的解耦。降低了系统的复杂性,减少了需要维护代码,也使得在这个代码库上工作的其它团队不受阻碍。同时减少了生产环境的故障和发布风险。我在这个项目上工作了8个月,完成了“一块功能”的拆分。当时我们并没有一个独立的Ops团队,所有的运维相关工作都是团队内自己完成的,那时候我们也不区分开发、测试、运维。只是不同的人去认领不同的任务,不会的就现学现用,或者请教Ops团队。这就是我最早接触的DevOps:一个全功能的端到端产品团队。在2014年的时候我们采用Docker进行部署,Docker在当时是个很新颖的东西

  • 调度队列的优先堆实现应用场景模拟应用分析代码实现

    应用场景模拟考虑优先堆的一种应用场景——按优先级的任务调度队列:每个任务有一个优先级和唯一标号,该调度队列需要具有以下功能:添加任务:将任务添加进调度队列并按优先级置于对应的位置执行任务:将优先堆中优先级最高的任务取出(并执行)删除任务:按标号删除队列中的未执行任务修改任务优先级:修改指定标号任务的优先级应用分析数据结构对于任务,考虑使用类封装,对于一个任务类需要以下特征:标号:int型,用于区别任务的标号,每个任务有一个且唯一优先级:int型,每个任务的优先级,该特征越小则优先级越高同时需要具有以下方法:任务执行方法:调用该任务表示执行了该任务优先级修改方法:调用该任务修改优先级优先堆定义了数据结构后,使用2D优先堆实现该优先队列,2D优先堆为完全二叉树,且任意一个节点的值小于其子节点的值。要实现场景中的几种功能,需要以下几种方法:Push:对应添加任务,将任务类插入该优先堆中,调用上移方法。Pop:对应执行任务,取出2D优先堆根节点的任务,调用下移方法。Delete:对应删除任务,按标号取出某一节点的任务并调整堆使其满足2D优先堆的条件,调用下移方法Change:对应修改任务优先级

  • Oracle DBA常用的系统表

    1.2DBA常用的表1.2.1  dba_开头    dba_users数据库用户信息    dba_segments  表段信息    dba_extents    数据区信息    dba_objects    数据库对象信息    dba_tablespaces   数据库表空间信息    dba_data_files    数据文件设置信息    dba_temp_files   临时数据文件信息    dba_rollback_segs &nbs

  • 咏南新CS三层开发框架

    咏南新CS三层开发框架 咏南WEB桌面框架演示:http://42.193.160.160/咏南WEB手机框架本地:http://42.193.160.160:8077咏南CS框架下载:链接:https://pan.baidu.com/s/1Vcs3WqdOC9Ct1Qtj9211Rg提取码:9999咏南原生手机框架下载:链接:https://pan.baidu.com/s/1golzFnZANbjcjQUwVlYtow提取码:ygvp 安卓手机安装APK运行 通用的权限管理。一个较完整的进销存方案。 新增聊天功能 登录 和WINDOWS桌面一样人性化的主界面,操作方式和WINDOWS桌面完全一样,适合用户的操作习惯。   《最近访问》存放用户最近访问过的菜单项。   新增快捷方式到我的桌面 鼠标右键点选菜单项会弹出——添加到我的桌面。   在我的桌面新建文件和更换桌面背景图片 在我的桌面鼠标右键会弹出如下图的菜单。   操作我的桌面上的快捷方式 1)鼠标左键双击我的桌面上的快捷方式 2)鼠标右键点击我的桌面上的快捷方式

  • EL表达式

    EL表达式可以出现在常规的文本或者JSP标签属性中例如:<ul>  <li>name:${expression1}</li>  <li>age:${expression2}</li></ul><jsp:includepage="${expresssion3}">但是EL表达式不能出现在JSP的表达式<%=%>、JSP脚本<%   %>声明语句<%!   %>等地方1、EL表达式的语法EL表达式是以”${”开头,以”}”结束,中间为合法的表达式,具体的语法格式为:${expression} expression用于指定要输出的内容,可以使字符串,也可以是由EL运算符组成的表达式。2、[]与.EL运算符EL提供“.“和“[]“两种运算符来存取数据"."运算符用来访问对象属性,例如:${student.name}"[]"运算符则用来访问对象属性、数组、集合以及Map

  • docker进入交互界面

    进入cmd交互界面 dockerrun-itpython:3.5/bin/bash 退出 exit ctrl+d

  • Flink学习笔记-新一代Flink计算引擎

    说明:本文为《Flink大数据项目实战》学习笔记,想通过视频系统学习Flink这个最火爆的大数据计算框架的同学,推荐学习课程:  Flink大数据项目实战:http://t.cn/EJtKhaz   新一代Flink计算引擎 (1) Flink概述 目前开源大数据计算引擎有很多的选择,比如流处理有Storm、Samza、Flink、Spark等,批处理有Spark、Hive、Pig、Flink等。既支持流处理又支持批处理的计算引擎只有ApacheFlink和ApacheSpark。   虽然Spark和Flink都支持流计算,但Spark是基于批来模拟流的计算,而Flink则完全相反,它采用的是基于流计算来模拟批计算。从技术的长远发展来看,Spark用批来模拟流有一定的技术局限性,并且这个局限性可能很难突破。而Flink基于流来模拟批,在技术上有更好的扩展性。所以大家把Flink称之为下一代大数据计算引擎。   从长远发展来看,阿里已经使用Flink作为统一的通用的大数据引擎,并投入了大量的人力、财力、物力。目前阿里巴巴所有的业务,包括

  • 修改 hostname

    #查看当前主机名 hostname hostnamectl #修改hostname hostnamectlset-hostnameaws.jiangxicheng.xyz #临时修改 hostnameaws.jiangxicheng.xyz 复制

  • odoo16,windows开发环境搭建

    python3.7 pipinstall-r requirements.txt PostgreSQL >12     https://www.cnblogs.com/inpool/p/pg-lite.html PostgreSQLForWindows全功能绿色精简版  默认用户,windows用户,密码为空

  • 内置函数:abs_all_any

    函数: abs(x)  返回一个数的绝对值.该参数可以是整数或浮点数.如果参数是一个复数,则返回其大小 复制代码 代码: >>>abs(1) 1 >>>abs(-1) 1 >>> 复制代码 ===================函数: all(iterable) 如果迭代器里面的任何元素都非零,返回True;否则返回False. 复制代码 代码: >>>all([-1,'  ',1]) True >>>all([-1,0,1]) False >>>all(['  ']) True >>>  >>>all(['']) False >>> 复制代码 ===================函数: any(iterable) 如果迭代器里面的任意元素非零,返回True;否则返回False. 复制代码 代码: >>

  • Macbook 虚拟机安装win7 64位

    ParallelsDesktop12Mac虚拟机 下载地址: https://www.52pojie.cn/thread-618641-1-1.html   下载的是win764位ghost,随便一个网站下载(32位的win7 还有win1032/64位安装不了,折腾了很久,各位看官可以自己折腾)   安装虚拟机教程: http://bbs.feng.com/read-htm-tid-10730257.html   安装好后,发现,MAC的文件和win7文件互传,实现不了。这个时候,必须安装工具 parallelsTools 就在parallelsdesktop这个软件的操作,里面有个选项可以安装tools。    

  • Class字节码文件

    Java文件经过编译后生产Class字节码文件。JVM时通过字节码来执行。对于程序员来说对class的机制熟悉很重要。 1.Class文件的组成   上图的class文件可以用下图来表达,U4便是4个无符号字节     Class文件结构的解析: 1.魔术: 所有的由Java编译器编译而成的class文件的前4个字节都是“0xCAFEBABE”(谐音咖啡宝贝)。它的作用在于:当JVM在尝试加载某个文件到内存中来的时候,会首先判断此class文件有没有JVM认为可以接受的“签名”,即JVM会首先读取文件的前4个字节,判断该4个字节是否是“0xCAFEBABE”,如果是,则JVM会认为可以将此文件当作class文件来加载并使用。 2. 版本号 版本号分为次版本号和主版本号。主版本号和次版本号在class文件中各占两个字节,副版本号占用第5、6两个字节,而主版本号则占用第7,8两个字节。 JDK1.0主版本号为45,1.1为46,依次类推,到JDK8的版本号为52,16进制为0x33.  一个JVM实例只能支持特定范围内的主版本号(Mi至

相关推荐

推荐阅读