- 如果在构造函数中使用new来初始化指针成员,则应在析构函数中使用delete
- new和delete必须相互兼容,new相对delete;new[]相对delete[]
- 因为只有一个析构函数,所有的构造函数都必须与它兼容
注意的是:delete或者delete[]都可以对空指针操作.
NULl和0和nullptr:空指针可以用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;
}
1.2 有关返回对象的说明类成员的类型是String,这是否意味着要为Magazine类编写复制构造函数和赋值运算符?不.
如果你将一个Magazine对象复制或者赋值给另一个Magazine对象,逐成员复制将使用成员类型定义的复制构造函数和赋值运算符.也就是说复制title时,将调用String的复制构造函数,而将title赋值给另一个Magazine对象时,也会使用String的赋值运算符.
- 返回指向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#include #include 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< show(); cout< show(); JustTesting *pc3,*pc4; pc3=new(buffer+sizeof(JustTesting)) JustTesting ("Bad Idea",6); pc4=new JustTesting ("Heap2",10); cout<<"Memory contents:n"; cout< show(); cout< 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 !
2.队列模拟上面这段代码演示了定位new的用法,这个我们之前在内存模型中谈过。这里需要注意的是,如果使用定位new创建对象,如何确保其析构函数被调用,我们不能使用delete p3;delete p1;,这是因为delete和定位new不匹配,我们必须显式调用析构函数p1->~JustTesting();。
和栈(Stack)一样,队列(Queue)也是一个很重要的抽象数据结构。这一节将会构建一个Queue类,顺便复习之前所学的技术和学习少量新知识。
我们采用链表来实现队列。
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; }可以直接在公有部分中禁用某种方法。
//queue.h #ifndef QUEUE_H_ #define QUEUE_H_ #includetypedef 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" #includeQueue::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: "< next) { cout< item; if(p!=rear) cout<<"-> "; } cout<<" :rearn"; } }
//queuetest.cpp #include"queue.h" #includeint 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 enqueuen"; break; case 'D': if (test.dequeue(temp)) { cout<<"the item gotten: "< PS D:studyc++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



