中国建设招标网站,用dw做的网站怎样弄上网上,wordpress设置导航菜单,定西做网站摘要 在C11新标准中#xff0c;语言本身和标准库都增加了很多新内容#xff0c;本文只涉及了一些皮毛。不过我相信这些新特性当中有一些#xff0c;应该成为所有C开发者的常规装备。你也许看到过许多类似介绍各种C11特性的文章。下面是我总结的#xff0c;C开发者都需要学习…摘要 在C11新标准中语言本身和标准库都增加了很多新内容本文只涉及了一些皮毛。不过我相信这些新特性当中有一些应该成为所有C开发者的常规装备。你也许看到过许多类似介绍各种C11特性的文章。下面是我总结的C开发者都需要学习和使用的C11新特性。 auto 在C11之前auto关键字用来指定存储期。在新标准中它的功能变为类型推断。auto现在成了一个类型的占位符通知编译器去根据初始化代码推断所声明变量的真实类型。各种作用域内声明变量都可以用到它。例如名空间中程序块中或是for循环的初始化语句中。 auto i 42; // i is an int
auto l 42LL; // l is an long long
auto p new foo(); // p is a foo* 使用auto通常意味着更短的代码除非你所用类型是int它会比auto少一个字母。试想一下当你遍历STL容器时需要声明的那些迭代器iterator。现在不需要去声明那些typedef就可以得到简洁的代码了。 std::mapstd::string, std::vectorint map;
for(auto it begin(map); it ! end(map); it) {} 需要注意的是auto不能用来声明函数的返回值。但如果函数有一个尾随的返回类型时auto是可以出现在函数声明中返回值位置。这种情况下auto并不是告诉编译器去推断返回类型而是指引编译器去函数的末端寻找返回值类型。在下面这个例子中函数的返回值类型就是operator操作符作用在T1、T2类型变量上的返回值类型。 template typename T1, typename T2
auto compose(T1 t1, T2 t2) - decltype(t1 t2)
{return t1t2;
}
auto v compose(2, 3.14); // vs type is double nullptr 以前都是用0来表示空指针的但由于0可以被隐式类型转换为整形这就会存在一些问题。关键字nullptr是std::nullptr_t类型的值用来指代空指针。nullptr和任何指针类型以及类成员指针类型的空值之间可以发生隐式类型转换同样也可以隐式转换为bool型取值为false。但是不存在到整形的隐式类型转换。 void foo(int* p) {}
void bar(std::shared_ptrint 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 为了向前兼容0仍然是个合法的空指针值。 Range-based for loops 基于范围的for循环 为了在遍历容器时支持”foreach”用法C11扩展了for语句的语法。用这个新的写法可以遍历C类型的数组、初始化列表以及任何重载了非成员的begin()和end()函数的类型。如果你只是想对集合或数组的每个元素做一些操作而不关心下标、迭代器位置或者元素个数那么这种foreach的for循环将会非常有用。 std::mapstd::string, std::vectorint map;
std::vectorint 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和final 我总觉得 C中虚函数的设计很差劲因为时至今日仍然没有一个强制的机制来标识虚函数会在派生类里被改写。vitual关键字是可选的这使得阅读代码变得很费劲。因为可能需要追溯到继承体系的源头才能确定某个方法是否是虚函数。为了增加可读性我总是在派生类里也写上virtual关键字并且也鼓励大家都这么做。即使这样仍然会产生一些微妙的错误。看下面这个例子 class A
{
public:virtual void f(short) {std::cout A::f std::endl;}
};
class B : public A
{
public:virtual void f(int) {std::cout B::f std::endl;}
}; B::f 按理应当重写 A::f。然而二者的声明是不同的一个参数是short另一个是int。因此B::f只是拥有同样名字的另一个函数重载而不是重写。当你通过A类型的指针调用f()可能会期望打印出B::f但实际上则会打出 f(int)而不是f(short) 。另一个很微妙的错误情况参数相同但是基类的函数是const的派生类的函数却不是。 class A
{
public:virtual void f(int) const {std::cout A::f std::endl;}
};
class B : public A
{
public:virtual void f(int) {std::cout B::f std::endl;}
}; 同样这两个函数是重载而不是重写。幸运的是现在有一种方式能描述你的意图。新标准加入了两个新的标识符不是关键字 override表示函数必须重写基类中的虚函数如果派生类没有重写到将编译报错。 final表示派生类不应当重写这个虚函数如果派生类重写了基类的虚函数将编译报错。 class A
{
public:virtual void f(short) { std::cout A::f std::endl; }virtual void g(int) final { std::cout A::g std::endl; }
};
class B : public A
{
public:virtual void f(short) override { std::cout B::f std::endl; }//virtual void g(int) { std::cout A::g std::endl; } // error C3248: “main::A::g”: 声明为“final”的函数无法被“main::B::g”重写virtual void g(float) { std::cout A::g std::endl; } // 重载
}; Strongly-typed enums 强类型枚举 传统的C枚举类型存在一些缺陷它们会将枚举常量暴露在外层作用域中这可能导致名字冲突如果同一个作用域中存在两个不同的枚举类型但是具有相同的枚举常量就会冲突而且它们会被隐式转换为整形无法拥有特定的用户定义类型。 在C11中通过引入了一个称为强类型枚举的新类型修正了这种情况。强类型枚举由关键字enum class标识。它不会将枚举常量暴露到外层作用域中也不会隐式转换为整形并且拥有用户指定的特定类型传统枚举也增加了这个性质。 enum class Options {None, One, All};
Options o Options::All; Smart Pointers 智能指针 已经有成千上万的文章讨论这个问题了所以我只想说现在能使用的带引用计数并且能自动释放内存的智能指针包括以下几种 unique_ptr: 如果内存资源的所有权不需要共享就应当使用这个它没有拷贝构造函数但是它可以转让给另一个unique_ptr存在move构造函数。 shared_ptr: 如果内存资源需要共享那么使用这个所以叫这个名字。 weak_ptr: 持有被shared_ptr所管理对象的引用但是不会改变引用计数值。它被用来打破依赖循环想象在一个tree结构中父节点通过一个共享所有权的引用(chared_ptr)引用子节点同时子节点又必须持有父节点的引用。如果这第二个引用也共享所有权就会导致一个循环最终两个节点内存都无法释放。 另一方面auto_ptr已经被废弃不会再使用了。 什么时候使用unique_ptr什么时候使用shared_ptr取决于对所有权的需求我建议阅读以下的讨论http://stackoverflow.com/questions/15648844/using-smart-pointers-for-class-members std::unique_ptrint p1(new int(42));
std::unique_ptrint p2 std::move(p1); // 移交unique指针
auto sp std::make_sharedint(42);
std::weak_ptrint wp sp;
{auto sp wp.lock(); // 提升shared_ptrstd::cout *sp std::endl;
}
sp.reset();if (wp.expired())std::cout expired std::endl; 如果你试图锁定(lock)一个过期指被弱引用对象已经被释放的weak_ptr那你将获得一个空的shared_ptr. Lambdas 匿名函数也叫lambda已经加入到C中并很快异军突起。这个从函数式编程中借来的强大特性使很多其他特性以及类库得以实现。你可以在任何使用函数对象或者函子(functor)或std::function的地方使用lambda。你可以从这里http://msdn.microsoft.com/en-us/library/dd293603.aspx找到语法说明。 std::vectorint 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%21;};
auto pos std::find_if(std::begin(v), std::end(v), is_odd);
if(pos ! std::end(v))std::cout *pos std::endl; 更复杂的是递归lambda。考虑一个实现Fibonacci函数的lambda。如果你试图用auto来声明就会得到一个编译错误。 auto fib [fib](int n) {return n 2 ? 1 : fib(n-1) fib(n-2);};
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 问题出在auto意味着对象类型由初始表达式决定然而初始表达式又包含了对其自身的引用因此要求先知道它的类型这就导致了无穷递归。解决问题的关键就是打破这种循环依赖用std::function显式的指定函数类型 std::functionint(int) lfib [lfib](int n) {return n 2 ? 1 : lfib(n-1) lfib(n-2);}; 非成员begin()和end() 也许你注意到了我在前面的例子中已经用到了非成员begin()和end()函数。他们是新加入标准库的除了能提高了代码一致性还有助于更多地使用泛型编程。它们和所有的STL容器兼容。更重要的是他们是可重载的。所以它们可以被扩展到支持任何类型。对C类型数组的重载已经包含在标准库中了。我们还用上一个例子中的代码来说明在这个例子中我打印了一个数组然后查找它的第一个偶数元素。如果std::vector被替换成C类型数组。代码可能看起来是这样的 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%21;}; 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; 如果使用非成员的begin()和end()来实现就会是以下这样的 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%21;};
auto pos std::find_if(std::begin(arr), std::end(arr), is_odd);
if(pos ! std::end(arr))std::cout *pos std::endl; 这基本上和使用std::vecto的代码是完全一样的。这就意味着我们可以写一个泛型函数处理所有支持begin()和end()的类型。 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%21;};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::vectorint v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
foo(v); static_assert和 type traits static_assert提供一个编译时的断言检查。如果断言为真什么也不会发生。如果断言为假编译器会打印一个特殊的错误信息。 template typename T, size_t Size
class Vector
{static_assert(Size 3, Size is too small);T _points[Size];
};
int main()
{Vectorint, 16 a1;Vectordouble, 2 a2;return 0;
} error C2338: Size is too small
see reference to class template instantiation VectorT,Size being compiledwith[Tdouble,Size2] static_assert和type traits一起使用能发挥更大的威力。type traits是一些class在编译时提供关于类型的信息。在头文件type_traits中可以找到它们。这个头文件中有好几种class: helper class用来产生编译时常量。type traits class用来在编译时获取类型信息还有就是type transformation class他们可以将已存在的类型变换为新的类型。 下面这段代码原本期望只做用于整数类型。 template typename T1, typename T2
auto add(T1 t1, T2 t2)
{return t1 t2;
} 但是如果有人写出如下代码编译器并不会报错 std::cout add(1, 3.14) std::endl;
std::cout add(one, 2) std::endl; 程序会打印出4.14和”e”。但是如果我们加上编译时断言那么以上两行将产生编译错误。 template typename T1, typename T2
auto add(T1 t1, T2 t2)
{static_assert(std::is_integralT1::value, Type T1 must be integral);static_assert(std::is_integralT2::value, Type T2 must be integral);return t1 t2;
} error C2338: Type T2 must be integral
see reference to function template instantiation T2 addint,double(T1,T2) being compiledwith[T2double,T1int]
error C2338: Type T1 must be integral
see reference to function template instantiation T1 addconst char*,int(T1,T2) being compiledwith[T1const char *,T2int] Move semantics Move语义 这是C11中所涵盖的另一个重要话题。就这个话题可以写出一系列文章仅用一个段落来说明显然是不够的。因此在这里我不会过多的深入细节如果你还不是很熟悉这个话题我鼓励你去地资料。C11加入了右值引用(value reference)的概念用标识用来区分对左值和右值的引用。左值就是一个有名字的对象而右值则是一个无名对象临时对象。move语义允许修改右值以前右值被看作是不可修改的等同于const T类型。C的class或者struct以前都有一些隐含的成员函数默认构造函数仅当没有显示定义任何其他构造函数时才存在拷贝构造函数析构函数还有拷贝赋值操作符。拷贝构造函数和拷贝赋值操作符提供bit-wise的拷贝浅拷贝也就是逐个bit拷贝对象。也就是说如果你有一个类包含指向其他对象的指针拷贝时只会拷贝指针的值而不会管指向的对象。在某些情况下这种做法是没问题的但在很多情况下实际上你需要的是深拷贝也就是说你希望拷贝指针所指向的对象。而不是拷贝指针的值。这种情况下你需要显示地提供拷贝构造函数与拷贝赋值操作符来进行深拷贝。如果你用来初始化或拷贝的源对象是个右值临时对象会怎么样呢你仍然需要拷贝它的值但随后很快右值就会被释放。这意味着产生了额外的操作开销包括原本并不需要的空间分配以及内存拷贝。现在说说move constructor和move assignment operator。这两个函数接收T类型的参数也就是一个右值。在这种情况下它们可以修改右值对象例如“偷走”它们内部指针所指向的对象。举个例子一个容器的实现例如vector或者queue可能包含一个指向元素数组的指针。当用一个临时对象初始化一个对象时我们不需要分配另一个数组从临时对象中把值复制过来然后在临时对象析构时释放它的内存。我们只需要将指向数组内存的指针值复制过来由此节约了一次内存分配一次元数组的复制以及后来的内存释放。以下代码实现了一个简易的buffer。这个buffer有一个成员记录buffer名称为了便于以下的说明一个指针封装在unique_ptr中指向元素为T类型的数组还有一个记录数组长度的变量。 template typename T
class Buffer
{std::string _name;size_t _size;std::unique_ptrT[] _buffer;public:// default constructorBuffer():_size(16),_buffer(new T[16]){}// constructorBuffer(const std::string name, size_t size):_name(name),_size(size),_buffer(new T[size]){}// copy constructorBuffer(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 operatorBuffer operator(const Buffer copy){if(this ! ©){_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 constructorBuffer(Buffer temp):_name(std::move(temp._name)),_size(temp._size),_buffer(std::move(temp._buffer)){temp._buffer nullptr;temp._size 0;}// move assignment operatorBuffer 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
BufferT getBuffer(const std::string name)
{BufferT b(name, 128);return b;
}
int main()
{Bufferint b1;Bufferint b2(buf2, 64);Bufferint b3 b2;Bufferint b4 getBufferint(buf4);b1 getBufferint(buf5);return 0;
} 默认的copy constructor以及copy assignment operator大家应该很熟悉了。C11中新增的是move constructor以及move assignment operator这两个函数根据上文所描述的move语义实现。如果你运行这段代码你就会发现b4构造时move constructor会被调用。同样对b1赋值时move assignment operator会被调用。原因就在于getBuffer()的返回值是一个临时对象——也就是右值。你也许注意到了move constuctor中当我们初始化变量name和指向buffer的指针时我们使用了std::move。name实际上是一个stringstd::string实现了move语义。std::unique_ptr也一样。但是如果我们写_name(temp._name)那么copy constructor将会被调用。不过对于_buffer来说不能这么写因为std::unique_ptr没有copy constructor。但为什么std::string的move constructor此时没有被调到呢这是因为虽然我们使用一个右值调用了Buffer的move constructor但在这个构造函数内它实际上是个左值。为什么因为它是有名字的——“temp”。一个有名字的对象就是左值。为了再把它变为右值以便调用move constructor)必须使用std::move。这个函数仅仅是把一个左值引用变为一个右值引用。更新虽然这个例子是为了说明如何实现move constructor以及move assignment operator但具体的实现方式并不是唯一的。某同学提供了另一种可能的实现。为了方便查看我把它也列在下面 template typename T
class Buffer
{std::string _name;size_t _size;std::unique_ptrT[] _buffer;public:// constructorBuffer(const std::string name , size_t size 16):_name(name),_size(size),_buffer(size? new T[size] : nullptr){}// copy constructorBuffer(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 operatorBuffer operator(Buffer copy){swap(*this, copy);return *this;}// move constructorBuffer(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);}
}; 结论 关于C11还有很多要说的。本文只是各种入门介绍中的一个。本文展示了一系列C开发者应当使用的核心语言特性与标准库函数。然而我建议你能更加深入地学习至少也要再看看本文所介绍的特性中的部分。