PS,1880后程序员

看不完的牙,写不完的程序,跑不完的步。
随笔 - 97, 文章 - 34, 评论 - 10, 引用 - 0
数据加载中……

C++ Primer 之 读书笔记 第十三章

 

Chapter 13. Copy Control

13.1 The Copy Constructor

首先,这真是一个有趣的命题。我老喜欢。这让我想起的是Javaclone。(写下这句话时我还没有看这一节,只是一个预感)

过瘾啊。(写下这句话时我刚刚读完这一节,还没有来得及整理笔记,理顺思路)

什么叫做拷贝构造函数?

拷贝构造函数有一个形参,并且这个形参是指向同一个类的对象的引用。(The constructor that takes a single parameter that is a (usually const) reference to an object of the class type itself is called the copy constructor.

对象定义的形式

c++支持两种形式的初始化:

1.      直接初始化,使用初始化式(initializer),直接调用对应的构造函数。

2.      拷贝初始化,使用’=’。先调用构造函数生成临时对象,然后再调用拷贝构造函数把临时对象拷贝到我们要创建的对象里。(Copy-initialization first uses the indicated constructor to create a temporary object. It then uses the copy constructor to copy that temporary into the one we are creating

string null_book = "9-999-99999-9"; // copy-initialization

string empty_direct;                // direct-initialization

对于类的对象,当指定单一的实参或者为了拷贝明确构建临时对象时,才能使用拷贝初始化。(For objects of class type, copy-initialization can be used only when specifying a single argument or when we explicitly build a temporary object to copy.

ifstream file2 = "filename"; // error: copy constructor is private

// This initialization is okay only if

// the Sales_item(const string&) constructor is not explicit

Sales_item item = string("9-999-99999-9");

额外唠唠Sales_item item = string("9-999-99999-9");

只有当Sales_item的构造函数中没有把Sales_item(const string&)这个构造函数声明为explicit时,这个初始化才正确。这其中包含了一个隐式的类型转换:由实参string("9-999-99999-9");生成Sales_item类型的对象,然后再调用copy函数拷贝给item

但是(咋这么多但是呢)如果这么写Sales_item item2 = item;就又不一样了。这样就变成了调用赋值操作符。

合成复制构造函数

复制构造函数是逐成员拷贝(memberwise copy)。

即使我们不能对array进行copy,但是如果一个类包含有数组类型的成员,合成的复制构造函数也会拷贝这个的数组。数组中的每个元素都会进行复制。(Even though we ordinarily cannot copy an array, if a class has a member that is an array, then the synthesized copy constructor will copy the array. does so by copying each element.

自定义拷贝函数

class Foo {

     public:

        Foo();           // default constructor

        Foo(const Foo&); // copy constructor

        // ...

     };

阻止拷贝

方法:就是把拷贝函数声明为private

大多数类都应该定义拷贝构造函数和默认构造函数。

如果一个类里面定义了一个构造函数,编译期就不会再为这个类生成默认构造函数,而需要自己来定义了。

温习一下什么是默认构造函数:(唉,忘记的真快啊)

默认构造函数没有形参。如果定义的对象不提供初始化式(initializer)就会调用默认构造函数。(The default constructor is used whenever we define an object but do not supply an initializer.

如果一个构造函数为所有形参提供缺省的实参,也可以叫做是默认构造函数。(A constructor that supplies default arguments for all its parameters also defines the default constructor.

13.2 The Assignment Operator

如果类自己没有定义赋值操作符,那么编译器合成赋值操作符。(注意,这活是编译器干的)

重载操作符(overloaded operators

操作符重载的形参列表(这里要包括this形参,如果操作符是成员函数的话)以及返回值必须和操作符所需要的操作数相同。

这句话咋理解呢?

Sales_item trans, accum;

trans = accum;

1.            形参列表

操作符’=’是二元操作符,它的操作数是两个:第一个操作数是左边的操作数,第二个操作数是右边的操作数。上面这个例子,如果我们需要重载’=’,第一个操作数是被绑定到this指针上,第二个操作数是一个Sales_item类型的对象一般来说,右边操作数一般都是作为const引用传递。Usually, the right-hand operand is passed as a const reference.)。

Sales_item& operator=(const Sales_item &);

2.            返回值

赋值操作符的返回值类型应该和内置类型的返回值类型相同。(The return type from the assignment operator should be the same as the return from assignment for the built-in types)内置类型的赋值返回的是左操作数的引用,因此赋值操作符也必须返回和它的类类型相同的引用。具体到Sales_item’=’操作符重载的返回值是Sales_item的引用。

class Sales_item {

 public:

      // other members as before

      // equivalent to the synthesized assignment operator

      Sales_item& operator=(const Sales_item &);

 };

合成赋值操作符

合成赋值操作符也是逐个成员赋值(memberwise assignment)。对于数组是对每个元素进行赋值。

// equivalent to the synthesized assignment operator

 Sales_item& Sales_item::operator=(const Sales_item &rhs)

 {

      isbn = rhs.isbn;              // calls string::operator=

      units_sold = rhs.units_sold; // uses built-in int assignment

      revenue = rhs.revenue;        // uses built-in double assignment

      return *this;

 }

13.3 The Destructor

什么时候调用析构函数?

// 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

   }         

以上这个例子说明在两种情况下会调用析构函数:

  1. 当变量在作用域范围之外的时候,自动被撤销(destroied
  2. 指向对象的指针被删除(deleted),这个对象也就被撤销(destroied

进一步说明的是指向对象的指针或者是引用即使出了作用域,析构函数也是不会被执行的。就还是以上面的例子来说明:

// p points to default constructed object

   Sales_item *p = new Sales_item;

   {

       // new scope

       Sales_item *ghost = new Sales_item;

       Sales_item item(*p); // copy constructor copies *p into item

       delete p;             // destructor called on object pointed to by p

   }         

ghost这个小鬼即使出了作用域实际上也是没有被删除滴,占用的内存空间也是没有被释放滴。这样的程序跑上N遍,机器就会想老牛一样慢了。哎:(

另外当容器撤销时,容器中的元素的析构函数也会被调用(Destructors are also run on the elements of class type in a container - whether a library container or built-in array - when the container is destroyed

啥时需要写显式的析构函数

析构函数用来释放构造函数或对象生存周期内所需的资源( Ordinarily they are used to relinquish resources acquired in the constructor or during the lifetime of the object.)

另外析构函数也不仅仅是用来释放资源。析构函数可以执行类设计者希望执行的任何操作,这些操作和类对象使用完毕之后执行。(A destructor is not limited only to relinquishing resources. A destructor, in general, can perform any operation that the class designer wishes to have executed subsequent to the last use of an object of that class.

首要原则是:如果类需要析构函数,那么它一定也需要拷贝构造函数(copy constructor)和赋值操作符(assignment operator)。

合成析构函数

析构函数和拷贝构造函数(copy constructor)和赋值操作符(assignment operator)不同,就是即使你定义了自己的析构函数,当delete时,先调用你自己的析构函数,然后还是要调用合成的析构函数(synthesized destructor)。

析构函数执行的顺序:和构造函数恰恰相反。按照成员在类里面定义的相反的顺序撤销成员。(it destroys the members in reverse order from which they are declared in the class. For each member that is of class type, the synthesized destructor invokes that member's destructor to destroy the object.

自定义析构函数和合成析构函数的执行顺序:先自定义析构函数,然后再执行合成析构函数。

class Sales_item {

 public:

     // empty; no work to do other than destroying the members,

     // which happens automatically

      ~Sales_item() { }

     // other members as before

 };

13.4 A Message-Handling Example

需求描述:

  1. 新建Message:是和folder无关的,通过调用saveMessage保存到指定的folder。一个Message可以同时属于不同的folder。因此Message中就包含了一个folder的指针集合。另外每个folder里也要包括指向这条Message的指针。
  2. copy Message:包括拷贝Message的文本内容以及一系列的folder指针。同时对于每个folder也要追加一个指向拷贝后的Message的指针。
  3. 赋值操作(assignment):这个最搞:D。先要把等号左边的message对象从folder中删除,再删除对象;然后把右边message对象的文本内容和folder拷贝过来,同时还要向folder指针追加指向左边message的指针。
  4. destroy:除了要撤销这个Message对象,同时也要删除掉各个folder里面指向这个Message的指针。

大师抽象出来两个操作:

  1. messagefolder中删除:

void remove_Msg_from_Folders();

  1. message追加到指定的folider

void put_Msg_in_Folders(const std::set<Folder*>&);

拷贝构造函数

如果我们定义自己的拷贝构造函数,那么我们必须显式拷贝任何我们想要拷贝的成员,因为显式定义考别构造函数是不会自动拷贝任何东西的。(When we write our own copy constructor, we must explicitly copy any members that we want copied. An explicitly defined copy constructor copies nothing automatically.)就是说你要打算自己干就全部自己干,别指望编译器会为你拾遗补缺。

13.5 Managing Pointer Members

Smart Point

大师给出的办法就是对指针进行封装,定义一个新类,它包含有指针,以及使用的次数。下面这个类的定义高!实在是高!就是这个友元的定义。

// 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; }

 };

拷贝控制成员copy-control members

包含指针成员的对象经常需要定义拷贝-控制成员。(Objects with pointer members often need to define the copy-control members.

拷贝-控制成员包括:

赋值操作(assignment operator

析构函数

拷贝构造函数(copy constructor

smart pointer类,使用计数器来管理它smart pointer类。拷贝-控制成员都会影响计数器的值。

复习澄清一下:

‘.’点操作符,’*’解引用操作符的优先级

‘.’点操作符的优先级高于解引用,所以要这样子写:

Sales_item *sp = &item1;

(*sp).same_isbn(item2); // run same_isbn on object to which sp points

valuelike classespointlike classes有什么区别?

这仅仅是一种很说法而已,valuelike classes,它的值copy后实际上是对应两个对象;而pointlike classescopy的是指针,因此copy后实际上是指向的同一个对象。

posted on 2009-06-24 08:17 amenglai 阅读(521) 评论(0)  编辑  收藏 所属分类: C++ Primer 之 读书笔记


只有注册用户登录后才能发表评论。


网站导航: