第12章 类
void fuc() const; //成员函数声明为常量,不能改变其所操作对象的数据成员,const必须同时出现在声明和定义中
可任意在成员函数的声明或定义中指定inline
前向声明,不完全类型,声明未定义的类型后,在类型定义前不能定义该类型的对象,只能定义指针及引用或函数形参或返回类型,
因为在定义对象及用指针或引用访问类成员时需要知道类的相应存储空间的大小。
基于const的重载: const对象只能用const成员函数
1.基于成员函数是否为const,可重载
2.基于一个指针形参是否是指向const,可重载
引用形参是否为const可重载
当形参以副本传递时,不能基于形参是否为const来实现重载。
f(int *);f(int *const); //redeclaration
可变数据成员mutable,放在声明前面,const成员函数也可改变其值。
成员函数类外定义的返回类型不在类的作用域中,此时要使用完全限定的类型名
class Screen {
public:
typedef std::string::size_type index;
index get_cursor() const;};
inline Screen::index Screen:: get_cursor() const{ //
return cursor;}
构造函数不能为const
在构造函数初始化列表中没有显式提及的每个成员,用该类类型的默认构造函数来初始化,内置或复合类型成员的初始值依赖于对象的作用域。
必须用初始化列表:
1.const或引用类型的成员 。 可以初始化const对象或引用类型对象,但不能对它们赋值。
2.没有默认构造函数的类类型成员
构造函数初始化列表中成员被初始化顺序就是成员被定义的次序。
只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。
隐式类型转换:单个实参来调用的构造函数定义了从形参类型到该类类型的一个隐式转换。
class Fruit //定义一个类,名字叫Fruit
{
string name;//定义一个name成员
string colour;//定义一个colour成员
public:
bool isSame(const Fruit& otherFruit)//期待的形参是另一个Fruit类对象,测试是否同名
{
return name == otherFruit.name;
}
void print()//定义一个输出名字的成员print()
{
cout << colour << " " << name << endl;
}
Fruit(const string& nst, const string& cst = "green"):name(nst), colour(cst){}
Fruit(){}
};
int main()
{
Fruit apple("apple");
Fruit orange("orange");
cout << "apple = orange? : " << apple.isSame(orange) << endl;
//单个实参,隐式转换
cout << "apple = \"apple\"? :" << apple.isSame(string("apple")) << endl;
return 0;
}
单个参数的构造函数前要加explicit,来抑制构造函数定义的隐式转换。
static成员函数没有this指针,可以直接访问所属类的static成员,但不能直接只用非static成员。
static关键字只能用于类定义体内部的声明中,不能用于定义。
static成员函数不能声明为const,因为static成员不是任何对象的组成部分,而const是不修改函数所属对象。
static成员函数不能声明为虚函数。
static数据成员必须在类定义体的外部定义(正好一次)。不在构造函数中初始化,而应该在定义时进行初始化。
static数据成员的类型可以是该成员所属的类类型,非static成员只能声明为自身类对象的指针或引用。
class Bar {
public:
//...
private:
static Bar mem1; //ok
Bar *mem2; //ok
Bar mem3; //error
};
static数据成员可用作默认实参,非static不能用作默认实参
class scope
encapsulation
第13章 复制控制 copy control 复制构造函数 copy constructor 赋值操作符 assignment operator 析构函数 destructor
复制构造函数:单个形参,且是对本类类型对象的引用(常用const修饰)。
作用:
1.根据另一个同类型对象显式或隐式初始化一个对象
2.复制一个对象,将他作为实参传给一个函数, 非引用类型,值传递
3.从函数返回时复制一个对象
4.初始化顺序容器的元素
5.根据元素初始化式列表初始化数组元素
对象定义的初始化形式:直接初始化(直接调用实参匹配的构造函数),复制初始化(先创建临时对象,再调用复制构造函数)。
对于不支持复制的类型,或使用非explicit构造函数的时候,它们有本质区别
ifstream file1("filename"); //ok,direct initialization
ifstream file2 = "filename"; //error,copy initialization,不能复制IO类型
//this initialization is okay only if
//the Sales_item(const string&) constructor is not explicit
Sales_item item = string("9-999-99999-9"); //隐式转换
//default string constructor and five string copy constructor invoked
vector<string> svec(5); //先用string默认构造函数创建一个临时值初始化svec,然后使用复制构造函数将临时值复制到svec的每个元素
合成的复制构造函数synthesized copy constructor:将现有对象的每个非static成员,依次复制到正创建的对象。内置类型和数组直接复制,类类型使用该类的复制构造函数。
如果没有定义复制构造函数,编译器就会合成一个,即使已经定义了其他的构造函数。与合成的默认构造函数不同。
合成的复制构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝,两个指针a=b指向同一资源,值拷贝或深拷贝进行资源的重新分配。
值传递,创建对象副本的时候,如果构造函数里为指针分配了内存,那么副本和原对象的指针指向相同的内存空间,当函数返回,副本的析构函数对该指针释放空间后,原对象的指针仍指向释放了的空间。而当原对象被销毁,执行析构函数的时候该内存空间又被释放了一次,又将产生严重错误。
当然我们可以用传地址或传引用,但有时我们不希望函数里面的操作影响原始对象,就可以用复制构造函数,复制构造函数就是在产生对象副本的时候执行的,我们可以定义自己的复制构造函数。在复制构造函数里面我们申请一个新的内存空间来保存构造函数里面的那个指针所指向的内容。这样在执行对象副本的析构函数时,释放的就是复制构造函数里面所申请的那个内存空间。
禁止复制:如IO类。 可以显示声明其复制构造函数为private。
但类的成员和友元仍可复制,可以声明一个private的复制构造函数而不定义。用户代码中的复制尝试编译时报错,成员和友元的复制尝试在链接时报错。
类似的,编译器可合成赋值操作符。
合成的析构函数:不删除指针成员指向的对象。
因为不能指定任何形参,所以不能重载析构函数。
即使我们编写了自己的析构函数,合成析构函数仍然运行。 先运行自己的,再运行合成的以撤销类的成员。
只有删除指向动态分配对象的指针或实际对象(而非对象的引用或指针)超出作用域时,才运行析构函数。
任何时候编译器都会合成析构函数,并且合成的析构函数总会运行。
撤销容器(标准库容器和内置数组)是按逆序进行的。
内存泄漏(Memory leak):删除指向动态分配内存的指针失败,因而无法将该块内存返还给自由存储区,这样的删除动态分配内存失败称为内存泄漏。
三法则(rule of three):如果类需要析构函数,则也需要赋值操作符和复制构造函数。
如果类里面有需要手动释放的资源,比如指针,就需要自己定义析构函数释放,为了防止浅拷贝后多次析构,需要自己定义拷贝构造函数,赋值操作符重载同理。
其他构造函数如果没有显式初始化某个类成员,则那个成员用该成员的默认构造函数初始化。
而显式定义的复制构造函数不会进行任何自动复制。
管理指针成员:
1. 智能指针
class HasPtr {
public:
HasPtr(int *p, int i): ptr(new U_Ptr(p)), val(i){}
HasPtr(const HasPtr &orig):
ptr(orig.ptr), val(orig.val) { ++ptr->use;}
HasPtr& operator=(const HasPtr&);
int *get_ptr() const {return ptr->ip;}
int get_int() const {return val;}
void set_ptr(int *p) { ptr->ip = p;}
void set_int(int i) { val = i;}
int get_ptr_val() const {return *ptr->ip;}
void set_ptr_val(int val) const { *ptr->ip = val;}
~HasPtr() { if (--ptr->use == 0) delete ptr; }
private:
U_Ptr *ptr;
int val;
};
HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
++rhs.ptr->use;
if (--ptr->use == 0)
delete ptr;
ptr = rhs.ptr;
val=rhs.val;
return *this;
}
//引入使用计数
class U_Ptr {
private:
friend class HasPtr;
int *ip;
size_t use;
U_Ptr(int *p):ip(p),use(1) {}
~U_Ptr() {delete ip;}
};
2.定义值型类
class HasPtr {
public:
HasPtr(const int *p, int i): ptr(new int(p)), val(i){}
HasPtr(const HasPtr &orig):
ptr(new int(*orig.ptr)), val(orig.val) {}
HasPtr& operator=(const HasPtr&);
int *get_ptr() const {return ptr;}
int get_int() const {return val;}
void set_ptr(int *p) { ptr = p;}
void set_int(int i) { val = i;}
int get_ptr_val() const {return *ptr;}
void set_ptr_val(int val) const { *ptr = val;}
~HasPtr() { delete ptr; }
private:
int *ptr;
int val;
};
HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
*ptr = *rhs.ptr;
val = rhs.val;
return *this;
}
第14章重载操作符与转换
不能重载的操作符 :: .* . ?:
可重载的:算术运算符,逻辑运算符, << >> <<= >>= [] () –> –>* new new[] delete delete[]
通过连接其他合法符号创建新的操作符,如operator**是合法的。
不能为内置数据类型重定义操作符,如不能定义两个数组类型操作符的operator+
重载操作符必须具有至少一个类类型或枚举类型的操作数。
优先级和结合性不变。
不具备短路求职特性: && || 逗号操作符,两边求值顺序无规定。
除了函数调用操作符operator()外,重载操作符时使用默认实参是非法的。
= [] () –>必须定义为成员函数
一般赋值操作符和复合赋值操作符要返回做操作数*this的引用。
IO操作符重载必须为非成员函数,友元。 如果为成员函数,左操作数为该类类型的对象,与正常相反。
ostream&
operator<<(ostream &os, const ClassType &object) //写入到流,ostream会变,object应不变,且避免复制实参
{
os<<//...
return os;
}
istream&
operator>>(istream &in, Sales_item &s) //错误处理
{
in>>//...
if (in) //...
else
s=Sales_item(); //input failed:reset object to default state
}
算术操作符和关系操作符一般定义为非成员函数
算术操作符返回新值而非任一操作数的引用,如果定义了该算术操作符的相关复合赋值操作符,则该算术操作符可用复合赋值操作符实现。
inline bool
operator==(const Sales_item &lhs, const Sales_item &rhs) //find函数{
return lhs.units_sold==rhs.units_sold && …;
}
inline bool
operator!=(const Sales_item &lhs, const Sales_item &rhs)
{
return !(lhs == rhs);
}
定义下标操作符一般要定义两个版本,非const成员返回引用,const成员返回const引用。因在用作赋值的左右操作数时都应表现正常。
class Foo {
public:
int& operator[] (const size_t);
const int& operator[] (const size_t) const;
private:
vector<int> data;
};
int& operator[] (const size_t index)
{
return data[index];
}
const int& operator[] (const size_t) const
{
return data[index];
}
point->action();
1.如果 point 是一个指针,指向具有名为 action 的成员的类对象,则编译器将代码编译为调用该对象的 action 成员。
2.否则,如果 point是定义了 operator-> 操作符的类的一个对象,则 point->action 与 point.operator->()->action 相同。即,执行 point 的 operator->(),然后使用该结果重复这三步。
3.否则,代码出错。
重载箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象。
class A{
public:
void action(){
cout << "Action in class A!" << endl;
}
};
class B{
A a;
public:
A* operator->(){
return &a;
}
void action(){
cout << "Action in class B!" << endl;
}
};
class C{
B b;
public:
B operator->(){
return b;
}
void action(){
cout << "Action in class C!" << endl;
}
};
int main(int argc, char *argv[])
{
C* pc = new C;
pc->action();
C c;
c->action(); //等价于c.operator->().operator->()->action();
getchar();
return 0;
}
上面代码输出结果是:
Action in class C!
Action in class A!
常用于智能指针
class ScrPtr {
friend class ScreenPtr;
Screen *sp;
size_t use;
ScrPtr(Screen *p): sp(p), use(1) { }
~ScrPtr() { delete sp; }
};
class ScreenPtr {
public:
ScreenPtr(Screen *p): ptr(new ScrPtr(p)) { }
ScreenPtr(const ScreenPtr &orig):
ptr(orig.ptr) { ++ptr->use; }
ScreenPtr& operator=(const ScreenPtr&);
Screen &operator*() { return *ptr->sp; }
Screen *operator->() { return ptr->sp; }
const Screen &operator*() const { return *ptr->sp; }
const Screen *operator->() const { return ptr->sp; }
~ScreenPtr() { if (--ptr->use == 0) delete ptr; }
private:
ScrPtr *ptr;
};
自增自减操作符
class CheckedPtr {
public:
// no default constructor; CheckedPtrs must be bound to an object
CheckedPtr(int *b, int *e): beg(b), end(e), curr(b) { }
CheckedPtr& operator++(); // prefix operators
CheckedPtr& operator--();
CheckedPtr operator++(int); // postfix operators后置
CheckedPtr operator--(int);
private:
int* beg; // pointer to beginning of the array
int* end; // one past the end of the array
int* curr; // current position within the array
};
CheckedPtr& CheckedPtr::operator++()
{
if (curr == end)
throw out_of_range
("increment past the end of CheckedPtr");
++curr; // advance current state
return *this;
}
CheckedPtr& CheckedPtr::operator--()
{
if (curr == beg)
throw out_of_range
("decrement past the beginning of CheckedPtr");
--curr; // move current state back one element
return *this;
}
CheckedPtr CheckedPtr::operator++(int)
{
// no check needed here, the call to prefix increment will do the check
CheckedPtr ret(*this); // save current value
++*this; // advance one element, checking the increment
return ret; // return saved state
}
CheckedPtr CheckedPtr::operator--(int)
{
// no check needed here, the call to prefix decrement will do the check
CheckedPtr ret(*this); // save current value
--*this; // move backward one element and check
return ret; // return saved state
}
调用操作符和函数对象 Call Operator and Function Objects
struct absInt {
int operator() (int val) {
return val < 0 ? -val : val;
}
};
int i = -42;
absInt absObj; // object that defines function call operator
unsigned int ui = absObj(i); // calls absInt::operator(int)
将函数对象用于标准库算法Using Function Objects with Library Algorithms
bool GT6(const string &s)
{
return s.size() >= 6;
}
vector<string>::size_type wc =
count_if(words.begin(), words.end(), GT6);
class GT_cls {
public:
GT_cls(size_t val = 0): bound(val) { }
bool operator()(const string &s)
{ return s.size() >= bound; }
private:
std::string::size_type bound;
};
//计算长度在 1 到 10 个字符的单词数
//for (size_t i = 0; i != 11; ++i)
// cout << count_if(words.begin(), words.end(), GT(i))
// << " words " << i
// << " characters or longer" << endl;
#include <functional>
算术函数对象类型 所对应的操作符
plus<Type> +
minus<Type> -
multiplies<Type> *
divides<Type> /
modulus<Type> %
negate<Type> -
关系函数对象类型
equal_to<Type> ==
not_equal_to<Type> !=
greater<Type> >
greater_equal<Type> >=
less<Type> <
less_equal<Type> <=
逻辑函数对象类型
logical_and<Type> &&
logical_or<Type> ||
logical_not<Type> !
sort(svec.begin(), svec.end(), greater<string>());
函数对象的函数适配器 Function Adaptors for Function Objects
1.绑定器binder,是一种函数适配器,它通过将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象。
标准库定义了两个绑定器适配器:bind1st 和 bind2nd, bind1st 将给定值绑定到二元函数对象的第一个实参,bind2nd 将给定值绑定到二元函数对象的第二个实参。
//计算一个容器中所有小于或等于 10 的元素的个数,可以这样给 count_if 传递值:
count_if(vec.begin(), vec.end(),
bind2nd(less_equal<int>(), 10));//第三个实参使用 bind2nd 函数适配器,该适配器返回一个函数对象,该对象用 10 作右操作数应用 <= 操作符
2.求反器Negator,是一种函数适配器,它将谓词函数对象的真值求反。
标准库还定义了两个求反器:not1 和 not2。你可能已经想到的,not1 将一元函数对象的真值求反,not2 将二元函数对象的真值求反。
count_if(vec.begin(), vec.end(),
not1(bind2nd(less_equal<int>(), 10)));//对不 <= 10 的那些元素进行计数
转换操作符 // 转换函数必须是成员函数,不能指定返回类型,但每个转换函数必须显式返回一个指定类型的值, 并且形参表必须为空。
operator type(); //type 表示内置类型名、类类型名或由类型别名定义的名字
类类型转换之后不能再跟另一个类类型转换。如果需要多个类类型转换,则代码将出错。
class SmallInt {
public:
SmallInt(int = 0);
SmallInt(double);
operator int() const { return val; }
operator double() const { return val; }
private:
std::size_t val;
};
void compute(int);
void fp_compute(double);
void extended_compute(long double);
SmallInt si;
compute(si); // SmallInt::operator int() const
fp_compute(si); // SmallInt::operator double() const
extended_compute(si); // error: ambiguous
void manip(const SmallInt &);
double d; int i; long l;
manip(d); // ok: use SmallInt(double) to convert the argument
manip(i); // ok: use SmallInt(int) to convert the argument
manip(l); // error: ambiguous
void compute(int);
void compute(double);
void compute(long double);
SmallInt si;
compute(si); // error: ambiguous
//显式强制转换消除二义性
SmallInt si;
compute(static_cast<int>(si)); // ok: convert and call compute(int)
///////////////////////////////////////////////////////
//当两个类定义了相互转换时,很可能存在二义性:
class Integral;
class SmallInt {
public:
SmallInt(Integral); // convert from Integral to SmallInt
// ...
};
class Integral {
public:
operator SmallInt() const;
// ...
};
void compute(SmallInt);
Integral int_val;
compute(int_val); // error: ambiguous
////////////////////////////////////////////////////
class SmallInt {
public:
SmallInt(int = 0);
};
class Integral {
public:
Integral(int = 0);
};
void manip(const Integral&);
void manip(const SmallInt&);
manip(10); // error: ambiguous
///////////////////////////////////////////////////
class SmallInt {
public:
SmallInt(int = 0); // convert from int to SmallInt
// conversion to int from SmallInt
operator int() const { return val; }
// arithmetic operators
friend SmallInt
operator+(const SmallInt&, const SmallInt&);
private:
std::size_t val;
};
SmallInt s1, s2;
SmallInt s3 = s1 + s2; // ok: uses overloaded operator+
int i = s3 + 0; // error: ambiguous