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. 小结

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

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

 

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

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

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

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

C++学习笔记8-操作符&amp;指针的更多相关文章

  1. Essential C++ 学习笔记02--Array/Vector 与指针

    Essential C++ 1.5-1.6节,3.1节笔记 Array/Vector/指针,难度偏大, 但若学习初期不熟悉基本用法,则难以写出有效代码. 1. 基本概念 Array 是一段连续内存,数 ...

  2. iOS: 学习笔记, Swift与C指针交互(译)

    Swift与C指针交互 Objective-C和C API经常需要使用指针. 在设计上, Swift数据类型可以自然的与基于指针的Cocoa API一起工作, Swift自动处理几种常用的指针参数. ...

  3. The C++ Programming Language 学习笔记 第5章 指针、数组和结构

    1.关于输出指向字符的指针的值. 现在定义,char c='a',char* pc=&c.在C中,输出该值只需要printf("%p\n",pc);而在C++中,如果cou ...

  4. C++ Primer 学习笔记_Chapter4 数组和指针–指针

    一.什么是指针? 指针与迭代器一样,指针提供对其所指对象的间接访问,指针保存的是另一个对象的地址: string s("hello"); string *ps = &s; ...

  5. C学习笔记(5)--- 指针第二部分,字符串,结构体。

    1. 函数指针(function pointer): 函数指针是指向函数的指针变量. 通常我们说的指针变量是指向一个整型.字符型或数组等变量,而函数指针是指向函数. 函数指针可以像一般函数一样,用于调 ...

  6. C++学习笔记(四)--指针

    1.指针(变量的地址): 指针变量:存放指针(地址)的变量 直接存取(访问):按变量地址取值 间接存取(访问):将变量的地址放入指针变量中 定义指针变量:基类型 *指针变量名 2.与指针有关的运算符: ...

  7. osg(OpenSceneGraph)学习笔记1:智能指针osg::ref_ptr<>

    OSG的智能指针,osg::ref_ptr<> osg::Referenced类管理引用计数内存块,osg::ref_ptr需要使用以它为基类的其它类作为模板参数. osg::ref_pt ...

  8. c++学习笔记(二)-指针

    1. 指向数组的指针 int balance[5] = { 1000, 2, 3, 17, 50 }; int *ptr; ptr = balance; //ptr是指向数组balance的指针 // ...

  9. 学习笔记之C/C++指针使用常见的坑

    https://mp.weixin.qq.com/s/kEHQjmhNtSmV3MgHzw6YeQ 避免内存泄露 不再用到的内存没有释放,就叫做内存泄露 在C/C++中,通过动态内存分配函数(如mal ...

  10. C学习笔记(2)--指针

    一.多文件结构总结 1.子源文件里面包含自己对应的头文件 2.无论是何源文件调用库函数,都需要包含该库函数的声明所在的头文件 3.头文件又叫接口文件,.c对数据和函数进行封装和包含, .h就是.c对外 ...

随机推荐

  1. JS-网页中分页栏

    原理 三部分 我给分页栏分成了3部分 上一页:调用prePage()函数 下一页:调用nextPage()函数 带有数字标识的部,调用skipPage()函数 prePage函数 function p ...

  2. Effective C++ 条款43

    学习处理模板化基类里的名称 本节作者编写的意图在我看来能够总结成一句话,就是"怎样定义并使用关于模板类的派生过程,怎样处理派生过程出现的编译不通过问题". 以下我们看一段说明性的代 ...

  3. 使用TCP协议的NAT穿透技术 (转载)

    其实很早我就已经实现了使用TCP协议穿透NAT了,但是苦于一直没有时间,所以没有写出来,现在终于放假有一点空闲,于是写出来共享之. 一直以来,说起NAT穿透,很多人都会被告知使用UDP打孔这个技术,基 ...

  4. 将 php 转换/编译为 EXE

    将 php 转换/编译为 EXE 本文仅仅是将原文用谷歌作了翻译,原文来源于 http://stackoverflow.com 资料来源  http://stackoverflow.com/quest ...

  5. python使用大漠插件进行脚本开发的尝试(一)

    关于游戏脚本是纯然的小白,记一下学习过程遇到的问题.是在win10系统下对PC端的游戏进行脚本编辑,不知道会不会半途放弃. 一.大漠插件 大漠插件在游戏脚本编辑过程中是比较常见的工具,按我理解大致做的 ...

  6. sql server还原数据库代码

    RESTORE DATABASE ExaminationsystemFROM DISK = 'C:\Users\admin\Desktop\20140324.bak'with replace,MOVE ...

  7. [NPM] Test npm packages locally in another project using npm link

    We will import our newly published package into a new project locally to make sure everything is wor ...

  8. node中间层

    node中间层 一.总结 1.node中间层作用:前端也是mvc,NodeJS之后,前端可以更加专注于视图层,而让更多的数据逻辑放在Node层处理 2.node中间层作用:当发现所有请求量太多应付不过 ...

  9. UART和RS232/RS485的关系,RS232与RS485编程

    http://wpp9977777.blog.163.com/blog/static/4625100720138495943540/ 串口通讯是电子工程师和嵌入式开发工程师面对的最基本问题,RS232 ...

  10. chrome 的input 上传响应慢问题解决方案

    <input type="file" accept="image/png,image/jpeg,image/gif" class="form-c ...