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

1.  重载操作符

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

 class Sales_item {
 public:
 // other members asbefore
 // equivalent to thesynthesized assignment operator
 Sales_item&operator=(const Sales_item &);
 };

2. 合成赋值操作符

合成赋值操作符与合成复制构造函数的操作类似。它会执行逐个成员赋值:右操作数对象的每个成员赋值给左操作数对象的对应成员。

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

3. 何时调用析构函数

动态分配的对象只有在指向该对象的指针被删除时才撤销。如果没有删除指向动态对象的指针,则不会运行该对象的析构函数,对象就一直存在,从而导致内存泄漏,而且,对象内部使用的任何资源也不会释放。  当对象的引用或指针超出作用域时,不会运行析构函数。只有删除指向动态分配对象的指针或实际对象(而不是对象的引用)超出作用域时,才会运行析构函数。 撤销类对象时会自动调用析构函数:

 // p points todefault constructed object
 Sales_item *p = newSales_item;
 {
 // new scope
 Sales_item item(*p);// copy constructor copies *p into item
 delete p; //destructor called on object pointed to by p
 } // exit localscope; destructor called on item
 //撤销一个容器(不管是标准库容器还是内置数组)时,也会运行容器中的类类型元素的析构函数:
 {
 Sales_item *p = newSales_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. 管理指针

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

指针可能出错:

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

指针成员默认具有与指针对象同样的行为。然而,通过不同的复制控制策略,可以为指针成员实现不同的行为。大多数C++ 类采用以下三种方法之一管理指针成员:

1. 指针成员采取常规指针型行为。这样的类具有指针的所有缺陷但无需特殊的复制控制。

2. 类可以实现所谓的“智能指针”行为。指针所指向的对象是共享的,但类能够防止悬垂指针。

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

7. 如何管理指针

A.定义智能指针类

a.引入使用计数

每次创建类的新对象时,初始化指针并将使用计数置为1。当对象作为另一对象的副本而创建时,复制构造函数复制指针并增加与之相应的使用计数的值。对一个对象进行赋值时,赋值操作符减少左操作数所指对象的使用计数的值(如果使用计数减至0,则删除对象),并增加右操作数所指对象的使用计数的值。最后,调用析构函数时,析构函数减少使用计数的值,如果计数减至0,则删除基础对象。

// private class for use by HasPtr only
 class U_Ptr {
 friend class HasPtr;
 int *ip;
 size_t use;  625
 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>

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

8. 小结

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

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

 

与复制构造函数和赋值操作符不同,无论类是否定义了自己的析构函数,都会创建和运行合成析构函数。

如果类定义了析构函数,则在类定义的析构函数结束之后运行合成析构函数。

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

分配内存或其他资源的类几乎总是需要定义复制控制成员来管理所分配的资源。如果一个类需要析构函数,则它几乎也总是需要定义复制构造函数和赋值操作符。

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

时间: 2024-11-07 21:53:05

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

C++ Primer 学习笔记_26_操作符重载与转换(1)--可重载/不可重载的操作符、成员函数方式重载、友元函数方式重载

C++ Primer 学习笔记_26_操作符重载与转换(1)--可重载/不可重载的操作符.成员函数方式重载.友元函数方式重载 引言: 明智地使用操作符重载可以使类类型的使用像内置类型一样直观! 一.重载的操作符名 像任何其他函数一样,操作符重载函数有一个返回值和一个形参表.形参表必须具有操作符数目相同的形参.比如赋值时二元运算,所以该操作符函数有两个参数:第一个形参对应着左操作数,第二个形参对应右操作数. 大多数操作符可以定义为成员函数或非成员函数.当操作符为成员函数时,它的第一个操作数隐式绑定

C++ Primer 学习笔记_28_操作符重载与转换(3)--成员函数的重载、覆盖与隐藏、类型转换运算符、*运算符重载、-&gt;运算符重载

C++ Primer 学习笔记_28_操作符重载与转换(3)--成员函数的重载.覆盖与隐藏.类型转换运算符.*运算符重载.->运算符重载 一.成员函数的重载.覆盖与隐藏 对于类层次的同名成员函数来说,有三种关系:重载.覆盖和隐藏,理清3种关系,有助于写出高质量的代码. 1.成员函数的重载 重载的概念相对简单,只有在同一类定义中的同名成员函数才存在重载关系,主要特点时函数的参数类型和数目有所不同:但不能出现函数参数的个数和类型均相同,仅仅依靠返回值类型不同来区分的函数,这和普通函数的重载是完全一致

C++ Primer 学习笔记_29_操作符重载与转换(4)--转换构造函数和类型转换运算符归纳、operator new 和 operator delete 实现一个简单内存泄漏跟踪器

C++ Primer 学习笔记_29_操作符重载与转换(4)--转换构造函数和类型转换运算符归纳.operator new 和 operator delete 实现一个简单内存泄漏跟踪器 一.转换构造函数 可以用单个实参来调用的构造函数定义从形参类型到该类型的一个隐式转换.如下: class Integral { public: Integral (int = 0); //转换构造函数 private: int real; }; Integral A = 1; //调用转换构造函数将1转换为In

iOS: 学习笔记, Swift操作符定义

Swift操作符可以自行定义, 只需要加上简单的标志符即可. @infix 中置运算. 如+,-,*,/运算 @prefix 前置运算. 如- @postfix 后置运算. a++, a-- @assignment 赋值运算. +=, -=, --a, ++a // // main.swift // SwiftBasic // // Created by yao_yu on 14-7-27. // Copyright (c) 2014年 yao_yu. All rights reserved.

C++学习笔记30,指针的引用(2)

可以创建任何类型的引用,包括指针类型. 看一个简单的指针的引用的例子.例如: #include <iostream> using namespace std; int main(){ int x=10; int y=20; int z=30; int* ptx=&x; int* ptz=&z; //指针的引用,声明从右往左看,rtp与&结合, //剩余的符号和左边结合 //引用一旦创建,不能改变其指向,只能改变其值 int* &rtp=ptx; cout<

C++ Primer 学习笔记_27_操作符重载与转换(2)--++/--运算符重载、!运算符重载、赋值运算符重载 、String类([]、 +、 += 运算符重载)、&gt;&gt;和&lt;&lt;运算符重载

C++ Primer 学习笔记_27_操作符重载与转换(2)--++/--运算符重载.!运算符重载.赋值运算符重载 .String类([]. +. += 运算符重载).>>和<<运算符重载 一.++/--运算符重载 1.前置++运算符重载 成员函数的方式重载,原型为: 函数类型 & operator++(); 友元函数的方式重载,原型为: friend 函数类型 & operator++(类类型 &); 2.后置++运算符重载 成员函数的方式重载,原型为:

C++学习笔记之this指针

为了说明这个问题,首先来建立一个简单的类 1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 class Book 7 { 8 private: 9 string title; 10 int year; 11 double price; 12 public: 13 double getPrice() const { return price;} 14 } 这个类是关于书籍信息的简单表示,它可以

C++学习笔记之函数指针

与数据项类似,函数也有地址.函数的地址是存储其机器语言代码的内存开始的地方. 一.函数指针的基础知识 假设要设计一个名为estimate()的函数,估算编写指定行数代码所需时间,并且希望不同的程序员都使用该函数,并且该函数允许每个程序员提供自己的算法来估计时间.为实现这种目标,采用的机制是,将程序员要使用的算法函数地址传给estimate(),必须完成以下工作: 获取函数地址 声明一个函数指针 用函数指针来调用函数 1.获取函数地址 使用函数名(后面不跟参数)即可.如:think()是一个函数,

C语言学习笔记 (001) - 常量指针与指针常量的区别(转帖)

三个名词虽然非常绕嘴,不过说的非常准确.用中国话的语义分析就可以很方便地把三个概念区分开. 一) 常量指针. 常量是形容词,指针是名词,以指针为中心的一个偏正结构短语.这样看,常量指针本质是指针,常量修饰它,表示这个指针乃是一个指向常量的指针(变量). 指针指向的对象是常量,那么这个对象不能被更改. 在C/C++中,常量指针是这样声明的: 1)const int *p; 2)int const *p; 常量指针的使用要注意,指针指向的对象不能通过这个指针来修改,可是仍然可以通过原来的声明修改,也

OC学习笔记 ARC 强指针和弱指针 内存管理

强调一些概念 类:是一种结构,它表示对象的类型,对象引用类来获取和本身有关的各种信息,特别是运行什么代码来处理每种操作. 对象:是一种结构,它包含值和指向其类的隐藏指针. 实例:对象的另一种称呼. 消息:是对象可以执行的操作,用于通知对象去做什么.对象接收消息后,将查询对应的类,以便查找正确的代码来运行. 方法:是为响应消息而运行的代码,根据对象的类,消息可以调用不同的方法. 接口:是对象的类应该提供特殊的特性的描述. 用法如:@property (attribute1,attribute2)