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

智能推荐

JWT(Json Web Token)实现无状态登录_无状态token登录-程序员宅基地

文章浏览阅读685次。1.1.什么是有状态?有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。缺点是什么?服务端保存大量数据,增加服务端压力 服务端保存用户状态,无法进行水平扩展 客户端请求依赖服务.._无状态token登录

SDUT OJ逆置正整数-程序员宅基地

文章浏览阅读293次。SDUT OnlineJudge#include<iostream>using namespace std;int main(){int a,b,c,d;cin>>a;b=a%10;c=a/10%10;d=a/100%10;int key[3];key[0]=b;key[1]=c;key[2]=d;for(int i = 0;i<3;i++){ if(key[i]!=0) { cout<<key[i.

年终奖盲区_年终奖盲区表-程序员宅基地

文章浏览阅读2.2k次。年终奖采用的平均每月的收入来评定缴税级数的,速算扣除数也按照月份计算出来,但是最终减去的也是一个月的速算扣除数。为什么这么做呢,这样的收的税更多啊,年终也是一个月的收入,凭什么减去12*速算扣除数了?这个霸道(不要脸)的说法,我们只能合理避免的这些跨级的区域了,那具体是那些区域呢?可以参考下面的表格:年终奖一列标红的一对便是盲区的上下线,发放年终奖的数额一定一定要避免这个区域,不然公司多花了钱..._年终奖盲区表

matlab 提取struct结构体中某个字段所有变量的值_matlab读取struct类型数据中的值-程序员宅基地

文章浏览阅读7.5k次,点赞5次,收藏19次。matlab结构体struct字段变量值提取_matlab读取struct类型数据中的值

Android fragment的用法_android reader fragment-程序员宅基地

文章浏览阅读4.8k次。1,什么情况下使用fragment通常用来作为一个activity的用户界面的一部分例如, 一个新闻应用可以在屏幕左侧使用一个fragment来展示一个文章的列表,然后在屏幕右侧使用另一个fragment来展示一篇文章 – 2个fragment并排显示在相同的一个activity中,并且每一个fragment拥有它自己的一套生命周期回调方法,并且处理它们自己的用户输_android reader fragment

FFT of waveIn audio signals-程序员宅基地

文章浏览阅读2.8k次。FFT of waveIn audio signalsBy Aqiruse An article on using the Fast Fourier Transform on audio signals. IntroductionThe Fast Fourier Transform (FFT) allows users to view the spectrum content of _fft of wavein audio signals

随便推点

Awesome Mac:收集的非常全面好用的Mac应用程序、软件以及工具_awesomemac-程序员宅基地

文章浏览阅读5.9k次。https://jaywcjlove.github.io/awesome-mac/ 这个仓库主要是收集非常好用的Mac应用程序、软件以及工具,主要面向开发者和设计师。有这个想法是因为我最近发了一篇较为火爆的涨粉儿微信公众号文章《工具武装的前端开发工程师》,于是建了这么一个仓库,持续更新作为补充,搜集更多好用的软件工具。请Star、Pull Request或者使劲搓它 issu_awesomemac

java前端技术---jquery基础详解_简介java中jquery技术-程序员宅基地

文章浏览阅读616次。一.jquery简介 jQuery是一个快速的,简洁的javaScript库,使用户能更方便地处理HTML documents、events、实现动画效果,并且方便地为网站提供AJAX交互 jQuery 的功能概括1、html 的元素选取2、html的元素操作3、html dom遍历和修改4、js特效和动画效果5、css操作6、html事件操作7、ajax_简介java中jquery技术

Ant Design Table换滚动条的样式_ant design ::-webkit-scrollbar-corner-程序员宅基地

文章浏览阅读1.6w次,点赞5次,收藏19次。我修改的是表格的固定列滚动而产生的滚动条引用Table的组件的css文件中加入下面的样式:.ant-table-body{ &amp;amp;::-webkit-scrollbar { height: 5px; } &amp;amp;::-webkit-scrollbar-thumb { border-radius: 5px; -webkit-box..._ant design ::-webkit-scrollbar-corner

javaWeb毕设分享 健身俱乐部会员管理系统【源码+论文】-程序员宅基地

文章浏览阅读269次。基于JSP的健身俱乐部会员管理系统项目分享:见文末!

论文开题报告怎么写?_开题报告研究难点-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏15次。同学们,是不是又到了一年一度写开题报告的时候呀?是不是还在为不知道论文的开题报告怎么写而苦恼?Take it easy!我带着倾尽我所有开题报告写作经验总结出来的最强保姆级开题报告解说来啦,一定让你脱胎换骨,顺利拿下开题报告这个高塔,你确定还不赶快点赞收藏学起来吗?_开题报告研究难点

原生JS 与 VUE获取父级、子级、兄弟节点的方法 及一些DOM对象的获取_获取子节点的路径 vue-程序员宅基地

文章浏览阅读6k次,点赞4次,收藏17次。原生先获取对象var a = document.getElementById("dom");vue先添加ref <div class="" ref="divBox">获取对象let a = this.$refs.divBox获取父、子、兄弟节点方法var b = a.childNodes; 获取a的全部子节点 var c = a.parentNode; 获取a的父节点var d = a.nextSbiling; 获取a的下一个兄弟节点 var e = a.previ_获取子节点的路径 vue