C++学习随笔之九:类和动态内存分配

1.动态内存和类:
类静态成员:不能在类声明中初始化类静态成员,应为声明描述了如何分配内存,但并不分配内存。类静态成员是单独存储的,而不是对象的组成部分。总而言之,静态数据成员应在类声明中声明,在包含类方法的文件中初始化,初始化时使用类作用域操作符::来指出静态成员所属的类,但静态成员如果是整型或枚举型const,则可以在类声明中初始化。
隐式成员函数:C++主要提供了这些隐式成员函数:
(1)默认构造函数,如果没有定义构造函数
(2)复制构造函数,如果没有定义
(3)赋值操作符,如果没有定义
(4)默认析构函数,如果没有定义
(5)地址操作符,如果没有定义
复制构造函数:用于将一个对象复制到新创建的对象中,也即是说,它用于初始化过程,而不是常规的赋值过程中,其函数原型为:
ClassName(const ClassName & )。
何时使用:具体的说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数
复制构造函数功能:默认的复制构造函数逐个复制非静态成员(成员复制也成为浅复制),复制的是成员的值
如果类中有这样的静态数据成员,即其值将在新对象被创建时发生变化,则应该提供一个显式复制构造函数来处理这类数据
如果类中包含了使用new初始化的指针成员,也应当定义一个复制构造函数,以复制指向的数据,而不是指针,这被称为深度复制
赋值操作符:赋值操作符重载函数原型为:ClassName &ClassName::operator=(const ClassName & );
何时使用:将已有的对象赋给另一个对象时,将使用重载的复制操作符
功能:和复制构造函数类似,复制操作符的隐式实现也是对成员逐个复制。如果成员本身就是类对象,则将使用该类定义的赋值操作符来复制该成员,但静态成员不受影响。
在构造函数中使用new时应该注意以下问题:
(1)如果在构造函数中用new来初始化指针成员,则应在析构函数中使用delete
(2)new和delete必须对应,new与delete对应,new[] 与delete[]对应。
(3)如果有多个构造函数,必须以相同的方式使用new,应析构函数只有一个,所以要不都带中括号[],要不都不带。
对象的返回:如果方法或函数要返回局部对象,则应返回对象,而不是指向对象的引用。在这种情况下,将使用复制的构造函数来生成返回的对象。如果方法或函数要返回一个没有公有复制构造函数的类(如ostream类)的对象,它必须返回一个指向这种对象的引用。,有些方法和函数(如重载的赋值操作符)既可以返回对象,也可以返回对象的引用,应首选引用,因为其效率更高。
再谈布局new操作符:
a.程序员必须负责管理布局new操作符从使用的缓冲区内存单元,要使用不同的内存单元,就需要提供两个位于缓冲区的不同地址,并确保这两个单元不重叠,例如,可以这样做:pc1= new (buffer)JustTesting;
pc3=new (buffer +sizeof(JustTesting)) JustTesting("Bad Idea",6);    其中pc3相对于pc1的偏移量为JustTesting对象的大小。
b.如果使用布局new操作符来为对象分配内存,必须确保其析构函数被调用,所以一般采取显式地为new操作符创建的对象调用析构函数。如
pc3->~JustTesting();
pc1->~JustTesting();
上面的代码示例如下:
#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 memory
    JustTesting *pc1,*pc2;
    pc1= new (buffer)JustTesting;//place object in buffer
    pc2 =new JustTesting("Heap1",20);//place object on heap
    
    cout<<"Memory block address:\n"<<"buffer: ";
    cout<<(void*)buffer<<"   heap:"<<pc2<<endl;
    cout<<"Memory contents:\n";
    cout<<pc1<<": ";
    pc1->Show();
    cout<<pc2<<": ";
    pc2->Show();
    
    JustTesting *pc3,*pc4;
    pc3=new (buffer +sizeof(JustTesting)) JustTesting("Bad Idea",6);
    pc4 = new JustTesting("Heap2",10);
    
    cout<<"Memory contents:\n";
    cout<<pc3<<": ";
    pc3->Show();
    cout<<pc4<<": ";
    pc4->Show();
    delete pc2;
    delete pc4;
    pc3->~JustTesting();
    pc1->~JustTesting();
    delete []buffer;
    cout<<"Done\n";
    getchar();
    return 0;
}
运行结果:

本节重要技术小结:
(1)重载<<操作符
要重新定义<<操作符,以便将它和cout一起用来显示对象的内容,一般定义如下的友操作符函数:
ostream & opertor <<(ostream & os,const ClassName & obj)  //ClassName 是类名
{
    os<<...;//display object contents
    return os;
}
 
(2)转换函数
要将单个值转换为类型,需要创建原型如下的类构造函数:
ClassName(typename,value);//ClassName为类名,typename是要转换的类型的名称
要将类转换为其他类型,需要创建原型如下的类成员函数:
operator typename();
虽然该函数没有声明返回类型,但应返回所需类型的值
还有就是,使用转换函数时要非常小心,可以在声明构造函数时使用explicit,以防止它被用于隐式转换。
(3)构造函数使用new的类
如果类使用new来分配内存的话,应该注意以下几个规则,因为编译器不知道这些,所以无法发现这些儿导致错误。
a.对于指向的内存是由new分配的所有类成员,都应该在类的析构函数中对其使用delete,以避免new和delete不配对。
b.如果析构函数通过指针类成员或是用delete来释放内存,则每个构造函数都应当使用new来初始化指针,或者将它设置为空指针。
c.构造函数中要么使用new[],要么使用new,而不能混用。而析构函数中使用的delete必须和构造函数中的new的格式(类型)是一样,如果构造函数
中使用new,则析构函数中应使用new,如果构造函数中使用new[],则析构函数中应使用delete[].
d.应定义一个分配内存(而不是就爱你过指针指向已有的内存)的复制构造函数。这样程序能够将类对象初始化为另一个对象。这种构造函数原型如下:
ClassName(cosnt className &)
e.应定义一个重载赋值符的类成员函数,其定义如下:
c_name & c_name::operator=(const c_name & cn)
{
  if(this == &cn)
    return *this;
delete[] c_pointer;
c_pointer = enw type_name[size];
...
return *this;
}
其中c_pointer是c_name的类成员.
2.队列模拟:
队列:是一种抽象的数据类型(Abstract Data Type,ADT),可以存储有序的项目序列,新项目被添加在队尾,并可以删除队首的项目,特点是FIFO(first in first out)
嵌套结构和类:在类声明中的结构,类或枚举被称为嵌套在类中,其作用域为整个类。这种声明不会创建数据对象,而只是指定了可以在类中使用的类型。
如果在类的私有部分声明,则只能在这个类使用被声明的类型,如果在公有部分声明,则可以从类的外部通过作用域解析操作符使用被声明的类。例如,如果
Node 是在Queue类的公有部分声明的,则可以在类的外面声明Queue::Node类型的变量。
成员初始化列表:由逗号分隔的初始化列表组成(前面带冒号):例如:
Queue::Queue(int qs):qsize(qs),front(NULL),rear(NULL)
{
}
冒号后面的括号里面的是要初始化的值,而括号外面都是被初始化的变量,如front(NULL)即等于:front =NULL;
如果Classy是一个类,而mem1,mem2,mem3是这个类的数据成员,则类构造函数可以使用如下初始化数据成员:
Classy::Classy(int m,int n):mem1(n),mem2(0),mem3(m*n+24)
{
...
}
上面的代码等价于
Classy::Classy(int m,int n)
{
mem1=n;
mem2=0;
mem3=m*n+24;
}
不过mem1=n;mem2=0;mem3=m*n+24;这几个初始化时在对象创建是完成的,此时还未执行花括号里面的任何代码,也即是说在执行花括号里面代码之前执行这几个初始化。
注意点:
(1)这种语法格式只能用于构造函数
(2)必须用这种格式来初始化非静态const数据成员
(3)必须用这种语法格式来初始化引用数据成员
(4)不能将该成员初始化列表语法用于非类构造函数之外的其他类方法
当然,初始化成员列表中使用的花括号方式也可以用于常规的初始化中:如:
int game=100;
double cpp =20.30;
可以写成:
int game(100);
double cpp(20.30);
这样看起来就像初始化类对象一样。
3.代码示例:
利用队列模拟ATM顾客排队时间的估测:
queue.h文件:
using namespace std;
class Customer
{
    private:
        long arrive;//arrival time for customer
        int processtime;//processing time for customer
    public:
        Customer(){arrive=processtime =0;}
        void Set(long when);
        long When()const{return arrive;}
        int Ptime()const{return processtime;}
};
typedef Customer Item;
class Queue
{
    private:
        struct Node
        {
            Item item;
            struct Node *next;
        };
        enum{Q_SIZE=10};
        Node *front;//pointer to front of Queue
        Node *rear;//pointer to rear of Queue
        int items;//current number of items in Queue
        const int qsize;//maximum number of items in Queue
        Queue(const Queue &q):qsize(0){}
        Queue & operator=(const Queue &q){return *this;}
    public:
        Queue(int qs=Q_SIZE);
        ~Queue();
        bool IsEmpty()const;
        bool IsFull()const;
        int QueueCount()const;
        bool EnQueue(const Item &item);//add item to end
        bool DeQueue(Item &item);//delete item from front
};
queue.cpp文件代码:
#include<iostream>
#include<cstdlib>
#include "queue.h"
using namespace std;

//Queue methods
Queue::Queue(int qs):qsize(qs)
{
    front = rear = NULL;
    items = 0;
}
Queue::~Queue()
{
    Node *temp;
    while(front!=NULL)
    {
        temp = front;
        front = front->next;
        delete temp;
    }
}
bool Queue::IsEmpty()const
{
    return (items ==0);
}
bool Queue::IsFull()const
{
    return (items == qsize);
}
int Queue::QueueCount()const
{
    return items;
}
bool Queue::EnQueue(const Item &item)
{
    if(IsFull())
        return false;
    Node *add = new Node;//creat node
    if(add == NULL)
        return false;//quit if none available
    add->item=item;
    add->next = NULL;
    items++;
    if(front == NULL)//if queue is empty
        front = add;
    else
        rear->next = add;
    rear = add;
    return true;
}
bool Queue::DeQueue(Item &item)
{
    if(front ==NULL)//if queue is empty
        return false;
    item = front->item;//set item to first item in queue
    items--;
    Node *temp = front;//save location of first item
    front = front->next;//reset front to next item
    delete temp;//delete former first item
    if(items == 0)
        rear = NULL;
    return true;
}
//Customer methods
void Customer::Set(long when)
{
    processtime = rand()%3+1;
    arrive = when ;
}
主文件atm.cpp文件:
#include <cstdlib>
#include <iostream>
#include <ctime>
#include "queue.h"
using namespace std;

const int MIN_PER_HR = 60;
bool NewCustomer(double x);//is there a new customer?
bool NewCustomer(double x)
{
    return (rand()*x/RAND_MAX <1);
}
int main(int argc, char *argv[])
{
    srand(time(0));
    
    cout<<"Case Study: Bank of Heather Automatic Teller\n";
    cout<<"Enter maximum size of queue: ";
    int qs;
    cin >> qs ;
    Queue line(qs);//line queue holds up to qs people
    cout<<"Enter the number of simulation hours: ";
    int hours ;//hours of simulation
    cin >> hours;
    //simulation will run 1 circle per minute
    long cyclelimit = MIN_PER_HR*hours;//# of cycles 
    
    cout<<"Enter the average number of customers per hour: ";
    double perhour;//average # of arrival per hour
    cin >> perhour;
    double min_per_cust;//average time between arrival
    min_per_cust = MIN_PER_HR/perhour;
    
    Item temp;//new customer data
    long turnaways =0;// turned away by full queue
    long customers =0;//joined the queue;
    long served =0;// served during the simulation
    long sum_line = 0;//cumlative lien length
    int wait_time =0;//time until autoteller is free
    long line_wait = 0;// cumulative time in line 
    
    //running the simulation
    for(int cycle =0 ;cycle<cyclelimit;cycle++)
    {
        if(NewCustomer(min_per_cust))//have new customer
        {
            if(line.IsFull())
                turnaways++;
            else
            {
                customers ++;
                temp.Set(cycle);//cycle = time of arrival
                line.EnQueue(temp);//add new customer to line
            }
        }
        if((wait_time<=0) && (!line.IsEmpty()))
        {
            line.DeQueue(temp);//attend next customer
            wait_time = temp.Ptime();//for wait_time minutes
            line_wait += cycle - temp.When();
            served++;
        }
        if(wait_time>0)
            wait_time--;
        sum_line += line.QueueCount();
    }
    //reporting results
    if(customers > 0)
    {
        cout << "customers accepted: " << customers <<endl;
        cout<<"  customer served: " << served<<endl;
        cout<<"      turnaways: "<< turnaways<<endl;
        cout<<"average queue size: ";
        cout.precision(2);
        cout.setf(ios_base::fixed,ios_base::floatfield);
        cout.setf(ios_base::showpoint);
        cout<<(double)sum_line/cyclelimit <<endl;
        cout<<" average wait time: ";
        cout<<(double)line_wait/served<<" minutes\n";
    }
    else
        cout<<"No customers"<<endl;
    
    system("PAUSE");
    return EXIT_SUCCESS;
}
原文地址:https://www.cnblogs.com/JczmDeveloper/p/2964821.html