c++ auto tuple decltype std::bind_auto tuple-程序员宅基地

tuple(元组)。tuple看似简单,其实它是简约而不简单,可以说它是c++11中一个既简单又复杂的东东,关于它简单的一面是它很容易使用,复杂的一面是它内部隐藏了太多细节,要揭开它神秘的面纱时又比较困难。

  tuple是一个固定大小的不同类型值的集合,是泛化的std::pair。和c#中的tuple类似,但是比c#中的tuple强大得多。我们也可以把他当做一个通用的结构体来用,不需要创建结构体又获取结构体的特征,在某些情况下可以取代结构体使程序更简洁,直观。

基本用法

构造一个tuple

tuple<const char*, int>tp = make_tuple(sendPack,nSendSize); //构造一个tuple

这个tuple等价于一个结构体

struct A
{
char* p;
int len;
};

用tuple<const char*, int>tp就可以不用创建这个结构体了,而作用是一样的,是不是更简洁直观了。还有一种方法也可以创建元组,用std::tie,它会创建一个元组的左值引用。

auto tp = return std::tie(1, "aa", 2);
//tp的类型实际是:
std::tuple<int&,string&, int&>

再看看如何获取它的值:

const char* data = tp.get<0>(); //获取第一个值
int len = tp.get<1>(); //获取第二个值

还有一种方法也可以获取元组的值,通过std::tie解包tuple

int x,y;
string a;
std::tie(x,a,y) = tp; 

通过tie解包后,tp中三个值会自动赋值给三个变量。

解包时,我们如果只想解某个位置的值时,可以用std::ignore占位符来表示不解某个位置的值。比如我们只想解第三个值时:

std::tie(std::ignore,std::ignore,y) = tp; //只解第三个值了

还有一个创建右值的引用元组方法:forward_as_tuple。

std::map<int, std::string> m;
m.emplace(std::forward_as_tuple(10, std::string(20, 'a')));

它实际上创建了一个类似于std::tuple<int&&, std::string&&>类型的tuple。

我们还可以通过tuple_cat连接多个tupe

复制代码
int main()
{
std::tuple<int, std::string, float> t1(10, "Test", 
3.14);
int n = 7;
auto t2 = std::tuple_cat(t1, std::make_pair("Foo", 
"bar"), t1, std::tie(n));
n = 10;
print(t2);
}
复制代码

输出结果:

(10, Test, 3.14, Foo, bar, 10, Test, 3.14, 10)

 

  到这里tuple的用法介绍完了,是不是很简单,也很容易使用,相信你使用它之后就离不开它了。我前面说过tuple是简约而不简单。它有很多高级的用法。它和模板元关系密切,要介绍它的高级用法的时候,读者需要一定的模板元基础,如果你只是把它当一个泛型的pair去使用时,这部分可以不看,如果你想用它高级用法的时候就往下看。让我们要慢慢揭开tuple神秘的面纱。

tuple的高级用法

获取tuple中某个位置元素的类型

  通过std::tuple_element获取元素类型。

复制代码
template<typename Tuple>
void Fun(Tuple& tp)
{
std::tuple_element<0,Tuple>::type first = std::get<0> 
(mytuple);
std::tuple_element<1,Tuple>::type second = std::get<1> 
(mytuple);
}
复制代码

  

获取tuple中元素的个数:

tuple t;

int size = std::tuple_size<decltype(t))>::value;

遍历tuple中的每个元素

  因为tuple的参数是变长的,也没有for_each函数,如果我们想遍历tuple中的每个元素,需要自己写代码实现。比如我要打印tuple中的每个元素。

复制代码
复制代码
template<class Tuple, std::size_t N>
struct TuplePrinter {
    static void print(const Tuple& t)
    {
        TuplePrinter<Tuple, N - 1>::print(t);
        std::cout << ", " << std::get<N - 1>(t);
    }
};

template<class Tuple>
struct TuplePrinter<Tuple, 1>{
    static void print(const Tuple& t)
    {
        std::cout << std::get<0>(t);
    }
};

template<class... Args>
void PrintTuple(const std::tuple<Args...>& t)
{
    std::cout << "(";
    TuplePrinter<decltype(t), sizeof...(Args)>::print(t);
    std::cout << ")\n";
}
复制代码
复制代码

根据tuple元素值获取其对应的索引位置

复制代码
复制代码
namespace detail
{
    template<int I, typename T, typename... Args>
    struct find_index
    {
        static int call(std::tuple<Args...> const& t, T&& val)
        {
            return (std::get<I - 1>(t) == val) ? I - 1 :
                find_index<I - 1, T, Args...>::call(t, std::forward<T>(val));
        }
    };

    template<typename T, typename... Args>
    struct find_index<0, T, Args...>
    {
        static int call(std::tuple<Args...> const& t, T&& val)
        {
            return (std::get<0>(t) == val) ? 0 : -1;
        }
    };
}

template<typename T, typename... Args>
int find_index(std::tuple<Args...> const& t, T&& val)
{
    return detail::find_index<0, sizeof...(Args) - 1, T, Args...>::
           call(t, std::forward<T>(val));
}

int main()
{
    std::tuple<int, int, int, int> a(2, 3, 1, 4);
    std::cout << find_index(a, 1) << std::endl; // Prints 2
    std::cout << find_index(a, 2) << std::endl; // Prints 0
    std::cout << find_index(a, 5) << std::endl; // Prints -1 (not found)
}
复制代码
复制代码

展开tuple,并将tuple元素作为函数的参数。这样就可以根据需要对tuple元素进行处理了

复制代码
#include <tuple>
#include <type_traits>
#include <utility>

template<size_t N>
struct Apply {
template<typename F, typename T, typename... A>
static inline auto apply(F && f, T && t, A &&... a)
-> decltype(Apply<N-1>::apply(
::std::forward<F>(f), ::std::forward<T>(t),
::std::get<N-1>(::std::forward<T>(t)), 
::std::forward<A>(a)...
))
{
return Apply<N-1>::apply(::std::forward<F>(f), 
::std::forward<T>(t),
::std::get<N-1>(::std::forward<T>(t)), 
::std::forward<A>(a)...
);
}
};

template<>
struct Apply<0> {
template<typename F, typename T, typename... A>
static inline auto apply(F && f, T &&, A &&... a)
-> decltype(::std::forward<F>(f) 
(::std::forward<A>(a)...))
{
return ::std::forward<F>(f)(::std::forward<A> 
(a)...);
}
};

template<typename F, typename T>
inline auto apply(F && f, T && t)
-> decltype(Apply< ::std::tuple_size<
typename ::std::decay<T>::type
>::value>::apply(::std::forward<F>(f), 
::std::forward<T>(t)))
{
return Apply< ::std::tuple_size<
typename ::std::decay<T>::type
>::value>::apply(::std::forward<F>(f), 
::std::forward<T>(t));
}

void one(int i, double d)
{
std::cout << "function one(" << i << ", " << d << 
");\n";
}
int two(int i)
{
std::cout << "function two(" << i << ");\n";
return i;
}

//测试代码
int main()
{
std::tuple<int, double> tup(23, 4.5);
apply(one, tup);

int d = apply(two, std::make_tuple(2));

return 0;
}
复制代码

 

  看到这里,想必大家对tuple有了一个全面的认识了吧,怎么样,它是简约而不简单吧。对模板元不熟悉的童鞋可以不看tuple高级用法部分,不要为看不懂而捉急,没事的,高级部分一般用不到,知道基本用法就够用了。

tuple和vector比较:

vector只能容纳同一种类型的数据,tuple可以容纳任意类型的数据;

vector和variant比较:

二者都可以容纳不同类型的数据,但是variant的类型个数是固定的,而tuple的类型个数不是固定的,是变长的,更为强大。

 

decltype Operator

The decltype operator yields the type of a specified expression_r. The decltype operator, together with the auto keyword, is useful primarily to developers who write template libraries. Use auto and decltype to declare a template function whose return type depends on the types of its template arguments. Or, use auto and decltype to declare a template function that wraps a call to another function, and then returns the return type of the wrapped function.

 

#include <iostream>
#include <string>
#include <utility>
#include <iomanip>

int var;
const int&& fx();
struct A {
 double x;
};

const A* a = new A();

decltype(fx()) aa = 20;
decltype(var) ab = 10;
decltype(a->x) ac = 1.0;
decltype((a->x)) ad = 2.0;

template<typename T, typename U>
auto myFunc(T&& t, U&& u) -> decltype (forward<T>(t) + forward<U>(u))
        { return forward<T>(t) + forward<U>(u); };

template<typename T1, typename T2>
auto Plus(T1&& t1, T2&& t2) ->
   decltype(std::forward<T1>(t1) + std::forward<T2>(t2))
{
   return std::forward<T1>(t1) + std::forward<T2>(t2);
}

class X
{
   friend X operator+(const X& x1, const X& x2)
   {
      return X(x1.m_data + x2.m_data);
   }

public:
   X(int data = 0) : m_data(data) {}
   int Dump() const { return m_data;}
private:
   int m_data;
};


int main(int argc,char** argv){
   int i = 4;
   std::cout << "Plus(i, 9) = " <<
    Plus(i, 9) << std::endl;

   // Floating point
   float dx = 4.0;
   float dy = 9.5;
   std::cout <<  
      std::setprecision(3) <<
      "Plus(dx, dy) = " <<
      Plus(dx, dy) << std::endl;

   // String     
   std::string hello = "Hello, ";
   std::string world = "world!";
   std::cout << Plus(hello, world) << std::endl;

   // Custom type
   X x1(20);
   X x2(22);
   X x3 = Plus(x1, x2);
   std::cout <<
      "x3.Dump() = " <<
      x3.Dump() << std::endl;


 std::cout << aa << std::endl;
 std::cout << ab << std::endl;
 std::cout << ac << std::endl;
 std::cout << ad << std::endl;
 return 0;
}

   在C++中经常要用到很长的变量名,如果已经有变量和你将使用的变量是一个类型,即可使用decltype关键字

来申明一样的类型变量。

decltype原理

     返回现有变量类型,decltype是一个关键字,而不是一个函数,这有啥区别呢?decltype在编译阶段返回变量类

型,而不是在运行阶段传递不同变量返回不同值。

decltype使用范例

1、复杂已知变量类型

  1. map<string, vector<string>> str_map;  
  2. decltype(str_map) str_map_new;  
map<string, vector<string>> str_map;
decltype(str_map) str_map_new;

2、表达式返回值类型

  1. int a, b;  
  2. decltype(a + b) a;  
int a, b;
decltype(a + b) a;


3、函数返回值

  1. int foo(int i) {  
  2.      return i;  
  3. }  
  4. double foo(double d) {  
  5.      return d;  
  6. }  
  7.   
  8. template<typename T>  
  9. auto getNum(T t)->decltype(foo(t)) {  
  10.     return foo(t);  
  11. }  
int foo(int i) {
     return i;
}
double foo(double d) {
     return d;
}

template<typename T>
auto getNum(T t)->decltype(foo(t)) {
    return foo(t);
}


注意
1、decltype两个括号返回变量引用类型

  1. int i;  
  2. decltype((i)) r = i;  
  3. decltype(i) a;  
int i;
decltype((i)) r = i;
decltype(i) a;

2、auto和decltype配合使用可以实现不同返回类型

返回值 decltype(表达式)

[返回值的类型是表达式参数的类型]


这个可也用来决定表达式的类型,就像Bjarne暗示的一样,如果我们需要去初始化某种类型的变量,auto是最简单的选择,但是如果我们所需的类型不是一个变量,例如返回值这时我们可也试一下decltype。


现在我们回看一些例子我们先前做过的,

  1. template <class U, class V>  
  2. void Somefunction(U u, V v)  
  3. {  
  4.     result = u*v;//now what type would be the result???   
  5.     decltype(u*v) result = u*v;//Hmm .... we got what we want   
  6. }  
template <class U, class V>
void Somefunction(U u, V v)
{
	result = u*v;//now what type would be the result???
	decltype(u*v) result = u*v;//Hmm .... we got what we want
}


 

在下面的一个段落我将会让你熟悉这个观念用 auto 和 decltype 来声明模板函数的返回值,其类型依靠模板参数。



1. 如果这个表达式是个函数,decltype 给出的类型为函数返回值的类型。

  1. int add(int i, int j){ return i+j; }  
  2. decltype(add(5,6)) var = 5;//Here the type of var is return of add() -> which is int  
	int add(int i, int j){ return i+j; }
	decltype(add(5,6)) var = 5;//Here the type of var is return of add() -> which is int


2.如果表达式是一个左值类型,那么 decltype 给出的类型为表达式左值引用类型。

  1. struct M { double x; };  
  2.   
  3. double pi = 3.14;  
  4. const M* m = new M();  
  5. decltype( (m->x) ) piRef = pi;  
  6.   
  7.     // Note: Due to the inner bracets the inner statement is evaluated as expression,   
  8.     // rather than member 'x' and as type of x is double and as this is lvale   
  9.     // the return of declspec is double& and as 'm' is a const pointer    
  10.     // the return is actually const double&.   
  11.     // So the type of piRef is const double&  
struct M { double x; };

double pi = 3.14;
const M* m = new M();
decltype( (m->x) ) piRef = pi;

    // Note: Due to the inner bracets the inner statement is evaluated as expression,
    // rather than member 'x' and as type of x is double and as this is lvale
    // the return of declspec is double& and as 'm' is a const pointer 
    // the return is actually const double&.
    // So the type of piRef is const double&

3.非常重要的标记一下,decltype 不会执行表达式而auto会,他仅仅推论一下表达式的类型。

  1. int foo(){}  
  2. decltype( foo() ) x; // x is an int and note that    
  3.                      // foo() is not actually called at runtime  
    int foo(){}
    decltype( foo() ) x; // x is an int and note that 
                         // foo() is not actually called at runtime


跟踪返回类型:

这对 C++ 开发者来说是一个全新的特性,直到现在函数的返回类型必须放在函数名的前面。到了 C++11,我们也可以将函数返回值的类型放在函数声明后,当然仅需要用 auto 替代返回类型。现在我们想知道怎么做,让我们来寻找答案:

  1. template<class U, class V>  
  2. ??? Multiply(U u, V v)    // how to specifiy the type of the return value   
  3. {   
  4.    return u*v;  
  5. }  
template<class U, class V>
??? Multiply(U u, V v)    // how to specifiy the type of the return value
{ 
   return u*v;
}


我们明显的不能像这样:

  1. template<class U, class V>  
  2. decltype(u*v) Multiply(U u, V v)    // Because u & v are not defined before Multiply.   
  3.                      //  What to do...what to do !!!   
  4. {   
  5.    return u*v;  
  6. }  
template<class U, class V>
decltype(u*v) Multiply(U u, V v)    // Because u & v are not defined before Multiply.
                     //  What to do...what to do !!!
{ 
   return u*v;
}


这种情况我们可也使用 auto 然后当我们使用 decltype(u*v) 作为返回值这个类型便知晓了.

这是不是很酷?

  1. template<class U, class V>  
  2. auto Multiply(U u, V v) -> decltype(u*v)    // Note -> after the function bracet.   
  3. {   
  4.    return u*v;  
  5. }  

最近群里比较热闹,大家都在山寨c++11的std::bind,三位童孩分别实现了自己的bind,代码分别在这里:

  • 木头云的实现:连接稍后补上。
  • mr.li的实现:https://code.google.com/p/y-code-svn/source/browse/#svn%2Ftrunk%2Fc%2B%2B%2FBex%2Fsrc%2FBex%2Fbind
  • null的实现:http://www.cnblogs.com/xusd-null/p/3693817.html#2934538

这些实现思路和ms stl的std::bind的实现思路是差不多的,只是在实现的细节上有些不同。个人觉得木头云的实现更简洁,本文中的简单实现也是基于木头云的bind之上的,在此表示感谢。下面我们来分析一下bind的基本原理。

bind的基本原理

bind的思想实际上是一种延迟计算的思想,将可调用对象保存起来,然后在需要的时候再调用。而且这种绑定是非常灵活的,不论是普通函数、函数对象、还是成员函数都可以绑定,而且其参数可以支持占位符,比如你可以这样绑定一个二元函数auto f = bind(&func, _1, _2);,调用的时候通过f(1,2)实现调用。关于bind的用法更多的介绍可以参考我博客中介绍:http://www.cnblogs.com/qicosmos/p/3302144.html。

要实现一个bind需要解决两个问题,第一个是保存可调用对象及其形参,第二个是如何实现调用。下面来分析如何解决这两个问题。

保存可调用对象

实现bind的首先要解决的问题是如何将可调用对象保存起来,以便在后面调用。要保存可调用对象,需要保存两个东西,一个是可调用对象的实例,另一个是可调用对象的形参。保存可调用对象的实例相很简单,因为bind时直接要传这个可调用对象的,将其作为一个成员变量即可。而保存可调用对象的形参就麻烦一点,因为这个形参是变参,不能直接将变参作为成员变量。如果要保存变参的话,我们需要用tuple来将变参保存起来。

可调用对象的执行

bind的形参因为是变参,可以是0个,也可能是多个,大部分情况下是占位符,还有可能占位符和实参都有。正是由于bind绑定的灵活性,导致我们不得不在调用的时候需要找出哪些是占位符,哪些是实参。如果某个一参数是实参我们就不处理,如果是占位符,我们就要将这个占位符替换为对应的实参。比如我们绑定了一个三元函数:auto f = bind(&func, _1, 2, _2);调用时f(1,3);由于绑定时有三个参数,一个实参,两个占位符,调用时传入了两个实参,这时我们就要将占位符_1替换为实参1,占位符_2替换为实参3。这个占位符的替换需要按照调用实参的顺序来替换,如果调用时的实参个数比占位符要多,则忽略多余的实参。
调用的实参,我们也会先将其转换为tuple,用于在后面去替换占位符时,选取合适的实参。

bind实现的关键技术

将tuple展开为变参

前面讲到绑定可调用对象时,将可调用对象的形参(可能含占位符)保存起来,保存到tuple中了。到了调用阶段,我们就要反过来将tuple展开为可变参数,因为这个可变参数才是可调用对象的形参,否则就无法实现调用了。这里我们会借助于一个整形序列来将tuple变为可变参数,在展开tuple的过程中我们还需要根据占位符来选择合适实参,即占位符要替换为调用实参。

根据占位符来选择合适的实参

这个地方比较关键,因为tuple中可能含有占位符,我们展开tuple时,如果发现某个元素类型为占位符,则从调用的实参生成的tuple中取出一个实参,用来作为变参的一个参数;当某个类型不为占位符时,则直接从绑定时生成的形参tuple中取出参数,用来作为变参的一个参数。最终tuple被展开为一个变参列表,这时,这个列表中没有占位符了,全是实参,就可以实现调用了。这里还有一个细节要注意,替换占位符的时候,如何从tuple中选择合适的参数呢,因为替换的时候要根据顺序来选择。这里是通过占位符的模板参数I来选择,因为占位符place_holder<I>的实例_1实际上place_holder<1>, 占位符实例_2实际上是palce_holder<2>,我们是可以根据占位符的模板参数来获取其顺序的。

bind的简单实现

#include <tuple>
#include <type_traits>
using namespace std;

template<int...>
struct IndexTuple{};

template<int N, int... Indexes>
struct MakeIndexes : MakeIndexes<N - 1, N - 1, Indexes...>{};

template<int... indexes>
struct MakeIndexes<0, indexes...>
{
	typedef IndexTuple<indexes...> type;
};

template <int I>
struct Placeholder
{
};

Placeholder<1> _1; Placeholder<2> _2; Placeholder<3> _3; Placeholder<4> _4; Placeholder<5> 

_5; Placeholder<6> _6; Placeholder<7> _7;
Placeholder<8> _8; Placeholder<9> _9; Placeholder<10> _10;

// result type traits

template <typename F>
struct result_traits : result_traits<decltype(&F::operator())> {};

template <typename T>
struct result_traits<T*> : result_traits<T> {};

/* check function */

template <typename R, typename... P>
struct result_traits<R(*)(P...)> { typedef R type; };

/* check member function */
template <typename R, typename C, typename... P> 
struct result_traits<R(C::*)(P...)> { typedef R type; };

template <typename T, class Tuple>
inline auto select(T&& val, Tuple&)->T&&
{
	return std::forward<T>(val);
}

template <int I, class Tuple>
inline auto select(Placeholder<I>&, Tuple& tp) -> decltype(std::get<I - 1>(tp))
{
	return std::get<I - 1>(tp);
}

// The invoker for call a callable
template <typename T>
struct is_pointer_noref
	: std::is_pointer<typename std::remove_reference<T>::type>
{};

template <typename T>
struct is_memfunc_noref
	: std::is_member_function_pointer<typename std::remove_reference<T>::type>
{};

template <typename R, typename F, typename... P>
inline typename std::enable_if<is_pointer_noref<F>::value,
R>::type invoke(F&& f, P&&... par)
{
	return (*std::forward<F>(f))(std::forward<P>(par)...);
}

template <typename R, typename F, typename P1, typename... P>
inline typename std::enable_if<is_memfunc_noref<F>::value && is_pointer_noref<P1>::value,
R>::type invoke(F&& f, P1&& this_ptr, P&&... par)
{
	return (std::forward<P1>(this_ptr)->*std::forward<F>(f))(std::forward<P>(par)...);
}

template <typename R, typename F, typename P1, typename... P>
inline typename std::enable_if<is_memfunc_noref<F>::value && !is_pointer_noref<P1>::value,
R>::type invoke(F&& f, P1&& this_obj, P&&... par)
{
	return (std::forward<P1>(this_obj).*std::forward<F>(f))(std::forward<P>(par)...);
}

template <typename R, typename F, typename... P>
inline typename std::enable_if<!is_pointer_noref<F>::value && !is_memfunc_noref<F>::value,
R>::type invoke(F&& f, P&&... par)
{
	return std::forward<F>(f)(std::forward<P>(par)...);
}

template<typename Fun, typename... Args>
struct Bind_t
{
	typedef typename decay<Fun>::type FunType;
	typedef std::tuple<typename decay<Args>::type...> ArgType;

	typedef typename result_traits<FunType>::type	 result_type;
public:
	template<class F, class... BArgs>
	Bind_t(F& f,  BArgs&... args) : m_func(f), m_args(args...)
	{	

	}

	template<typename F, typename... BArgs>
	Bind_t(F&& f, BArgs&&... par) : m_func(std::move(f)), m_args(std::move(par)...)
	{}

	template <typename... CArgs>
	result_type operator()(CArgs&&... args)
	{
		return do_call(MakeIndexes<std::tuple_size<ArgType>::value>::type(), 

std::forward_as_tuple(std::forward<CArgs>(args)...));
	}

	template<typename ArgTuple, int... Indexes >
	result_type do_call(IndexTuple< Indexes... >& in, ArgTuple& argtp)
	{
		return simple::invoke<result_type>(m_func, select(std::get<Indexes>(m_args), 

argtp)...);
		//return m_func(select(std::get<Indexes>(m_args), argtp)...);
	}

private:
	FunType m_func;
	ArgType m_args;
};

template <typename F, typename... P>
inline Bind_t<F, P...> Bind(F&& f, P&&... par)
{
	return Bind_t<F, P...>(std::forward<F>(f), std::forward<P>(par)...);
}

template <typename F, typename... P>
inline Bind_t<F, P...> Bind(F& f, P&... par)
{
	return Bind_t<F, P...>(f, par...);
}
View Code

测试代码:

void TestFun1(int a, int b, int c)
{
}

void TestBind1()
{
    Bind(&TestFun1,  _1,  _2,  _3)(1, 2, 3);
    Bind(&TestFun1, 4, 5, _1)(6);
    Bind(&TestFun1, _1, 4, 5)(3);
    Bind(&TestFun1, 3, _1,  5)(4);
}
View Code

bind更多的实现细节

由于只是展示bind实现的关键技术,很多的实现细节并没有处理,比如参数是否是引用、右值、const volotile、绑定非静态的成员变量都还没处理,仅仅供学习之用,并非是重复发明轮子,只是展示bind是如何实现, 实际项目中还是使用c++11的std::bind为好。null同学还图文并茂的介绍了bind的过程:http://www.cnblogs.com/xusd-null/p/3698969.html,有兴趣的童孩可以看看.

关于bind的使用

在实际使用过程中,我更喜欢使用lambda表达式,因为lambda表达式使用起来更简单直观,lambda表达式在绝大多数情况下可以替代bind。

如果你觉得这篇文章对你有用,可以点一下推荐,谢谢。

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/flyingleo1981/article/details/26679267

智能推荐

python简易爬虫v1.0-程序员宅基地

文章浏览阅读1.8k次,点赞4次,收藏6次。python简易爬虫v1.0作者:William Ma (the_CoderWM)进阶python的首秀,大部分童鞋肯定是做个简单的爬虫吧,众所周知,爬虫需要各种各样的第三方库,例如scrapy, bs4, requests, urllib3等等。此处,我们先从最简单的爬虫开始。首先,我们需要安装两个第三方库:requests和bs4。在cmd中输入以下代码:pip install requestspip install bs4等安装成功后,就可以进入pycharm来写爬虫了。爬

安装flask后vim出现:error detected while processing /home/zww/.vim/ftplugin/python/pyflakes.vim:line 28_freetorn.vim-程序员宅基地

文章浏览阅读2.6k次。解决方法:解决方法可以去github重新下载一个pyflakes.vim。执行如下命令git clone --recursive git://github.com/kevinw/pyflakes-vim.git然后进入git克降目录,./pyflakes-vim/ftplugin,通过如下命令将python目录下的所有文件复制到~/.vim/ftplugin目录下即可。cp -R ...._freetorn.vim

HIT CSAPP大作业:程序人生—Hello‘s P2P-程序员宅基地

文章浏览阅读210次,点赞7次,收藏3次。本文简述了hello.c源程序的预处理、编译、汇编、链接和运行的主要过程,以及hello程序的进程管理、存储管理与I/O管理,通过hello.c这一程序周期的描述,对程序的编译、加载、运行有了初步的了解。_hit csapp

18个顶级人工智能平台-程序员宅基地

文章浏览阅读1w次,点赞2次,收藏27次。来源:机器人小妹  很多时候企业拥有重复,乏味且困难的工作流程,这些流程往往会减慢生产速度并增加运营成本。为了降低生产成本,企业别无选择,只能自动化某些功能以降低生产成本。  通过数字化..._人工智能平台

electron热加载_electron-reloader-程序员宅基地

文章浏览阅读2.2k次。热加载能够在每次保存修改的代码后自动刷新 electron 应用界面,而不必每次去手动操作重新运行,这极大的提升了开发效率。安装 electron 热加载插件热加载虽然很方便,但是不是每个 electron 项目必须的,所以想要舒服的开发 electron 就只能给 electron 项目单独的安装热加载插件[electron-reloader]:// 在项目的根目录下安装 electron-reloader,国内建议使用 cnpm 代替 npmnpm install electron-relo._electron-reloader

android 11.0 去掉recovery模式UI页面的选项_android recovery 删除 部分菜单-程序员宅基地

文章浏览阅读942次。在11.0 进行定制化开发,会根据需要去掉recovery模式的一些选项 就是在device.cpp去掉一些选项就可以了。_android recovery 删除 部分菜单

随便推点

echart省会流向图(物流运输、地图)_java+echart地图+物流跟踪-程序员宅基地

文章浏览阅读2.2k次,点赞2次,收藏6次。继续上次的echart博客,由于省会流向图是从echart画廊中直接取来的。所以直接上代码<!DOCTYPE html><html><head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /&_java+echart地图+物流跟踪

Ceph源码解析:读写流程_ceph 发送数据到其他副本的源码-程序员宅基地

文章浏览阅读1.4k次。一、OSD模块简介1.1 消息封装:在OSD上发送和接收信息。cluster_messenger -与其它OSDs和monitors沟通client_messenger -与客户端沟通1.2 消息调度:Dispatcher类,主要负责消息分类1.3 工作队列:1.3.1 OpWQ: 处理ops(从客户端)和sub ops(从其他的OSD)。运行在op_tp线程池。1...._ceph 发送数据到其他副本的源码

进程调度(一)——FIFO算法_进程调度fifo算法代码-程序员宅基地

文章浏览阅读7.9k次,点赞3次,收藏22次。一 定义这是最早出现的置换算法。该算法总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面予以淘汰。该算法实现简单,只需把一个进程已调入内存的页面,按先后次序链接成一个队列,并设置一个指针,称为替换指针,使它总是指向最老的页面。但该算法与进程实际运行的规律不相适应,因为在进程中,有些页面经常被访问,比如,含有全局变量、常用函数、例程等的页面,FIFO 算法并不能保证这些页面不被淘汰。这里,我_进程调度fifo算法代码

mysql rownum写法_mysql应用之类似oracle rownum写法-程序员宅基地

文章浏览阅读133次。rownum是oracle才有的写法,rownum在oracle中可以用于取第一条数据,或者批量写数据时限定批量写的数量等mysql取第一条数据写法SELECT * FROM t order by id LIMIT 1;oracle取第一条数据写法SELECT * FROM t where rownum =1 order by id;ok,上面是mysql和oracle取第一条数据的写法对比,不过..._mysql 替换@rownum的写法

eclipse安装教程_ecjelm-程序员宅基地

文章浏览阅读790次,点赞3次,收藏4次。官网下载下载链接:http://www.eclipse.org/downloads/点击Download下载完成后双击运行我选择第2个,看自己需要(我选择企业级应用,如果只是单纯学习java选第一个就行)进入下一步后选择jre和安装路径修改jvm/jre的时候也可以选择本地的(点后面的文件夹进去),但是我们没有11版本的,所以还是用他的吧选择接受安装中安装过程中如果有其他界面弹出就点accept就行..._ecjelm

Linux常用网络命令_ifconfig 删除vlan-程序员宅基地

文章浏览阅读245次。原文链接:https://linux.cn/article-7801-1.htmlifconfigping &lt;IP地址&gt;:发送ICMP echo消息到某个主机traceroute &lt;IP地址&gt;:用于跟踪IP包的路由路由:netstat -r: 打印路由表route add :添加静态路由路径routed:控制动态路由的BSD守护程序。运行RIP路由协议gat..._ifconfig 删除vlan