原版:http://www.codeproject.com/Articles/570638/Ten-Cplusplus-Features-Every-Cplusplus-Developer

译版:http://blogs.ejb.cc/archives/7190/top-10-new-features-you-should-know-about-c-11

This article discusses a series of features new to C++11 that all developers should learn and use. There are lots of new additions to the language and the standard library, and this article barely scratches the surface. However, I believe some of these new features should become routine for all C++ developers. You could probably find many similar articles evangelizing different C++11 features. This is my attempt to assemble a list of C++ features that should be a norm nowadays.

Table of contents:

auto

Before C++11 the auto keyword was used for storage duration specification. In the new standard its purpose was changed towards type inference. auto is now a sort of placeholder for a type, telling the compiler it has to deduce the actual type of a variable that is being declared from its initializer. It can be used when declaring variables in different scopes such as namespaces, blocks or initialization statement of for loops.

 Collapse | Copy Code
auto i = 42;        // i is an int
auto l = 42LL; // l is an long long
auto p = new foo(); // p is a foo*

Using auto usually means less code (unless your type is int which is one letter shorter). Think of iterators in STL that you always had to write while iterating over containers. It makes obsolete creating typedefs just for the sake of simplicity.

 Collapse | Copy Code
std::map<std::string, std::vector<int>> map;
for(auto it = begin(map); it != end(map); ++it)
{
}

You should note that auto cannot be used as the return type of a function. However, you can use auto in place of the return type of function, but in this case the function must have a trailing return type. In this case auto does not tell the compiler it has to infer the type, it only instructs it to look for the return type at the end of the function. In the example below the return type of function compose is the return type of operator+ that sums values of types T1 and T2.

 Collapse | Copy Code
template <typename T1, typename T2>
auto compose(T1 t1, T2 t2) -> decltype(t1 + t2)
{
return t1+t2;
}
auto v = compose(2, 3.14); // v's type is double

nullptr

Zero used to be the value of null pointers, and that has drawbacks due to the implicit conversion to integral types. The keyword nullptr denotes a value of type std::nullptr_t that represents the null pointer literal. Implicit conversions exists from nullptr to null pointer value of any pointer type and any pointer-to-member types, but also to bool (as false). But no implicit conversion to integral types exist.

 Collapse | Copy Code
void foo(int* p) {}

void bar(std::shared_ptr<int> p) {}

int* p1 = NULL;
int* p2 = nullptr;
if(p1 == p2)
{
} foo(nullptr);
bar(nullptr); bool f = nullptr;
int i = nullptr; // error: A native nullptr can only be converted to bool or, using reinterpret_cast, to an integral type

For backward compatibility 0 is still a valid null pointer value.

Range-based for loops

C++11 augmented the for statement to support the "foreach" paradigm of iterating over collections. In the new form, it is possible to iterate over C-like arrays, initializer lists and anything for which the non-member begin() and end()functions are overloaded.

This for each for is useful when you just want to get and do something with the elements of a collection/array and don't care about indexes, iterators or number of elements.

 Collapse | Copy Code
std::map<std::string, std::vector<int>> map;
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
map["one"] = v; for(const auto& kvp : map)
{
std::cout << kvp.first << std::endl; for(auto v : kvp.second)
{
std::cout << v << std::endl;
}
} int arr[] = {1,2,3,4,5};
for(int& e : arr)
{
e = e*e;
}

Override and final

I always founded the virtual methods badly designed in C++ because there wasn't (and still isn't) a mandatory mechanism to mark virtual methods as overridden in derived classes. The virtual keyword is optional and that makes reading code a bit harder, because you may have to look through the top of the hierarchy to check if the method is virtual. I have always used, and encouraged people to use the virtual keyword on derived classes also, to make the code easier to read. However, there are subtle errors that can still arise. Take for instance the following example:

 Collapse | Copy Code
class B
{
public:
virtual void f(short) {std::cout << "B::f" << std::endl;}
}; class D : public B
{
public:
virtual void f(int) {std::cout << "D::f" << std::endl;}
};

D::f is supposed to override B::f. However, the signature differ, one takes a short, one takes an int, thereforB::f is just another method with the same name (and overload) and not an override. You may call f() through a pointer to B and expect to print D::f, but it's printing B::f.

Here is another subtle error: the parameters are the same, but the method in the base class is marked const, while me method in the derived is not.

 Collapse | Copy Code
class B
{
public:
virtual void f(int) const {std::cout << "B::f " << std::endl;}
}; class D : public B
{
public:
virtual void f(int) {std::cout << "D::f" << std::endl;}
};

Again, these two are overloads and not overrides, so if you call f() through a pointer to B it will print B::f and notD::f.

Fortunately there is now a way to describe your intentions. Two new special identifiers (not keywords) have been added: override, to indicate that a method is supposed to be an override of a virtual method in a base class, andfinal, to indicate that a derived class shall not override a virtual method. The first example would become:

 Collapse | Copy Code
class B
{
public:
virtual void f(short) {std::cout << "B::f" << std::endl;}
}; class D : public B
{
public:
virtual void f(int) override {std::cout << "D::f" << std::endl;}
};

This now triggers a compiler error (the same error you'd get for the second example too, if using the overridespecifier):

'D::f' : method with override specifier 'override' did not override any base class methods

On the other hand if you intend to make a method impossible to override any more (down the hierarchy) mark it asfinal. That can be in the base class, or any derived class. If it's in a derived classes you can use both the overrideand final specifiers.

 Collapse | Copy Code
class B
{
public:
virtual void f(int) {std::cout << "B::f" << std::endl;}
}; class D : public B
{
public:
virtual void f(int) override final {std::cout << "D::f" << std::endl;}
}; class F : public D
{
public:
virtual void f(int) override {std::cout << "F::f" << std::endl;}
};

function declared as 'final' cannot be overridden by 'F::f'

Strongly-typed enums

"Traditional" enums in C++ have some drawbacks: they export their enumerators in the surrounding scope (which can lead to name collisions, if two different enums in the same have scope define enumerators with the same name), they are implicitly converted to integral types and cannot have a user-specified underlying type.

These issues have been fixed in C++ 11 with the introduction of a new category of enums, called strongly-typed enums. They are specified with the enum class keywords. They no longer export their enumerators in the surrounding scope, are no longer implicitly converted to integral types and can have a user-specified underlying type (a feature also added for traditional enums).

 Collapse | Copy Code
enum class Options {None, One, All};
Options o = Options::All;

Smart pointers

There have been tons of articles written on this subject, therefore I just want to mention the smart pointers with reference counting and auto releasing of owned memory that are available:

  • unique_ptr: should be used when ownership of a memory resource does not have to be shared (it doesn't have a copy constructor), but it can be transferred to another unique_ptr (move constructor exists).
  • shared_ptr: should be used when ownership of a memory resource should be shared (hence the name).
  • weak_ptr: holds a reference to an object managed by a shared_ptr, but does not contribute to the reference count; it is used to break dependency cycles (think of a tree where the parent holds an owning reference (shared_ptr) to its children, but the children also must hold a reference to the parent; if this second reference was also an owning one, a cycle would be created and no object would ever be released).

On the other hand the auto_ptr is obsolete and should no longer be used.

When you should unique_ptr and when you should use shared_ptr depends on the ownership requirements and I recommend reading this discussion.

The first example below shows unique_ptr. If you want to transfer ownership of an object to another unique_ptruse std::move (I'll discuss this function in the last paragraph). After the ownership transfer, the smart pointer that ceded the ownership becomes null and get() returns nullptr.

 Collapse | Copy Code
void foo(int* p)
{
std::cout << *p << std::endl;
}
std::unique_ptr<int> p1(new int(42));
std::unique_ptr<int> p2 = std::move(p1); // transfer ownership if(p1)
foo(p1.get()); (*p2)++; if(p2)
foo(p2.get());

The second example shows shared_ptr. Usage is similar, though the semantics are different since ownership is shared.

 Collapse | Copy Code
void foo(int* p)
{
}
void bar(std::shared_ptr<int> p)
{
++(*p);
}
std::shared_ptr<int> p1(new int(42));
std::shared_ptr<int> p2 = p1; bar(p1);
foo(p2.get());

The first declaration is equivalent to this one

 Collapse | Copy Code
auto p3 = std::make_shared<int>(42);

make_shared<T> is a non-member function and has the advantage of allocating memory for the shared object and the smart pointer with a single allocation, as opposed to the explicit construction of a shared_ptr via the contructor, that requires at least two allocations. In addition to possible overhead, there can be situations where memory leaks can occur because of that. In the next example memory leaks could occur if seed() throws an error.

 Collapse | Copy Code
void foo(std::shared_ptr<int> p, int init)
{
*p = init;
}
foo(std::shared_ptr<int>(new int(42)), seed());

No such problem exists if using make_shared. The third sample shows usage of weak_ptr. Notice that you always must get a shared_ptr to the referred object by calling lock(), in order to access the object.

 Collapse | Copy Code
auto p = std::make_shared<int>(42);
std::weak_ptr<int> wp = p; {
auto sp = wp.lock();
std::cout << *sp << std::endl;
} p.reset(); if(wp.expired())
std::cout << "expired" << std::endl;

If you try to lock on an expired weak_ptr (the object is weakly reference has been released) you get an empty shared_ptr.

Lambdas

Anonymous functions, called lambda, have been added to C++ and quickly rose to prominence. It is a powerful feature borrowed from functional programming, that in turned enabled other features or powered libraries. You can use lambdas wherever a function object or a functor or a std::function is expected. You can read about the syntaxhere.

 Collapse | Copy Code
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3); std::for_each(std::begin(v), std::end(v), [](int n) {std::cout << n << std::endl;}); auto is_odd = [](int n) {return n%2==1;};
auto pos = std::find_if(std::begin(v), std::end(v), is_odd);
if(pos != std::end(v))
std::cout << *pos << std::endl;

A bit trickier are recursive lambdas. Imagine a lambda that represents a Fibonacci function. If you attempt to write it using auto you get compilation error:

 Collapse | Copy Code
auto fib = [&fib](int n) {return n < 2 ? 1 : fib(n-1) + fib(n-2);};
 Collapse | Copy Code
error C3533: 'auto &': a parameter cannot have a type that contains 'auto'
error C3531: 'fib': a symbol whose type contains 'auto' must have an initializer
error C3536: 'fib': cannot be used before it is initialized
error C2064: term does not evaluate to a function taking 1 arguments

The problem is auto means the type of the object is inferred from its initializer, yet the initializer contains a reference to it, therefore needs to know its type. This is a cyclic problem. The key is to break this dependency cycle and explicitly specify the function's type using std::function.

 Collapse | Copy Code
std::function<int(int)> lfib = [&lfib](int n) {return n < 2 ? 1 : lfib(n-1) + lfib(n-2);};

non-member begin() and end()

You probably noticed I have used in the samples above non-member begin() and end() functions. These are a new addition to the standard library, promoting uniformity, consistency and enabling more generic programming. They work with all STL containers, but more than that they are overloadable, so they can be extended to work with any type. Overloads for C-like arrays are also provided.

Let's take for instance the previous example where I was printing a vector and then looking for its first odd element. If the std::vector was instead a C-like array, the code might have looked like this:

 Collapse | Copy Code
int arr[] = {1,2,3};
std::for_each(&arr[0], &arr[0]+sizeof(arr)/sizeof(arr[0]), [](int n) {std::cout << n << std::endl;}); auto is_odd = [](int n) {return n%2==1;};
auto begin = &arr[0];
auto end = &arr[0]+sizeof(arr)/sizeof(arr[0]);
auto pos = std::find_if(begin, end, is_odd);
if(pos != end)
std::cout << *pos << std::endl;

With non-member begin() and end() it could be put as this:

 Collapse | Copy Code
int arr[] = {1,2,3};
std::for_each(std::begin(arr), std::end(arr), [](int n) {std::cout << n << std::endl;}); auto is_odd = [](int n) {return n%2==1;};
auto pos = std::find_if(std::begin(arr), std::end(arr), is_odd);
if(pos != std::end(arr))
std::cout << *pos << std::endl;

This is basically identical code to the std::vector version. That means we can write a single generic method for all types supported by begin() and end().

 Collapse | Copy Code
template <typename Iterator>
void bar(Iterator begin, Iterator end)
{
std::for_each(begin, end, [](int n) {std::cout << n << std::endl;}); auto is_odd = [](int n) {return n%2==1;};
auto pos = std::find_if(begin, end, is_odd);
if(pos != end)
std::cout << *pos << std::endl;
} template <typename C>
void foo(C c)
{
bar(std::begin(c), std::end(c));
} template <typename T, size_t N>
void foo(T(&arr)[N])
{
bar(std::begin(arr), std::end(arr));
} int arr[] = {1,2,3};
foo(arr); std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
foo(v);

static_assert and type traits

static_assert performs an assertion check at compile-time. If the assertion is true, nothing happens. If the assertion is false, the compiler displays the specified error message.

 Collapse | Copy Code
template <typename T, size_t Size>
class Vector
{
static_assert(Size < 3, "Size is too small");
T _points[Size];
}; int main()
{
Vector<int, 16> a1;
Vector<double, 2> a2;
return 0;
}
 Collapse | Copy Code
error C2338: Size is too small
see reference to class template instantiation 'Vector<T,Size>' being compiled
with
[
T=double,
Size=2
]

static_assert becomes more useful when used together with type traits. These are a series of classes that provide information about types at compile time. They are available in the <type_traits> header. There are several categories of classes in this header: helper classes, for creating compile-time constants, type traits classes, to get type information at compile time, and type transformation classes, for getting new types by applying transformation on existing types.

In the following example function add is supposed to work only with integral types.

 Collapse | Copy Code
template <typename T1, typename T2>
auto add(T1 t1, T2 t2) -> decltype(t1 + t2)
{
return t1 + t2;
}

However, there are no compiler errors if one writes

 Collapse | Copy Code
std::cout << add(1, 3.14) << std::endl;
std::cout << add("one", 2) << std::endl;

The program actually prints 4.14 and "e". But if we add some compile-time asserts, both these lines would generate compiler errors.

 Collapse | Copy Code
template <typename T1, typename T2>
auto add(T1 t1, T2 t2) -> decltype(t1 + t2)
{
static_assert(std::is_integral<T1>::value, "Type T1 must be integral");
static_assert(std::is_integral<T2>::value, "Type T2 must be integral"); return t1 + t2;
}
 Collapse | Copy Code
error C2338: Type T2 must be integral
see reference to function template instantiation 'T2 add<int,double>(T1,T2)' being compiled
with
[
T2=double,
T1=int
]
error C2338: Type T1 must be integral
see reference to function template instantiation 'T1 add<const char*,int>(T1,T2)' being compiled
with
[
T1=const char *,
T2=int
]

Move semantics

This is yet another important and well covered topic from C++11, that one could write a series of articles, not just a paragraph. Therefore I will not get into too many details, but encourage you to find additional readings, if you're not already familiar with the topic.

C++11 has introduced the concept of rvalue references (specified with &&) to differentiate a reference to an lvalue or an rvalue. An lvalue is an object that has a name, while an rvalue is an object that does not have a name (a temporary object). The move semantics allow modifying rvalues (previously considered immutable and indistinguishable from const T& types).

A C++ class/struct used to have some implicit member functions: default constructor (only if another constructor is not explicitly defined) and copy constructor, a destructor and a copy assignment operator. The copy constructor and the copy assignment operator perform a bit-wise (or shallow) copy, i.e. copying the variables bitwise. That means if you have a class that contains pointers to some objects, they just copy the value of the pointers and not the objects they point to. This might be OK in some cases, but for many cases you actually want a deep-copy, meaning that you want to copy the objects pointers refer to, and not the values of the pointers. In this case you have to explicitly write copy constructor and copy assignment operator to perform a deep-copy.

What if the object you initialize or copy from is an rvalue (a temporary). You still have to copy its value, but soon after the rvalue goes away. That means an overhead of operations, including allocations and memory copying that after all, should not be necessary.

Enter the move constructor and move assignment operator. These two special functions take a T&& argument, which is an rvalue. Knowing that fact, they can modify the object, such as "stealing" the objects their pointers refer to. For instance, a container implementation (such as a vector or a queue) may have a pointer to an array of elements. When an object is instantiating from a temporary, instead of allocating another array, copying the values from the temporary, and then deleting the memory from the temporary when that is destroyed, we just copy the value of the pointer that refers to the allocated array, thus saving an allocation, copying a sequence of elements, and a later de-allocation.

The following example shows a dummy buffer implementation. The buffer is identified by a name (just for the sake of showing a point revealed below), has a pointer (wrapper in an std::unique_ptr) to an array of elements of type T and variable that tells the size of the array.

 Collapse | Copy Code
template <typename T>
class Buffer
{
std::string _name;
size_t _size;
std::unique_ptr<T[]> _buffer; public:
// default constructor
Buffer():
_size(16),
_buffer(new T[16])
{} // constructor
Buffer(const std::string& name, size_t size):
_name(name),
_size(size),
_buffer(new T[size])
{} // copy constructor
Buffer(const Buffer& copy):
_name(copy._name),
_size(copy._size),
_buffer(new T[copy._size])
{
T* source = copy._buffer.get();
T* dest = _buffer.get();
std::copy(source, source + copy._size, dest);
} // copy assignment operator
Buffer& operator=(const Buffer& copy)
{
if(this != &copy)
{
_name = copy._name; if(_size != copy._size)
{
_buffer = nullptr;
_size = copy._size;
_buffer = _size > 0 > new T[_size] : nullptr;
} T* source = copy._buffer.get();
T* dest = _buffer.get();
std::copy(source, source + copy._size, dest);
} return *this;
} // move constructor
Buffer(Buffer&& temp):
_name(std::move(temp._name)),
_size(temp._size),
_buffer(std::move(temp._buffer))
{
temp._buffer = nullptr;
temp._size = 0;
} // move assignment operator
Buffer& operator=(Buffer&& temp)
{
assert(this != &temp); // assert if this is not a temporary _buffer = nullptr;
_size = temp._size;
_buffer = std::move(temp._buffer); _name = std::move(temp._name); temp._buffer = nullptr;
temp._size = 0; return *this;
}
}; template <typename T>
Buffer<T> getBuffer(const std::string& name)
{
Buffer<T> b(name, 128);
return b;
}
int main()
{
Buffer<int> b1;
Buffer<int> b2("buf2", 64);
Buffer<int> b3 = b2;
Buffer<int> b4 = getBuffer<int>("buf4");
b1 = getBuffer<int>("buf5");
return 0;
}

The default copy constructor and copy assignment operator should look familiar. What's new to C++11 is the move constructor and move assignment operator, implemented in the spirit of the aforementioned move semantics. If you run this code you'll see that when b4 is constructed, the move constructor is called. Also, when b1 is assigned a value, the move assignment operator is called. The reason is the value returned by getBuffer() is a temporary, i.e. an rvalue.

You probably noticed the use of std::move in the move constructor, when initializing the name variable and the pointer to the buffer. The name is actually a string, and std::string also implements move semantics. Same for thestd::unique_ptr. However, if we just said _name(temp._name) the copy constructor would have been called. For _buffer that would not have been even possible because std::unique_ptr does not have a copy constructor. But why wasn't the move constructor for std::string called in this case? Because even if the object the move constructor for Buffer is called with is an rvalue, inside the constructor it is actually an lvalue. Why? Because it has a name, "temp" and a named object is an lvalue. To make it again an rvalue (and be able to invoke the appropriate move constructor) one must use std::move. This function just turns an lvalue reference into an rvalue reference.

UPDATE: Though the purpose of this example was to show how move constructor and move assignment operator should be implemented, the exact details of an implementation may vary. An alternative implementation was provided by Member 7805758 in the comments. To be easier to see it I will show it here:

 Collapse | Copy Code
template <typename T>
class Buffer
{
std::string _name;
size_t _size;
std::unique_ptr<T[]> _buffer; public:
// constructor
Buffer(const std::string& name = "", size_t size = 16):
_name(name),
_size(size),
_buffer(size? new T[size] : nullptr)
{} // copy constructor
Buffer(const Buffer& copy):
_name(copy._name),
_size(copy._size),
_buffer(copy._size? new T[copy._size] : nullptr)
{
T* source = copy._buffer.get();
T* dest = _buffer.get();
std::copy(source, source + copy._size, dest);
} // copy assignment operator
Buffer& operator=(Buffer copy)
{
swap(*this, copy);
return *this;
} // move constructor
Buffer(Buffer&& temp):Buffer()
{
swap(*this, temp);
} friend void swap(Buffer& first, Buffer& second) noexcept
{
using std::swap;
swap(first._name , second._name);
swap(first._size , second._size);
swap(first._buffer, second._buffer);
}
};

Conclusions

There are many more things to say about C++11; this was just one of many possible beginnings. This article presented a series of core language and standard library features that every C++ developer should use. However, I recommend you additional readings, at least for some of these features.

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Ten C++11 Features Every C++ Developer Should Use的更多相关文章

  1. 【译】C++工程师需要掌握的10个C++11特性

    原文标题:Ten C++11 Features Every C++ Developer Should Use 原文作者:Marius Bancila 原文地址:codeproject 备注:非直译,带 ...

  2. 转载:每个C++开发者都应该使用的十个C++11特性

    这篇文章讨论了一系列所有开发者都应该学习和使用的C++11特性,在新的C++标准中,语言和标准库都加入了很多新属性,这篇文章只会介绍一些皮毛,然而,我相信有一些特征用法应该会成为C++开发者的日常用法 ...

  3. C++11 并发指南一(C++11 多线程初探)

    引言 C++11 自2011年发布以来已经快两年了,之前一直没怎么关注,直到最近几个月才看了一些 C++11 的新特性,今后几篇博客我都会写一些关于 C++11 的特性,算是记录一下自己学到的东西吧, ...

  4. C++11 并发指南一(C++11 多线程初探)(转)

    引言 C++11 自2011年发布以来已经快两年了,之前一直没怎么关注,直到最近几个月才看了一些 C++11 的新特性,今后几篇博客我都会写一些关于 C++11 的特性,算是记录一下自己学到的东西吧, ...

  5. 【C/C++开发】C++11 并发指南一(C++11 多线程初探)

    引言 C++11 自2011年发布以来已经快两年了,之前一直没怎么关注,直到最近几个月才看了一些 C++11 的新特性,今后几篇博客我都会写一些关于 C++11 的特性,算是记录一下自己学到的东西吧, ...

  6. PLSQL Developer 11 使用技巧(持续更新)

    PLSQL Developer 11 使用技巧 (持续更新) 目录(?)[-] 首先是我的颜色配置 常用快捷键 提升PLSQL编程效率 按空格自动替换 关闭Window窗口 PLSQL 实用技巧 TI ...

  7. Java 8 Features – The ULTIMATE Guide--reference

    Now, it is time to gather all the major Java 8 features under one reference post for your reading pl ...

  8. c++builder XE7 C++11 C++0x 新语法

    Non-static data member initializers 非静态成员变量初始化变得简单,这个XE7 64位编译成功,32位还是不支持 As a simple example, struc ...

  9. CUDA 11功能展示

    CUDA 11功能展示 CUDA 11 Features Revealed 新的NVIDIA A100 GPU基于NVIDIA安培GPU架构,实现了加速计算的最大一代飞跃.A100 GPU具有革命性的 ...

随机推荐

  1. oracle12c之三 控制PDB中CPU 资源使用

      CPU资源隔离 数据库中,不同的PDB对主机CPU资源使用要求不同,那么我们就可以使用CDB resourceplans来管理不同pdb对CPU资源的使用. CDB Resource Plans ...

  2. Java super和this小结

    区别 this() / this. super() / super. 功能 调用本类构造.方法.属性 调用父类构造.方法.属性 操作方法 先查找本类是否有制定的调用结构,如果没有则调用父类 直接调用父 ...

  3. (转)Systemd 入门教程:命令篇

    Systemd 入门教程:命令篇 原文:http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html Systemd 入门 ...

  4. (转)CentOS7 搭建LVS+keepalived负载均衡(一)

    原文:http://blog.csdn.net/u012852986/article/details/52386306 CentOS7 搭建LVS+keepalived负载均衡(一) CentOS7 ...

  5. WCF系列教程之WCF消息交换模式之单项模式

    1.使用WCF单项模式须知 (1).WCF服务端接受客户端的请求,但是不会对客户端进行回复 (2).使用单项模式的服务端接口,不能包含ref或者out类型的参数,至于为什么,请参考C# ref与out ...

  6. MySQL PRIMARY KEY 和 UNIQUE的区别

    primary key = unique +  not null unique 就是唯一,当你需要限定你的某个表字段每个值都唯一,没有重复值时使用.比如说,如果你有一个person 表,并且表中有个身 ...

  7. Robot Framework(Collections 库)

    Collections 库 Collections 库同样为Robot Framework 标准类库,它所提供的关键字主要用于列表.索引.字典的处理. 在使用之前需要在测试套件(项目)中添加:

  8. hibernate的中的查询与级联操作

    1.Criteria查询接口适用于组合多个限制条件来搜索一个查询集. 要使用Criteria,需要遵循以下步骤: *创建查询接口: Criteria criteria=session.createCr ...

  9. Scrapy框架学习(三)Spider、Downloader Middleware、Spider Middleware、Item Pipeline的用法

    Spider有以下属性: Spider属性 name 爬虫名称,定义Spider名字的字符串,必须是唯一的.常见的命名方法是以爬取网站的域名来命名,比如爬取baidu.com,那就将Spider的名字 ...

  10. shell程序设计小知识

    一.用户登陆进入系统后的系统环境变量:$HOME 使用者自己的目录$PATH 执行命令时所搜寻的目录$TZ 时区$MAILCHECK 每隔多少秒检查是否有新的信件$PS1 在命令列时的提示号$PS2 ...