class Info
{
public:
Info() : Info(1) { } // 委托构造函数
Info(int i) : Info(i, 'a') { } // 既是目标构造函数,也是委托构造函数
Info(char e): Info(1, e) { }
private:
Info(int i, char e): type(i), name(e) { /* 其它初始化 */ } // 目标构造函数
int type;
char name;
// ...
};
class B1 final {}; // 此类不能被继承
//class D1: public B1 {}; // error!
class B
{
public:
// virtual void func() override // error! 指定了重写但实际并没重写,没有基类
// {
// cout << __func__ << std::endl;
// }
virtual void f() const
{
cout << __func__ << std::endl;
}
virtual void fun()
{
cout << __func__ << std::endl;
}
};
class D : public B
{
public:
virtual void f(int) // ok! 隐藏,由于没有重写同名函数B::f,在D中变为不可见
{
cout << "hiding: " <<__func__ << std::endl;
}
// virtual void f() override // error! 指定了重写但实际并没重写,类型声明不完全相同
// {
// cout << __func__ << std::endl;
// }
virtual void fun() override final // ok! 指定了重写实际上也重写了,同时,指定为最终,后代类中不能再重写此虚函数
{
cout << __func__ << std::endl;
}
};
class D2 : public D
{
public:
virtual void f() const // ok! 重写B::f(),同时,由于没有重写D::f(int),在D2中变不可见
{
cout << __func__ << std::endl;
}
// virtual void fun() // error! 基类的此虚函数被指定为最终,不能被重写,虽然没有显示指定"override"
// {
// cout << __func__ << std::endl;
// }
// virtual void fun() override // error! 基类的此虚函数被指定为最终,不能被重写
// {
// cout << __func__ << std::endl;
// }
};
class X
{
public:
X(){} // 手动定义默认构造函数
X(int i)
{
a = i;
}
private:
int a;
};
X obj; //必须手动定义默认构造函数X(){} 才能编译通过
原本期望编译器自动生成的默认构造函数却需要程序员手动编写,即程序员的工作量加大了。此外,手动编写的默认构造函数的代码执行效率比编译器自动生成的默认构造函数低。
类的其它几类特殊成员函数也和默认构造函数一样,当存在用户自定义的特殊成员函数时,编译器将不会隐式的自动生成默认特殊成员函数,而需要程序员手动编写,加大了程序员的工作量。类似的,手动编写的特殊成员函数的代码执行效率比编译器自动生成的特殊成员函数低。
C++11 标准引入了一个新特性:"=default"函数。程序员只需在函数声明后加上“=default;”,就可将该函数声明为 "=default"函数,编译器将为显式声明的 "=default"函数自动生成函数体。
class X
{
public:
X()= default; //该函数比用户自己定义的默认构造函数获得更高的代码效率
X(int i)
{
a = i;
}
private:
int a;
};
X obj;
"=default"函数特性仅适用于类的特殊成员函数,且该特殊成员函数没有默认参数。例如:
class X
{
public:
int f() = default; // err , 函数 f() 非类 X 的特殊成员函数
X(int, int) = default; // err , 构造函数 X(int, int) 非 X 的特殊成员函数
X(int = 1) = default; // err , 默认构造函数 X(int=1) 含有默认参数
};
"=default"函数既可以在类体里(inline)定义,也可以在类体外(out-of-line)定义。例如:
class X
{
public:
X() = default; //Inline defaulted 默认构造函数
X(const X&);
X& operator = (const X&);
~X() = default; //Inline defaulted 析构函数
};
X::X(const X&) = default; //Out-of-line defaulted 拷贝构造函数
X& X::operator= (const X&) = default; //Out-of-line defaulted 拷贝赋值操作符
"=delete"函数
为了能够让程序员显式的禁用某个函数,C++11 标准引入了一个新特性:"=delete"函数。程序员只需在函数声明后上“=delete;”,就可将该函数禁用。
class X
{
public:
X();
X(const X&) = delete; // 声明拷贝构造函数为 deleted 函数
X& operator = (const X &) = delete; // 声明拷贝赋值操作符为 deleted 函数
};
int main()
{
X obj1;
X obj2=obj1; // 错误,拷贝构造函数被禁用
X obj3;
obj3=obj1; // 错误,拷贝赋值操作符被禁用
return 0;
}
"=delete"函数特性还可用于禁用类的某些转换构造函数,从而避免不期望的类型转换:
class X
{
public:
X(double)
{
}
X(int) = delete;
};
int main()
{
X obj1(1.2);
X obj2(2); // 错误,参数为整数 int 类型的转换构造函数被禁用
return 0;
}
模板的改进
右尖括号>改进
在C++98/03的泛型编程中,模板实例化有一个很繁琐的地方,就是连续两个右尖括号(>>)会被编译解释成右移操作符,而不是模板参数表的形式,需要一个空格进行分割,以避免发生编译时的错误。
template <int i> class X{};
template <class T> class Y{};
int main()
{
Y<X<1> > x1; // ok, 编译成功
Y<X<2>> x2; // err, 编译失败
return 0;
};
在实例化模板时会出现连续两个右尖括号,同样static_cast、dynamic_cast、reinterpret_cast、const_cast表达式转换时也会遇到相同的情况。C++98标准是让程序员在>>之间填上一个空格,在C++11中,这种限制被取消了。在C++11标准中,要求编译器对模板的右尖括号做单独处理,使编译器能够正确判断出">>"是一个右移操作符还是模板参数表的结束标记。
模板的别名
#include <iostream>
#include <type_traits> //std::is_same
using namespace std;
using uint = unsigned int;
typedef unsigned int UINT;
using sint = int;
int main()
{
//std::is_same 判断类型是否一致
//这个结构体作用很简单,就是两个一样的类型会返回true
cout << is_same<uint, UINT>::value << endl; // 1
return 0;
}
函数模板的默认模板参数
C++11之前,类模板是支持默认的模板参数,却不支持函数模板的默认模板参数:
//1、普通函数带默认参数,c++98 编译通过,c++11 编译通过
void DefParm(int m = 3) {}
//2、类模板是支持默认的模板参数,c++98 编译通过,c++11 编译通过
template <typename T = int>
class DefClass {};
//3、函数模板的默认模板参数, c++98 - 编译失败,c++11 - 编译通过
template <typename T = int> void DefTempParm() {}
类模板的默认模板参数必须从右往左定义,数模板的默认模板参数则没这个限定:
template<class T1, class T2 = int> class DefClass1;
template<class T1 = int, class T2> class DefClass2; // 无法通过编译
template<class T, int i = 0> class DefClass3;
template<int i = 0, class T> class DefClass4; // 无法通过编译
template<class T1 = int, class T2> void DefFunc1(T1 a, T2 b);
template<int i = 0, class T> void DefFunc2(T a);
template<class ... T> void func(T ... args)//T叫模板参数包,args叫函数参数包
{//可变参数模板函数
}
func(); // OK:args不含有任何实参
func(1); // OK:args含有一个实参:int
func(2, 1.0); // OK:args含有两个实参int和double
template<class ... T> void func(T ... args)
{//可变参数模板函数
//sizeof...(sizeof后面有3个小点)计算变参个数
cout << "num = " << sizeof...(args) << endl;
}
int main()
{
func(); // num = 0
func(1); // num = 1
func(2, 1.0); // num = 2
return 0;
}
//递归终止函数
void debug()
{
cout << "empty\n";
}
//展开函数
template <class T, class ... Args>
void debug(T first, Args ... last)
{
cout << "parameter " << first << endl;
debug(last...);
}
int main()
{
debug(1, 2, 3, 4);
/*
运行结果:
parameter 1
parameter 2
parameter 3
parameter 4
empty
*/
return 0;
}
非递归方式展开
template <class T>
void print(T arg)
{
cout << arg << endl;
}
template <class ... Args>
void expand(Args ... args)
{
int a[] = { (print(args), 0)... };
}
int main()
{
expand(1, 2, 3, 4);
return 0;
}
expand函数的逗号表达式:(print(args), 0), 也是按照这个执行顺序,先执行print(args),再得到逗号表达式的结果0。
同时,通过初始化列表来初始化一个变长数组,{ (print(args), 0)... }将会展开成( (print(args1), 0), (print(args2), 0), (print(args3), 0), etc...), 最终会创建一个元素只都为0的数组int a[sizeof...(args)]。
6.2 可变参数模板类
6.2.1 继承方式展开参数包
可变参数模板类的展开一般需要定义2 ~ 3个类,包含类声明和特化的模板类:
template<typename... A> class BMW{}; // 变长模板的声明
template<typename Head, typename... Tail> // 递归的偏特化定义
class BMW<Head, Tail...> : public BMW<Tail...>
{//当实例化对象时,则会引起基类的递归构造
public:
BMW()
{
printf("type: %s\n", typeid(Head).name());
}
Head head;
};
template<> class BMW<>{}; // 边界条件
int main()
{
BMW<int, char, float> car;
/*
运行结果:
type: f
type: c
type: i
*/
return 0;
}
6.2.2 模板递归和特化方式展开参数包
template <long... nums> struct Multiply;// 变长模板的声明
template <long first, long... last>
struct Multiply<first, last...> // 变长模板类
{
static const long val = first * Multiply<last...>::val;
};
template<>
struct Multiply<> // 边界条件
{
static const long val = 1;
};
int main()
{
cout << Multiply<2, 3, 4, 5>::val << endl; // 120
return 0;
}