当前位置: 首页 > 编程日记 > 正文

【C++】Effective STL:50条有效使用STL的经验

第一条:慎重选择容器类型

1、C++容器:先混个眼熟

序列容器:array、vector、string、deque、list、forward_list
有序关联容器:set、map、multiset、multimap
无序关联容器:unordered_set、unordered_map、unordered_multiset、unordered_multimap
容器适配器:stack、queue、priority_queue

2、C++容器:简介
2.1 序列容器

序列容器实现了可以顺序访问的数据结构。

array:静态连续容器;
vector:动态连续容器,超出容器大小会自动分配内存;
deque:双端队列;
list:双链表;
forward_list:单链表;

2.2 有序关联容器

关联容器实现可以快速搜索的排序数据结构(O(log n)复杂度)。

set:只有键,没有值的集合,按键排序,并且键是唯一的;
map:键值对的集合,按键排序,并且键是唯一的;
multiset:只有键,没有值的集合,按键排序,键可以不唯一;
multimap:键值对的集合,按键排序,键可以不唯一;

2.3 无序关联容器

无序关联容器实现了可以快速搜索的未排序(哈希)数据结构(O(1)最好,O(n)最坏情况的复杂性)。

unordered_set:只有键,没有值的集合,按键哈希散列,并且键是唯一的;
unordered_map:键值对的集合,按键哈希散列,并且键是唯一的;
unordered_multiset:只有键,没有值的集合,按键哈希散列,键可以不唯一;
unordered_multimap:键值对的集合,按键哈希散列,键可以不唯一;

2.4容器适配器

容器适配器为顺序容器提供了不同的接口。
stack:先进后出;
queue:先进先出;
priority_queue:优先级队列

3、选择容器

默认序列容器:vector,先考虑vector是否符合要求,如果不符合再选择其它的;
容器大小从始至终不会变,选择array:例如记录一年中每个月的收入;
需要在序列中间做插入和删除操作,选择list;
需要在序列头部、尾部做插入和删除操作时,选择deque;

第二条:不要试图编写独立于容器类型的代码

1、禁止对容器进一步泛化

每一种容器是针对一类场景的泛化,不要试图进一步泛化容器。比如:针对当下场景使用vector容器,写代码时就围绕vector接口来写,不要试图写出能够兼容list、deque的代码。虽然这么做出发点是好的,但是最终总是误入歧途。

2、建议封装容器到类中

要想减少再替换容器类型时所需要修改的代码,可以把容器隐藏到一个类中,并尽量减少与容器相关的外部可见的接口。

第三条:确保容器中的对象拷贝正确而高效

1、使用智能指针

使拷贝动作高效、正确,并防止剥离问题发生的一个简单办法是使容器包含指针而不是对象,最佳选择是使用智能指针。

第四条:调用empty而不是检查size()是否为0

理由很的简单:empty对所有的标准容器都是常数时间操作,而对一些list实现,size耗时线性增长。

第五条:区间成员函数优于与之对应的单元素成员函数

1、区间成员函数

区间成员函数像STL算法一样,使用两个迭代器参数来确定该成员操作所执行的区间。

2、区间成员函数的优点

代码量少、表达清晰直接,易写易懂。

3、何时使用区间成员函数

1)通过利用插入迭代器的方式来限定目标区间的copy调用,几乎都应该被替换为对区间成员函数的调用;
2)所有标准容器都提供了使用区间创建的构造函数;
3)所有标准容器都提供了使用区间插入的insert函数;
4)所有标准容器都提供了使用区间删除的erase函数;
5)所有标准容器都提供了使用区间赋值的assign函数

第六条:当心C++编译器最烦人的分析机制

请使用命名的迭代器对象,这消除二义性,维护代码的人更容易理解。
这里举例一个容易犯错的代码:将变量定义写成了函数声明。

第七条:如果容器中包含了通过new操作创建的指针,切记在容器对象析构前将指针delete掉

STL容器很智能,但没有智能到知道是否该删除自己所包含的指针所指向的空间。为了避免内存泄漏,建议使用引用计数形式的智能指针对象。

第八条:切勿创建包含auto_ptr的容器对象

不多说了,很多年以前就不再使用auto_ptr了。

第九条:慎重选择删除元素的方法

1、删除特定值

对于连续容器,使用erase-remove:v.erase(remove(v.begin(), v.end(), 1987), c.end());
对于非连续容器list,使用remove函数;
对于关联容器,使用erase;

2、删除满足特定条件的对象

对于连续容器,使用erase-remove_if;
对于非连续容器,使用list::remove_if
对于关联容器,使用remove_copy_if和swap或者遍历使用erase,注意参数使用后缀递增;

第十条:了解分配子(allocator)的约定和限制

一个类型为T的对象,它的默认分配子allocator的两个类型定义分别是allocator::pointer和allocator::reference。
其他待完善,没有理解分配子的作用。

第十一条:理解自定义分配子的合理用法

第十二条:切勿对STL容器的线程安全性有不切实际的依赖

对容器成员函数的每次调用,都锁住容器直到调用结束;
在容器返回的每个迭代器的生存期结束前,都锁住容器;

第十三条:vector和string优先于动态分配的数组

不解释了,直接照做就是了。

第十四条:使用reserver来避免不必要的重新分配

reserver成员函数能使你把重新分配的次数减少到最低限度。
size:容器中有多少元素;注意:不等于容器容纳多少元素;
capacity:容器已经分配的内存可以容纳多少元素;
resize:强迫容器改变到包含n个元素的状态。
reserve:强迫容器把它的容器变为至少是n,如果n不小于当前的大小,会重新分配;如果小于,什么也不做。

第十五条:注意string实现的多样性

不同的string实现以不同的方式来组织下列信息:字符串的大小、容量capacity、字符串的值、对值的引用计数。

第十六条:了解如果把vector和string数据传给旧的API

vector返回C API:

vector<int> v;
if(!v.empty()){doSomething(&v[0], v.size());
}

注意:不要用v.begin()代替&v[0]

string返回C API:s.c_str();

第十七条:使用“swap技巧”除去多余的容量

class Contestant{... ...};
vector<Contestant> contestants;
... ...
vector<Contestant>(contestants).swap(contestants);
string s;
... ...
string(s).swap(s);

第十八条:避免使用vector

首先,它不是一个STL容器;
其次,它并不存储bool

代替方法:

deque<bool>
或者使用bitset

第十九条:理解相等(equality)和等价(equivalence)的区别

相等:operator==
等价:!(x<y) && !(y<x),对于两个对象x和y,如果按照关联容器c的排列顺序,每个都不在另一个的前面,那么称这两个对象按照c的排列顺序是等价的。

第二十条:为包含指针的关联容器指定比较类型

如果什么也不做,默认是对指针的地址做排序。必须自己编写比较函数子类。
比较函数模板如下:

struct DereferenceLess {tumplate<typename PtrType>bool operator()(PtrType pT1, PtyType pT2) const{return *pT1 < *pT2;}
}

第二十一条:总是让比较函数在等值情况下返回false

如果比较函数在等值情况下返回true,两个相等的值,可能不等价。
比如使用less_equal(operator<=)作为比较函数,x=y=10时,!(x<=y) && !(y<=x)的结果为false,即最终会得出:x!=y

第二十二条:切勿直接修改set或multiset的值

所有的标准关联容器是按照一定顺序来存放的,如果修改了会打破容器的有序性。

第二十三条:考虑用排序的vector替代关联容器

如果元素少并且几乎不会有插入删除操作,可以考虑使用排序的vector替代关联容器,无论是空间还是时间都是最优的。

第二十四条:当效率至关重要时,请在map::operator[]和map::insert之间谨慎做出选择

map::operator[]的设计目的是为了提供“添加和更新”的功能;
在作为“添加”操作时,insert比operator[]效率高;
当作为“更新”操作时,优先使用operator[]

第二十五条:熟悉非标准的散列表

非标准的散列表:hast_set、hast_multiset、hash_map、hash_multimap
注意这里说的是旧版本的STL

第二十六条:iterator优先于const_iterator、reverse_iterator及const_reverse_iterator。

原因:
1、容器中的instert和crase函数的形参只接受iterator类型,不接受const_iterator、reverse_iterator及const_reverse_iterator。
2、从iterator到const_iterator,或者从reverse_iterator到const_reverse_iterator之间都存在隐式转换,但是反过来技术上可以实现,但不推荐,效率不能保证。
3、在同一个表达式中混用iterator和const_iterator,比如比较操作,会发生隐式转换,有时会出现问题。

第二十七条:使用distance和advance将容器的const_iterator转换成iterator

distance:用于取得两个迭代器之间的距离;
advance:用于将一个迭代器移动指定的距离。

typedef deque<int> IntDeque;
typedef IntDeque::iterator Iter;
typedef IntDeque::const_iterator ConstIter;IntDeque d;
ConstIter ci;
...
Iter i(d.begin()); /迭代器 i 指向容器 d 起始位置
advance(i, distance<ConstIter>(i, ci)); /注意这里指明distance所使用的类型为ConstIter

效率问题:对于随机访问的迭代器(如vector、string、deque产生的迭代器)而言,执行时间是常数时间;对于其他标准容器以及散列表的迭代器而言,执行时间是一个线性时间,因此推荐二十六条,尽量用iterator代替const_iterator

第二十八条:正确理解由reverse_iterator的base()成员函数所产生的iterator的用法

1、插入操作:如果要在一个迭代器reverse_iterator ri指定的位置上插入一个新元素,则只需在ri.base()位置处插入元素即可。
2、删除操作:如果要在一个迭代器reverse_iterator ri指定的位置上删除一个元素,要先递增ri,然后在调用base()函数,例如:v.erase((++ri).base());

第二十九条:对于逐个字符的输入请考虑使用istreambuf_iterator

对比istream_iterator,它在默认请看下会跳过空白字符,并且效率低。
读取一个文本文件的内容到一个string对象中,推荐的方案如下:

ifstream inputFile("test.txt");
string fileData((istreambuf_iterator<char>(inputFile)), istreambuf_iterator<char>());

第三十条:确保目标区间足够大

对于vector、string、deque、list容器在尾部插入对象时,需要使用back_inserter函数;
其中deque和list容器还可以使用front_inserter在头部插入对象。
为了提供插入操作的性能,对于vector和string容器可以使用reserve来提前分配好内存。

第三十一条:了解各种与排序有关的选择

按照性能由高到底排序:
1、partition:把满足特定条件的元素放到前面;
2、stable_partition:把满足特定条件的元素放到前面,并且是稳定的排序(在遇到相等的对象时,还会按照出场顺序排序);
3、nth_element:找出部分最优的对象,可以不按照顺序,比如列出前十个,而且这十个可以不用排序;
4、partial_sort:部分排序;
5、sort:全部排序
6、stable_sort:稳定版本的全部排序,不仅全部排好序,而且在遇到相等的对象时,还会按照出场顺序排序。
注意:list::sort是稳定排序。

第三十二条:如果确实需要删除元素,则需要在remove这一类算法之后调用erase

注意:remove不能删除容器中元素。
因为从容器中删除元素的唯一方法是调用该容器的成员函数,而remove并不知道它操作的元素所在的容器,所以remove不可能从容器中输出删除元素。
remove实际功能:把不用被删除的元素放到容器前部,把需要被删除的元素放到容器尾部。
如果需要真正删除元素,需要使用erase和remove配合

vector<int> v;
...
v.erase(remove(v.begin(), v.end(), 99), v.end());

同理unique也需要和erase配合使用。

注意:list::remove会真正删除元素,并且比使用erase-remove配合使用更高效,list::unique也会真正删除元素。

第三十三条:对包含指针的容器使用remove这一类算法时要特别小心

对指针容器使用erase-remove组合来删除时,很可能造成内存泄漏。
其实在执行remove后,还没有执行erase之前已经发生内存泄漏。因为remove在将不需要删除的指针元素移动到容器头部时,会覆盖掉需要删除的指针元素,造成内存泄漏;
再执行erase时,也会造成内存泄漏,因为还没释放内存,就删除了指针。
同理:remove_if和unique也会造成内存泄漏。

解决方法有两种:
1、使用带有引用计数的智能指针
2、使用partition代替remove

第三十四条:了解哪些算法要求使用排序的区间作为参数

需要排序后才能使用的算法:

// 使用二分法查找的算法,需要先排序
binary_search
lower_bound
upper_bound
equal_range// 集合操作,为了提高效率(为了保证线性时间效率),需要先排序
set_union
set_intersection
set_defference
set_symmetric_defference// 合并操作,为了提高效率
merge
inplace_merge// 判断一个区间中的所有的对象是否都在另一个区间中,为了提高效率
includes

第三十五条:通过mismatchlexicographical_compace实现简单的忽略大小写的字符串比较

第三十六条:理解copy_if算法的正确实现

STL中包含copy的算法

copy
copy_backward
replace_copy
replace_copy_if
reverse_copy
unique_copy
remove_copy
remove_copy_if
rotate_copy
partial_sort_copy
uninitialized_copy

但是就是没有copy_if算法,需要自己实现

template<typename InputIterator,typename OutputIteratortypename Predicate>
OutputIterator copy_if(InputIterator begin,InputIterator end,OutputIterator destBegin,Predicate p)
{while (begin != end) {if (p(*begin))*destBegin++ = *begin;++begin;}return destBegin;
}

第三十七条:使用accumulate或者for_each进行区间统计

count:统计一个区间中有多少个元素;
count_if:统计满足某个条件的元素个数;
min_element:获取区间中最小值;
max_element:获取区间中最大值;

accumulate:对区间进行自定义的操作,比如计算一个容器中字符串长度的总和。

/计算和
list<double> ld;
...
double sum = accumulate(ld.begin(), ld.end(), 0.0);/ 计算容器中字符串长度的总和
string::size_type
stringLengthSum(string::size_type sumSoFar ,const string& s)
{return sumSoFar + s.size();
}set<string> ss;
...//插入一些字符串
string::size_type lengthSum = accumulate(ss.begin(), ss.end(), static_cast<string::size_type>(0),stringLengthSum);
};

第三十八:遵循按值传递的原则来设计函数子类

1、在C和C++的标准库函数中,如果需要函数作为参数,需要使用函数指针,并且函数指针是按值传递的(被复制);
2、在STL中,函数对象在函数之间来回传递也是按值传递(被复制);
3、因为函数对象是按值传递,即需要来回复制,因此函数对象要尽可能的小;
4、函数对象必须是单态的(不能是多态),也就是说,它们不能有虚函数,因此在传递过程中,会出现剥离问题(slicing problem):在对象复制过程中,派生部分可能会被去掉,而仅保留了基类部分。

第三十九:确保判别式是“纯函数”。

1、判别式函数(predicate function):是一个返回值为bool类型(或者可以隐式地转换为bool类型)的函数。
2、判别式类(predicate class):是一个函数类,它的operator()函数是一个判别式。
3、纯函数(pure function):是指返回值仅仅依赖于其参数的函数。
4、在STL中容器使用的比较函数都是判别式,比如:find_if以及各种与排序相关的算法,这些算法要求每执行一次都要保证结果是唯一,不会受其他参数的干扰,比如全局变量、静态变量等。

第四十条:若一个类是函数类,则应使它可配接

1、STL函数配接器not1、not2、bind1st、bind2nd等都要求一些特殊的类型定义,提供这些必要的类型定义的函数对象被称为可配接的(adaptable)函数对象。
2、特殊的类型定义:argument_type、first_argument_type、second_argument_type、result_type
3、继承 std::unary_function、std::binary_function来完成上述的特殊的类型定义

第四十一条:理解ptr_fun、mem_fun和mem_fun_ref的来由

STL算法只支持非成员函数,不支持成员函数(无法通过编译),如果要使用成员函数需要使用ptr_fun、mem_fun或者mem_fun_ref来将成员函数封装到一个函数类中

第四十二条:确保less与operator<具有相同的语义

1、不要特化一个位于std名字空间中的模板
2、operator<是std::less默认实现方式,不要修改std::less的行为,因为这样做很可能会误导其它的程序员。

第四十三条:算法调用优先于手写的循环

1、先看一个手写循环和使用算法的例子
一个支持重画的类Widget

class Widget{
public:...void redraw() const;...
}

使用手写循环来重画容器list中的所有的Widget:

list<Widget> lw;
...
for (list<Widget>::iterator i=iw.begin(); i!=iw.end(); ++i){i->redraw();
}

使用for_each循环算法来完成

for_each(lw.begin(), lw.end()),mem_fun_ref(&Widget::redray)); 

mem_fun_ref:封装成员函数,使它可以用在算法中,因为算法只能使用非成员函数

2、使用算法的优点
效率高、可维护性好。
理由:略,一定要用起来

第四十四条:容器的成员函数优先于同名的算法

1、列举出这些函数
关联容器:count、find、lower_bound、upper_bound、equal_range
list容器:remove、remove_if、unique、sort、merge、reverse

2、理由
速度更快、成员函数通常与容器结合的更加紧密

第四十五条:正确区分count、find、binary_search、lower_bound、upper_bound和equal_range

1、如果容器的区间是排序好的使用:binary_search、lower_bound、upper_bound和equal_range,
这些算法是对数时间的效率。
2、如果容器的区间不是排序好的使用:count、count_if、find、find_if,
这些算法是线性时间的效率。

count:区间中是否存在某个特定的值?如果存在的话,有多少个拷贝?
find:区间中有这样的值吗?如果有,它在哪里?
两者还有一个区别:find找到后就返回,效率高;count要遍历一遍容器的区间

binary_search:测试一个排序的容器区间中是否存在某一个特定的值,返回true或者false;
equal_range:查找一个值在容器区间中的位置;
lower_bound:查找一个值在容器区间中第一次出现的位置,如果没有,则返回适合插入该值的位置;

第四十六条:考虑使用函数对象而不是函数作为STL算法的参数

使用函数对象比直接使用函数作为STL算法的参数,更高效。
原因是:编译器可以对函数对象做内敛优化,而函数作为参数,其实是指针,编译器不会对指针做优化。

第四十七条:避免产生“直写型”(wirte-only)的代码

1、不要写过于复杂的嵌套函数调用
2、注意添加代码的注释

第四十八条:总是包含(#include)正确的头文件

1、有些标准头文件可以省略,也可以通过编译,但是考虑移植性,强烈建议不要省略;
2、标准的STL容器都被声明在与之同名的头文件中:vector、list等,特殊的:multiset和set都在<set>中,map和multimap都在<map>中
3、算法被声明在<algorithm>中,除了下面四个
4、accumulate、inner_product、adjacent_difference、partial_sum被声明在<numeric>中
5、特殊类型的迭代器被声明在<iterator>中
6、标准的函数类和函数配接器被声明在<functional>中:如not1、bind2nd

第四十九条:学会分析与STL相关的编译器诊断信息

1、std::basic_string<… …>很长的一个信息,翻译成string就好;
2、vector和string的迭代器通常就是指针,所以错误的使用iterator错误信息中会有:double *
3、如果错误消息源于某一个STL算法的内部实现,那么也许在调用算法的时候,使用了错误的类型。

第五十条:熟悉与STL相关的web站点

SGI站点:www.sgi.com/tech/stl/
STLport站点:www.stlport.org
Boost:www.boost.org

相关文章:

ICML 2019接受论文:清华、北大领跑,谷歌强压枝头,BAT略显“低调”

整理 | 刘畅责编 | Jane、Rachel出品 | AI科技大本营&#xff08;id&#xff1a;rgznai100&#xff09;【AI科技大本营导语】今年 6 月&#xff0c;机器学习领域顶会 ICML 2019 将在美国举行。为了帮助大家更好了解会议论文的情况&#xff0c;博世&#xff08;Bosch&#xff09…

C++STL 优先队列priority_queue使用

头文件&#xff1a;#include <queue> 一.申明方式 std::priority_queue<T> q; std::priority_queue<T, std::vector<T>, cmp> q;1.普通方法&#xff1a; priority_queue<int> q; //大的优先级高 priority_queue<int,vector<int>, …

【SVN】linux下svn命令参数详解(二)

svn全部子命令详解1、svn help2、svn add3、svn blame4、svn cat5、svn changelist6、svn checkout7、svn cleanup8、svn commit9、svn copy10、svn delete11、svn diff12、svn export13、svn help14、svn import15、svn info16、svn list17、svn lock18、svn log19、svn merge…

VClient 无法连接Vcenter

前一段在一台windows server 2008 R2上使用local Administrator 安装了VCenter 4.1. 一开始使用VClient 是可以登录的&#xff0c;但是重新启动这台安装有VCenter 的Server 之后就连接不到了。为什么呢&#xff1f; 打开这台Windows Server 2008 查看服务发现SQL 和 VM 都有服务…

智能音箱玩出新花样?这家公司推出2699元的智能虚拟机器人

家居控制、备忘提醒、媒体播放、智能聊天… …如今&#xff0c;AI 应用备受瞩目&#xff0c;智能虚拟机器人也成为了 AI 应用的新方向。 5 月 20 日下午&#xff0c;专注于虚拟机器人研发、平台软件开发运用及销售为一体的公司欧博思发布了 AI BOX 智能虚拟机器人产品。 乍一…

干货 :数据可视化的10个关键术语

2019独角兽企业重金招聘Python工程师标准>>> Format 交互方式 交互式可视化允许您修改&#xff0c;操作和探索计算机显示的数据。绝大多数交互式可视化系统在计算机网络上&#xff0c;但越来越多出现在平板电脑和智能手机上。相比之下&#xff0c;静态可视化只显示单…

【设计模式】三大类:创建型模式、结构型模式、行为型模式

1 创建型模式 本人理解&#xff1a;以C为例&#xff0c;创建对象时&#xff0c;用关键字new来创建&#xff08;实例化&#xff09;对象。用一个函数或类将new的过程封装起来&#xff0c;就是创建型模式。 《设计模式》中的描述&#xff1a; 创建型模式抽象了实例化过程。它们…

单机训练速度提升高达640倍,快手开发GPU广告模型训练平台

作者&#xff5c;廉相如&#xff08;快手FeDA智能决策实验室 &#xff09;如何有效处理大规模用户数据进行广告推荐&#xff1f;对于互联网企业的发展和进步至关重要。这也是为何快手成立西雅图实验室并实现新一代GPU广告模型训练平台的原因之一。快手新创建的“Persia”GPU广告…

比特币SPV节点启动流程图

2019独角兽企业重金招聘Python工程师标准>>> 比特币SPV节点启动流程图 图小点击右键在新窗口打开 转载于:https://my.oschina.net/penghaozhong/blog/1924687

Android中后台定时任务实现,即时数据同步问题思考!

为什么80%的码农都做不了架构师&#xff1f;>>> 如果你正在找Android后台定时任务实现,那么你找对了,但是其实如果你正在找Java后台任务实现,你就不会找到我的这个博客了.但是我的实现方式没有使用多少Android相关的东西.确实.但是如果你进来了,Thank you!你还是会…

面对996,程序员如何利用“碎片时间”涨薪?

图片来源|视觉中国作为一个程序员&#xff0c;需要不断学习更新知识技能来提升自己。但爆炸式的信息量&#xff0c;总使人抓不到学习重点。所以&#xff0c;笔者为大家筛选了几个程序员会阅读的小众公众号&#xff0c;覆盖全面&#xff0c;囊括了不同的技术类别。小道消息&…

JakartaEE Exception: Invalid bound statement (not found): com.mazaiting.blog.dao.UserDao.selectUs...

异常 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.mazaiting.blog.dao.UserDao.selectUserByNameat org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:227) ~[mybatis-3.4.6.jar:3.4.6]at org.a…

【Qt】AVFrame转QImage

FFmpeg的AVFrame转成Qt的QImage //头文件 #ifdef __cplusplus extern "C" {#endif #include <libavcodec/avcodec.h> #include <libavcodec/avformat.h>

C# 回发或回调参数无效

回发或回调参数无效。在配置中使用 <pages enableEventValidation"true"/> 或在页面中使用<% Page EnableEventValidation"true" %> 启用了事件验证。出于安全目的&#xff0c;此功能验证回发或回调事件的参数是否来源于最初呈现这些事件的服务…

【QT】Qt正则表达式的使用:使用QRegExp来限制QLineEdit的输入格式

QLineEdit中使用QRegExp来限制输入格式,下面以IP输入框为例 QLineEdit *lineEditIP = new QLineEdit("192.168.147.200", this);QRegExpValidator *reg = new QRegExpValidator(GW::RegExp::ipRegExp(), this)

小小的Python编程故事

现在Python的火热已经不是整个编程界的事情了&#xff0c;Python的火热已经蔓延到小学、大学课程&#xff0c;之前看新闻报道说青岛的小学生课程里都开始设置Python的课程了&#xff0c;大学里由原来必考的VB也改成了Python。一时大家都掀起了学习了Python的热潮&#xff0c;而…

python 验证码识别示例(二) 复杂验证码识别

在这篇博文中手把手教你如何去分割验证&#xff0c;然后进行识别。 一&#xff1a;下载验证码 验证码分析&#xff0c;图片上有折线&#xff0c;验证码有数字&#xff0c;有英文字母大小写&#xff0c;分类的时候需要更多的样本&#xff0c;验证码的字母是彩色的&#xff0c;图…

★核心关注点_《信息系统项目管理师考试考点分析与真题详解》

★核心关注点_《信息系统项目管理师考试考点分析与真题详解》真诚感谢你选用《信息系统项目管理师考试考点分析与真题详解》作为高级项管的辅导用书。对于使用该书的读者们&#xff0c;在备考2012.5.26信息系统项目管理师考试之下午案例分析和论文写作考试时&#xff0c;以下一…

告别低分辨率网络,微软提出高分辨率深度神经网络HRNet | CVPR 2019

来源 | 微软研究院AI头条&#xff08;ID&#xff1a;MSRAsia&#xff09;作者简介&#xff1a;孙可&#xff0c;中国科学技术大学信息学院在读博士生&#xff0c;目前在微软亚洲研究院视觉计算组实习&#xff0c;导师是王井东和肖斌老师。他的研究兴趣包括人体姿态估计、语义分…

sudo提权实战讲解 对用户对组的权限配置分析

Linux是多用户多任务的操作系统, 共享该系统的用户往往不只一个。出于安全性考虑, 有必要通过useradd创建一些非root用户, 只让它们拥有不完全的权限; 如有必要&#xff0c;再来提升权限执行。 sudo就是来解决这个需求的: 这些非root用户不需要知道root的密码&#xff0c;就可以…

【Qt】在Ubuntu16.04中安装QSerialPort模块

1、问题描述 在pro中添加&#xff1a;QT serialport 编译时报错&#xff1a;Project ERROR Unknown module(s) in QT:serialport 2、解决方法 安装QSerialPort模块库 sudo apt-get install libqt5serialport5-dev

Velocity文档(3)

2019独角兽企业重金招聘Python工程师标准>>> velocity.properties 的一些配置项 velocimcro.library属性&#xff1a;指定自己的模板库&#xff0c;多个模板库以逗号分隔。默认情况下&#xff0c;velocity查找唯一的一个库&#xff1a;VM_global_library.vmvelocima…

Java 24岁!Google加持的Kotlin真能取代它?

作者 | 屠敏出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;1995 年 5 月 23 日&#xff0c;Sun 公司在 Sun world 会议上正式宣布了 Java 的到来&#xff0c;从此一代编程语言界的翘楚就此诞生。而在不断地迭代与适配中&#xff0c;今时今日&#xff0c;Java 以需…

三,ES6中需要注意的特性(重要)

-----书接上文&#xff0c;前文中我们总结了关于JS的组成部分以及如何快速开展学习JS&#xff0c;相信已经有很多朋友掌握到这些方法。接下来就从更深的ECMAScript开始说起。 1.什么是ES6? ECMAScript(European Computer Manufacturers Association) 6: 是JavaScript语言的下一…

【SVN】在阿里云上创建svn服务器

1、创建用户及相关设置 阿里云搭建ubuntu18.04工作台&#xff0c;默认只有root超级用户。为了安全起见&#xff0c;我们使用普通用户来创建svn&#xff1a; useradd hello // 创建用户hello mkdir -p /home/hello // 在hello用户的根目录 cd /home/hello chown hello . // 设…

【转】on delete cascade

关系表的级联更新&#xff1a; on update cascade on delete cascade 是级联删除的意思 意思是 当你更新或删除主键表时&#xff0c;那么外键表也会跟随一起更新或删除 CREATE TABLE Countries(CountryId INT PRIMARY KEY) INSERT INTO Countries (CountryId) VALUES (1) INSER…

倒计时1天!CTA核心技术及应用峰会报名通道即将关闭(附参会攻略)

全球智能化趋势当前&#xff0c;人工智能技术正蓬勃发展。为了直击开发者在机器学习与人工智能技术研究与落地过程中的痛点与瓶颈&#xff0c;深入解析机器学习和知识图谱技术在行业中的实践和落地。2019 年 5 月 26 - 27 日&#xff0c;由中国 IT 社区 CSDN 与数字经济人才发展…

【Qt】在QtCreator中使用Ctrl+Shift+f快捷键打开高级查找窗口失效的解决方法

1、问题描述 Win10操作系统环境下,在QtCreator中使用Ctrl+Shift+f快捷键打开高级查找窗口失效。 2、原因分析 Ctrl+Shift+f快捷键在各种输入法中,常用来做中文简体和繁体输入的切换。QtCreator中该快捷键失效的的原因,多半是因为和输入法的快捷键冲突。可以在输入法的快捷…

Python修行之字符串(一):连接、切割、大小写、排版

字符串:1.是由一个个字符组成有序的序列&#xff0c;是字符的集合2.字符串是不可变对象3.使用单引号、双引号、三引号引住的字符序列4.python3中、字符串就是unicode类型、在2中分两种一种unicode一种非unicode字符串元素访问--下标:1.字符串支持使用索引访问2.有序的字符集合&…

UI自动化测试随笔

昨天给开发的同事讲我们正在做的自动化测试&#xff0c;同事问了句&#xff1a;为什么API的测试不需要写代码了&#xff0c;而UI的测试还需要写那么多代码呢&#xff1f; 能不写代码么&#xff1f; 目前我们的自动化测试的现状&#xff1a; 目前主要覆盖两个部分&#xff1a;A…