C++学习笔记8-操作符&指针

 1.  重载操作符

赋值操作符的返回类型应该与内置类型赋值运算返回的类型同样。内置类型的赋值运算返回对右操作数的引用,因此,赋值操作符也返回对同一类类型的引用。比如。Sales_item的赋值操作符能够声明为:

 class Sales_item {
 public:
 // other members as before
 // equivalent to the synthesized assignment operator
 Sales_item& operator=(const Sales_item &);
 };

2. 合成赋值操作符

合成赋值操作符与合成复制构造函数的操作类似。

它会运行逐个成员赋值:右操作数对象的每一个成员赋值给左操作数对象的相应成员。

除数组之外,每一个成员用所属类型的常规方式进行赋值。对于数组。给每一个数组元素赋值。 


3. 何时调用析构函数

动态分配的对象仅仅有在指向该对象的指针被删除时才撤销。假设没有删除指向动态对象的指针,则不会执行该对象的析构函数。对象就一直存在。从而导致内存泄漏,并且,对象内部使用的不论什么资源也不会释放。  当对象的引用或指针超出作用域时,不会执行析构函数。

仅仅有删除指向动态分配对象的指针或实际对象(而不是对象的引用)超出作用域时,才会执行析构函数。 撤销类对象时会自己主动调用析构函数:

 // p points to default constructed object
 Sales_item *p = new Sales_item;
 {
 // new scope
 Sales_item item(*p);// copy constructor copies *p into item
 delete p; //destructor called on object pointed to by p
 } // exit local scope; destructor called on item
 //撤销一个容器(无论是标准库容器还是内置数组)时。也会执行容器中的类类型元素的析构函数:
 {
 Sales_item *p = new Sales_item[10]; // dynamically allocated
 vector<Sales_item> vec(p, p + 10); //local object
 // ...
 delete [] p; // arrayis freed; destructor run on each element
 }
 

4. 何时编写显式析构函数

很多类不须要显式析构函数,尤其是具有构造函数的类不一定须要定义自己的析构函数。仅在有些工作须要析构函数完毕时,才须要析构函数。

析构函数通经常使用于释放在构造函数或在对象生命期内获取的资源。

 

假设类须要析构函数。则它也须要赋值操作符和复制构造函数,这是一个实用的经验法则。这个规则常称为三法则,指的是假设须要析构函数,则须要全部这三个复制控制成员。

5. 怎样编写析构函数

在类名字之前加上一个代字号(~)。它没有返回值,没有形參。

析构函数与复制构造函数或赋值操作符之间的一个重要差别是。

即使我们编写了自己的析构函数,合成析构函数仍然执行。比如,能够为Sales_item: 类编写例如以下的空析构函数:

 class Sales_item {
 public:
 // empty; no work todo other than destroying the members,
 // which happensautomatically
 ~Sales_item() { }
 // other members asbefore
 };

撤销Sales_item 类型的对象时,将执行这个什么也不做的析构函数,它执行完成后,将执行合成析构函数以撤销类的成员。

合成析构函数调用string 析构函数来撤销string 成员。string析构函数释放了保存isbn 的内存。

units_sold 和 revenue 成员是内置类型。所以合成析构函数撤销它们不须要做什么。

6. 赋值构造函数

直接初始化和复制初始化。复制初始化使用 = 符号,而直接初始化将初始化式放在圆括号里。

 当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实參匹配的构造函数。复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个暂时对象,然后用复制构造函数将那个暂时对象拷贝到正在创建的对象: 
 string null_book = "9-999-99999-9";          // copy-initialization
 string dots(10, '.');                                      // direct-initialization 
 string empty_copy = string();                     // copy-initialization
 string empty_direct;                                   // direct-initialization 
对于类类型对象。仅仅有指定单个实參或显式创建一个暂时对象用于复制时。才使用复制初始化。 


7. 管理指针

假设对左操作数进行解引用,则改动的是指针所指对象的值;假设没有使用解引用操作。则改动的是指针本身的值

在类的实现中。包括指针的类须要特别注意复制控制,原因是复制指针时仅仅复制指针中的地址,而不会复制指针指向的对象。

指针可能出错:

设计具有指针成员的类时,类设计者必须首先须要决定的是该指针应提供什么行为。将一个指针拷贝到还有一个指针时。两个指针指向同一对象。当两个指针指向同一对象时,可能使用任一指针改变基础对象。类似地。非常可能一个指针删除了一对象时。还有一指针的用户还觉得基础对象仍然存在。

指针成员默认具有与指针对象相同的行为。然而,通过不同的复制控制策略,能够为指针成员实现不同的行为。

大多数C++ 类採用下面三种方法之中的一个管理指针成员: 

1. 指针成员採取常规指针型行为。

这种类具有指针的全部缺陷但无需特殊的复制控制。

2. 类可以实现所谓的“智能指针”行为。

指针所指向的对象是共享的。但类可以防止悬垂指针。

3. 类採取值型行为。指针所指向的对象是唯一的,由每一个类对象独立管理

8. 怎样管理指针

A.定义智能指针类

                a.引入使用计数

 每次创建类的新对象时,初始化指针并将使用计数置为1。当对象作为还有一对象的副本而创建时,复制构造函数复制指针并添加与之对应的使用计数的值。对一个对象进行赋值时。赋值操作符降低左操作数所指对象的使用计数的值(假设使用计数减至0。则删除对象),并添加右操作数所指对象的使用计数的值。

最后,调用析构函数时,析构函数降低使用计数的值,假设计数减至0,则删除基础对象。

// private class for use by HasPtr only
 class U_Ptr {
 friend class HasPtr;
 int *ip;
 size_t use;  
 U_Ptr(int *p): ip(p),use(1) { }
 ~U_Ptr() { delete ip;}
 };
 
 /* smart pointerclass: takes ownership of the dynamically allocated
 * object to which it isbound
 * User code must dynamically allocate an object to initialize a HasPtr
 * and must not deletethat object; the HasPtr class will delete it
 */
 class HasPtr {
 public:
 // HasPtr owns the pointer; p must have been dynamically allocated
 HasPtr(int *p, inti): ptr(new U_Ptr(p)), val(i) { }
 
 // copy members andincrement the use count
 HasPtr(const HasPtr&orig):
 ptr(orig.ptr),val(orig.val) { ++ptr->use; }
 HasPtr&operator=(const HasPtr&);
 
 // if use count goesto zero, delete the U_Ptr object
 ~HasPtr() { if(--ptr->use == 0) delete ptr; }
 private:
 U_Ptr *ptr; // pointsto use-counted U_Ptr class
 int val;
 };

b. 赋值与使用计数.赋值操作符比复制构造函数复杂一点: 

 HasPtr&HasPtr::operator=(const HasPtr &rhs)
 {
 ++rhs.ptr->use; //increment use count on rhs first
 if (--ptr->use ==0)
 delete ptr; // if usecount goes to 0 on this object,delete it
 ptr = rhs.ptr; //copy the U_Ptr object
 val = rhs.val; //copy the int member
 return *this;
 }

c. 改变其它成员.如今须要改变訪问int* 的其它成员,以便通过U_Ptr 指针间接获取int:

 class HasPtr {
 public:
 // copy control andconstructors as before
 // accessors mustchange to fetch value from U_Ptr object
 int *get_ptr() const{ return ptr->ip; }
 int get_int() const {return val; }
 
 // change theappropriate data member
 void set_ptr(int *p){ ptr->ip = p; }
 void set_int(int i) {val = i; }
 
 // return or changethe value pointed to, so ok for const objects
 // Note: *ptr->ipis equivalent to *(ptr->ip)  628
 int get_ptr_val()const { return *ptr->ip; }
 void set_ptr_val(inti) { *ptr->ip = i; }
 private:
 U_Ptr *ptr; // pointsto use-counted U_Ptr class
 int val;
 };


B. 定义值型类

处理指针成员的还有一个全然不同的方法,是给指针成员提供值语义。具有值语义的类所定义的对象。其行为非常像算术类型的对象:复制值型对象时,会得到一个不同的新副本。对副本所做的改变不会反映在原有对象上,反之亦然。string类是值型类的一个样例。

要使指针成员表现得像一个值,复制HasPtr 对象时必须复制指针所指向的对象:

 /*
 * Valuelike behavioreven though HasPtr has a pointer member:
 * Each time we copy aHasPtr object, we make a new copy of the
 * underlying intobject to which ptr points.
 */
 class HasPtr {
 public:
 // no point topassing a pointer if we're going to copy it anyway
 // store pointer to acopy of the object we're given  630
 HasPtr(const int&p, int i): ptr(new int(p)), val(i) {}
 
 // copy members andincrement the use count
 HasPtr(const HasPtr&orig):
 ptr(new int(*orig.ptr)), val(orig.val) { }
 
 HasPtr&operator=(const HasPtr&);
 ~HasPtr() { deleteptr; }
 // accessors mustchange to fetch value from Ptr object
 int get_ptr_val()const { return *ptr; }
 int get_int() const {return val; }
 
 // change theappropriate data member
 void set_ptr(int *p){ ptr = p; }
 void set_int(int i) {val = i; }
 
 // return or changethe value pointed to, so ok for const objects
 int *get_ptr() const{ return ptr; }
 void set_ptr_val(intp) const { *ptr = p; }
 private:
 int *ptr; // pointsto an int
 int val;
 };

复制构造函数不再复制指针。它将分配一个新的int 对象,并初始化该对象以保存与被复制对象同样的值。每一个对象都保存属于自己的int 值的不同副本。由于每一个对象保存自己的副本,所以析构函数将无条件删除指针。

赋值操作符不须要分配新对象。它仅仅是必须记得给其指针所指向的对象赋新值,而不是给指针本身赋值:

 HasPtr&HasPtr::operator=(const HasPtr &rhs)
 {
 // Note: Every HasPtris guaranteed to point at an actual int;
 // We know that ptrcannot be a zero pointer
 *ptr = *rhs.ptr; //copy the value pointed to
 val = rhs.val; //copy the int
 return *this;
 }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

换句话说。改变的是指针所指向的值,而不是指针。

9. 小结

类除了定义该类型对象上的操作,还须要定义复制、赋值或撤销该类型对象的含义。特殊成员函数(复制构造函数、赋值操作符和析构函数)可用于定义这些操作。这些操作统称为“复制控制”函数。

假设类未定义这些操作中的一个或多个。编译器将自己主动定义它们。合成操作运行逐个成员初始化、赋值或撤销:合成操作依次取得每一个成员,依据成员类型进行成员的复制、赋值或撤销。假设成员为类类型的,合成操作调用该类的对应操作(即。复制构造函数调用成员的复制构造函数,析构函数调用成员的析构函数,等等)。假设成员为内置类型或指针。则直接复制或赋值,析构函数对撤销内置类型或指针类型的成员没有影响。假设成员为数组。则依据元素类型以适当方式复制、赋值或撤销数组中的元素。

 

与复制构造函数和赋值操作符不同,不管类是否定义了自己的析构函数,都会创建和执行合成析构函数。假设类定义了析构函数,则在类定义的析构函数结束之后执行合成析构函数。

定义复制控制函数最为困难的部分通常在于认识到它们的必要性。

分配内存或其它资源的类差点儿总是须要定义复制控制成员来管理所分配的资源。

假设一个类须要析构函数,则它差点儿也总是须要定义复制构造函数和赋值操作符。