C++动态内存分配

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

C++动态内存分配

Shawn-Summer   2022-09-29 我要评论

1.在类中使用动态内存分配的注意事项

1.1 构造函数中使用new

  • 如果在构造函数中使用new来初始化指针成员,则应在析构函数中使用delete
  • newdelete必须相互兼容,new相对delete;new[]相对delete[]
  • 因为只有一个析构函数,所有的构造函数都必须与它兼容

注意的是:delete或者delete[]都可以对空指针操作.

NULl0nullptr:空指针可以用0或者NULL来表示,C++11使用一个特殊的关键词:nullptr来表示空指针.

应该定义一个复制构造函数,通过深度复制将一个对象初始化成另一个对象.

String::String(const String &st)//复制构造函数
{
    len=st.len;
    str=new char[len+1];
    std::strcpy(str,st.str);
    num_strings++;
}

应该定义一个赋值运算符。

String& String::operator=(const String& st)//赋值运算符
{
    if(this==&st)
    return *this;
    delete[] str;
    len=st.len;
    str=new char[len+1];
    std::strcpy(str,st.str);
    return *this;
}

具体来说,操作是:检查自我赋值情况,释放成员指针以前指向的内存,复制数据而不仅仅是地址,返回一个指向调用对象的引用.

一个典型错误

String::String()
{
    str="default string";
    len=std::strlen(str);
}

上面这段代码定义了默认构造函数,但是它犯了一个错误:无法和析构函数中的delete[]匹配.

包含类成员的类的逐成员复制

class Magazine
{
    private:
        String title;
        String publisher;
}

类成员的类型是String,这是否意味着要为Magazine类编写复制构造函数和赋值运算符?不.

如果你将一个Magazine对象复制或者赋值给另一个Magazine对象,逐成员复制将使用成员类型定义的复制构造函数和赋值运算符.也就是说复制title时,将调用String的复制构造函数,而将title赋值给另一个Magazine对象时,也会使用String的赋值运算符.

1.2 有关返回对象的说明

返回指向const对象的引用

返回对象会调用复制构造函数生成临时对象,而返回const对象的引用不会.

引用指向的对象不能是局部变量. 总之,返回指向const对象的引用,就是按值传递的升级版,但是它不能返回局部变量.

返回指向非const对象的引用

例如我们重载<<时,

ostream& operator<<(ostream & os,class_name object); 返回指向非const对象的引用,主要是我们希望对函数返回对象进行修改.

返回对象

就是按值传递.

如果我们返回的对象是局部变量,那么我们不能使用引用来返回了,只能采用返回对象.

返回const对象

不太常用.防止用户对临时对象进行赋值操作,而编译器不会对这种操作报错.

总之,如果要返回局部对象就必须返回对象;如果,那必须返回对象的引用;如果返回对象也行,返回指向对象的引用也行,那优先使用引用版本,因为效率更高.

1.3 使用new创建对象

String * glop=new String("my my my");

这句话会使用构造函数String(const char *);

glop->类成员

可以使用这种方式调用对象成员,学过C语言的应该明白。

对于动态分配的对象,它的析构函数当且仅当使用delete删除对象时,它的析构函数才会调用。

定位new的用法

#include<iostream>
#include<string>
#include<new>
using std::string;
using std::cout;
using std::cin;
using std::endl;
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<<" destoryed!\n";}
        void show() const {cout<<words<<", "<<number<<endl;}
};
int main()
{
    char * buffer=new char[BUF];//获得一块512B内存
    JustTesting *pc1,*pc2;
    pc1=new(buffer) JustTesting;//在该块内存中分配空间
    pc2=new JustTesting ("Heap1",20);
    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;
    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";
}

Just Testing constructed.
Heap1 constructed.
Memory block addresses:
buffer: 0xf040a0 heap: 0xf042d0
Memory contents:
0xf040a0: Just Testing, 0
0xf042d0: Heap1, 20
Bad Idea constructed.
Heap2 constructed.
Memory contents:
0xf040c8: Bad Idea, 6
0xf04330: Heap2, 10
Heap1 destoryed!
Heap2 destoryed!
Bad Idea destoryed!
Just Testing destoryed!
done !

上面这段代码演示了定位new的用法,这个我们之前在内存模型中谈过。这里需要注意的是,如果使用定位new创建对象,如何确保其析构函数被调用,我们不能使用delete p3;delete p1;,这是因为delete和定位new不匹配,我们必须显式调用析构函数p1->~JustTesting();

2.队列模拟

和栈(Stack)一样,队列(Queue)也是一个很重要的抽象数据结构。这一节将会构建一个Queue类,顺便复习之前所学的技术和学习少量新知识。

我们采用链表来实现队列。

2.1 类声明中的一些思考

typedef  std::string Item;
class Queue
{
    private:
        struct Node
        {
            Item item;
            struct Node *next;
        };
        enum{Q_SIZE=10};
        Node* front;//队首指针
        Node* rear;//队尾指针
        int items;//队列中的元素个数
        const int qsize;//队列的最大元素个数
        //抢占式定义
        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 &i);//入队
        bool dequeue(Item & i);//出队
        void show() const;        
};

类作用域中的结构体

类似于类作用域中的常量,通过将结构体Node声明放在Queue类的私有部分,就可以在类作用域中使用该结构体。这样就不用担心,Node声明和某些全局声明发生冲突。此外,类声明中还能使用Typedef或者namespace等声明,都可以使其作用域变成类中。

利用构造函数初始化const数据成员

在类中qsize是队列最大元素个数,它是个常量数据成员

Queue::Queue(int qs)
{
    qsize=qs;
    front =rear=nullptr;
    items=0;
}

上面这段代码是错误的。因为常量是不允许被赋值的。C++提供了一种新的方式来解决这一问题–成员初始化列表。

成员初始化列表语法

它的作用是,在调用构造函数的时候,能够初始化数据。对于const类成员,引用数据成员,都应该使用这种语法。

于是,构造函数可以这样:

Queue::Queue(int qs):qsize(qs)
{
    front =rear=nullptr;
    items=0;
}

而且这种方法不限于初始化常量,还能初始化非const变量。则构造函数也可以这样:

Queue::Queue(int qs):qsize(qs),front(nullptr),rear(nullptr),items(0){}

但是,成员初始化列表语法只能用于构造函数。

类内初始化

在C++中,其实还有一种更直观的初始化方式,那就是直接在类声明中进行初始化。

class Classy
{
  int mem1=10;
  const int mem2=20;
};

相当于在构造函数中使用

Classy::Classy():mem1(10),mem2(20){...}

但是如果你同时使用类内初始化和成员列表语法时,调用相应构造函数时,成员列表语法会覆盖类内初始化。

Classy::Classy(int n):mem1(n){...}

调用上面这个构造函数时,mem1会被设置成n,而mem2由于类内初始化的原因被设置成20.

是否需要显式析构函数?

Queue类的构造函数中是不需要使用new的,因为构造函数只是构造一个空队列,那这是不是意味著不需要在析构函数中使用delete?

我们知道,虽然构造函数不需要new,但是在enqueue入队时,我们需要new一个新元素加入队列。那么我们必须在析构函数中使用delete以确保所有动态分配的空间被释放。

伪私有方法(抢占式定义)

既然我们在Queue类中,使用了动态内存分配,那么编译器提供的默认复制构造函数,和默认赋值运算符是不正确的。我们假设队列是不允许被赋值或者复制的,那么我们可以使用伪私有方法,目的是禁用某些默认接口。

class Queue
{
  private:
    Queue(const Queue & q):qsize(0){}
    Queue & operator=(const Queue & q){return *this;}
}

这样做的原理是:在私有部分抢先定义了复制构造函数,赋值运算符,那么编译器就不会提供默认方法了,那么对象就无法调用这些方法。

C++提供了另一种禁用方法的方式–使用关键词delete

class Queue
{
  public:
    Queue(const Queue & q)=delete;
    Queue & operator=(const Queue & q)=delete;
}

可以直接在公有部分中禁用某种方法。

2.2 代码实现

//queue.h
#ifndef QUEUE_H_
#define QUEUE_H_
#include<string>
typedef  std::string Item;
class Queue
{
    private:
        struct Node
        {
            Item item;
            struct Node *next;
        };
        enum{Q_SIZE=10};
        Node* front;//队首指针
        Node* rear;//队尾指针
        int items;//队列中的元素个数
        const int qsize;//队列的最大元素个数
        //抢占式定义
        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 &i);//入队
        bool dequeue(Item & i);//出队
        void show() const;        
};
#endif
//queue.cpp
#include"queue.h"
#include<iostream>
Queue::Queue(int qs):qsize(qs)
{
    front =rear=nullptr;
    items=0;
}
Queue::~Queue()
{
    Node * p;
    while (front!=nullptr)
    {
        p=front;
        front=front->next;
        delete p;
    }
}
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 &i)
{
    if(isfull())
        return false;
    Node *add=new Node;
    add->item=i;
    add->next=nullptr;
    items++;
    if(front==nullptr)//队空
        front=rear=add;
    else
    {
        rear->next=add;
        rear=add;
    }
    return true;
}
bool Queue::dequeue(Item & i)
{
    if(isempty())
        return false;
    i=front->item;
    items--;
    if(items==0)
    {
        delete front;
        front=rear=nullptr;
    }
    else
    {
        Node *p=front;
        front=front->next;
        delete p;
    }
    return true;
} 
void Queue::show() const
{
    using std::cout;
    using std::endl;
    cout<<"the items: "<<items<<endl;
    if(isempty())
        cout<<"Empty queue!\n";
    else
    {
        cout<<"front: ";
        for(Node*p=front;p!=nullptr;p=p->next)
        {
            cout<<p->item;
            if(p!=rear)
                cout<<"-> ";
        }
        cout<<" :rear\n";
    }
}
//queuetest.cpp
#include"queue.h"
#include<iostream>
int main()
{
    using std::cin;
    using std::cout;
    using std::endl;
    using std::string;
    Queue test(8);
    char choice;
    cout<<"Enter E to enqueue ,D to dequeue,Q to quit: ";
    while(cin>>choice)
    {
        string temp;
        switch (choice)
        {
        case 'E':
            cout<<"Enter the string: ";
            cin>>temp;
            if(test.enqueue(temp))
                test.show();
            else
                cout<<"can't enqueue\n";
            break;
        case 'D':
            if (test.dequeue(temp))
            {
                cout<<"the item gotten: "<<temp<<endl;
                test.show();
            }
            else
                cout<<"can't dequeue\n";
            break;
        case 'Q':
            goto aa;
            break;
        default:
            break;
        }
        cout<<endl;
        cout<<"Enter E to enqueue ,D to dequeue,Q to quit: ";
        cin.ignore();
    }
    aa:test.~Queue();
    cout<<"Bye!: ";
    test.show();
}

PS D:\study\c++\path_to_c++> .\queue.exe
Enter E to enqueue ,D to dequeue,Q to quit: E
Enter the string: apple
the items: 1
front: apple :rear

Enter E to enqueue ,D to dequeue,Q to quit: E
Enter the string: banana
the items: 2
front: apple-> banana :rear

Enter E to enqueue ,D to dequeue,Q to quit: E
Enter the string: candy
the items: 3
front: apple-> banana-> candy :rear

Enter E to enqueue ,D to dequeue,Q to quit: E
Enter the string: dizzy
the items: 4
front: apple-> banana-> candy-> dizzy :rear

Enter E to enqueue ,D to dequeue,Q to quit: D
the item gotten: apple
the items: 3
front: banana-> candy-> dizzy :rear

Enter E to enqueue ,D to dequeue,Q to quit: D
the item gotten: banana
the items: 2
front: candy-> dizzy :rear

Enter E to enqueue ,D to dequeue,Q to quit: Q
Bye!: the items: 2
front:  :rear

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们