c++11扩大了用·大括号括起的列表的适用范围,使其可适用所有内置类型和用户定义的类型(类对象)。
。int x={5};double y{2.75}; short quar[5] {4,5,2,76,1};
class Stump{private:int roots;double wight;public:Stump(int r,double w):roots(r),weight(w){}};Stump s1(3,15.6); //老式 Stump s2{5,43.4};//c++11 Stump s3={4,32.1};c++11
如果类有模板initializer_list作为参数的构造函数,则只有该构造函数可以使用列表初始化形式初始化列表语法可防止缩窄,即禁止将数值赋给无法存储它的数值变量。常规初始化允许程序执行可能没有意义的操作:
char c1 = 1.57e27;//double到char 未定义的行为
char c2 = 459585821;//int到char 未定义的行为
如果使用初始化列表语法,编译器将禁止进行这样的类型转换,即将值存储到比它“窄”的变量中:
char c1{1.57e27};//double到char,编译出错
char c2={459585821};//int到char,超出范围出错
但允许转换为更宽的类型。另外,只要值在较窄类型的取值范围内,将其转换为较窄的类型是允许的:
char c1{66};//在范围内,允许
double c2={66};//int 到double,允许
c++11提供了模板类initializer_list,可将其用作构造函数的参数。如果类有接受initializer_list作为参数的构造函数,则初始化列表语法就只能用于该构造函数。列表中的元素必须是同一种类型或可转换为同一种类型。STL容器提供了将initializer_list作为参数的构造函数:
vector<int>a1(10);//包含10个元素的未初始化变量
vector<int>a2{10};//初始值设定-lsit,a2有个元素设置为10
vector<int>a3{4,6,1};//3个元素被设置为4,6,1
头文件initializer_list提供了对模板类initializer_list的支持。这个类包含成员函数begin()和end(),可用于获悉列表的范围。除用于构造函数外,还可将initializer_list用作常规函数的参数:
#include
#include
using namespace std;
double sum(initializer_list<double> il);
int main()
{
double total=sum({2.5,3.1,4});//4转变4.0
...
}
double sum(initializer_list<double> il)
{
double tot=0;
for(auto p=il.begin();p!=il.end();p++)
tot+=*p;
return 0;
}
c++11提供了多种简化声明的功能,尤其在使用模板时。
auto;存储类型说明符,c++11将其用于自动类型推断。要求进行显示初始化。 auto maton=112;//int auto pt=&maton;//int * double fm(double int); auto pf=fm;//pf=double(*)(double,int);
还可以简化模板声明,il为initializer_lsit对象,代码为:for(initialzier_list
替换为:for(auto p=il.begin();p !=il.end();p++)
decltype将变量的类型声明为表达式指定的类型。例:让y类型与x相同,其中x是个表达式:decltype(x) y;
这在定义模板时特别有用,因为只有等到模板被实例化才能确定类型:template
c++11新增了一种函数声明语法:在函数名和参数列表后面(而不是前面)指定返回类型:
double f1(double,int);//传统语法
auot f2(double,int)->double;//新语法,返回类型double
常规函数的可读性而言,这种语法好像是倒退,但能使用decltype来指定模板函数的返回类型:
template<typename T,typename U)
auto eff(T t,U u)->decltype(T*U){...}
这里解决的问题是,在编译器遇到eff的参数列表前面,T和U还不在作用域内,因此必须在参数列表后使用decltype。这种新语法使得能够这样做。
对于冗长或复制的标识符,如果能够创建其别名将很方便。以前,c++为此提供typedef:
typedef ::vector<string>::iterator itType;
c++11提供了另一种创建别名的语法。:
using itType = vector<string>::iterator;
差别在于,新语法也可用于模板部分具体化,但typedef不能:
template<typename T>
using arr12=array<T,12>;//多个模板别名
上述语句具体化模板array
array<double,12>a1;
array<string,12>a2;
可将它们替换为下面声明:
arr12<double> a1;
arr12<string> a2;
空指针是不会指向有效数据的指针,以前,c++在源代码中使用0表示这种指针,但内部表示可能不同。有个问题,因为这使得0即可表示指针常量,也可表示整数常量。c++11新增了关键字nullptr,用于表示空指针;它是指针类型,不能转换为整数类型。但c++11仍允许使用0表示空指针,因此表达式nullptr==0为true,但使用nullptr而不是0提供更高的类型安全。例如:可将0传递给接受int参数的函数,但如果试图将nullptr传递给这样的函数,编译器将此视为错误。所以应使用nullptr。
9.智能指针
使用new从堆分配内存,不需要时应使用delete将其释放。c++11引入智能指针auto_ptr,帮助自动完成这个过程。c++11 摒弃了auto_ptr,并新增了三种智能指针:unique_ptr、shared_ptr和weak_ptr。
以前,c++提供了一种方法,可用于指出函数可能引发那种异常:
void f501(int) throw(bad_dog);
void f733(long long) throw();
与auto_ptr一样,c++编程社区的集体经验表明,异常规范的效果没有预期的好,因此,c++11摒弃的异常规范。然而,标准认为,指出函数不会引发异常有一定的价值,所以添加了:noexcept:
void f875(short,short)noexcept;
传统的c++枚举提供了一种创建名称常量的方式,但其类型检查相当低级。枚举名的作用域为枚举定义所属的作用域,但如果在同一个作用域内定义两个 枚举,它们的枚举成员不能同名。枚举可能不是可完全移植的,因为不同的实现可能选择不同的底层类型。所以,c++11新增一种枚举,使用class或struct定义:
enum 0ld1{yes,no,maybe};
enum class New1{never,sometimes,often,always};
enum struct New2{never,lever,sever};
新枚举要求进行显示限定 ,以免发生名称冲突。因此,引用特定枚举时,需要使用New1::never和New2::never等。
1.显示转换运运算符
c++支持对象自动转换。但自动转换可能导致意外转换的问题。为解决问题,c++引入了关键字explicit,以禁止单参数构造函数导致自动转换:
class Plebe
{
Plebe(int);//自动int-to-plebe转换
explicit Plebe(double);//需要显示转换
...
};
Plebe a,b;
a=5;//隐式转换,调用Plebe(5)
b=0.5;//不允许
b=Plebe(0.5);//显示转换
c++11拓展了explicit的这种用法,使得可对转换函数做类似的处理:
class Plebe
{
...
//转换函数
operator int()const;
explicit operator double() const;
...
};
Plebe a,b;
int n=a;//int-to-plebe自动转换
double x=b;//不允许
x=double(b);//允许显示转换
2.类内成员初始化
class Session
{
int mem1=10;//类内初始化
double mem2{1966.54};//类初始化
short mem3;
public:
Session(){}
Session(short s):mem3(s){}
Session(int n,double d,short s):mem1(n),mem2(d),mem3(s){}
...
};
可使用等号或大括号版本的初始化,但不能使用圆括号版本的初始化。其结果与给前两个构造函数提供成员初始化列表,并指定mem1和mem2的值相同:
Seeion():mem1(10),mem2(1966.54){}
Seeion(short):mem1(10),mem2(1966.54),mem3(s){}
通过使用类内初始化,可避免在构造函数中编写重复的代码,从而降低工资量。
如果构造函数在成员初始化列表提供了相应的值,这些默认值将被覆盖,因此第三个构造函数覆盖了类内成员初始化。
1.基于范围的for循环
对于内置数组以及包含方法begin()和end()的类和STL容器,基于范围的for循环可简化。例:循环对数组或容器中每个元素执行指定的操作
double prices[5]={4.99,10.99,6.87,7.99,8.49};
for(double x :prices)
cout<<x<<endl;
x将依次为prices中每个元素的值,x的类型应与数组类型匹配。更安全的方法是使用auto来声明x,来推断x的类型:
double prices[5]={4.99,10.99,6.87,7.99,8.49};
for(auto x:prices)
cout<<x<<endl;
#
如果要循环中修改数组或容器的每个元素,可使用引用类型:
vector<int> vi(6);
for(auot & x:vi)
x=rand();
#
c++11新增STL容器forward_list、unordered_map、unordered_multimap、unordered_set和unordered_multiset。容器forward_list是一种单向链表,只能沿着一个方向遍历;与双向链接的lsit容器相比,它更简单,其他四种容器都是使用哈希表实现的。
c++11还新增模板array,要实例化这种模板,可指定元素类型和固定的元素数:
array<int,360> ar;
c++11新增了STL方法cbegin()和cend()。与begin()和end()一样,这些方法也返回一个迭代器,指向第一个和最后一个,可用于指定包含全部元素的区间。另外,这些方法将元素视为const。crbegin()和crend()是rbegin()和rend()的cosnt版本。
模板valarray独立于STL开发的,c++11添加了两个函数(begin()和end()),他们都接受valarray作为参数,并返回迭代器,分别指向第一个和最后一个元素。
c++98新增关键字export,提供一种途径,能够将模板定义放在接口文件和实现文件中,其中前者包含原型和模板声明,而后者包含模板函数和方法的定义。应用中不现实,因此c++11终止了这种用法,但仍保留了关键字export供以后使用。
为避免与运算符>>混淆,c++要求在声明嵌套时使用空格将尖括号分开:
vector<list<int> > v1;//不可以
不再这样要求:
vector<list<int>> v1;
传统的c++引用(现在称为左值引用,使得标识符关联到左值。左值是一个表示数据的表达式(如变量名或解除引用的指针)程序可获取其地址。开始,左值可出现现在赋值语句的左边,但修饰符const的出现使得可以声明这样的标识符,即不能给它赋值,但可获取其地址:
int n;
int * pt = new int;
const int b = 101;
int & rn = n;
int & rt = *pt;
const int & rb = b;
c++11新增了右值引用,这是使用&&表示的,右值引用可关联到右值,即可出现在赋值表达式右值,但不能对其应用地址运算符的值。右值包括字面常量、诸如x+y等表达式以及返回的函数(条件是该函数返回的不是引用):
int x=10;
int y=23;
int && r1 = 13;
int && r2 = x+y;
double && r3 = sqrt(2.0);
注:r2关联的是当时计算x+y得到的结果,也就是,r2关联到的是23,即使以后修改了x或y,也不会影响到r2。
将右值关联到右值引用导致该右值被存储到特定的位置,且可以获取该位置的地址。
c++11支持移动语句。
1.为何需要移动语句
vector<string> vstr;
...
vector<string>vstr_copy1(vstr);
vector和string类都使用动态内存分配,因此它们必须定义使用某种new版本的复制构造函数。为初始化对象vstr_copy1,赋值构造函数vector将使用new给20000个string对象分配内存。而每个string对象又将调用string的复制构造函数,该构造函数使用new为1000个字符分配空间。接下来,全部20000000个字符都将从vstr控制的内存中复制到vstr_copy1控制的内存中。工作量很大,只要妥当就行。但有时答案时否定的。例如:假设有个函数,它返回个vector对象:
vector<string>allcaps(const vector<string> & vs)
{
vector<string> temp;
//在temp中存储全大写版本vs的代码
return temp;
}
调用:
vector<string> vstr;
//构建一个2000个字符串的向量,每个字符串包含1000个字符。
vector<string> vstr_copy1(vstr);//#1
vector<string> vstr_copy2(allcaps(vstr));//#2
不将字符复制到新地方,在删除原来的字符,而将字符留在原来的地方,并将vstr_copy2与之相关联。这类似在计算机中移动文件的情形:实际文件还留在原来的地方,而只修改记录。这种方法被称为移动语义。
要实现移动语义,需要采取某种方式,让编译器知道什么时候需要复制,什么时候不需要。这就是右值引用发挥作用的地方。可定义两个构造函数。其中一个是常规复制构造函数,它使用const左值引用作为参数,这个引用关联到左值实参,如语句#1中的vstr;另一个是移动构造函数,它使用右值引用作为参数,giant引用关联到右值实参,如语句#2中allcaps(vstr)的返回值。复制构造函数可执行深复制,而移动构造函数只调整记录。移动构造函数可能修改其实参,意味着右值引用参数不应是const。
移动示例:
#include
using namespace std;
//接口
class Useless
{
private:
int n;
char * pc;
static int ct;
void ShowObject()const;
public:
Useless();
explicit Useless(int k);
Useless(int k,char ch);
Useless(const Useless & f);//常规复制构造函数
Useless(Useless && f);//移动构造函数
~Useless();
Useless operator +(const Useless & f)const;
//需要operator=()在复制和移动版本中
void ShowData()const;
};
//启用
int Useless::ct=0;
Useless::Useless()
{
++ct;
n=0;
pc=nullptr;
cout<<"default constructor called;number of objectts:"<<ct<<endl;
ShowObject();
}
Useless::Useless(int k):n(k)
{
++ct;
cout<<"int constructor called; number of of object:"<<ct<<endl;
pc=new char[n];
ShowObject();
}
Useless::Useless(int k, char ch):n(k)
{
++ct;
cout<<"int,char constructor called;number of objects:"<<ct<<endl;
pc=new char[n];
for(int i=0;i<n;i++)
pc[i]=ch;
ShowObject();
}
Useless::Useless(const Useless &f):n(f.n)
{
++ct;
cout<<"copy const called;number of objects:"<<ct<<endl;
pc=new char[n];
for(int i=0;i<n;i++)
pc[i]=f.pc[i];
ShowObject();
}
Useless::Useless(Useless &&f):n(f.n)
{
++ct;
cout<<"move constuctor called;number of object:"<<ct<<endl;
pc=f.pc;//窃取地址
f.pc=nullptr;//不给老对象如何返回
f.n=0;
ShowObject();
}
Useless::~Useless()
{
cout<<"destructor called;object left:"<<--ct<<endl;
cout<<"deleted object:\n";
ShowObject();
delete [] pc;
}
Useless Useless::operator +(const Useless & f)const
{
cout<<"Entering operator+()\n";
Useless temp = Useless(n+f.n);
for(int i=0;i<n;i++)
temp.pc[i]=pc[i];
for(int i=0;i<temp.n;i++)
temp.pc[i]=f.pc[i-n];
cout<<"temp object:\n";
cout<<"Leaving operator+()\n";
return temp;
}
void Useless::ShowObject()const
{
cout<<"Number of elements:"<<n;
cout<<"Data address:"<<(void *)pc<<endl;
}
void Useless::ShowData()const
{
if(n==0)
cout<<"(object empty)";
else
for(int i=0;i<n;i++)
cout<<pc[i];
cout<<endl;
}
//申请
int main()
{
{
Useless one(10,'x');
Useless two=one;//调用复制构造函数
Useless three(20,'o');
Useless four(one+three);//调用operator+(),移动构造函数
cout<<"Object one:";
one.ShowData();
cout<<"object two:";
two.ShowData();
cout<<"object three:";
three.ShowData();
cout<<"object four:";
four.ShowData();
}
}
虽然使用右值引用可支持移动语义,但这并不会神奇地发生。要让移动语义发生,需要两个步骤:首先,右值引用让编译器知道何时可使用移动语义:
Useless two=one;
Useless four(one+three);
one左值,与左值引用匹配,而表达式one+three为右值,与右值匹配。因此,右值引用让编译器使用移动构造函数来初始化对象four,实现移动语义第二步是,编写移动构造函数,使其提供所需的行为。
总之,通过提供个使用左值引用的构造函数和个使用右值引用的构造函数,将初始化分成两组。使用左值对象初始化对象时,将使用复制构造函数,而使用右值对象初始化对象时,将使用移动构造函数。
引入右值引用前,情况如何?没有移动构造函数,不能消除复制构造函数的需求,结果如何?c++98中,下面的语句将调用复制构造函数:
Useless four(one+three);
但左值引用不能指向右值。结果如何?如果实参为右值,const引用形参将指向一个临时变量:
int twice(const & rx){return 2 * rx;}
...
int main()
{
int m=6;
//下面rx指向m
int n = twice(m);
//下面rx指向各临时变量初始化为21
int k = twice(21);
...
适用于构造函数的移动语义考虑也适用于赋值运算符。例如:下面演示了如何给Useless类编写赋值运算符和移动赋值运算符:
Useless & Useless::operator=(const Useless & f)//复制构造函数
{
if(this==&f)
return *this;
delete [] pc;
n=f.n;
for(int i=0;i<n;i++)
pc[i]=f.pc[i];
return *thsi;
}
Useless & Useless::operator=(Useless && f)//移动构造函数
{
if( this==&f)
return *this;
delete [] pc;
n=f.n;
pc=f.pc;
f.n=0;
f.pc=nullptr;
return *this;
}
注:复制运算符采用常规模式,而移动赋值运算符删除目标对象中的原始数据,并将源对象的所有权转让给目标。不能让那多个指针指向相同的数据。因此上述代码将源对象中的指针设置为空指针。与构造函数一样,移动赋值运算符的参数也不能是cosnt引用,因为这个方法修改源对象。
移动构造函数和移动赋值运算符使用右值。如果要让它们使用左值,如何办?例如:程序可能分析个包含候选对象的数组,选择其中一个对象供以后使用,并丢弃数组,如果可以使用移动构造函数或移动赋值运算符来保留选定的对象,那多好。假设这样做:
Useless choices[10];
Useless best;
int pick;
...//选择一个对象,将选择设置为索引
best = choices[pick];
由于choices[pick]是左值,异常上述赋值语句将使用赋值运算符,而不是移动赋值运算符。但如果能让choices[pick]看起来像右值,便将使用移动赋值运算符。为此可以使用运算符static_cast<>将对象的类型转换强制转换为Useless &&,但C++11提供了一种更简单的方式:使用头文件utility中声明的函数move()。
例如:
#include
#include
using namespace std;
//接口
class Useless
{
private:
int n;
char * pc;
static int ct;
void ShowObject()const;
public:
Useless();
explicit Useless(int k);
Useless(int k,char ch);
Useless(const Useless & f);//常规复制构造函数
Useless(Useless && f);//移动构造函数
~Useless();
Useless operator +(const Useless & f)const;
Useless &operator =(const Useless &f);//复制分配
Useless &operator =(Useless && f);//移动分配
//需要operator=()在复制和移动版本中
void ShowData()const;
};
//启用
int Useless::ct=0;
Useless::Useless()
{
++ct;
n=0;
pc=nullptr;
}
Useless::Useless(int k):n(k)
{
++ct;
pc=new char[n];
}
Useless::Useless(int k, char ch):n(k)
{
++ct;
pc=new char[n];
for(int i=0;i<n;i++)
pc[i]=ch;
}
Useless::Useless(const Useless &f):n(f.n)
{
++ct;
pc=new char[n];
for(int i=0;i<n;i++)
pc[i]=f.pc[i];
}
Useless::Useless(Useless &&f):n(f.n)
{
++ct;
pc=f.pc;//窃取地址
f.pc=nullptr;//不给老对象如何返回
f.n=0;
}
Useless::~Useless()
{
delete [] pc;
}
Useless &Useless::operator =(const Useless &f)//复制构造函数
{
cout<<"copy assignment operator called:\n";
if(this==&f)
return *this;
delete [] pc;
n=f.n;
pc=new char[n];
for(int i=0;i<n;i++)
pc[i]=f.pc[i];
return *this;
}
Useless & Useless::operator=(Useless && f)//移动构造函数
{
cout<<"move assignment operator called:\n";
if( this==&f)
return *this;
delete [] pc;
n=f.n;
pc=f.pc;
f.n=0;
f.pc=nullptr;
return *this;
}
Useless Useless::operator +(const Useless & f)const
{
Useless temp = Useless(n+f.n);
for(int i=0;i<n;i++)
temp.pc[i]=pc[i];
for(int i=0;i<temp.n;i++)
temp.pc[i]=f.pc[i-n];
return temp;
}
void Useless::ShowObject()const
{
cout<<"Number of elements:"<<n;
cout<<"Data address:"<<(void *)pc<<endl;
}
void Useless::ShowData()const
{
if(n==0)
cout<<"(object empty)";
else
for(int i=0;i<n;i++)
cout<<pc[i];
cout<<endl;
}
//申请
int main()
{
{
Useless one(10,'x');
Useless two=one+one;//调用移动构造函数
cout<<"object one:";
one.ShowData();
cout<<"object two:";
two.ShowData();
Useless three,four;
cout<<"three=one\n";
three=one;//自动复制分配
cout<<"now object three=";
three.ShowData();
cout<<"and object one=";
one.ShowData();
cout<<"four=one+two\n";
four=one+two;//自动移动分配
cout<<"now object four=";
four.ShowData();
cout<<"four=move(one)\n";
four=move(one);//强制移动分配
cout<<"now object four=";
four.ShowData();
cout<<"and object one=";
one.ShowData();
}
}
注:move()并非一点导致移动操作。例如:Chunk是个包含私有数据的类:
Chunk one;
...
Chunk two;
two=move(one);//移动含义?
move(one)是右值,因此将调用Chunk的移动赋值运算符:如果定义了这样的运算符。如果没定义,编译器将使用复制赋值运算符。如果也没有定义复制赋值运算符,将不允许上述赋值。
c+11在原有4个特殊成员函数(默认构造函数、复制构造函数、复制赋值运算符和析构函数)的基础上新增两个:移动构造函数和移动赋值运算符。这些成员函数是编译器在各种情况下自动提供。
例:
类名为Someclass,默认的构造含原型为:
Someclass::Someclass(const Someclass &);//默认复制构造函数
Someclass::Someclass(Someclass &&);//默认移动构造函数
类似情况下,编译器提供默认的复制运算符和默认的移动运算符,如:
Someclass & Someclass::operator(const Someclass &);//默认复制分配
Someclass & Someclass::operator(Someclass &&);//默认移动分配
最后,如果没提供析构函数,编译器将提供一个。
前对于前面的描述,有些例外:
另外,默认的移动构造函数和移动赋值运算符的工作方式与复制版本类似:执行逐成员并复制内置类型。如果成员是类对象,将使用相应的构造函数和赋值运算符,就像参数为右值一样。如果定义了移动构造函数和移动赋值运算符,这将调用它们:否则将调用复制构造函数和复制赋值运算符。
c++11让你更好地控制要使用的方法。假设要使用某个默认的函数,而这个函数由于某种原因不会自动创建。所以,可使用关键字default
显示地声明这些方法的默认版本:
class Someclass
{
public:
Someclass(Someclass &&);
Someclass()=default;//使用编译器生成的默认构造函数
Someclass(const Someclass &)=default;
Someclass & operator=(const Someclass &)=default;
...
}
关键字delete
可用于禁止编译器使用特定方法。例如:要禁止复制对象,可使用复制构造函数和复制赋值运算符:
class Someclass
{
public:
Someclass()=default;//使用编译器生成的默认构造函数
//禁用复制构造函数和复制赋值运算符
Someclass(const Someclass &)=delete;
Someclass & operator=(const Someclass &)=delete;
//使用编译器生成的移动构造函数和移动赋值运算符
Someclass(Someclass &&)=default;
Someclass & operator=(Someclass &&)=default;
Someclass & operator+(const Someclass &)const;
...
};
如果在启动移动方法的同时禁用复制方法,结果如何?移动操作使用的右值引用只能关联到右值表达式,意味着:
Someclass one;
Someclass two;
Someclass three(one);//不允许,一个左值
Someclass four(one+two);//允许,表达式右值
关键字default只能用于6个特殊成员函数,但delete可用于任何成员函数。delete的一种可能用法是禁止特定的转换。例如:假设Someclass类接受个double参数的方法:
class Someclass
{
public:
...
void redo(doube);
...
};
在如下面的代码:
Someclass sc;
sc.redo(5);
int值5提升为5.0,进而执行方法redo()。
现在假设将Someclass类定义改成下面:
class Someclass
{
public:
...
void redo(double);
void redo(int)=double;
...
};
这时,方法调用sc.redo(5)与原型redo(int)匹配。
如果给类提供了多个构造函数,可能重复编写相同的代码。也就是,有些构造函数可能需要包含其他构造函数中已有的代码。这时,c++11允许在一个构造函数的定义中使用另一个构造函数。这被称为委托。因为构造函数构造函数暂时将创建对象的工作委托给另一个构造函数。委托使用成员初始化列表语法的变种:
class Notes{
int k;
double x;
public:
Notes();
Notes(int);
Notes(int,double);
Notes(int,double,string);
};
Notes::Notes(int kk,double xx, string stt):k(kk),x(xx),st(stt){/*do stuff*/}
Notes::Notes():Notes(0,0.01,"Oh"}{/*do yet other stuff*/}
Notes::Notes(int kk,double xx):Notes(kk,xx,"Uh"){/*ditto*/}
上述默认构造函数使用第一个构造函数初始化数据成员并执行其函数体,然后再执行自己的函数体。
为进一步简化编码工作,c++11提供了一种让派生类能继承基类构造函数的机制。c++98提供了一种让名称空间中函数可用的语法:
namespace Box
{
int fn(int){...}
int fn(double){...}
int fn(const char *){...}
}
using Box::fn;
这让函数fn的所有重载可用,也可使用这种方法让基类的所有非特殊成员函数对派生类可用。
例如:
class c1
{
...
public:
...
int fn(int j){...}
double fn(double w){...}
void fn(const char * s){...}
};
class c2:public c1
{
...
public:
...
using c1::fn;
double fn(double){...};
};
...
c2 c2;
int k = c2.fn(3);
double z =c2.fn(2.4);
c2中的using声明让c2对象可使用c1的三个fn()方法,但将选择c2而不是c1定义的方法fn(double)。
c++11将这种方法用于构造函数。这让派生继承基类的所有构造函数(默认构造函数、复制构造函数和移动构造函数除外)但不会使用与派生类构造函数的特征标匹配的构造函数:
class Bs
{
int q;
double w;
public:
Bs():q(0),w(0){}
Bs(int k):q(k),w(100){}
Bs(double x):q(-1),w(x){}
B0(int k,double x):q(k),w(x){}
void Show()const{cout<<q<<","<<w<<'\n';}
};
class DR:public BS
{
short j;
public:
using BS::BS;
DR():J(-100){}//DR需要自己的默认构造函数
DR(double x):BS(2*X),j(int(x)){}
DR(int i):j(-2),BS(i,0.5*i){}
void Show()const{cout<<j<<",";BS::Show();}
};
int main()
{
DR o1;//使用DR()
DR o2(18.81);//使用DR(double)instead of BS(double)
DR o3(10,1.8);//使用BS(int,double)
...
}
由于没有构造函数DR(int,double),因此创建DR对象o3时,将使用继承而来的BS(int,double)。注意:继承的基类构造函数只初始化基类成员;如果还要初始化派生类成员,则应使用成员列表初始化语法:
DR(int i,int k,double x):j(i),BS(k,x){}
虚方法对实现多态类层次结构很重要,让基类引用或指针能够根据指向的对象调用相应的方法,但虚方法也带来一些编程陷阱。例如:假设基类声明个虚方法,而决定在派生类中提供不同的版本,这将覆盖旧版本。如果特征标不匹配,将隐藏而不是覆盖旧版本:
class Action
{
int a;
public:
Action(itnt i=0):a(i){}
int val()cosnt {return a;}
virtual void f(char ch)const{cout<<val()<<ch<<"\n"}
};
class Bingo:public Action
{
public:
Bingo(int i=0)Action(i){}
virtual void f(char * ch)const{cout<<val()<<ch<<"!\n";}
};
由于类Bingo定义的是f(char *ch),将对Bingo对象隐藏f(char ch),这导致程序不能使用类似下面的代码:
Bingo b(10);
b.f('@');
在c++11中,可使用虚说明符override指出要覆盖一个虚函数:将其放在参数列表的后面。如果声明与基类方法不匹配,编译器将视为错误。因此下面的Bingo::f
()版本将生成一条编译错误信息:
virtual void f(char * ch)const override{cout<<val()<<ch<<"!\n";}
说明符final解决了另一个问题。你可能想禁止派生类覆盖特定的虚方法,为此可在参数列表后面加上final。例如:下面的代码禁止Action的派生类重载新定义函数f():
virtual void f(char ch)const final{cout<<val()<<ch<<"\n";}
说明符override和final并非关键字,而是具有特殊含义的标识符。这意味着编译器根据上下文确定它们是否有特殊含义;在其上下文中,可将它们用作常规标识符,如变量或枚举。