当初始化列表包含多个项目时,这些项目被初始化的顺序为他们被声明的顺序,而不是他们在初始化列表中的顺序。
使用私有继承,基类的公有成员和保护成员都将称为派生类的私有成员。使用公有继承,基类的公有方法将称为派生类的公有方法;使用私有继承,基类的公有方法将称为派生类的私有方法,派生类不继承基类的接口,这种不完全继承是has-a关系的一部分。
使用私有继承,类将继承实现。例如从String中派生出一个Student类,派生类将有一个String类组件用于保存字符串,另外STudent方法可以使用String的方法来访问String组件。
使用保护继承,基类的公有成员和保护乘员都将称为派生类的保护成员。
虚基类:虚基类使得从多个类(他们的基类相同)派生出的对象只继承一个基类对象:
class worker
{};
class singer : virtual public worker
{};
class waiter : public virtual worker
{};
class SingingWaiter : public singer, public waiter
{};
使用虚基类需要对C++规则进行调整:新的构造函数规则
使用虚基类时,需要对类构造函数采用一种新的方法。C++在基类是虚的时,禁止信息通过中间类自动传递给基类,编译器必须在构造派生对象之前构造基类的对象组件,所以编译器会调用虚基类的默认构造函数,或者要显式调用所需的基类构造函数。
class SingingWaiter : public singer, public waiter
{
public:
SingingWaiter(const worker &wk, int n, double k)
:worker(wk), singer(n), waiter(k)
{}
};
除了修改类构造函数规则外,还要调整其他代码解决多重继承可能导致函数调用的二义性问题。我们可以在派生类中重新定义方法来指出要使用哪个。
以下是一个类模板示例:
template
class Stack
{
public:
Stack();
bool isempty() const;
bool isfull() const;
bool push(const Type & item);
bool pop(Type &item);
private:
enum { MAX = 10 };
Type items[MAX];
int top;
};
template
Stack::Stack()
{
top = 0;
}
template
bool Stack::isempty() const
{
return top == 0;
}
template
bool Stack::isfull() const
{
return top == MAX;
}
template
bool Stack::push(const Type & item)
{
if (top < MAX)
{
items[top++] = item;
return true
}
else
{
return false;
}
}
template
bool Stack::pop(Type &item)
{
if (top > 0)
{
item = items[--top];
return true;
}
else
{
return false;
}
}
模板常用作容器类,有两种方法可以指定数组大小的简单数组模板:
在类中使用动态数组和构造函数参数来提供元素数目;
使用模板参数来提供常规数组的大小:
template
class MyArray
{
private:
Type array[n];
};
模板类可以用作基类,也可用作组件类,还可用作其他模板的类型参数。
template
class MyArray
{
private:
Type entry[n];
};
template
class GrowArray : public MyArray
{
};
1、递归使用模板:
MyArray, 10> ar;
// int arr[10][5]
声明了一个包含10个元素的数组,每个元素都是一个包含5个int元素的数组。
2、使用多个类型参数:
3、默认类型模板参数
template
class to
{};
1、隐式具体化:声明一个对象,指出所需类型
MyArray, 10> *ar;
arr = new MyArray, 10>;
编译器在需要对象之前,不会生成类的隐式实例化,即运行到上面第二句才会生成类定义。
2、显式具体化:使用template并指出所需类型来声明类,声明必须位于模板定义所在的命名空间中
3、显式具体化:特定类型的定义(用于需要为特殊类型实例化,对模板进行修改,使其行为不同)
template
class MyArray
{
public:
MyArray() {
for (size_t i = 0; i < n; i++)
{
entry[i] = n;
}
}
void print() const {
for (size_t i = 0; i < n; i++)
{
cout << "n = " << n << " ," << " entry = " << entry[i] << endl;
}
}
private:
Type entry[n];
};
template<> class MyArray
{
public:
MyArray() {
for (size_t i = 0; i < 5; i++)
{
entry[i] = i;
}
}
void print() const {
for (size_t i = 0; i < 5; i++)
{
cout << "n[" << i << "]" << " = " << entry[i] << endl;
}
}
private:
double entry[5];
};
MyArray arr;
arr.print();
/*
n[0] = 0
n[1] = 1
n[2] = 2
n[3] = 3
n[4] = 4
*/
4、部分具体化:部分限制模板的通用性
template
class A
{};
template class A
{};
模板可用作结构、类或模板类的成员。要完全实现STL的设计,必须使用这项特性。
template
class beta
{
private:
template
class hold
{
private:
V val;
public:
hold(V v = 0) :val(v) {}
void show() const { cout << val << endl; }
V Value() const { return val; }
};
hold q;
hold n;
public:
beta(T t, int i) :q(t), n(i) {}
template
U blab(U u, T t) { return (n.Value() + q.Value()) * u / t; }
void show() const { q.show(); n.show(); }
};
beta guy(3.5, 3);
这里要看的是blab方法,blab方法的U类型由该方法被调用时的参数值显式确定,T类型由对象的实例化类型确定。
cout << guy.blab(3, 2.1) << endl;
上面的这种调用方式将会返回一个int类型的值。
template
class King
{
};
template class C>
class Crab
{
private:
C c;
};
Crab c;
这里的template
1、模板类的非模板友元
template
class HasFriend
{
public:
friend void counts();
friend void report(HasFriend &t);
};
上面的声明使得counts函数称为模板所有实例化的友元。但是report需要显式具体化其参数才能称为一个友元。
2、模板类的约束模板友元函数
3、模板类的非约束模板友元函数
template
class HasFriend
{
public:
friend void counts();
friend void report(HasFriend &t);
};
typedef HasFriend HFI;
还有另一种别名
template
class A
{
};
template
using AInt = A;
AInt adi;
友元声明可以位于公有、私有或者保护部分,所在位置无关紧要。什么时候需要友元类?根据书上的例子,遥控器可以改变电视机状态,即访问到电视机的属性,表示一些关联性。
友元成员函数:
class Tv
{
friend void Remote::set_chan(Tv &t, int c);
};
class Remote
{
public:
void set_chan(Tv &t, int c);
};
如果只有set_chan方法真正访问了Tv的私有成员变量,那么只需要该方法称为友元方法,不需要整个类成为友元。但是这样做可能会有点麻烦,必须小心排列各种声明和定义的顺序。
上面这种声明,Tv需要知道Remote是一个类,set_chan是其中一个方法;而Remote需要知道Tv这个类,所以Tv要定义在Remote之前。
避开这种循环依赖的方法是,使用前向声明,顺序如下:
class Tv;
class Remote{};
class Tv{};
另外,Remote声明中的内联代码都应该放到实际的Tv定义之后。
// Remote.h
class Tv;
class Remote
{
public:
void set_chan(Tv &t, int c);
void chanup(Tv &t);
};
// Tv.h
#include "Remote.h"
class Tv
{
public:
void chanup();
private:
friend void Remote::set_chan(Tv &t, int c);
};
C++中可以将类声明放在另一个类中,在另一个类中声明的类被称为嵌套类,它通过提供新的类型类作用域来避免名称的混乱。包含类的成员的成员函数可以创建和使用嵌套类的对象;而仅当声明位于公有部分,才能在包含类的外面使用嵌套类,而且必须使用类作用域解析运算符。
包含意味着将类对象作为另一个类的成员,而对类进行嵌套不创建类成员,而是定义了一种类型。
class Outer
{
private:
enum { MIN };
class Inner
{
public:
enum { MAX = 10 };
void print() {
cout << MAX << endl;
cout << MIN << endl;
}
};
Inner in;
public:
void print()
{
in.print();
}
};
struct Data
{
double data[10];
};
struct Junk
{
int junk[10];
};
Data d = { 1.1, 1.2, 1.3 };
char *pch = (char*)(&d);
char ch = char(&d);
Junk *pj = (Junk*)(&d);
C语言中允许以上三种强制类型转换,而C++更严格地限制允许的类型转换,并且提供了四个类型转换运算符:
dynamic_cast:使得类能够在类层次结构中进行向上转换
class A {};
class B :public A
{};
B b;
A *a = dynamic_cast(&b);
const_cast:改变值为const或者volatile
const int c = 10;
int &d = const_cast(c);
d = 20;
cout << "c = " << c << endl;
static_cast:可以进行向上或者向下转换,也可以用作枚举值的转换
A a = B();
B *b = static_cast(&a);
reinterpret_cast:可以进行无关类型的转换,不能删除const
try块表示特定的异常可能被激活的代码块,它后面跟一个或多个catch块,异常可以由throw抛出。
try
{
throw "ABC";
}
catch (const char* s)
{
cout << s; // ABC
}
执行完try块中的语句后,如果没有引发任何异常,则程序跳过catch块。
将对象用作异常类型:引发异常的函数将传递一个对象,这样做的有点之一是可以使用不同的异常类型来区分不同函数在不同情况下引发的异常。
try
{
throw std::exception("ABC");
}
catch (const std::exception& excp)
{
cout << excp.what(); // ABC
}
这里来使用三个智能指针模板:
auto_ptr a(new A());
unique_ptr c(new A());
shared_ptr b(new A());
auto_ptr和unique_ptr建立所有权的概念,对于特定的对象只能由一个智能指针可拥有它,这样只拥有对象的智能指针的构造函数会删除该对象,然后让赋值操作转让所有权。unique_ptr的策略会更加严格。
auto_ptr a(new A());
auto_ptr b;
b = a;
unique_ptr c(new A);
unique_ptr d;
d = c; // error
d = unique_ptr(new A); // OK
unique_ptr禁止指针赋值,避免了所有权转移之后,对原先智能指针的非法访问。
shared_ptr使用引用计数来管理,赋值计数+1,过期计数-1。
当程序要使用多个指向同一个对象的指针,应使用shared_ptr。其他则可以使用unique_ptr。
unique_ptr mk_int(int n)
{
return unique_ptr(new int(n));
}
void show(unique_ptr& n)
{
cout << *n << endl;
}
unique_ptr n = mk_int(3);
show(n);
typedef bool(*is3)(int val);
void exec_func(is3 func, int val)
{
cout << func(val);
}
exec_func([](int x) {return x == 3; }, 3); // 1
以上lambda表达式没有声明返回类型,返回类型相当于用decltyp根据返回值推断得到的。如果lambda不包含返回语句,推断出的返回类型将为void。
如果要指定返回类型,可以使用如下方式:
[](int x) -> bool {return x == 3; }
我们也可以给lambda 一个名称,使其可以多次使用:
auto f = [](int x) -> bool {return x == 3; };
exec_func(f, 4); // 0
lambda可访问作用域内的任何动态变量,要捕获使用的变量可以将其名称放在中括号内,如果只指定了变量名,如[z],那么将按值访问变量;如果在名称前面加上&,如[&a],那么将按引用访问变量。[&]将让我们可以按引用访问所有动态变量,[=]可以让我们按值访问所有动态变量;另外我么也可以混用:如[&a, b],[=, &a],[&, b]