zoukankan      html  css  js  c++  java
  • 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;
    }
  • 相关阅读:
    LeetCode 566 重塑矩阵
    LeetCode 283 移动零
    C++Template(类模板二)
    Qt之简单绘图实现
    QT控件之QSlider
    Redis
    布局总结三: icon图标+标题上下两排排列
    vue中在data中引入图片的路径方法
    布局总结二:宽高比固定比例---移动端
    在vue中使用vue-awesome-swiper插件
  • 原文地址:https://www.cnblogs.com/JczmDeveloper/p/2964821.html
Copyright © 2011-2022 走看看