valarray类:由头文件valarray支持,是用于处理数值的数组,语法如下:
/* 一个int类型数组a,长度是0 */
valarray<int> a;
/* 一个int类型数组,长度是8 */
valarray<int> a(8);
/* 一个int类型数组,长度是8,初始值全是10 */
valarray<int> a(10, 8)
/* 使用a1的前2个元素初始化a2 */
int a1[3] = {1, 2, 3};
valarray<int> a2(a1, 2);
常用的类方法如下:
operator[]():使用下标访问每个元素
size(): 获取包含的元素个数
sum(): 返回所有元素的总和
max(): 返回最大的元素
min(): 返回最小的元素
接口和实现:使用public继承,类可以继承接口,可能继承实现(基类的纯虚函数没有实现)。获取接口是is-a关系。使用嵌套,类可以获取实现,但是不能获取接口。获取实现是has-a关系。
列表初始化顺序:按照类中定义的顺序,而不是初始化列表中的顺序,如下:
class Test
{
private:
string i;
int j;
public:
Test(string _i, int _j) : j(_j), i(_i){}
};
虽然初始化列表中先写的j,但是i仍然先被初始化,因为在类的对象中i是先被定义的。
private继承:除了嵌套之外,另一种实现has-a关系的途径是私有继承。使用public继承的时候,基类的公有方法成为派生类的公有方法,即派生类继承了基类的公有接口,使用private继承的时候,基类的公有方法和保护方法成为派生类的私有方法,继承了实现但是没有继承接口。
嵌套和私有继承的区别:
1.嵌套包含了显式命令的对象成员,private继承提供了无名称的子对象成员
2.同样适用列表初始化方法,嵌套使用对象名,private继承使用类名(因为没有成员名)
私有继承访问基类对象的方法是,对this指针进行强制类型转换,从而访问到继承而来的基类对象。在private继承中,不使用显式类型转换,不能将派生类指针或者引用赋值给基类指针或者引用。
使用嵌套还是private继承:嵌套允许创建多个对象,继承只能包含一个;若需要访问基类的保护成员或者重写虚方法的时候,才使用私有继承,否则一般使用嵌套。
保护继承:基类的protected成员和public成员在派生类中成为保护成员,存在第三代继承的时候能看出来区别,第三代继承可以使用第二代继承的保护方法。
一个在派生类外访问基类函数的方法:使用using,如下:
class father
{
public:
func();
};
class son : private father
{
public:
using father::func;
};
/* 调用者 */
son s;
s.func();
多重继承:c++允许多重继承,即一个类可以继承自多个父类,这和ruby不同。多重继承MI的主要问题是:
1.多个不同的基类有同名方法
2.多个基类有共享的始祖类,导致派生类继承同一个始祖类的多个实例
一个简单的例子:
class Worker
{
};
class Waiter : public Worker
{
};
class Singer : public Worker
{
};
class SingingWaiter : public Watier, public Singer
{
};
由于Waiter类和Singer类共有基类Worker类,因此一个SingingWatier类的对象中将有两个Worker类对象,一个是Waiter类中的,一个是Singer类中的,通常可以将基类指针指向派生类对象,但是此时会有问题,因为SingingWaiter对象中有两个Worker对象,不知道应该选择哪个地址,需要显式类型转换。
虚基类:多重继承的派生类拥有多个始祖类的实例是不必要的,使用虚基类防止这种情况。虚基类不是修改类声明,而是继承时指定虚继承,如下:
class Worker
{
};
class Singer : virtual public Worker
{
};
class Waiter : virtual public Worker
{
};
这样,Singer和Waiter共享一个Worker对象。使用虚基类后注意的点:
1.需要修改构造函数,比如,SingingWaiter构造函数初始化列表使用了Singer和Waiter的构造函数,而Singer和Waiter的构造函数都使用了Worker的构造函数,SingingWatier自动传递信息的时候,将通过两条不同的途径,因此C++在基类是虚的时候,禁止通过中间类将信息传递给基类,但这样的语法会通过编译,编译器的处理是,调用虚基类的默认构造函数。因此,需要显式调用虚基类的构造函数,注意只能在虚基类的时候这样用,否则编译报错
2.可能调用二义性的方法,在单继承中,派生类使用最近祖先的方法,但是多重继承中,每个直接祖先都有一个同名函数,那么调用此函数就会二义性。可以通过域解析运算符::或者重写此方法来解决。但是还会有问题,比如如下:
class T1
{
private:
int t1;
public:
void show() const{cout << t1 << endl;}
};
class T2 : virtual public T1
{
private:
int t2;
public:
void show() const{cout << t2 << endl;}
};
class T3 : virtual public T1
{
private:
int t3;
public:
void show() const[cout << t3 << endl;}
};
class T4 : public T2, public T3
{
public:
void show() const
{
T2::show();
T3::show();
}
};
T4是多重继承,希望打印所有成员,但是同时使用T2和T3的show(),导致T1的show()被调用了两次,但是若不同时使用T2和T3的,就会导致有的数据没有被打印。这种情况只能通过精致的编码来解决,每个类将打印自己成员的功能抽象出来,封装成public的data(),然后用show()选择自己想要的进行组合,这样在T4中就可以调用T1,T2,T3的data或者T2的show()加T3的data()。data()应该设计成protected的,只有类的继承层级中可以使用,外界不能使用。
关于多重继承派生链中的函数二义性:
1.若同名函数只在一条链上,那么优先级同单链继承
2.若同名函数在不同链上,不管哪条链上祖先更近,优先级相同,二义性
3.这种派生链中的优先级关系不管函数是否是私有的,若某个私有函数优先级更高,那么即使派生类没有访问权限,仍然试图访问这个函数,导致编译期报错
容器类:用来存储其他对象或者数据类型,需要泛型编程,模板类用来解决这个问题。模板类需要将实现和声明都放在头文件中,因为模板不是函数,不能单独编译,必须和实例化请求一起使用。一个简单的例子如下:
template <typename T, int n>
class Test
{
private:
T a[n];
};
其中T是泛型,n叫做表达式参数,它指定了特殊的类型而不是泛型。表达式参数只能是整型、枚举、引用、指针,比如double n就是不合法的,但是double *n是可以的。模板代码中不能修改参数的值也不能使用参数的地址。
可以给类模板参数提供默认值,这和函数模板不同。模板也有3种具体化方式,如下:
template <class T, int n>
class ArrayTP
{
private:
T a[n];
};
1.隐式实例化
ArrayTP<int, 100> stuff;
ArrayTP<int, 100> * p; //这只是个指针,只有需要对象的时候,才会生成类的隐式实例化
p = new ArrayTP<int ,100>;
2.显式实例化
template class ArrayTP<int, 100>;
3.显式具体化
template <> class ArrayTP<const char *, 100>
{
private:
int j;
};
成员模板:模板可用作结构、类、模板类的成员。声明时使用不同的typename,定义时嵌套typename,如下:
template <typename T>
class beta
{
private:
T t;
public:
template <typename U>
void blab(U u, T t);
};
template <typename T>
template <typename U>
void beta<T>::blab(U u, T t)
{
}
模板用作参数:如下代码:
template <template <typename> class thing>
其中template <typename> class是参数,thing是参数。
模板类的友元:模板类可以有友元,分如下三种:
1.非模板友元
2.约束模板友元,友元类型取决于类被实例化时的类型
3.非约束模板友元,友元的所有具体化都是类的每一个具体化的友元,如下:
1.非模板友元,counts函数是模板所有实例化的友元
template <class T>
class HasFriend
{
private:
static int ct;
public:
friend void counts();
friend void report(HasFriend<T> &);
};
2.约束模板友元
template <typename T> void counts();
template <typename V>
class Test
{
friend void counts<TT>();
};
3.非约束模板友元
template <typename T>
class Test
{
template <typename C, Typename D>
friend void show(C &, D&);
};
1.非模板友元
对于非模板友元,有2种形式,一种是形如counts这样的函数,没有对象参数,可以访问独立于对象的类模板的静态数据成员,比如ct。需要访问对象的时候,使用report这样的函数,注意参数不是HasFriend,因为没有HasFriend这样的对象,必须使用HasFriend<T>作为参数,gcc会报declares a non-template function告警,因为不希望创建非模板友元函数。
2.约束模板友元,本质是对每个具体化的类产生一个友元模板,包含3步,首先在类定义前面声明模板友元,然后在类模板中再次声明为友元,最后给出定义。
3.非约束模板友元,本质是一个模板函数,是所有类具体化的友元