C++智能指针简单剖析
导读
最近在补看《C++ Primer Plus》第六版,这的确是本好书,其中关于智能指针的章节解析的非常清晰,一解我以前的多处困惑。C++面试过程中,很多面试官都喜欢问智能指针相关的问题,比如你知道哪些智能指针?shared_ptr的设计原理是什么?如果让你自己设计一个智能指针,你如何完成?等等……。而且在看开源的C++项目时,也能随处看到智能指针的影子。这说明智能指针不仅是面试官爱问的题材,更是非常有实用价值。
下面是我在看智能指针时所做的笔记,希望能够解决你对智能指针的一些困扰。
目录
- 智能指针背后的设计思想
- C++智能指针简单介绍
- 为什么摒弃auto_ptr?
- unique_ptr为何优于auto_ptr?
- 如何选择智能指针?
正文
1. 智能指针背后的设计思想
我们先来看一个简单的例子:
void remodel(std::string & str)
{std::string * ps = new std::string(str);...if (weird_thing())throw exception();str = *ps; delete ps;return;
}
当出现异常时(weird_thing()返回true),delete将不被执行,因此将导致内存泄露。
如何避免这种问题?有人会说,这还不简单,直接在throw exception();
之前加上delete ps;
不就行了。是的,你本应如此,问题是很多人都会忘记在适当的地方加上delete语句(连上述代码中最后的那句delete语句也会有很多人忘记吧),如果你要对一个庞大的工程进行review,看是否有这种潜在的内存泄露问题,那就是一场灾难!
这时我们会想:当remodel这样的函数终止(不管是正常终止,还是由于出现了异常而终止),本地变量都将自动从栈内存中删除—因此指针ps占据的内存将被释放,如果ps指向的内存也被自动释放,那该有多好啊。
我们知道析构函数有这个功能。如果ps有一个析构函数,该析构函数将在ps过期时自动释放它指向的内存。但ps的问题在于,它只是一个常规指针,不是有析构凼数的类对象指针。如果它指向的是对象,则可以在对象过期时,让它的析构函数删除指向的内存。
这正是 auto_ptr、unique_ptr和shared_ptr这几个智能指针背后的设计思想。我简单的总结下就是:将基本类型指针封装为类对象指针(这个类肯定是个模板,以适应不同基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。
因此,要转换remodel()函数,应按下面3个步骤进行:
- 包含头义件memory(智能指针所在的头文件);
- 将指向string的指针替换为指向string的智能指针对象;
- 删除delete语句。
下面是使用auto_ptr修改该函数的结果:
# include <memory>
void remodel (std::string & str)
{std::auto_ptr<std::string> ps (new std::string(str));...if (weird_thing ())throw exception(); str = *ps; // delete ps; NO LONGER NEEDEDreturn;
}
2. C++智能指针简单介绍
STL一共给我们提供了四种智能指针:auto_ptr、unique_ptr、shared_ptr和weak_ptr(本文章暂不讨论)。
模板auto_ptr是C++98提供的解决方案,C+11已将将其摒弃,并提供了另外两种解决方案。然而,虽然auto_ptr被摒弃,但它已使用了好多年:同时,如果您的编译器不支持其他两种解决力案,auto_ptr将是唯一的选择。
使用注意点
- 所有的智能指针类都有一个explicit构造函数,以指针作为参数。比如auto_ptr的类模板原型为:
templet<class T>
class auto_ptr {explicit auto_ptr(X* p = 0) ; ...
};
因此不能自动将指针转换为智能指针对象,必须显式调用:
shared_ptr<double> pd;
double *p_reg = new double;
pd = p_reg; // not allowed (implicit conversion)
pd = shared_ptr<double>(p_reg); // allowed (explicit conversion)
shared_ptr<double> pshared = p_reg; // not allowed (implicit conversion)
shared_ptr<double> pshared(p_reg); // allowed (explicit conversion)
- 对全部三种智能指针都应避免的一点:
string vacation("I wandered lonely as a cloud.");
shared_ptr<string> pvac(&vacation); // No
-
pvac过期时,程序将把delete运算符用于非堆内存,这是错误的。
使用举例
#include <iostream>
#include <string>
#include <memory>class report
{
private:std::string str;
public:report(const std::string s) : str(s) {std::cout << "Object created.\n";}~report() {std::cout << "Object deleted.\n";}void comment() const {std::cout << str << "\n";}
};int main() {{std::auto_ptr<report> ps(new report("using auto ptr"));ps->comment();}{std::shared_ptr<report> ps(new report("using shared ptr"));ps->comment();}{std::unique_ptr<report> ps(new report("using unique ptr"));ps->comment();}return 0;
}
3. 为什么摒弃auto_ptr?
先来看下面的赋值语句:
auto_ptr< string> ps (new string ("I reigned lonely as a cloud.”);
auto_ptr<string> vocation;
vocaticn = ps;
上述赋值语句将完成什么工作呢?如果ps和vocation是常规指针,则两个指针将指向同一个string对象。这是不能接受的,因为程序将试图删除同一个对象两次——一次是ps过期时,另一次是vocation过期时。要避免这种问题,方法有多种:
- 定义陚值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本,缺点是浪费空间,所以智能指针都未采用此方案。
- 建立所有权(ownership)概念。对于特定的对象,只能有一个智能指针可拥有,这样只有拥有对象的智能指针的构造函数会删除该对象。然后让赋值操作转让所有权。这就是用于auto_ptr和uniqiie_ptr 的策略,但unique_ptr的策略更严格。
- 创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。例如,赋值时,计数将加1,而指针过期时,计数将减1,。当减为0时才调用delete。这是shared_ptr采用的策略。
当然,同样的策略也适用于复制构造函数。
每种方法都有其用途,但为何说要摒弃auto_ptr呢?
下面举个例子来说明。
#include <iostream>
#include <string>
#include <memory>
using namespace std;int main() {auto_ptr<string> films[5] ={auto_ptr<string> (new string("Fowl Balls")),auto_ptr<string> (new string("Duck Walks")),auto_ptr<string> (new string("Chicken Runs")),auto_ptr<string> (new string("Turkey Errors")),auto_ptr<string> (new string("Goose Eggs"))};auto_ptr<string> pwin;pwin = films[2]; // films[2] loses ownership. 将所有权从films[2]转让给pwin,此时films[2]不再引用该字符串从而变成空指针cout << "The nominees for best avian baseballl film are\n";for(int i = 0; i < 5; ++i)cout << *films[i] << endl;cout << "The winner is " << *pwin << endl;cin.get();return 0;
}
运行下发现程序崩溃了,原因在上面注释已经说的很清楚,films[2]已经是空指针了,下面输出访问空指针当然会崩溃了。但这里如果把auto_ptr换成shared_ptr或unique_ptr后,程序就不会崩溃,原因如下:
- 使用shared_ptr时运行正常,因为shared_ptr采用引用计数,pwin和films[2]都指向同一块内存,在释放空间时因为事先要判断引用计数值的大小因此不会出现多次删除一个对象的错误。
- 使用unique_ptr时编译出错,与auto_ptr一样,unique_ptr也采用所有权模型,但在使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译器因下述代码行出现错误:
unique_ptr<string> pwin;
pwin = films[2]; // films[2] loses ownership.
指导你发现潜在的内存错误。
这就是为何要摒弃auto_ptr的原因,一句话总结就是:避免潜在的内存崩溃问题。
4. unique_ptr为何优于auto_ptr?
可能大家认为前面的例子已经说明了unique_ptr为何优于auto_ptr,也就是安全问题,下面再叙述的清晰一点。
请看下面的语句:
auto_ptr<string> p1(new string ("auto") ; //#1
auto_ptr<string> p2; //#2
p2 = p1; //#3
在语句#3中,p2接管string对象的所有权后,p1的所有权将被剥夺。前面说过,这是好事,可防止p1和p2的析构函数试图刪同—个对象;
但如果程序随后试图使用p1,这将是件坏事,因为p1不再指向有效的数据。
下面来看使用unique_ptr的情况:
unique_ptr<string> p3 (new string ("auto"); //#4
unique_ptr<string> p4; //#5
p4 = p3; //#6
编译器认为语句#6非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。
但unique_ptr还有更聪明的地方。
有时候,会将一个智能指针赋给另一个并不会留下危险的悬挂指针。假设有如下函数定义:
unique_ptr<string> demo(const char * s)
{unique_ptr<string> temp (new string (s)); return temp;
}
并假设编写了如下代码:
unique_ptr<string> ps;
ps = demo('Uniquely special");
demo()返回一个临时unique_ptr,然后ps接管了原本归返回的unique_ptr所有的对象,而返回时临时的 unique_ptr 被销毁,也就是说没有机会使用 unique_ptr 来访问无效的数据,换句话来说,这种赋值是不会出现任何问题的,即没有理由禁止这种赋值。实际上,编译器确实允许这种赋值,这正是unique_ptr更聪明的地方。
总之,党程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做,比如:
unique_ptr<string> pu1(new string ("hello world"));
unique_ptr<string> pu2;
pu2 = pu1; // #1 not allowed
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string ("You")); // #2 allowed
其中#1留下悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销毁。这种随情况而已的行为表明,unique_ptr 优于允许两种赋值的auto_ptr 。
当然,您可能确实想执行类似于#1的操作,仅当以非智能的方式使用摒弃的智能指针时(如解除引用时),这种赋值才不安全。要安全的重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。下面是一个使用前述demo()函数的例子,该函数返回一个unique_ptr<string>对象:
使用move后,原来的指针仍转让所有权变成空指针,可以对其重新赋值。
unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;
5. 如何选择智能指针?
在掌握了这几种智能指针后,大家可能会想另一个问题:在实际应用中,应使用哪种智能指针呢?
下面给出几个使用指南。
(1)如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:
- 有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;
- 两个对象包含都指向第三个对象的指针;
- STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。
(2)如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。如果函数使用new分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器在那个,只要不调用将一个unique_ptr复制或赋给另一个算法(如sort())。例如,可在程序中使用类似于下面的代码段。
unique_ptr<int> make_int(int n)
{return unique_ptr<int>(new int(n));
}
void show(unique_ptr<int> &p1)
{cout << *a << ' ';
}
int main()
{...vector<unique_ptr<int> > vp(size);for(int i = 0; i < vp.size(); i++)vp[i] = make_int(rand() % 1000); // copy temporary unique_ptrvp.push_back(make_int(rand() % 1000)); // ok because arg is temporaryfor_each(vp.begin(), vp.end(), show); // use for_each()...
}
其中push_back调用没有问题,因为它返回一个临时unique_ptr,该unique_ptr被赋给vp中的一个unique_ptr。另外,如果按值而不是按引用给show()传递对象,for_each()将非法,因为这将导致使用一个来自vp的非临时unique_ptr初始化pi,而这是不允许的。前面说过,编译器将发现错误使用unique_ptr的企图。
在unique_ptr为右值时,可将其赋给shared_ptr,这与将一个unique_ptr赋给一个需要满足的条件相同。与前面一样,在下面的代码中,make_int()的返回类型为unique_ptr<int>:
unique_ptr<int> pup(make_int(rand() % 1000)); // ok
shared_ptr<int> spp(pup); // not allowed, pup as lvalue
shared_ptr<int> spr(make_int(rand() % 1000)); // ok
模板shared_ptr包含一个显式构造函数,可用于将右值unique_ptr转换为shared_ptr。shared_ptr将接管原来归unique_ptr所有的对象。
在满足unique_ptr要求的条件时,也可使用auto_ptr,但unique_ptr是更好的选择。如果你的编译器没有unique_ptr,可考虑使用Boost库提供的scoped_ptr,它与unique_ptr类似。
相关文章:
C++智能指针简单剖析
导读 最近在补看《C Primer Plus》第六版,这的确是本好书,其中关于智能指针的章节解析的非常清晰,一解我以前的多处困惑。C面试过程中,很多面试官都喜欢问智能指针相关的问题,比如你知道哪些智能指针?shar…...
mysql笔记:25. docker环境中mysql主从复制、主主复制实操
文章目录 一、准备工作1. 安装配置Docker2. 准备MySQL相关的配置和数据目录 二、基于日志点的主从复制1. 配置Master服务器1.1 修改配置文件1.2. 在docker中启动Master节点1.3. 创建用户并授权 2. 配置Slave1服务器2.1. 修改配置2.2. 启动服务2.3. 指定Master2.4. 开始复制 3. …...
大数据面试专题 -- kafka
1、什么是消息队列? 是一个用于存放数据的组件,用于系统之间或者是模块之间的消息传递。 2、消息队列的应用场景? 主要是用于模块之间的解耦合、异步处理、日志处理、流量削峰 3、什么是kafka? kafka是一种基于订阅发布模式的…...
深度学习入门简单实现一个神经网络
实现一个三层神经网络 引言测试数据 代码 引言 今天我们实现一个简单的神经网络 俩个输入神经元 隐藏层两个神经元 一个输出神经元 激活函数我们使用sigmoid 优化方法使用梯度下降 我们前期准备是需要把这些神经元的关系理清楚 x1:第一个输入 x2:第二个…...
win11 环境配置 之 Jmeter(JDK17版本)
一、安装 JDK 1. 安装 jdk 截至当前最新时间: 2024.3.27 jdk最新的版本 是 官网下载地址: https://www.oracle.com/java/technologies/downloads/ 建议下载 jdk17 另存为到该电脑的 D 盘下,新建jdk文件夹 开始安装到 jdk 文件夹下 2. 配…...
Windows下载使用nc(netcat)命令
‘nc’ 不是内部或外部命令,也不是可运行的程序? 点击链接地址,下载压缩包。 完成后解压 使用方式(三种): 1、直接双击exe使用 2、把这个exe放到cmd启动的默认路径下 放到默认路径下,使用nc&a…...
istio 设置 istio-proxy sidecar 的 resource 的 limit 和 request
方式一 修改 configmap 查看当前 sidecar 的 cpu 和 memory 的配额 在 istio-sidecar-injector 中查找,修改后重启 pod 可以生效(下面那个 proxy_init 配置不管,不知道是干嘛的) 方式二 如果是通过 iop 安装的 istio…...
flutter弹框
alertDialog:弹框 simpleDialog:选择弹框 showModalBottomSheet:底部弹出弹框 showtoast:三方插件弹框 Navigator.of(context).pop(点击取消) 关闭弹框,传递参数 import package:flutter/material.dart; // import package:flutter/cupertino.dart; import package:flut…...
2013年认证杯SPSSPRO杯数学建模B题(第一阶段)流行音乐发展简史全过程文档及程序
2013年认证杯SPSSPRO杯数学建模 B题 流行音乐发展简史 原题再现: 随着互联网的发展,流行音乐的主要传播媒介从传统的电台和唱片逐渐过渡到网络下载和网络电台等。网络电台需要根据收听者的已知喜好,自动推荐并播放其它音乐。由于每个人喜好…...
代码随想录算法训练营第39天 | 62.不同路径, 63不同路径II
Leetcode - 62:不同路径 题目: 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” &#…...
Redis 的慢日志
Redis 的慢日志 Redis 的慢日志(Slow Log)是用于记录执行时间超过预设阈值的命令请求的系统。慢日志可以帮助运维人员和开发人员识别潜在的性能瓶颈,定位那些可能导致 Redis 性能下降或响应延迟的慢查询。以下是 Redis 慢日志的相关细节&…...
第十四届蓝桥杯第十题:蜗牛分享
问题描述 输入格式 输出格式 输出共一行,一个浮点数表示答案(四舍五入保留两位小数)。 样例输入 3 1 10 11 1 1 2 1样例输出 4.20样例说明 蜗牛路线:(0,0)→(1,0)→(1,1)→(10,1)→(10,0)→(11,0)(0,0)→(1,0)→(1,1)→(10,1…...
不懂技术的老板,如何避免过度依赖核心技术人员
在这个日新月异、技术驱动的时代,即使作为非技术背景的老板,也深知核心技术人员的价值。然而,过度依赖某几位核心技术人员,不仅可能带来经营风险,还可能限制企业的创新与发展。那么,不懂技术的老板…...
Vue系列-el挂载
<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>el:挂载点</title> </head> <body&g…...
python--os和os.path模块
>>> import os >>> #curdir #获取当前脚本的绝对路径 >>> os.curdir . >>> import os.path >>> #获取绝对路径 >>> os.path.abspath(os.curdir) C:\\Users\\GUOGUO>>> #chdir #修改当前目录 >&g…...
前端通用命名规范和Vue项目命名规范
通用命名规范 变量和常量命名:变量和常量的命名应具有描述性,清晰明了,使用驼峰命名法或下划线命名法,例如:firstName、MAX_VALUE。 函数和方法命名:函数和方法的命名应该能够准确描述其功能&…...
NTP服务搭建
一、ntpd和ntpdate区别 1.ntpd是自动执行的远程更新本地系统时钟的服务,是平滑同步; 2.ntpdate是手工执行的服务,也就是一般用它执行一次本地时间更新,如果做成半自动,可以写入到crontab自动任务,从而变成…...
Linux离线安装mysql,node,forever
PS:本文是基于centos7实现的,要求系统能够查看ifconfig和unzip解压命令, 实现无网络可安装运行 首先现在百度网盘的离线文件包****安装Xftp 和 Xshell 把机房压缩包传到 home目录下****解压unzip 包名.zip 获取IP先获取到 linux 主机的ip ifconfig Xftp 连接输入IP,然后按照…...
WPF中获取TreeView以及ListView获取其本身滚动条进行滚动
实现自行调节scoll滚动的位置(可相应获取任何控件中的内部滚动条) TreeView:TreeViewAutomationPeer lvap new TreeViewAutomationPeer(treeView); var svap lvap.GetPattern(PatternInterface.Scroll) as ScrollViewerAutomationPeer; var scroll svap.Owner as ScrollVie…...
C语言: 指针讲解
为什么需要指针? (1)指针的使用使得不同区域的代码可以轻易的共享内存数据。当然你也可以通过数据的复制达到相同的效果,但是这样往往效率不太好,因为诸如结构体等大型数据,占用的字节数多,复制很消耗性能…...
C#使用Stopwatch类来实现计时功能
前言 在 C# 中,Stopwatch 类是用于测量经过的时间的工具类,提供了高精度的计时功能。Stopwatch 类位于 System.Diagnostics 命名空间中。通常情况下,使用 Stopwatch 的流程是创建一个 Stopwatch 对象,然后调用 Start 方法开始计时…...
ubuntu18.04安装qt
ubuntu18.04安装qt 1、下载文件 比如我下载的是5.13.0版本 下载链接 2、安装 wget https://download.qt.io/archive/qt/5.13/5.13.0/qt-opensource-linux-x64-5.13.0.runsudo chmod x qt-opensource-linux-x64-5.13.0.runsudo ./qt-opensource-linux-x64-5.13.0.run参考文…...
ElasticSearch、java的四大内置函数式接口、Stream流、parallelStream背后的技术、Optional类
第四周笔记 一、ElasticSearch 1.安装 apt-get install lrzsz adduser -m es 创建用户组: useradd *-m* xiaoming(用户名) *PS:追加参数-m* passwd xiaoming(用户名) passwd xiaoming 输入新的 UNIX 密码: 重新输入新的 UNIX 密码&…...
深入MNN:开源深度学习框架的介绍、安装与编译指南
引言 在人工智能的世界里,深度学习框架的选择对于研究和应用的进展至关重要。MNN,作为一个轻量级、高效率的深度学习框架,近年来受到了众多开发者和研究人员的青睐。它由阿里巴巴集团开源,专为移动端设备设计,支持跨平…...
[LeetCode][400]第 N 位数字
题目 400. 第 N 位数字 给你一个整数 n ,请你在无限的整数序列 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, …] 中找出并返回第 n 位上的数字。 示例 1: 输入:n 3 输出:3 示例 2: 输入:n 11 输出:…...
clickhouse 查询group 分组最大值的一行数据。
按照 sql_finger_md5 分组取query_time_ms 最大的一行数据。 使用any函数可以去匹配到的第一行数据,所以可以先让数据按照query_time_ms 排序,然后再使用group by 和any结合取第一行数据,就是最大值的那一行数据。 selectany (time) as time…...
Python装饰器与生成器:从原理到实践
一、引言 Python 是一种功能强大且易于学习的编程语言,其丰富的特性使得开发者能够高效地完成各种任务。在 Python 中,装饰器和生成器是两个非常重要的概念,它们能够极大地增强代码的可读性和可维护性。本文将详细介绍如何学习 Python 装饰器…...
python-函数引入模块面向对象编程创建类继承
远离复读机行为 def calculate_BMI(weight,height):BMI weight / height**2if BMI < 18.5:category "偏瘦"elif BMI < 25:category "正常"elif BMI < 30:category "偏胖"else:category "肥胖"print(f"您的BMI分类…...
Spring:面试八股
文章目录 参考Spring模块CoreContainerAOP 参考 JavaGuide Spring模块 CoreContainer Spring框架的核心模块,主要提供IoC依赖注入功能的支持。内含四个子模块: Core:基本的核心工具类。Beans:提供对bean的创建、配置、管理功能…...
Flask Python:请求上下文和应用上下文
请求上下文和应用上下文详解 一、背景二、什么是上下文2.1、请求上下文2.2、应用上下文2.3、两种上下文的底层逻辑 三、写在最后 一、背景 在如何实现异步发送邮件的时候,遇到过这样一个报错 RuntimeError: Working outside of request context.This typically me…...
新疆生产建设兵团人社厅网站/衡水seo营销
一、题目[LeetCode-38] 给定一个正整数 n ,输出外观数列的第 n 项。 「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。 你可以将其视作是由递归公式定义的数字字符串序列: countAndSay(1) &quo…...
文化建设基金管理有限公司网站/百度下载安装免费
为什么80%的码农都做不了架构师?>>> 用winusb 先安装winusb $ sudo add-apt-repository ppa:colingille/freshlight $ sudo apt-get update $ sudo apt-get install winusb 转载于:https://my.oschina.net/lyyrj/blog/505384...
如何卸wordpress/销售系统
课程网址 项目地址 发布对象 发布对象:使一个对象能够被当前范围之外的代码所使用对象溢出:一种错误的发布,当一个对象还没有构造完成时,就使它被其他线程所见不正确的发布可变对象导致的两种错误: 1.发布线程意外的所…...
网站升级建设方案/云优客seo排名公司
Lock是java.util.concurrent.locks包下的一个接口 主要方法有: Lock接口的实现类 Lock相关实现类还可以定义公平锁,如ReentrantLock(boolean) package cn.itcats.thread.safe.Test1;import java.util.concurrent.locks.Lock; import java.util.concurre…...
海澜之家网站建设的计划/网站制作流程图
MySQL不支持的功能(转)[more]本节介绍其他数据库中有而MySQL中无的功能。它介绍省略了什么功能,以及在需要这些功能时怎么办。一般情况下, MySQL之所以忽略某些功能是因为它们有负面性能影响。有的功能正在开发者的计划清单上,一旦找到一种方…...