新C++标准:C++0x教程(二):面向所有开发者的特性(上)
2012-11-13 13:37
405 查看
译者:yurunsun@gmail.com
新浪微博@孙雨润 新浪博客
CSDN博客日期:2012年11月12日
原作:Scott Meyers
这些是Scott Meyers培训教程《新C++标准:C++0x教程》的官方笔记,培训课程的描述请见 http://www.aristeia.com/C++0x.html,版权信息请见 http://aristeia.com/Licensing/licensing.html.
漏洞和建议请发送邮件到 smeyers@aristeia.com. 翻译错误请发送邮件给yurunsun@gmail.com (译者注).
在C++98中需要以空格分隔:
衍生的语义推导变化:
2.1
const/volatile和引用/指针修饰符都可以添加在
对于没有显示声明为引用的变量:初始化类型中的顶级
先前一段例子:
新的容器函数
2.2
除了对于新特性“花括号初始列表”,模板无法推导而
这在后续将详细讨论。
2.3
再强调一遍,必须推导出相同类型的变量:
构造函数是否声明为
3. 基于范围的
对比C++0x
3.2 迭代的变量可以是
注意变量
【Note】新的基于范围的
4.
4.1
新的关键词,专指空指针。
4.2 仅能够类型转换成其他指针类型和
以前
4.3
这一点与
在字符前使用前缀,标识字符类型
C++98的语法仍然可用
在C++0x中新增了如下功能:
R可以与任意字符编码:
需要注意的是
默认的字符串
容器的初始化需要另一个容器:
成员变量和堆上数组无法初始化:
6.2 C++0x使用
当通过花括号
一些以前不敢想象的方式:
非聚合类型:调用构造函数。
严格说来聚合类型的定义比上边描述的稍微复杂一些,标准的定义是:“聚合是一个数组或者这样的类:没有用户提供的构造函数,没有非静态成员的默认初始化函数,没有private或者protected的数据成员,没有基类,没有虚函数。”
6.4.1
统一初始化语法可以用在
6.4.3 C++98中
对容器也一样有效,注意和聚合类型初始化的区别
6.5.2 使用
大部分是OK的:
下面是非法的情况:
注意这种语法无法调用以
因此推荐养成不适用
6.6
所谓有损转换是指:目标类型无法表示源类型的所有值,或者编译器不能保证源值会在目标类型能表达的范围之内。C++98允许赋值时隐式有损转换,在C++0x总会编译报错:
这会导致:直接使用构造函数,与使用
但不仅有初始化的功能:
任何函数都可以使用初始化list作为参数。
函数可以以
注意
标准库中
这个例子表达的意思是:
Case2:
Case3:可以用作模板
注意推导过程中出现元素类型不一致会报错:
6.7.4
如果一个类实现了
进一步理解:
也就是说如果实现了
6.7.5 多个
如果一个对象实现了多个版本的以
如果即使最优的选择也包含有损转换,则编译报错:
禁止有损转换
如果这篇文章对您有帮助,请到CSDN博客留言;
转载请注明:来自雨润的技术博客 http://blog.csdn.net/sunyurun
新浪微博@孙雨润 新浪博客
CSDN博客日期:2012年11月12日
原作:Scott Meyers
这些是Scott Meyers培训教程《新C++标准:C++0x教程》的官方笔记,培训课程的描述请见 http://www.aristeia.com/C++0x.html,版权信息请见 http://aristeia.com/Licensing/licensing.html.
漏洞和建议请发送邮件到 smeyers@aristeia.com. 翻译错误请发送邮件给yurunsun@gmail.com (译者注).
1. ">>" 作为嵌套模板的结尾
当可能时,">>"作为嵌套模板的结尾:std::vector<std::list<int>> vi1; // fine in C++0x, error in C++98
在C++98中需要以空格分隔:
std::vector<std::list<int> > vi2; // fine in C++0x and C++98
衍生的语义推导变化:
const int n = … ; // n, m 是编译时常量 cosnt int m = … ; std::array<int, n>m?n:m > a1; // error (as in C++98) std::array<int, (n>m?n:m) > a2; // fine (as in C++98) std::list<std::array<int, n>>2 >> L1; // error in ’98: 2 shifts; error in ’0x: 1st “>>” closes both templates std::list<std::array<int, (n>>2) >> L2; // fine in C++0x, error in ’98 (2 shifts)
2. auto 类型声明
2.1 auto
修饰的变量具有它们初始化表达式的类型
auto x1 = 10; // x1: int std::map<int, std::string> m; auto i1 = m.begin(); // i1: std::map<int, std::string>::iterator
const/volatile和引用/指针修饰符都可以添加在
auto上
const auto *x2 = &x1; // x2: const int* const auto& i2 = m; // i2: const std::map<int, std::string>&
对于没有显示声明为引用的变量:初始化类型中的顶级
const/volatile会被忽略;初始化类型中的数组和函数名会退化成指针
const std::list<int> li; auto v1 = li; // v1: std::list<int> auto& v2 = li; // v2: const std::list<int>& float data[BufSize]; auto v3 = data; // v3: float* auto& v4 = data; // v4: float (&)[BufSize]
先前一段例子:
auto x1 = 10; // x1: int std::map<int, std::string> m; auto i1 = m.begin(); // i1: std::map<int, std::string>::iteratorconst auto *x2 = &x1; // x2: const int* (const isn’t top-level)
const auto& i2 = m; // i2: const std::map<int, std::string>&
auto ci = m.cbegin(); // ci: std::map<int, std::string>::const_iterator
新的容器函数
cbegin/cend/crbegin/crend表示
const_iterator
auto ci = m.cbegin(); // ci: std::map<int, std::string>::const_iterator
2.2 auto
具有和模板相似的推导能力
template<typename T> void f(T t); … f(expr); // 从expr推导T的类型 auto v = expr; // 实质上做了类似的事情
除了对于新特性“花括号初始列表”,模板无法推导而
auto能推导。“与模板具有相似的推导能力”意味着右值引用会退化成左值引用
int x; auto&& a1 = x; // x 是左值, 所以a1的类型是int& auto&& a2 = std::move(x); // std::move(x) 是右值, 所以a2的类型是int&&
这在后续将详细讨论。
2.3 auto
可以同时声明多个相同类型变量
void f(std::string& s) { auto temp = s, *pOrig = &s; // temp: std::string, pOrig: std::string* }
再强调一遍,必须推导出相同类型的变量:
auto i = 10, d = 5.0; // error!
2.4 直接初始化和赋值拷贝意义相同
auto v1(expr); // 直接初始化 auto v2 = expr; // 赋值拷贝
构造函数是否声明为
explicit并不影响直接初始化,因为推导类型时不涉及类型转换;但如果拷贝构造函数声明为
explicit,会影响对
auto变量的赋值拷贝:
struct Explicit { Explicit(){} explicit Explicit(const Explicit&){} } ex; auto ex2 = ex; // Error auto ex3(ex); // OK
3. 基于范围的for
循环
3.1 遍历容器的新方法
std::vector<int> v; … for (int i : v) std::cout << i; // 将v中每一个元素赋值给i
对比C++0x
for ( iterVarDeclaration : expression ) statementToExecute 相当于 { auto&& range = expression; for (auto b = begin(range), e = end(range); b != e; ++b ) { iterVarDeclaration = *b; statementToExecute } }
3.2 迭代的变量可以是reference/auto/const/volatile
for (int& i : v) std::cout << ++i; // 将v中每一个元素递增 for (auto i : v) std::cout << i; // 同上 for (auto& i : v) std::cout << ++i; // 同上 for (volatile int i : v) someOtherFunc(i); // 甚至 "volatile auto i"
3.3 适用对象
如果对于T obj,
begin(obj)和
end(obj)合法,那么基于范围的
for循环也适用。包括所有的C++0x容器、数组和
valarray、初始化lists、正则表达式、任何能提供合适的迭代器的用户定义类型。
std::unordered_multiset<std::shared_ptr<Widget>> msspw; for (const auto& p : msspw) { std::cout << p << '\n'; // print pointer value } short vals[ArraySize]; for (auto& v : vals) { v = -v; }
注意变量
auto& p使用了引用的方式,避免对
shared_ptr造成不必要的引用计数的操作。
【Note】新的基于范围的
for循环,不适用于
while和
do...while循环,也就是后两者没有这种新语义
4. nullptr
4.1 nullptr
是没有二义性的指针
新的关键词,专指空指针。nullptr的类型是
std::nullptr,其他指针类型可以使用
static_cast或者C风格类型转换转成
nullptr,结果永远是空指针。
void f(int *ptr); // 重载 ptr and int void f(int val); f(nullptr); // calls f(int*) f(0); // calls f(int) f(NULL); // 有可能 calls f(int),有可能报二义性错误
4.2 仅能够类型转换成其他指针类型和bool
const char *p = nullptr; // p is null if (p) … // 编译通过,被转成false值 int i = nullptr; // 编译报错
以前
NULL和
0的使用方法保持兼容
int *p1 = nullptr; // p1 is null int *p2 = 0; // p2 is null int *p3 = NULL; // p3 is null if (p1 == p2 && p1 == p3) … // 编译通过,expression值为true
4.3 nullptr
可以用于forwarding模板
这一点与0, NULL不同:
template<typename F, typename P> // 调用func,传入param参数 void Call(F func, P param) { func(param); } void f(int* p); // some function to call f(0); // fine f(nullptr); // also fine logAndCall(f, 0); // error! P 被推导成int, f(int) 非法 logAndCall(f, NULL); // error! logAndCall(f, nullptr); // fine, P 被推导成 std::nullptr_t, f(std::nullptr_t) is okay
5. Unicode支持
5.1 两种新的字符类型
char16_t // 16-bit character (if available) 和 uint_least16_t 类似 char32_t // 32-bit character (if available) 和 uint_least32_t 类似
在字符前使用前缀,标识字符类型
u'x' // 'x' as a char16_t using UCS-2 U'x' // 'x' as a char32_t using UCS-4/UTF-32
C++98的语法仍然可用
'x' // 'x' as a char L'x' // 'x' as a wchar_t
5.2 相应的字符串表示法
u"UCS-2 string literal" // ⇒ char16_ts in UTF-16 U"UCS-4 string literal" // ⇒ char32_ts in UCS-4/UTF-32 "Ordinary/narrow string literal" // "ordinary/narrow" ⇒ chars L"Wide string literal" // "wide" ⇒ wchar_ts u8"UTF-8 string literal" // ⇒ chars in UTF-8
stl中相应的
string类
std::string s1; // std::basic_string<char> std::wstring s2; // std::basic_string<wchar_t> std::u16string s3; // std::basic_string<char16_t> std::u32string s4; // std::basic_string<char32_t>
5.3 字符编码转换
std::codecvt在C++98中能够使
wchar_t与
char互转:
std::codecvt<wchar_t, char, std::mbstate_t>
在C++0x中新增了如下功能:
UTF-16 ⇄ UTF-8 (std::codecvt<char16_t, char, std::mbstate_t>) UTF-32 ⇄ UTF-8 (std::codecvt<char32_t, char, std::mbstate_t>) UTF-8 ⇄ UCS-2, UTF-8 ⇄ UCS-4 (std::codecvt_utf8) UTF-16 ⇄ UCS-2, UTF-16 ⇄ UCS-4 (std::codecvt_utf16) // Behaves like std::codecvt<char16_t, char, std::mbstate_t>. UTF-8 ⇄ UTF-16 (std::codecvt_utf8_utf16)
5.4 支持Raw String
对特殊字符\"/等不需要再手动escape:std::string noNewlines(R"(\n\n)"); std::string cmd(R"(ls /home/docs | grep ".pdf")"); std::string withNewlines(R"(Line 1 of the string... Line 2... Line 3)");
R可以与任意字符编码:
LR"(Raw Wide string literal \t (without a tab))" u8R"(Raw UTF-8 string literal \n (without a newline))" uR"(Raw UTF-16 string literal \\ (with two backslashes))" UR"(Raw UTF-32 string literal \u2620 (without a code point))"
需要注意的是
R必须放在表示字符编码的字母后边。
5.5 自定义Raw String的定界符
// "operator()"|"operator->" std::regex re1(R"!("operator\(\)"|"operator->")!"); // "(identifier)" std::regex re2(R"xyzzy("\([A-Za-z_]\w*\)")xyzzy");
默认的字符串
R"(XXXX)";里的左右括号之间的部分;如果在
"(之间插入一个不超过16个字符不含空格的字符串,则会以这段字符串为界,如re2的
xyzzy
6. 统一的初始化语法
注意初始化不等于赋值,例如const对象不能被赋值但是可以被初始化6.1 C++98中有多种初始化方式
const int y(5); // “direct initialization” syntax const int x = 5; // “copy initialization” syntax int arr[] = { 5, 10, 15 }; // brace initialization struct Point1 { int x, y; }; const Point1 p1 = { 10, 20 }; // brace initializtion class Point2 { public: Point2(int x, int y); }; const Point2 p2(10, 20); // function call syntax
容器的初始化需要另一个容器:
int vals[] = { 10, 20, 30 }; const std::vector<int> cv(vals, vals+3); // init from another container
成员变量和堆上数组无法初始化:
class Widget { public: Widget(): data(???) {} private: const int data[5]; // not initializable }; const float * pData = new const float[4]; // not initializable
6.2 C++0x使用{}
作为统一初始化方式
{}初始化可以用在所有地方:
const int val1 {5}; const int val2 {5}; int a[] { 1, 2, val1, val1+val2 }; struct Point1 { … }; // as before const Point1 p1 {10, 20}; class Point2 { … }; // as before const Point2 p2 {10, 20}; // calls Point2 ctor const std::vector<int> cv { a[0], 20, val2 }; class Widget { public: Widget(): data {1, 2, a[3], 4, 5} {} private: const int data[5]; }; const float * pData = new const float[4] { 1.5, val1-val2, 3.5, 4.5 };
当通过花括号
{}初始化成员变量时,花括号可以包含在小括号
()中:
Widget(): data({1, 2, a[3], 4, 5}) {}
一些以前不敢想象的方式:
Point2 makePoint() { return { 0, 0 }; } // return expression; calls Point2 ctor void f(const std::vector<int>& v); // func. declaration f({ val1, val2, 10, 20, 30 }); // function argument
6.3 统一初始化语法中的不同语义
聚合类型,例如array和struct:从头到尾初始化元素非聚合类型:调用构造函数。
严格说来聚合类型的定义比上边描述的稍微复杂一些,标准的定义是:“聚合是一个数组或者这样的类:没有用户提供的构造函数,没有非静态成员的默认初始化函数,没有private或者protected的数据成员,没有基类,没有虚函数。”
6.4 聚合类型的初始化
6.4.1 union
统一初始化语法可以用在union上,但是只有
union的第一个成员会被初始化
union u { int a; char* b; }; u a = { 1 }; // okay u d = { 0, "asdf" }; // error u e = { "asdf" }; // error (can’t initialize an int with a char array)
6.4.2 元素个数不匹配时
如果初始化元素数量超过容器大小,则编译报错,如果小于容器大小,剩下的对象进行"值初始化":内置类型初始为0、自定义类型调用构造函数、没有构造函数则对自定义类型的成员进行递归的值初始化struct Point1 { int x, y; }; // as before const Point1 p1 = { 10 }; // same as { 10, 0 } const Point1 p2 = { 1, 2, 3 }; // error! too many initializers long f(); std::array<long, 3> arr = { 1, 2, f(), 4, 5 }; // error! too many initializers
6.4.3 C++98中.x
的初始化方式被取消
struct Point { int x, y, z; }; Point p { .x = 5, .z = 8 }; // error!
6.5 非聚合类型的初始化
6.5.1 间接调用构造函数
class Point2 { // as before public: Point2(int x, int y); }; short a, b; const Point2 p1 {a, b}; // same as p1(a, b) const Point2 p2 {10}; // error! too few ctor args const Point2 p3 {5, 10, 20}; // error! too many ctor args
对容器也一样有效,注意和聚合类型初始化的区别
std::vector<int> v { 1, a, 2, b, 3 }; // calls a vector ctor std::unordered_set<float> s { 0, 1.5, 3 }; // calls an unordered_set ctor
6.5.2 使用= {}
进行赋值
大部分是OK的:const int val1 = {5}; const int val2 = {5}; int a[] = { 1, 2, val1, val1+val2 }; struct Point1 { … }; const Point1 p1 = {10, 20}; class Point2 { … }; const Point2 p2 = {10, 20}; const std::vector<int> cv = { a[0], 20, val2 };
下面是非法的情况:
class Widget { public: Widget(): data = {1, 2, a[3], 4, 5} {} // error! private: const int data[5]; }; const float * pData = new const float[4] = { 1.5, val1-val2, 3.5, 4.5 }; // error! Point2 makePoint() { return = { 0, 0 }; } // error! void f(const std::vector<int>& v); // as before f( = { val1, val2, 10, 20, 30 }); // error!
注意这种语法无法调用以
explicit声明的构造函数:
class Widget { public: explicit Widget(int); }; Widget w1(10); // okay, direct init: explicit ctor callable Widget w2{10}; // 同上 Widget w3 = 10; // error! copy init: explicit ctor not callable Widget w4 = {10}; // 同上
因此推荐养成不适用
= {}而只是用
{}的习惯
6.6 {}
初始化方式禁止隐式有损转换
所谓有损转换是指:目标类型无法表示源类型的所有值,或者编译器不能保证源值会在目标类型能表达的范围之内。C++98允许赋值时隐式有损转换,在C++0x总会编译报错:struct Point { int x, y; }; Point p1 { 1, 2.5 }; // fine in C++98: implicit double ⇒ int conversion; error in C++0x Point p2 { 1, static_cast<int>(2.5) }; // fine in both C++98 and C++0x
这会导致:直接使用构造函数,与使用
{}初始化、由编译器间接调用构造函数,两种方法有些细微差别:
class Widget { public: Widget(unsigned u); }; int i; Widget w1(i); // okay, implicit int ⇒ unsigned Widget w2 {i}; // error! int ⇒ unsigned narrows unsigned u; Widget w3(u); // fine Widget w4 {u}; // also fine, same as w3’s init.
6.7 初始化list
6.7.1 使用方法
初始化list与聚合类型初始化相似int x, y; int a[] { x, y, 7, 22, -13, 44 }; // 数组 std::vector<int> v { 99, -8, x-y, x*x }; // std. library type Widget w { a[0]+a[1], x, 25, 16 }; // 自定义类型
但不仅有初始化的功能:
std::vector<int> v {}; // initialization v.insert(v.end(), { 99, 88, -1, 15 }); // 一次插入多个元素 v = { 0, 1, x, y }; // repace的动作
任何函数都可以使用初始化list作为参数。
6.7.2 实现原理
将{}转换成
std::initializer_list对象
函数可以以
std::initializer_list作为参数
std::initializer_list存储初始元素的值,并提供几个函数:
size()/begin()/end()
注意
initializer_list与其他容器不同,没有
rbegin/rend/cbegin/cend/crbegin/crend几个迭代游标。
标准库中
initializer_list对象永远按值传递。
6.7.3 示例
Case1:#include <initializer_list> // necessary header std::u16string getName(int ID); // lookup name with given ID class Widget { public: Widget(std::initializer_list<int> nameIDs){ names.reserve(nameIDs.size()); for (auto id: nameIDs) names.push_back(getName(id)); } private: std::vector<std::u16string> names; }; ... // copies values into an array wrapped by an initializer_list passed to the Widget ctor. Widget w { a[0]+a[1], x, 25, 16 };
这个例子表达的意思是:
Widget对象在构造过程中,将初始化列表中的
ID转成
UTF-16的字符串存储。注意在
getName函数中可以使用move语义提高效率。
Case2:
std::initializer_list可以与其他参数一起使用:
class Widget { public: Widget(const std::string& name, double epsilon, std::initializer_list<int> il); … }; std::string name("Buffy"); // same as Widget w(name, 0.5, std::initializer_list({5,10,15})); Widget w { name, 0.5, {5, 10, 15} };
Case3:可以用作模板
class Widget { public: template<typename T> Widget(std::initializer_list<T> il); ... }; ... Widget w1 { -55, 25, 16 }; // fine, T = int
注意推导过程中出现元素类型不一致会报错:
Widget w2 { -55, 2.5, 16 }; // error, T can’t be deduced
6.7.4 initializer_list
与统一初始化方式{}
的冲突
如果一个类实现了initializer_list为参数的构造函数,编译器为
{}优先匹配这个构造函数。例如
class Widget { public: Widget(double value, double uncertainty); // #1 Widget(std::initializer_list<double> values); // #2 … }; double d1, d2; … Widget w1 { d1, d2 }; // calls #2 Widget w2(d1, d2); // calls #1
进一步理解:
class Widget { public: Widget(double value, double uncertainty); // #1 Widget(std::initializer_list<std::string> values); // #2 … }; double d1, d2; … Widget w1 { d1, d2 }; // 调用失败,编译报错。因为没有double ⇒ string的转换 Widget w2(d1, d2); // still calls #1
也就是说如果实现了
initializer_list参数的版本,那么
{}永远考虑这个版本,即便出错。
6.7.5 多个initializer_list
之间的冲突
如果一个对象实现了多个版本的以initializer_list为参数的构造函数,编译器会转换成本最小的版本。
class Widget { public: Widget(std::initializer_list<int>); // #1 Widget(std::initializer_list<double>); // #2 Widget(std::initializer_list<std::string>); // #3 Widget(int, int, int); // due to above ctors, this ctor not }; // considered for “{... }” args // int ⇒ double same rank as double ⇒ int, so ambiguous Widget w1 { 1, 2.0, 3 }; // float ⇒ double better than float ⇒ int, so calls #2 Widget w2 { 1.0f, 2.0, 3.0 }; std::string s; Widget w3 { s, "Init", "Lists" }; // calls #3
如果即使最优的选择也包含有损转换,则编译报错:
class Widget { public: Widget(std::initializer_list<int>); Widget(int, int, int); // due to above ctor, not }; // considered for “{... }” args Widget w { 1, 2.0, 3 }; // error! double ⇒ int narrows
6.8 统一初始化方式的总结
{}初始化可以用在任何地方,语义上区分聚合与非聚合
禁止有损转换
initializer_list对象允许初始化list传给函数,并不限制构造函数,例如
std::vector::insert
如果这篇文章对您有帮助,请到CSDN博客留言;
转载请注明:来自雨润的技术博客 http://blog.csdn.net/sunyurun
相关文章推荐
- 新C++标准:C++0x教程(三):面向所有开发者的特性(中)
- 新C++标准:C++0x教程(二):面向所有开发者的特性(上)
- 新C++标准:C++0x教程(三):面向所有开发者的特性(中)
- 新C++标准:C++0x教程(四):面向所有开发者的特性(下)
- 新C++标准:C++0x教程(一):导论
- C++开发者都应该使用的10个C++11特性
- 详解C++最新标准C++0x
- 面向所有初学者的一篇教程
- 每个C++开发者都应该使用的十个C++11特性
- C++开发者都应该使用的10个C++11特性
- 转载:每个C++开发者都应该使用的十个C++11特性
- C++开发者都应该使用的10个C++11特性
- C++开发者都应该使用的10个C++11特性
- 详解C++最新标准C++0x
- 按照C++ standard,所有C++标准头文件都不带后缀,有些例外(为了兼容性?!)
- PHP的语言特性-面向对象和C++/java/python的相似之处
- C++面向对象特性实现机制的初步分析 Part1
- C++开发者都应该使用的10个C++11特性
- 标准c++去 string 对象 所有空格%%%%%%%%%%去 左右空格代码,在网上找半天,不如自己写的好
- 每个C++开发者都应该使用的十个C++11特性