一、两个细节
1.1 Template表达式中的空格
vector<vector<int> >; //在所有cpp版本中可用
vector<vector<int>>; //在cpp11标准中可用
1.2 nullpter与NULL
nullptr为无类型的指针,NULL类型为int。
下面通过一段代码来演示。
#include <iostream>
using namespace std;
void func(int);
void func(void*);
int main()
{
func(NULL); //calls func(int)
func(nullptr); //calls func(void*)
return 0;
}
void func(int)
{
cout << "calls func(int)" << endl;
}
void func(void*)
{
cout << "calls func(void*)" << endl;
}
二、以auto完成类型自动推导
在cpp11中,你可以使用“auto”关键字创建一个对象而不“直接指定”其类型。
auto产生的对象根据其初始化值自动推导出该对象的类型,这种初始化可以是显示的,也可以是隐式的。
下面通过一段代码来演示。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
//基本用法:
auto MyInt = 5;
auto MyDouble(5.6);
cout << "MyInt == " << MyInt << endl;
cout << "MyDouble == " << MyDouble << endl;
//auto a; 错误,auto应该在初始化时候为其指定类型
//常见用法:auto代替复杂类型
vector<int>MyVector; //创建容器
MyVector.push_back(0);
vector<int>::iterator it = MyVector.begin();
auto itr = MyVector.begin();
if (it == itr)
{
cout << "it == itr" << endl;
}
//display: it == itr
return 0;
}
三、初始化相关
3.1 一致性初始化(Uniform Initialization)
在cpp11表中中,所有的初始化操作都被可以被大括号所代替。
下面通过一段代码来演示。
#include <iostream>
#include <vector>
#include <complex>
using namespace std;
int main()
{
int a(5); //ok
int b{ 5 }; //ok
complex<int>c1{ 5,6 }; //ok
complex<int>c2(5, 6); //ok
vector<int> v1{ 0, 1, 2 }; //ok 数组的初始化
//vector<int> v2(0, 1, 2); //wrong
int c[]{ 1,2,3 }; //ok 数组的初始化
//int d[](1, 2, 3); //wrong
return 0;
}
3.2 初值列(Initializer List)
初值列(用大括号进行初始化)会造成强迫初始化,如果变量属于某种基本类型,那么它将被初始化为0(或者nullptr——对指针而言)。
下面通过一段代码来演示。
#include <iostream>
using namespace std;
int main()
{
int a(5); //如果是:int a(); 则会报错:无法解析的外部命令
int b{};
cout << "a == " << a << endl; //display 5
cout << "b == " << b << endl; //display 0
return 0;
}
初始列可以避免窄化(narrowing)
下面通过一段代码来演示。
#include <iostream>
using namespace std;
int main()
{
int a(5.6); //warning
int b{ 5.6 }; //errow
cout << "a == " << a << endl;
cout << "b == " << b << endl;
return 0;
}
cpp11提供了自定义初值列来进行初始化。
下面通过一段代码来演示。
#include <iostream>
using namespace std;
int main()
{
initializer_list<int>list{ 5,6,7 };
for (auto it = list.begin(); it != list.end(); ++it)
{
cout << *it << ' ';
}
cout << endl;
//display 5 6 7
return 0;
}
当初值列和指明参数个数的构造函数同时存在时,初值列构造函数将胜出。
下面通过一段代码来演示。
#include <iostream>
using namespace std;
class Initializer
{
public:
Initializer(int, int)
{
cout << "calls (int, int)" << endl;
}
Initializer(initializer_list<int>)
{
cout << "call list" << endl;
}
};
int main()
{
//参数个数相同的时候:
Initializer a(5, 6); //显式小括号:calls (int, int)
Initializer b{ 5,6 }; //显式大括号:call list
Initializer e = { 1,2 }; //隐式:call list
//参数个数不同的时候:
Initializer c{ 1 }; // call list
Initializer d{ 1,2,3 }; // call list
return 0;
}
四、Range-Based for循环
cpp11引入了新的循环形式,可以逐一迭代某个区间、集合、数组内的每一个元素。这种方式和Python中的for in range()类似。
下面通过一段代码来演示。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int>num{ 0,1,2 };
for (const auto& it : num)
{
cout << it << " ";
}
//display 0 1 2
return 0;
}
五、move语义
注意:move语义是cpp11标准中最重要的一部分,建议联合其他资料一起阅读。
cpp11提供了一种避免非必要拷贝(copy)和临时对象(temporary)的方法,这种方法被称为搬迁语义(move semantic)。
请看下面一段代码。
#include <iostream>
#include <vector>
using namespace std;
class Base
{
private:
int *element;
public:
Base(const int& _el = 0)
{
element = new int(_el);
}
Base(const Base& _b):element{ _b.element }
{
cout << "call copy constructor" << endl;
}
Base(Base&& _b) :element{ _b.element }
{
_b.element = nullptr;
cout << "call move constructor" << endl;
}
};
Base func()
{
Base b;
return b;
}
int main()
{
Base Abase{ 5 };
Base Bbase{ Abase }; //左值 调用copy构造函数
Base Cbase{ func() }; //右值 调用move构造函数
Base Dbase{ move(Abase) }; //move左值 调用move构造函数
return 0;
}
在上述结果中对于左值,采用复制构造函数;对于右值采用move构造函数。
那么为什么move构造中要把原始指针置为null呢?因为如果不置为null,那么临时变量消失时,会析构,释放自己的指针,还资源给os,那么新对象中的指针也就随之会被delete掉。所以需要把原始指针置为null,这样临时变量在析构时就找不到给自己分配的那块内存了,那块内存也就可以被新对象正常使用了。
注意:这里的重载规则要求如果要调用move构造,那么一定要传一个临时变量,并且一定要是一个可以修改的临时变量,如果不能修改,那么临对象内的指针就不能置为null了,如果一个函数返回的是const类型,那么就不能调用move构造了
下面来看move函数。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> num{ 1,2,3,4,5,6 };
move(num.begin() + 2, num.begin() + 3, num.begin());
for (const auto& it : num)
{
cout << it << ' ';
}
//display 3 2 3 4 5 6等效于copy函数
//范围[first, second)
return 0;
}
六、新式的字符串字面常量(String Literal)
Row String Literal允许我们定义字符序列(character sequence)。这样可以节省掉不必要的特殊符号。
下面通过一段代码来演示。
#include <iostream>
using namespace std;
int main()
{
string a = R"(\\n)"; //等价于 "\\\\n"
cout << a << endl; //display \\n
string b = R"nc(a\
b\nc()"
)nc";
cout << b << endl; //等价于"a\\\n b\\nc()"\n";注意空格
return 0;
}
Row string以R”(开头,以)”结尾,同时可以嵌套字母,例如:用R”abc(_yourchars)abc”来来书写。
七、Lambda表达式
7.1 Lambda语法
所谓Lambda是一份功能定义式,可被用于语句(statement)或表达式(expression)内部。因此Lambda可以被用作内联函数来使用。
下面通过一段代码来演示。
#include <iostream>
using namespace std;
int main()
{
auto lambda = []{
cout << "call Lambda" << endl;
};
lambda();
return 0;
}
同时,Lambda也可以接受参数,比如下面这样。
#include <iostream>
using namespace std;
int main()
{
string a = "call Lambda";
auto lambda = [](const string& _l){
cout << _l << endl;
};
lambda(a);
return 0;
}
注意:Lambda不可以是template,必须指明所有类型。
7.2 Lambda的返回类型
Lambda也可以像普通函数一样拥有返回值,这里你可以指定返回值的类型,也可以不指定。
#include <iostream>
using namespace std;
int main()
{
auto UnSpecifyType = []() {
return 1;
};
auto SpecifyType = []()->int {
return 2;
};
cout << UnSpecifyType() << endl;
cout << SpecifyType() << endl;
return 0;
}
7.3 Capture用以访问外部作用域
在Lambda introducer(每个Lambda开始的方括号)内,你可以指明一个capture来处理作用域内未被传递为实参的数据。
- [=]意味着以by value的方式传入(传值,不可修改)。因此当这个Lambda被定义时,你可以读取所有外部数据,但是不能更改他们。
- [&]意味着以by reference的方式传入(引用,可以修改)。因此当这个Lambda被定义时,你对所有数据的修改都是合法的,前提是拥有修改的权限。
你还可以用mutable的方式传入(传值,可以修改)。这种方式虽然已by value的方式传入,但是你却可以修改其中的所有数据。
下面通过一段代码来演示。
#include <iostream>
using namespace std;
int main()
{
int x = 0, y = 0;
auto ByValue = [x, y]()
{
//++x; error:必须是可以修改的左值
//++y;
};
auto ByReference = [&x, &y]()
{
++x; //ok
++y;
};
auto Mutable = [x, y]()mutable
{
++x; //ok
++y;
};
return 0;
}
值得注意的是,想要在Lambda中使用外部参数,必须要将其放入introducer中。
7.4 Lambda的类型
Lambda的类型是个不具名function object(或称functor)。每个Lambda表达式的类型是独一无二的。如果想根据类型声明对象,可借助于template或auto。如果实在需要写下类型,可使用decltype(),例如把一个Lambda作为hash fucntion或是ordering准则或是sorting准则传递给关联式(associative)容器或者不定序(unordered)容器。
或者使用标准库提供的std::functioon<>class template,指明一个一般化类型给functional programing使用。这个class template提供了“明确指出函数的返回类型为Lambda的唯一方法”。
下面通过一段代码来演示。
#include <iostream>
#include <functional>
using namespace std;
function<int(int, int)> ReturnLambda()
{
return [](int x, int y)
{
return x + y;
};
}
int main()
{
auto l = ReturnLambda();
cout << l(5, 6) << endl;
//返回类型为lambda,不能够直接cout,这里用一个中间变量代替函数本身
return 0;
}
八、虽旧犹新的语言特性
8.1 非模板类型参数(Nontype Template Parameter)
在类型参数(Type parameter)之外,我们也可以为template使用非类型参数(Nontype parameter)。这样的参数亦被视为template的一部分。例如对于标准的class bitset<>,你可以传递bit个数作为template实参。下面的语句定义了两个bitfield:一个带有62bit,另外一个带有64bit。
#include <iostream>
#include <bitset>
using namespace std;
int main()
{
bitset<32>flags32 = 5;
cout << flags32 << endl;
//display:00000000000000000000000000000101
bitset<64>flags64 = 5;
cout << flags64 << endl;
//display:0000000000000000000000000000000000000000000000000000000000000101
return 0;
}
因为使用了不同的实参,使用这些bitset的类型是不相同的。因此不可以对上述二者使用运算符,除非进行类型转换。
8.2 基础类型的明确化初始值
如果你使用“一个明确的构造函数调用,但不给实参”这样的语法,基础类型会被设定初值0。
下面通过一段代码来说明。
#include <iostream>
using namespace std;
int main()
{
int a; //undefined value
int b = int(); //initialized with zero
int c(); //error
int d{}; //initialized with zero(since C++11)
cout << a << ' ' << b << ' ' << c << ' ' << d << endl;
return 0;
}
8.3 main()定义式
main函数只有两种定义式具备可移植性,那就是:
int main()
{
}
//和
int main(int argc, char** argv)
{
}
注意其返回类型必须是int。
你可以(但不必要)以一个return结束main()。不同于c,c++定义了一个隐晦的return 0;于main()的终点。这意味着在程序离开main()的时候不用写return语句。0意外的任何值都代表着某种失败。
如果不想以“main()返回”方式结束c++程序,通常应该调用exit()、quick_exit()(c++11)或terminate()。
Comments | NOTHING