读书笔记||类和动态内存分配

一、动态内存和类

C++在分配内存的时候是让程序是在运行时决定内存分配,而不是在编译时再决定。C++使用new和delete运算符来动态控制内存。但是在类中使用这些运算符将导致许多新的编程问题,在这种情况下,析构函数是必不可少的。有时候还必须重载赋值运算符,以保证程序正常运行。
静态类成员函数,首先设计一个StringBad类,然后设计一个功能稍强的String。StringBad和String类对象将包含一个字符串指针和一个表示字符串长度的值。这里使用StringBad和String类,主要是深入了解new、delete和静态成员的工作原理。因此,构造函数和析构函数调用时将显示一些消息,以便你能够按照提示来完成操作。另外,将省略一些有用的成员和友元函数,如重载++和>>运算符以及转换函数,以简化类接口。

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

为何将这个类命名为StringBad?为了说明这是一个不太完整的类,它是使用动态内存分配开发类的第一个阶段,正确地完成了一些显而易见的工作,例如,在构造函数和析构函数中正确的使用new和delete。这个类并没有什么错误,但忽略了一些不明显却必不可少的定西。通过了解这个类的存在,将有助于后面将其转换为更强大的String类,所做的不明显修改。
在这个声明里,首先,它使用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;//初始化为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; 
}
// vegnews.cpp -- using new and delete with classes
// compile with strngbad.cpp
#include <iostream>
using std::cout;
#include "strngbad.h"
void callme1(StringBad &);  // pass by reference
void callme2(StringBad);    // pass by value
int main()
{
    using std::endl;
    {
        cout << "Starting an inner block.\n";
        StringBad headline1("Celery Stalks at Midnight");
        StringBad headline2("Lettuce Prey");
        StringBad sports("Spinach Leaves Bowl for Dollars");
        cout << "headline1: " << headline1 << endl;
        cout << "headline2: " << headline2 << endl;
        cout << "sports: " << sports << endl;
        callme1(headline1);
        cout << "headline1: " << headline1 << endl;
        callme2(headline2);
        cout << "headline2: " << headline2 << endl;
        cout << "Initialize one object to another:\n";
        StringBad sailor = sports;
        cout << "sailor: " << sailor << endl;
        cout << "Assign one object to another:\n";
        StringBad knot;
        knot = headline1;
        cout << "knot: " << knot << endl; 
        cout << "Exiting the block.\n";
    }
    cout << "End of main()\n";
    // std::cin.get();
    return 0;
}
void callme1(StringBad & rsb)
{
    cout << "String passed by reference:\n";
    cout << "    \"" << rsb << "\"\n";
}
void callme2(StringBad sb)
{
    cout << "String passed by value:\n";
    cout << "    \"" << sb << "\"\n";
}

二、改进后新的string类

首先,复制构造函数和赋值运算符,使类能够正确管理类对象使用的内存。其次,由于已经知道了对象何时被创建和释放,因此可以让类构造函数和析构函数保持沉默,不再在每次被调用时都显示消息。另外,也不用再监视构造函数的工作情况,因此可以简化默认构造函数,使之创建一个空字符串,而不是“C++"。
1.修订后的默认构造函数
str = new char[1]与类析构函数兼容。析构函数中包含如下的代码:delete [] str;
2.比较成员函数
在String类中,执行比较操作的方法有3个。按机器排序序列,第一个字符串在第二个字符串之前,则Operator<()函数返回true。要实现字符串比较函数,最简单的方法时使用标准的trcmp()函数,如果按照字母顺序,第一个参数位于第二个参数之前,则该函数返回一个负值:如果两个字符串相同,则返回0;如果第一个参数位于第二个参数之后,则返回一个正值。
3.使用中括号表示法访问字符
可以使用方法operator来重载该运算符,通常,二元C++运算符位于两个操作数之间。但对于中括号运算符,一个操作数位于第一个中括号的前面,另一个操作数位于两个中括号之间。
4.静态成员函数
可以将成员函数声明为静态的,(函数声明必须包含关键字static,但如果函数定义是独立的,则其中不能包含关键字static)这样做有两个重要的后果。
首先,不能通过对象调用静态成员函数:实际上,静态成员函数甚至不能使用this指针,如果静态成员函数是在公有部分声明的,则可以使用类名个作用域接写运算符来调用它。
其次,由于静态成员函数不与特定的对象相关联,因此只能使用静态数据成员。
5.进一步重载赋值运算符
程序使用构造函数String(const char*)来创建一个临时String对象,其中包含temp中的字符副本。
使用String&String::operator = (const String &)函数将临时对象中的信息复制到name对象中。
程序调用析构函数~String()删除临时对象。

三、在构造函数中使用new时应注意的事项

  • 如果构造函数中使用new来初始化指针成员,则应在析构函数中使用delete;
  • new和delete必须相互兼容,new对应于delete,new[]对应于delete[];
  • 如果有多个构造函数,则必须以相同的方式使用new,要么都带中括号,要么都不带。因为只有一个析构函数,所有的构造函数都必须与它兼容。然而,可以在一个构造函数中使用new初始化指针,而在另一个构造函数中将指针初始化为空,这是因为delete可以用于空指针。
  • 应定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象。
  • 应当定义一个赋值运算符,通过深度复制将一个对象复制给另一个对象。

四、有关返回对象的说明

当成员函数或独立的函数返回对象时,有几种返回方式可供选择。可以返回指向对象的引用、指向对象的const引用或const对象。
1.返回指向const对象的引用
使用const引用的常见原因是旨在提高效率,但对于何时可以采用这种方式存在一些限制。如果函数返回(通过调用对象的方法或将对象作为参数)传递给它的对象,可以通过返回引用来提高其效率。

//version 1
Vector Max (const Vector & v1,const Vector & v2)
{
   if (v1.magva1() > v2.magva1())
      return v1;
   else 
      return v2;
}      
//version 2
const Vector & Max (const Vector & v1,const Vector & v2)
{
   if (v1.magva1() > v2.magva1())
      return v1;
   else 
      return v2;
}      

首先,返回对象将调用复制构造函数,而返回引用不会。因此,第二个版本所做的工作更少,效率更高。
其次,引用指向的对象应该在调用函数执行时存在。
第三,v1和v2都被声明为const引用,因此返回类型必须为const,这样才匹配。
2.返回指向非const对象的引用
两种常见的返回非const对象的情形是,重载赋值运算符以及重载与cout一起使用的<<运算符,前者这样做旨在提高效率,而后者必须这样做。
3.返回对象
如果被返回对象是在被调用函数中的局部变量,则不应该按引用的方式返回它,因为在被调用函数执行完毕时,局部对象将调用析构函数。因此,当控制权回到调用函数时,引用指向的对象将不再存在,在这种情况下,应返回对象而不是引用。通常,被重载的算术运算符属于这一类。
4.返回const对象
前面的Vector::operator+()定义有一个奇异的属性,它旨在让您能够以下面的方式使用它:
net = force1+force2
然而,这种定义也允许您这样使用它:
force1 + force2 = net;
cout << (force1 +force2 = net).magval()<<endl;

五、使用指向对象的指针

// sayings2.cpp -- using pointers to objects
// compile with string1.cpp
#include <iostream>
#include <cstdlib>      // (or stdlib.h) for rand(), srand()
#include <ctime>        // (or time.h) for time()
#include "string1.h"
const int ArSize = 10;
const int MaxLen = 81;
int main()
{
    using namespace std;
    String name;
    cout <<"Hi, what's your name?\n>> ";
    cin >> name;
    cout << name << ", please enter up to " << ArSize
         << " short sayings <empty line to quit>:\n";
    String sayings[ArSize];
    char temp[MaxLen];               // temporary string storage
    int i;
    for (i = 0; i < ArSize; i++)
    {
        cout << i+1 << ": ";
        cin.get(temp, MaxLen);
        while (cin && cin.get() != '\n')
            continue;
        if (!cin || temp[0] == '\0') // empty line?
            break;                   // i not incremented
        else
            sayings[i] = temp;       // overloaded assignment
    }
    int total = i;                   // total # of lines read
    if (total > 0)
    {
        cout << "Here are your sayings:\n";
        for (i = 0; i < total; i++)
            cout << sayings[i] << "\n";
    // use pointers to keep track of shortest, first strings
        String * shortest = &sayings[0]; // initialize to first object
        String * first = &sayings[0];
        for (i = 1; i < total; i++)
        {
            if (sayings[i].length() < shortest->length())
                shortest = &sayings[i];
            if (sayings[i] < *first)     // compare values
                first = &sayings[i];     // assign address
        }
        cout << "Shortest saying:\n" << * shortest << endl;
        cout << "First alphabetically:\n" << * first << endl;
        srand(time(0));
        int choice = rand() % total; // pick index at random
    // use new to create, initialize new String object
        String * favorite = new String(sayings[choice]);
        cout << "My favorite saying:\n" << *favorite << endl;
        delete favorite;
    }
    else
        cout << "Not much to say, eh?\n";
    cout << "Bye.\n";
// keep window open
/*    if (!cin)
        cin.clear();
    while (cin.get() != '\n')
        continue;
    cin.get();
*/
    return 0; 
}

1,再谈new和delete
使用new创建的每一个对象的名称字符串分配存储空间,这是在构造函数中进行的,因此析构函数使用delete来释放这些内存。因为字符串是一个字符数组,所以析构函数使用的是带中括号的delete。这样,当对象被释放时,用于存储字符串内容的内存将被自动释放。
这不是为要存储的字符串分配内存,而是为对象分配内存。也就是说,为保存字符串地址的str指针和len成员分配内存。创建对象将调用构造函数,后者分配用以保存字符串的内存,并将字符串的地址赋给str。然后,当程序不再需要该对象时,使用delete删除它。对象是单个的,因此,程序使用不带中括号的delete。与前面介绍的相同,这将只释放用于保存str指针和len成员的空间,并不释放str指向的内存,而该任务由析构函数来完成。
在下述情况下析构函数将被调用:

  • 如果对象是动态变量,则当执行定义该对象的程序块时,将调用该对象的析构函数。

  • 如果对象是静态变量,则在程序结束时将调用对象的析构函数。

  • 如果对象是new创建的,则仅当您显示使用delete删除对象是,其析构函数才会被调用。
    2.指针和对象小结

  • 使用常规表示法来声明指向对象的指针:String * glamuor;

  • 可以将指针初始化为指向已有的对象:string *first = &sayings[0];

  • 可以使用new来初始化指针,这将创建一个新的对象:String * favorite = new String(saying [choice])

  • 对类使用new将调用相应的类构造函数来初始化创建的对象:String * gleep = new String;

  • 可以使用->运算符通过指针访问类方法:if (sayings[i].length() < shortest ->length())

  • 可以对对象指针应用解除引用运算符(* )来获得对象;
    if(saying[i]< * first)
    first = &sayings[i];
    3.再谈定位new运算符
    定位new运算符能够在分配内存时能够指定内存位置。

// placenew2.cpp  -- new, placement new, no delete
#include <iostream>
#include <string>
#include <new>
using namespace std;
const int BUF = 512;
class JustTesting
{
private:
    string words;
    int number;
public:
    JustTesting(const string & s = "Just Testing", int n = 0) 
    {words = s; number = n; cout << words << " constructed\n"; }
    ~JustTesting() { cout << words << " destroyed\n";}
    void Show() const { cout << words << ", " << number << endl;}
};
int main()
{
    char * buffer = new char[BUF];       // get a block of memory
    JustTesting *pc1, *pc2;
    pc1 = new (buffer) JustTesting;      // place object in buffer
    pc2 = new JustTesting("Heap1", 20);  // place object on heap  
    cout << "Memory block addresses:\n" << "buffer: "
        << (void *) buffer << "    heap: " << pc2 <<endl;
    cout << "Memory contents:\n";
    cout << pc1 << ": ";
    pc1->Show();
    cout << pc2 << ": ";
    pc2->Show();
    JustTesting *pc3, *pc4;
// fix placement new location
    pc3 = new (buffer + sizeof (JustTesting))
                JustTesting("Better Idea", 6);
    pc4 = new JustTesting("Heap2", 10);    
    cout << "Memory contents:\n";
    cout << pc3 << ": ";
    pc3->Show();
    cout << pc4 << ": ";
    pc4->Show();    
    delete pc2;           // free Heap1         
    delete pc4;           // free Heap2
// explicitly destroy placement new objects
    pc3->~JustTesting();  // destroy object pointed to by pc3
    pc1->~JustTesting();  // destroy object pointed to by pc1
    delete [] buffer;     // free buffer
    // std::cin.get();
    return 0;
}

在这里插入图片描述

六、复习各种技术

1.重载<<运算符
要重新定义<<运算符,以便将它和cout一起用来显示对象的内容。请定义下面的友元运算符函数:

ostream & operator << (ostream & os, const c_name & obj)
{
    os << ……;
    return os ;
}

其中,c_name是类名,如果该类提供了能够返回所需内容的公有方法,则可在运算符函数中使用这些方法,这样便不用将它们设置为友元函数。
2.转换函数
要将单个值转换为类型,需要创建原型如下所示的类构造函数:
c_name(type_name value)
其中c_name为类名,type_name是要转换的类型的名称。
要将类型转换为其他类型,需要创建原型如下所示的类成员函数:
operator type_name();
虽然该函数没有返回声明类型,但应该返回所需类型的值。
3.其构造函数使用new的类

  • 对于指向的内存是new分配的所有成员,都应在类的析构函数中对其使用delete,该运算符
    将释放分配的内存。
  • 如果析构函数通过对指针类成员使用delete来释放内存,则每个构造函数都应当使用new来初始化指针,或将它设置为空指针。
  • 构造函数中要么使用new[],要么使用new,而不能混用。如果构造函数使用的new[],则析构函数应使用delete[];反之。
  • 应定义一个分配内存的复制构造函数,这样程序将能够将类对象初始化为另一个类对象。

七、队列模拟

队列是一种抽象的数据类型,可以存储有序的项目序列。新项目被添加在队尾,并可ui删除队首的项目。队列有点像栈,但栈在同一端进行添加和删除。栈是后进先出的结构,而队列是先进先出的。
1.队列类
队列的特征:

  • 存储有序的项目序列
  • 容纳的项目数有一定的限制
  • 能够创建空队列
  • 检查队列是否为空
  • 检查队列是否为满的
  • 在队尾添加项目
  • 从队首删除项目
  • 能够确定队伍的项目数
    2.Customer类
class Customer
{
private:
    long arrive;
    int processtime;
public:
    Customer() {arrive = processtime = 0;}
    void set(long when);
    long when() cosnt{return arrive;}
    int ptime() const{return processtime;}
};
void Customer::set(long when)
{
    procresstime = std::rand() %3/1;
    arrive = when;
}

默认构造函数创建一个空客户。set()成员函数将到达时间设置为参数,并将处理时间设置为1~3中的一个随机值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值