注:本文所有内容均来自侯捷老师的C++视频,非转载
一、模板
参数数量不定的模板参数
当使用typename…Types关键字时,表明参数个数随意,参数类型也随意。可以用于函数递归,每次减少一个参数。
示例代码
void print() {
cout << "11111" << endl;
}
template<typename T,typename...Types>
void print(const T& firstArg, const Types&...args) {
cout << firstArg << endl;
print(args...);
}
int main()
{
print(7.5, "hello", 2021, 42);
system("pause");
return 0;
}
每次的函数调用都会使用第一个参数作为firstArg,其余的作为剩下的其他参数,当最后一个参数作为firstArg的时候,此时调用print参数已经为空。所以需要一个print()空参数的函数,作为调用,为了证明这点,我在void print()中输出 “111”。
特化&泛化
调用会优先选择特化的,如下代码所示
void print() {
cout << "11111" << endl;
}
template<typename T,typename...Types>
void print(const T& firstArg, const Types&...args) {
cout << firstArg << endl;
print(args...);
}
template<typename...Types>
void print(const Types&...args) {
cout << 111 << endl;
print(args...);
}
int main()
{
print(7.5, "hello", 2021, 42);
system("pause");
return 0;
}
二、Spaces in template Expressions
vector<list<int> >// 之前的版本要求两个>之间空格
vector<list<int>> // OK since C++ 11
三、nullptr
C++11出现了nullptr用来表示空指针,防止NULL=0时表示整数而不是指针, 出现模棱两可的情况
void f(int);
void f(void*);
f(0); //call f(int)
f(NULL); //call f(int) if NULL is 0, ambiguous otherwise
f(nullpter): //call f(void*)
四、auto
在C++11中,我们使用auto像函数去使用,不必指明每个参数的类型,编译器会自动指明函数的类型
auto i=42; //i has type int
double f():
auto d= f(); //d has type double
当类型特别长,或者type很复杂,一时间想不出来,使用auto尤其有用。
不能因为有了auto,就什么地方都使用,而不加以思考
vector<string>v;
...
auto pos =v.begin(); //pos has type vector<string>::iterator
auto I =[](int x)->bool{ //I has the type of a lambda tanking an int and returning a bool
...,
};
五、Uniform Initialization
C++11提供了一个统一的大括号初始化方法,当编译器看到{t1,t2…n}便做出一个initializer_list,它关系至一个arrat<T,n>。调用函数时该array内的元素可被编译器逐一传给函数。但若该函数参数为initializer_list,调用者却不能给予数个T参数然后以为它们会自动转为一个initializer_list传入
int values[]{1,2,3};
vector<int>v{1,2,3,4,5};
vector<string> cities{"Beijing",Wuhan"};
complex<double> c{4.0,3.0}; //复数 4.0+3.0i
如果我们使用列表初始化值存在丢失信息的风险,则编译器将报错:
int a{double}; //Error, 缩小赋值
六、initializer_list<>
class P{
public:
P(int a, int b)
{
cout<<"a="<<a<<"b="<<b<<endl;
}
P(initializer_list<int>initlist)
{
for(auto i:initlist)
{
cout<<i<<" ";
}
cout<<endl;
}
};
P p (75,5); //P(int ,int ),a=75,b=5
P q{77,5}; //P(initializer_list<int>),value =77,5
P g{77,1,2}; //P(initializer_list<int>initlist),value=77,1,2
P h={77,5}; //P(initializer_list<int>initlist),value=77 ,5
有了initializer_list之后,对于STL的container的初始化就方便多了,比如以前初始化一个vector需要这样:
vector v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
c++11添加了initializer_list后,我们可以这样初始化
std::vector v = { 1, 2, 3, 4 };
需要注意的是,initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中元素的值。并且,拷贝或赋值一个initializer_list对象进行一个浅拷贝,只是把指针拷贝过去,两个指针指向同一个对象,这是危险的!
七、explicit
用途很少,主要用在构造函数上,表达要明确的调用函数,不需要转换
class P
{
public:
P(int a,int b)
{
cout<<" P(int a,int b)"<<endl;
}
P(initializer_list<int>)
{
cout<<"initializer_list<int>"<<endl;
}
explicit P(int a,int b,int c)
{
cout<<"explicit P(int a,int b,int c)"<<endl;
}
};
P p1(77,5); //P(int a,int b)"
P p2{77,5}; //initializer_list<int>
P p3{77,5,42}; //initializer_list<int>
P p4={77,5}; //initializer_list<int>
P p5={77,5,42}; //Error,传给Pinitializer_list会使用explicit构造P
P p4(77,5,42); //explict P(int a,int b,int c)
八、for(decl:coll){statement}
for(int i:{2,3,4,5,7,8,9,10})
{
cout<<i<<endl;
}
vector<double>vec;
...
for(auto elem:vec)
{
cout<<elem<<endl;
}
for(auto& elem:vec)
{
elem*=3;
}
九、=default,=delete
如果你自行定义了一个构造,那么编译器就不会再给你一个默认构造。
如果你强制加上 =default, 就可以重新获得并使用默认构造。
class Zoo {
public:
Zoo(int i1,int i2):d1(i1),d2(i2){}
Zoo(const Zoo&) = delete;
Zoo(Zoo &&) = default;
Zoo& operator=(const Zoo&) = default;
Zoo& operator=(const Zoo&&) = delete;
virtual ~Zoo();
private:
int d1, d2;
};
十、Alias Template(别名)
template <typename T>
using Vec = std::vector<T,allocator<T>>
Vec<int>coll;
//等同于 std::vector<int,allocator<int>> coll;
十一、noexcept、override
在函数名后添加noexcept,保证这个函数不会丢出异常
void foo() noexcept; //=void foo() except(true)
也可以添加无异常条件
void swap(Type &x,Type &y)noexcept(except(x.swap(y)))
{
x.swap(y);
}
//如果x.swap(y)不丢异常,则swap不丢异常
十二、decltype
使用新的decltype关键字,你可以让编译器找出一个表达式的类型。
map<string,float>coll;
...
decltype(coll)::value_type elem;
通过decltype获取类型,然后声明一个变量(容器都有value_type),然后就可以知道这个容器的类型。
decltype的应用
1、used to declare return types
template<typename T1, typename T2>
decltype(x + y) add(T1 x, T2 y);
//以上方法在C++11之前不可以,因为返回的表达式使用了未定义的变量
//C++11之后
template<typename T1, typename T2>
auto add(T1 x, T2 y)->decltype(x + y) ;
2、use it in metaprogramming
template<typename T>
//T必须是个容器
void test_decltype(T obj)
{
map<string, float>::value_type elem1;
//当我们手上有type,可取其inner typedef,没问题
map<string, float>coll;
decltype(coll)::value_type elem2;
//面对obj取其class type 的inner typedef
//因为如今我们有了工具的decltype
//如果是接受任意参数 T obj
//如今有了decltype我可以这样
typedef typename decltype(obj)::iterator iType;
typedef typename T::iterator iType;
decltype(obj) anotherObj(obj);
}
3、pass the type of a lambda
面对lambda,我们手上往往没有object,没有type。
要获得其type,就得借助于decltype
auto cmp = [](const Person& p1, const Person& p2) {
return p1.lastname() < p2.lastname() ||
(p1.lastname() == p2.lastname() &&
p1.firstname() < p2.firstname());
};
...
std::set<Person, decltype(cmp)>coll(cmp);
十三、lambdas
[](参数){操作;}
示例:删除一定范围内的数值
vector<int>vi{ 5,2,28,94,30,15,72,46,23,15 };
int x = 30;
int y = 100;
//删除30 到100之间的数
vi.erase(remove_if(vi.begin(), vi.end(), [x, y](int n) {return x < n&&n < y; }));
for (auto elem : vi)
{
cout << elem << endl;
}