前言
本序列笔记是来自阅读裘宗燕翻译的C++程序设计特别版的总结。
运算符号重载
不能重载的运算符
::(作用域解析)
。(成员选择)
。*
(通过成员的指针做成员选择)
一元运算、二元运算
[1]aa@bb
可以解析
Operator@ (aa,bb)
——>非成员函数
aa.Operator@(bb)
——>成员函数
[2]@aa
operator@(aa)
[3]
运算符的预定义
operator = ,operator[] ,operator(),operator->
只能作为类的非静态成员函数,这样才能保证第一个运算对象一定是一个左值
[4]x@y
解析
n 查找对象x的成员函数
n 围绕x@y的环境中查找
n 在X,Y的空间中查找
n 注意歧义
混合模式、隐式转换
n
构造函数、转换
Ex:
class complex
{
//…
complex( double);
//…
}
这样就存在一个从内部类型
double 到
complex 的转换。
在表达式中,通过隐式、显式转换的都是临时对象,将在最早可能的时候被销毁。
任何用户定义的类型转换,都不会用到
-> (.) 左边。即使是在隐含的情况下也是如此。
通过构造函数的机制,可以实现从一个类型
到另一个类型(该类型拥有另一个类型的成员对象)的转换。
n
初始化
一般是通过
复制构造函数,可能需要类型转换。
n
显式构造函数,定义单个参数的构造函数,就存在一个该参数类型到该类的隐式转换,如果要防止这样的事件发生
可以在该构造函数声明explicit
转换运算符号
通过构造函数去刻画类型转换确实很方便,但是也意味着存在一些并不是我们希望的情况。构造函数不能刻画
[1] 从用户定义类型到内部类型的转换,因为内部类型不是类。
[2] 从新类型 到某个已有的类型的转换(而不是修改那个已有的类型)。
通过以下的技术,就可以实现
从 X
类型
到 T
类型的转换。
operator X::T()
是成员函数
通过这样,就任何需要转换的情况下从X 到T的转换。
[3]
一个类
的类型转换
可以通过
用户定义转换(构造函数机制),或者用户定义转换运算符,但是不能同时存在这两种情况,因为这样会存在歧义。
友元
一个常规的成员函数声明描述了三件在逻辑上相互不同的事情:
[1] 访问类声明的私有部分。
[2] 位于类的作用域中。
[3] 需要一个对象去激活(有this 指针)
声明为
static 成员函数具有[1]、[2]的功能,声明为友元具有[1]的功能。
友元注意事项:
[1] 友元可以在类的任何部分声明。
[2]
friend
类只应该用于那些密切相关的概念,常常会做出这样的选择,是将一个类声明一个类的成员(嵌套类),或是友元。
友元的寻找
[1]像成员的声明一样,一个友元声明不会给外围作用域引进一个名字。
Ex:
class Matrix{
friend class Xfrom;
friend Matrix invert(const Matrix&);
//…
};
Xfrom x; //
作用域内无Xfrom
(Matrix *p)(const Matrix&) = &invert() //
错误,作用域内无invert
()
对于大型程序和大的类,一个类不能“默不做声地”给它的外围作用域加入一些名字。
[2] 一个友元类 或者是外围作用域里做先行声明,或者是将它作为友元的那个类的直接外围的非类作用域里定义的。
Ex:
Class X{/*……*/}; //
是Y
的友元
Namespace N
{
Class Y{
Friend class X;
Friend class Z;
Friend class AE;
};
Class Z{
//……
}; //
是Y
的友元
}
Class AE{/*……*/}; //
不是Y
的友元
友元函数
[1] 一个友元函数可以像友元类那样的声明。此外还可以通过它的参数找到它,即使它并没有在外围最近的作用域里声名。
[2] 一个友元函数或者需要在某个外围作用域里显式声明,或者是以它的类或者该类的派生类作为一个参数,通过参数去寻找它,否则就无法利用这个友元了。
Ex
:
//
假定作用域里
无f
()和h
(。。。)
Class X{
Friend void f(); //
无用的
Friend void h(const X&); //
可以通过参数找到,通过参数解析(调用函数的机制)[
没有通过函数名称解析]
}
;
Void g(const X& x)
{
f(); //There is no f();
h(x); //Ok
}
成员和友元
Ex:
Class X{
//…
X(int);
Int m1();
Int m2()const;
Friend int f1(X&);
Friend int f2(const X&);
Friend int f3(X);
};
Void g()
{
99.m1(); //Eorro
f1(99); //Eorro
f2(99); //ok
f3(99); //ok
}
[1] 成员函数只能通过有关类的对象去调用,用户定义的转换不会用到
->(.)的左边
[2] 隐式转换不会用到非
const 引用的初始化,但是可以用到
const 引用的初始化。
[3] 如果希望某个运算的所有运算对象都能允许隐式转换,实现它的函数就应该作为
非成员函数,取const X&
参数,非引用参数。
基本运算符
一般说来,对于类型
X,复制构造函数
X
(const X&
)负责完成从同类型X的一个对象出发的初始化。
强调赋值和初始化是不同的是决不过分的。
复制构造函数利用在
参数传递、值返回、异常的原理
[1]
参数传递,给形式参数初始化
[2]
异常也一样
[3]
值返回,是给一个临时对象初始化
下标
函数
operator[]可以用于为类的对象定义下标运算的意义。
operator[] 的第二个参数([下标])可以具有任何类型。
函数调用
函数调用,即记法形式
expression(expression-list),可以解释为一种二元运算,其中
expression
是左值对象,expression-list
是右运算对象。这一运算符()可以通过重载方式。
形式 operator
()(arg - list
)
函数对象
Class Add{
complex val;
Public:
Add (complex c);
Add (double r,double i);
Void operator()(complex& c)const;
};
间接
间接运算符—> 可以被定义为一个一元的后缀运算。
Ex:
Class Ptr{
//……
X* operator ->();
};
Void f(Ptr p)
{
p->m = 7; //(p.operator->())->m =7
X* q1 = p->; //error
X* q2 = p.operator ->();//ok
}
增量(++) 和减量(――)
Ex:
Class Ptr_to_T{
T* p;
T* array;
int size;
public:
Ptr_to_T& operator ++(); //
前缀
Ptr_to_T operator ++(int ); //
后缀,是个左值
Ptr_to_T& operator
―― ();
Ptr_to_T operator
――(int);
T& operator* ();
};
派生类
继承(派生)
是对类功能的一种扩展行为。
[1] 派生类可以使用基类的公用部分的和保护部分,但不能使用私有部分。
[2] 派生类 对象 给基类对象赋值存在对象切片问题。
[3] 一般说来,最清晰的设计是派生类 只使用它的基类的公用成员。
构造函数、析构函数
有些派生类需要构造函数,如果某个基类中有构造函数,那么就必须调用这些构造函数中的某一个。默认构造函数可以被隐含调用,但是如果该基类的所有构造函数都有参数,那么就必须显式调用。
复制
类对象的复制由复制构造函数 和赋值操作定义的。
复制构造函数、赋值操作绝对不是继承的。
对于给定的一个类型为Base* 指针的指向
[1]
保证被指的只能是一个惟一类型的对象(没有派生类吗?)
[2]
在基类里安排一个类型域,供函数检查。在检查后进行类型转换
[3]
使用 dynamic_cast
[4]
使用虚函数
Ex:
Class Employee
{
//…
enum Empl_type{M,E};
Empl_type type;
//…
};
类型域很容易出错误