C++ primer plus 学习笔记(第12章)

第12章 类和动态内存分配
12.1 动态内存和类

// strngbad.h -- flawed string class definition
#include <iostream>
#ifndef STRNGBAD_H_
#define STRNGBAD_H_
class StringBad
{
private:
    char * str;                // pointer to string
    int len;                   // length of string
    static int num_strings;    // number of objects
public:
    StringBad(const char * s); // constructor
    StringBad();               // default constructor
    ~StringBad();              // destructor
// friend function
    friend std::ostream & operator<<(std::ostream & os, 
                       const StringBad & st);
};
#endif

对于该类声明,需要注意两点:
使用char指针(而不是char 数组)来表示姓名。这意味着类声明中没有为字符串本身分配存储空间,而是在构造函数中使用new来为字符串分配空间。
将num_strings成员声明为静态存储类。静态类成员 有一个特点:无论创建了多少对象,程序都只创建一个静态类变量副本。这对于所有类对象都具有相同值的类私有数据是非常方便的。
类方法的实现:

// strngbad.cpp -- StringBad class methods
#include <cstring>                    // string.h for some
#include "strngbad.h"
using std::cout;
// initializing static class member
int StringBad::num_strings = 0;
// class methods
// construct StringBad from C string
StringBad::StringBad(const char * s)
{
    len = std::strlen(s);             // set size
    str = new char[len + 1];          // allot storage
    std::strcpy(str, s);              // initialize pointer
    num_strings++;                    // set object count
    cout << num_strings << ": \"" << str
         << "\" object created\n";    // For Your Information
}
StringBad::StringBad()                // default constructor
{
    len = 4;
    str = new char[4];
    std::strcpy(str, "C++");          // default string
    num_strings++;
    cout << num_strings << ": \"" << str
         << "\" default object created\n";  // FYI
}
StringBad::~StringBad()               // necessary destructor
{
    cout << "\"" << str << "\" object deleted, ";    // FYI
    --num_strings;                    // required
    cout << num_strings << " left\n"; // FYI
    delete [] str;                    // required
}
std::ostream & operator<<(std::ostream & os, const StringBad & st)
{
    os << st.str;
    return os; 
}

上述代码中存在一条code:

int StringBad::num_strings=0;

注意:不能再类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存。初始化是在方法文件中,而不是在类声明文件中进行的。
构造函数中使用strcpy()将传递的字符串复制到新的内存中,字符串并不保存在对象中。字符串单独保存在堆内存中,对象仅指出到哪里去查找字符串的信息,不能使用如下方法:

str=s;   //not the way to go

该code只保存了地址,而没有创建字符串副本。

使用new构造函数对应的析构函数也要使用delete

String::~StringBad()
{
  cout<<"\"<<str<<"\"object deleted,";
  --num_strings;
  cout<<num_strings<<"left\n";
  delete [] str;
}

12.1.2 特殊成员函数
C++自动提供了下面这些成员函数:
默认构造函数,如果没有定义构造函数
默认析构函数,如果没有定义
复制构造函数,如果没有定义
赋值构造函数,如果没有定义
地址运算符,如果没有定义
1)默认构造函数
如果没有提供任何构造函数,C++将创建默认构造函数。假设定义了一个Klunk类,但没有提供任何构造函数,则编译器将提供下述默认构造函数:
Klunk:Klunk(){} //implicit default constructor
即编译器将提供一个不接受任何参数,也不执行任何操作的构造函数。也就是说,它的值在初始化时是未知的。
但是如果定义了构造函数,C++将不会定义默认构造函数。可以显式地定义默认构造函数,即:

Klunk:Klunk()  //explict default constuctor
{
  Klunk_ct=0;
  ...
}

  1. 复制构造函数
    复制构造函数用于将一个对象复制到新创建的对象中。也就是说,它用于初始化过程中(包括按值传递参数)。类的复制构造函数原型如下:
Class_name(const Class_name &);

它接受一个指向类对象的常量引用作为参数。例如,String类的复制构造函数的原型如下:

StringBad(const StringBad &);

那么何时调用复制构造函数。即:新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。例如,假设motto是一个StringBad对象,则下面4种声明都将调用复制构造函数:

StringBad ditto(motto);  //calls StringBad(const StringBad &)
StringBad metto=motto;   //calls StringBad(const StringBad &)
StringBad also =StringBad(motto); 
                        //calls StringBad(const StringBad &)
StringBad *pStringBad=new StringBad(motto);
                        //calls StringBad(const StringBad &)

由于按值传递对象将调用复制构造函数,因此应该按引用传递对象。
默认的复制构造函数逐个复制非静态成员(成员复制也称为浅复制),复制的是成员的值。即:

StringBad sailor=sports;

该语句与下面的代码等效:

扫描二维码关注公众号,回复: 5903576 查看本文章
StringBad  sailor;
sailor.str=sports.str;
sailor.len=sports.len;

浅复制仅仅只是复制地址,这就会导致一些问题存在。为了解决该问题,利用以下方法解决浅复制的问题:
定义一个显式复制构造函数以解决问题(复制构造函数应当复制字符串并将副本的地址赋给str成员)

StringBad::StringBad(const StringBad &st)
{
   num_strings++;
   len=st.len;
   str=new char [len+1];
   std::strcpy(str,st.str);
   cout<<num_strings<<": \""<<str<<"\" object created\n";
}

必须显式定义复制构造函数的原因在于,一些类成员是使用new初始化的、指向数据的指针,而不是数据本身。如果类中包含了使用new初始化的指针成员,应当定义一个复制构造函数,以复制指向的数据,而不是指针,这就被称为深度复制。

12.1.4 赋值运算符
C++允许类对象赋值,这是通过自动为类重载赋值运算符实现的。这种运算符的原型如下:

Class_name & Class_name::operator=(const Class_name &);

它接受并返回一个指向类对象的引用。例如,StringBad类的赋值运算符的原型如下:

StringBad & StringBad::operator=(const StringBad &);

对于由于默认赋值运算符不合适而导致的问题,解决办法是提供赋值运算符(进行深度复制)定义。下面的代码是说明了显式重载赋值运算符:

StringBad & StringBad::operator=(const StringBad & st)
{
   if (this == &st)      //object assigned to itself
      return *this;      //all done
   delete  [] str;       //free  old string
   len=st.len;
   str=new char[len+1];   //get space for new string
   std::strcpy(str,st.str);  //copy the string
   return *this;           //return reference to invoking object
}

在有了上述的知识后,改进后的新string类。
此时在类声明中添加以下部分:

int length () const {return len;}
friend bool operator<(const String &st,const String &st2);
friend bool operator>(const String &st1,const String &st2);
friend bool operator==(const String &st,const String &st2);
friend operator>>(istream &is,String &st);
char & operator[](int i);
const char & operator[](int i)const;
static int HowMany;

修改后的默认构造函数,它与下面类似:

String::String()
{
  len=0;
  str=new char[len+1];
  str[0]='\0';
}

12.2.3 使用中括号表示法访问字符
在C++中,两个中括号组成一个运算符–中括号运算符,可以使用operator来重载该运算符。
假设opera是一个String对象,下面是该方法的简单实现:

char &String::operator[](int i)
{
   return str[i];
}

12.2.4 静态类成员函数
可以将成员函数声明为静态的(函数声明必须包含关键字static),这样就有两个结果:
首先,不能通过对象调用静态成员函数;如果静态成员函数是在公有部分声明的,则可以使用类名和作用域解析运算符来调用它。例如,可以给String类添加一个名为HowMany()的静态成员函数,方法是在类声明中添加如下原型/定义:

statics int HowMany(){return num_strings;}

调用它的方式如下:

int count=String::HowMany();  //invoking a static member function

由于静态成员函数不与特定的对象相关联,因此只能使用静态数据成员。

12.2.5 进一步重载赋值运算符
为提高处理效率,最简单的方法是重载赋值运算符,下面是一种可能实现的方法:

String & String::operator=(const char *s)
{
   delete []str;
   len=std::strlen(s);
   str=new char[len+1];
   std::strcpy(str,s);
   return *this;
}

一般来说,必须释放str指向的内存,并为新字符串分配足够的内存。

12.3 在构造函数中使用new时应注意的事项
在使用new初始化对象的指针成员时必须注意以下事项:
如果在构造函数中使用new来初始化指针成员,则应在析构函数中使用delete;
new和delete必须相互兼容。new对应于delete,new[ ]对应于delete[ 。
如果有多个构造函数,则必须以相同的方式使用new,要么都带中括号,要么都不带。
应定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象。通常,这种构造函数与下面类似:

StringBad::StringBad(const StringBad &st)
{
   num_strings++;
   len=st.len;
   str=new char [len+1];
   std::strcpy(str,st.str);
}

应当定义一个赋值运算符,通过深度复制将一个对象复制给另一个对象,即:

StringBad & StringBad::operator=(const StringBad & st)
{
   if (this == &st)      //object assigned to itself
      return *this;      //all done
   delete  [] str;       //free  old string
   len=st.len;
   str=new char[len+1];   //get space for new string
   std::strcpy(str,st.str);  //copy the string
   return *this;           //return reference to invoking object
}

12.5 使用指向对象的指针
使用 new初始化对象
如果Class_name是类,value的类型为Type_name,则下面的语句:

Class_name *pclass = new Class_name(value);

将调用如下构造函数:

Class_name(Type_name);

12.5.2 指针和对象小结
使用对象指针时,需要注意以下几点:
使用常规表示法来声明指向对象的指针:

String  * glamour;

可以将指针初始化为指向已有的对象:

String * first=&sayings[0];

可以使用new来初始化指针,这将创建一个新的对象

String * favorite=new String(sayings[choice]);

对类使用new将调用相应的类构造函数来初始化新创建的对象:

//invokes  default constructor
String * gleep=new String;
//invokes the String(const char *)constructor
String * glop=new String("my my my");
//invokes the String(const String &)constructor
String * favorite=new String(sayings(choices));

可以使用->运算符通过指针访问类方法:

if (sayings[i].length() < shortest->length())

可以对对象指针应用解除引用运算符(*)来获得对象:

if (sayings[i] < *first)   // compare object values
    first = &sayings[i];   // assign object addresds

12.6 复习各种技术
12.6.1 重载<<运算符
12.6.2 转换函数
12.6.3 构造函数使用new的类

12.7 队列模拟

猜你喜欢

转载自blog.csdn.net/qq_24251645/article/details/86532546