第十二章 类和动态内存分配
2020-02-24 10:39:50 4 举报
AI智能生成
C++ plus
作者其他创作
大纲/内容
动态内存和类
12.1
代码
类声明
class StringBad{
private:
char *str;//用指针。
int len;
static int num_strings;//静态变量。
public:
StringBad( const char *s );
StringBad();
~StringBad();// 析构函数。
friend std::ostream & operator<<(std::ostream & os,const StringBad &st);
};
类方法定义
//构造函数
StringBad::StirngBad( const char *s)
{
len = std::strlen(s);//strlen()在std中!!
str = new char[len+1];
std::strcpy(str,s);
num_string++;
std::cout<<num_strings<<":\""<<str<<"\"object created\n";
}
StringBad::StringBad()
{
len=4;
str=new char[4];
std::strcpy(str,C++);
num_string++;
cout<<num_strings<<":\""<<str<<"\"default object created";
}
//析构函数
String Bad::~StringBad()
{
--numstrings;
cout<<numstrings<<"left\n";
delete [] str;
}
说明
静态类成员
类声明中:(声明)
static int num_strings;
static int num_strings;
无论创建多少对象,程序只创建一个静态类
副本。所用对象共享一个静态类变量副本。
副本。所用对象共享一个静态类变量副本。
类定义文件中:(初始化)
int StringBad::num_strings = 0;
int StringBad::num_strings = 0;
要指出类型,作用域,不用指明关键字static
在方法定义文件中声明,不在头文件中声明原因
会出现多个初始化语句副本,引起错误。
静态数据成员在类声明中声明,在包含类方法的文件(成员函数定义)中初始化
不能在类声明中初始化,但是静态常量除外
即在类声明中,可以有
const static int a=1;
const static int a=1;
原因
不能在类声明中初始化
声明描述了如何分配内存,不分配内存
类声明外使用单独语句初始化
是单独存储,不是对象的组成部分。
如果num_strings为公有数据成员,
在类外使用静态数据。
在类外使用静态数据。
直接String::num_string
不能用对象引用
不能用对象引用
构造函数
字符串不保存在对象中,单独保存在堆内存中,str存储的是它的地址。
不能直接
str=s
这样保存的是地址,没有创建字符串副本。
要创建字符串,所以最好new + strcpy
str=s
这样保存的是地址,没有创建字符串副本。
要创建字符串,所以最好new + strcpy
析构函数
必要性
当对象过期的时候,对象被删除,但是str指向的内存没有释放,仍被分配,所以需要delete [] str;
问题
如果是系统默认,它是怎么形式??//是什么内容都没有的析构函数
上代码不合理之处
复制构造函数
问题
int main()
{
StringBad headline1("don`t be worry");
StringBad headline2=headline;
}
{
StringBad headline1("don`t be worry");
StringBad headline2=headline;
}
默认的复制构造函数,是按值传递,所以headline2是浅复制,
headline1与headline2指向的是同一个地址,当对象无效时,析构
函数释放一个内存两次,产生问题
headline1与headline2指向的是同一个地址,当对象无效时,析构
函数释放一个内存两次,产生问题
解决
显式定义一个复制构造函数
StringBad::StringBad(const StirngBad &st )
{
num_strings++;
len = st.len;
str = new char [len+1];
std::strcpy(str ,st.str);
...
}
{
num_strings++;
len = st.len;
str = new char [len+1];
std::strcpy(str ,st.str);
...
}
赋值运算符
问题
int main()
{
StringBad headline1("i am ok");
StringBad headline2;
headline2=headline1;
}
{
StringBad headline1("i am ok");
StringBad headline2;
headline2=headline1;
}
同复制构造函数一样
解决
显式定义赋值运算符
StringBad & StringBad::operator=(const StirngBad & st)
{
if (this=&that)
return *thid;
delete [] str;
len=st.len;
str=new char [len+1];
std::strcpy(str,st.str);
return *this;
}
{
if (this=&that)
return *thid;
delete [] str;
len=st.len;
str=new char [len+1];
std::strcpy(str,st.str);
return *this;
}
说明
同复制构造函数有不同
步骤
避免将对象赋值给自身,否则释放内存操作可能删除对象的内容
目标对象(被赋值的对象)可能之前分配了数据,所用有delete [] str 删除之前的。
//有些可以不删除,直接把值覆盖过去。
//有些可以不删除,直接把值覆盖过去。
不删除会造成浪费
函数返回一个指向调用对象的引用
可以S0 = S1 = S2;
特殊成员函数
说明
这些函数没有定义的时候。C++自动提供
内容
默认构造函数,如果没有定义构造函数
默认析构函数,如果没有定义析构函数
复制构造函数,如果没有定义复制构造函数
赋值运算符,如果没有定义赋值运算符
地址运算符,如果没有定义地址运算符//??&?
返回调用对象的地址,this??
C++11
移动构造函数
移动赋值函数
默认构造函数
系统默认
(隐式默认)
(隐式默认)
Klunk::Klunk(){ };
不能delete[]其动态分配的数据?
显式默认
(用户自定义)
(用户自定义)
Klunk::Klunk()
{
klunk_ct=0;
........
};
{
klunk_ct=0;
........
};
默认参数类型
Klunk::Klunk(int n = 0)
{
klunk_ct=n;
}
{
klunk_ct=n;
}
注意
这两个不可以同时存在
复制构造函数
原型
StringBad( const StringBad& );
注意const
何时调用
总体
用于将一个对象复制到新创建的对象中,当程序生成
对象副本的时候。
对象副本的时候。
具体
将新对象显式初始化为现有对象
(假设motto是现有对象)
(假设motto是现有对象)
StringBad ditto(motto);
StringBad meto=motto;
如果是
StringBad meto;
meto=motto;
不会调用复制构造函数。而是直接用赋值函数。
StringBad meto;
meto=motto;
不会调用复制构造函数。而是直接用赋值函数。
StringBad also=StringBad(motto);
String *pStringBad=new StringBad(motto);
说明
中间两种可能使用复制构造函数直接创建metoo,also,
也可能使用复制构造函数生成一个临时对象,然后临时对象的
内容赋给meto和also
//一般是前者
也可能使用复制构造函数生成一个临时对象,然后临时对象的
内容赋给meto和also
//一般是前者
函数按值传递
原型:StringBad callme2( StringBad sb);
调用:callme2(metto);
metto 传递给sb的时候。
调用:callme2(metto);
metto 传递给sb的时候。
函数返回对象(不是对象的引用)
默认复制函数的功能
逐个复制非静态成员的值(浅复制)
赋值运算符
原型
Class_name & Class_name::operator = (const Class_name & );
StingBad & StringBad::operator = (const Class_name & );
StingBad & StringBad::operator = (const Class_name & );
注意const
何时使用
总体
将已有对象赋值给另一个对象
具体
StringBad headline1("cat");
StringBad sports;
sports = headline1;//使用赋值运算符
StringBad sports;
sports = headline1;//使用赋值运算符
说明
StringBad sports = headline1;
不一定使用赋值运算符。
可能使用复制构造函数直接创建sports——不使用赋值运算符
可能创建临时对象——使用赋值运算符
可能使用复制构造函数直接创建sports——不使用赋值运算符
可能创建临时对象——使用赋值运算符
默认系统赋值运算符
浅复制
改进后的新string类
前言
String类
类声明
// string1.h -- fixed and augmented string class definition
#ifndef STRING1_H_
#define STRING1_H_
#include <iostream>
using std::ostream;
using std::istream;
class String
{
private:
char * str; // pointer to string
int len; // length of string
static int num_strings; // number of objects
static const int CINLIM = 80; // cin input limit
public:
// constructors and other methods
String(const char * s); // constructor
String(); // default constructor
String(const String &); // copy constructor
~String(); // destructor
int length () const { return len; }
// overloaded operator methods
String & operator=(const String &);
String & operator=(const char *);
char & operator[](int i);
const char & operator[](int i) const;
// overloaded operator friends
friend bool operator<(const String &st, const String &st2);
friend bool operator>(const String &st1, const String &st2);
friend bool operator==(const String &st, const String &st2);
friend ostream & operator<<(ostream & os, const String & st);
friend istream & operator>>(istream & is, String & st);
// static function
static int HowMany();
};
#endif
类定义
// string1.cpp -- String class methods
#include <cstring> // string.h for some
#include "string1.h" // includes <iostream>
using std::cin;
using std::cout;
// initializing static class member
int String::num_strings = 0;
// static method
int String::HowMany()
{
return num_strings;
}
// class methods
String::String(const char * s) // construct String from C string
{
len = std::strlen(s); // set size
str = new char[len + 1]; // allot storage
std::strcpy(str, s); // initialize pointer
num_strings++; // set object count
}
String::String() // default constructor
{
len = 4;
str = new char[1];
str[0] = '\0'; // default string
num_strings++;
}
String::String(const String & st)
{
num_strings++; // handle static member update
len = st.len; // same length
str = new char [len + 1]; // allot space
std::strcpy(str, st.str); // copy string to new location
}
String::~String() // necessary destructor
{
--num_strings; // required
delete [] str; // required
}
// overloaded operator methods
// assign a String to a String
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;
}
// assign a C string to a String
String & String::operator=(const char * s)
{
delete [] str;
len = std::strlen(s);
str = new char[len + 1];
std::strcpy(str, s);
return *this;
}
// read-write char access for non-const String
char & String::operator[](int i)
{
return str[i];
}
// read-only char access for const String
const char & String::operator[](int i) const
{
return str[i];
}
// overloaded operator friends
bool operator<(const String &st1, const String &st2)
{
return (std::strcmp(st1.str, st2.str) < 0);
}
bool operator>(const String &st1, const String &st2)
{
return st2 < st1;
}
bool operator==(const String &st1, const String &st2)
{
return (std::strcmp(st1.str, st2.str) == 0);
}
// simple String output
ostream & operator<<(ostream & os, const String & st)
{
os << st.str;
return os;
}
// quick and dirty String input
istream & operator>>(istream & is, String & st)
{
char temp[String::CINLIM];
is.get(temp, String::CINLIM);
if (is)
st = temp;
while (is && is.get() != '\n')
continue;
return is;
}
新的默认构造函数
String::String()
{
len=0;
str=new char[1];
str[0]='\0';
}
{
len=0;
str=new char[1];
str[0]='\0';
}
str=new char[1]
不是 str=new char;原因
不是 str=new char;原因
与类析构函数兼容,
既delete [] str
既delete [] str
//也可以
str = new char[1];
str[0] = '\0';
//改成
str = 0;
str = new char[1];
str[0] = '\0';
//改成
str = 0;
因为delete [] 与使用new[]初始化的指针
和空指针都兼容。
和空指针都兼容。
其他
空指针
0
字面值0有两个意思,数值和空指针,
用(void*)0——标识空指针
用(void*)0——标识空指针
NULL
C宏定义
C++11
nullptr
nullptr
eg:
str=nullptr;
str=nullptr;
比较成员函数
代码
bool operator<(const String &str1,const String &str2 )
{
return ( std::strcmp(st1.str , st2.str) < 0 );
}
{
return ( std::strcmp(st1.str , st2.str) < 0 );
}
bool operator>(const String &str1,const String &str2 )
{
return str2<str1;
}
{
return str2<str1;
}
bool operator==(const String &str1,const String &str2 )
{
return std::strcmp(st1.str , st2.str) ==0;
}
{
return std::strcmp(st1.str , st2.str) ==0;
}
说明
可以作为内联函数
bool operator<(const String &str1 )
可以作为友元函数
友元函数有助于将String对象与常规的C字符串进行比较
//(因为是两个参数)
//(因为是两个参数)
if("love" == answer )
转换为
if( operator==( “love”,answer ) )
if( operator==(String("love") , answer ) )
转换为
if( operator==( “love”,answer ) )
if( operator==(String("love") , answer ) )
中括号表示法访问字符
操作数位置
city[0] // city 在前,0在操作数中间
操作数位置能代表什么??
代码
可修改
char & String::operator[](int i)
{
return str[i];
}
{
return str[i];
}
不可修改版本
const char & String::operator[]( int i ) const
{
return str[i];
}
{
return str[i];
}
说明
提供了访问私有函数方法。
返回引用
作用
给特定函数赋值
例子
String means("might");
means[0]='r';
means[0]='r';
问题
const String answer("fytile");
....
cout<<answer[1];// error
....
cout<<answer[1];// error
原因
answer 是const 不能返回不是const 的引用
//定义了不可修改版本就可以了。!!!!
解决问题
//重载一个[]运算符函数
const char & String::operator[](int i) const
{
return str[i];
}
const char & String::operator[](int i) const
{
return str[i];
}
静态类成员函数
前言
成员函数可以声明为静态。独立定义函数(包括友元)不可以是静态的
代码
类声明中
class Strings{
......
public:
static int Howmany();
};
......
public:
static int Howmany();
};
类定义中
int String::Howmany()
{
return num_strings;
}
{
return num_strings;
}
调用方法
int count = String::Howmany();
不用带对象。
后果
对象不能调用函数
String str1;
str1.howmany();X不可行
//调用方法如上
str1.howmany();X不可行
//调用方法如上
不与特定的对象相关联
只能使用静态数据成员。
只能使用静态数据成员。
其他功能
使用静态成员函数设置类级标记,控制某些类接口的行为
类级标记可以控制显示类内容的方法所使用的格式。
进一步重载
赋值运算符
赋值运算符
代码
原来代码
StringBad & StringBad::operator=(const StirngBad & st)
{
if (this=&that)
return *thid;
delete [] str;
len=st.len;
str=new char [len+1];
std::strcpy(str,st.str);
return *this;
}
{
if (this=&that)
return *thid;
delete [] str;
len=st.len;
str=new char [len+1];
std::strcpy(str,st.str);
return *this;
}
问题
String name;
char temp[40];
cin.getline(temp,40);
name = temp;
char temp[40];
cin.getline(temp,40);
name = temp;
temp是c-风格字符串,运行name = temp 时会产生
右列结果:
总结果:
//处理效率不高。
右列结果:
总结果:
//处理效率不高。
1. 构造函数String (const char *)创建一个临时String对象
2.使用String & String::operator=(const String &) 将临时对象复制到name对象中
3.调用析构函数~String删除临时对象
重载=赋值运算符
代码
代码
StringBad & StringBad::operator=(const char * s)
{
delete [] str;
len=std::strlen(s);
str=new char [len+1];
std::strcpy(str,st.str);
return *this;
}
{
delete [] str;
len=std::strlen(s);
str=new char [len+1];
std::strcpy(str,st.str);
return *this;
}
可以直接name = temp (c-风格字符串)了。
重载+运算符
//??
//??
代码
MyString operator +(MyString &s)
{
MyString s1;
if(m_data==NULL)//可以不要这部吗?
{
s1.m_data=new char[strlen(s.m_data)+1];
strcpy(s1.m_data,s.m_data);
}
else
{
s1.m_data=new char[strlen(m_data)+strlen(s.m_data)+1];
strcpy(s1.m_data,m_data);
strcpy(s1.m_data+strlen(m_data),s.m_data);
}
return s1;
}
{
MyString s1;
if(m_data==NULL)//可以不要这部吗?
{
s1.m_data=new char[strlen(s.m_data)+1];
strcpy(s1.m_data,s.m_data);
}
else
{
s1.m_data=new char[strlen(m_data)+strlen(s.m_data)+1];
strcpy(s1.m_data,m_data);
strcpy(s1.m_data+strlen(m_data),s.m_data);
}
return s1;
}
重载+=运算符??
重载>>运算符
代码
//友元函数。
istream & operator>>( istream & is ,String &st )
{
istream & operator>>( istream & is ,String &st )
{
char temp[String::CLNLIM];
is.get(temp,String::CINLIM);
if (is)//如果is(cin/文件)没有错误。错误:1.输入的是空行 。2.其它
st=temp;
while (is&&is.get()!='\n')
continue;
return is;
} 说明
不直接
is.get(st, String::CLNLIM ),原因:输入可能是错误的。
is.get(st, String::CLNLIM ),原因:输入可能是错误的。
早期的get(char * ,int )版本在读取空行后,返回的值部位false。
所以:
while( is && is.get()!='\n' )
所以:
while( is && is.get()!='\n' )
在构造函数中使用new
时应注意的事项
时应注意的事项
注意事项
构造函数——new初始化指针成员。
析构函数——应该使用delete
析构函数——应该使用delete
new和delete要兼容。new--delete, new[]--delete[]
有多个构造函数,要都使用[]或者都不使用。
也可以把指针初始化为空指针(或直接=0)
也可以把指针初始化为空指针(或直接=0)
应定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象
StringBad::String(const Stirng &st )
{
num_strings++;
len = st.len;
str = new char [len+1];
std::strcpy(str ,st.str);
...
}
{
num_strings++;
len = st.len;
str = new char [len+1];
std::strcpy(str ,st.str);
...
}
应定义一个赋值运算符,通过深度复制将一个对象初始化为另一个对象
StringBad & String::operator=(const Stirng & st)
{
if (this=&that)
return *thid;//检查自我赋值
delete [] str;//释放成员以前指向
len=st.len;
str=new char [len+1];//分配内存
std::strcpy(str,st.str);//
return *this;//返回引用
}
{
if (this=&that)
return *thid;//检查自我赋值
delete [] str;//释放成员以前指向
len=st.len;
str=new char [len+1];//分配内存
std::strcpy(str,st.str);//
return *this;//返回引用
}
应该不应该
不应该1
String::String()
{
str="default string";
len=std::strlen(str);
}
{
str="default string";
len=std::strlen(str);
}
delete[]释放不使用new初始化的指针,结果是不确定的
修改
String::String()
{
len = 0;
str = new char[1];
str[0] = '\0';
}
{
len = 0;
str = new char[1];
str[0] = '\0';
}
String::String()
{
len = 0;
str[0] = 0;//是str[0]=0还是str=0??都可以??个人感觉是后者
}
{
len = 0;
str[0] = 0;//是str[0]=0还是str=0??都可以??个人感觉是后者
}
不应该2
String::String(const char *s)
{
len = std::strlen(s);
str = new char;//no room
strcpy(str, s);
}
{
len = std::strlen(s);
str = new char;//no room
strcpy(str, s);
}
只复制到s字符的第一个字
包含类成员的类的逐成员复制
class Magine
{
private:
String title;
String publisher;
...
};
{
private:
String title;
String publisher;
...
};
将一个Magine对象赋值给另一个Magzine对象,
(默认的逐成员复制)会使用成员的类型定义的复制构造函数。
(默认的赋值行为也是这样)。
(默认的逐成员复制)会使用成员的类型定义的复制构造函数。
(默认的赋值行为也是这样)。
如果Magine中还有需要new分配的成员数据,需要定义复制构造函数
(赋值函数),情况会复杂点。13章
(赋值函数),情况会复杂点。13章
有关返回对象的说明
返回指向const对象的引用
好处
提高效率
要求
返回的对象引用,要在主程序中存在。
例如,返回的对象作为函数的参数形式传递给被调函数。
不能返回在被调函数中声明的对象。
例如,返回的对象作为函数的参数形式传递给被调函数。
不能返回在被调函数中声明的对象。
只能作为右值
说明
返回对象将调用复制构造函数,返回引用不调用
返回指向非const对象的引用
常见情形
重载赋值运算符
原因
提高效率//(相对于非引用来说),
赋值的数一般为变量,所以是非const引用。
赋值的数一般为变量,所以是非const引用。
重载与cout一起使用的<<运算符
原因
必须这样做
ostream & operator<<(ostream & os, string s)
{
....
}
ostream 类没有公用的复制构造函数
{
....
}
ostream 类没有公用的复制构造函数
返回对象
情形
返回的对象是被调用函数中的局部变量
返回const对象
情形
Vector类:
Vector force1(50,60);
Vector force2(10,70);
Vector net;
force1+force2=net;//valid,no meaningful
Vector force1(50,60);
Vector force2(10,70);
Vector net;
force1+force2=net;//valid,no meaningful
分析
复制构造函数创建一个临时对象表示force1+force2的结果。
net把值赋给了临时对象。
使用完临时对象后,将它丢弃。
net把值赋给了临时对象。
使用完临时对象后,将它丢弃。
避免这种问题返回const对象,使引用或者指针的返回值不能用于修改对象中的数据。
对象此后能够修改???
const修饰函数返回值。
1、函数返回const指针,表示该指针不能被改动,只能把该指针赋给const修饰的同类型指针变量。
2、函数返回值为值传递,函数会把返回值赋给外部临时变量,用const无意义!不管是内部还是非内部数据类型。
3、函数采用引用方式返回的场合不多,只出现在类的赋值函数中,目的是为了实现链式表达。
1、函数返回const指针,表示该指针不能被改动,只能把该指针赋给const修饰的同类型指针变量。
2、函数返回值为值传递,函数会把返回值赋给外部临时变量,用const无意义!不管是内部还是非内部数据类型。
3、函数采用引用方式返回的场合不多,只出现在类的赋值函数中,目的是为了实现链式表达。
使用指向对象的指针
应用
不用new
声明
String *ptr ;
初始化
String sayings ;
String *pclass = &saying;
String *pclass = &saying;
引用
*pclass
cout<<*pclass;//重载了<<运算符。
pclass->length();
(*pclass).length()
(*pclass).length()
new
初始化
String sayings ;
String *pclass = new String(sayings) ;
String *pclass = new String(sayings) ;
用复制构造函数。此时saying与pclass无关系。(虽然值是一样的)
即pclass不是指向saying。
即pclass不是指向saying。
String *pclass = new String(" my my my ")
String sayings ;
String *pclass = new String;
//使用默认构造函数String( );
String *pclass = new String;
//使用默认构造函数String( );
引用
ditto
释放动态分配对象,
释放对象的动态分配数据成员
释放对象的动态分配数据成员
String *favourite = new String( saying s[choice] );
delete favourite;
delete favourite;
原理
delete favourite 时释放整个对象,(即释放保存str指针和len成员空间)。
再调用对象析构函数释放str(内部数据成员)的空间
再调用对象析构函数释放str(内部数据成员)的空间
析构函数被调用的情况
对象是动态变量
执行完定义该对象的程序块
对象是静态变量
(外部、静态、静态外部或来自名称空间)
(外部、静态、静态外部或来自名称空间)
程序结束时
对象时new创建的
显式使用delete删除对象时,析构函数才被调用
定位new运算符
代码
class JustTesting
{
private:
string words;
int number;
public:
JustingTesting(const string & s = "Justing 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];
JustTesting *pc1,*pc2;
pc1=new (buffer) JustTesting;//会得到 pc1=buffer,即他们指向的地址是一样的。(指向同一个堆内存单位)
pc2=new Justing("Heap1",20);
.........
pc3=new (buffer) JustTesting("Bad Idea",6);
pc4=new JustTesting("Heap2",10);
delete pc2;
delete pc4;
delete [] buffer;
}
{
private:
string words;
int number;
public:
JustingTesting(const string & s = "Justing 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];
JustTesting *pc1,*pc2;
pc1=new (buffer) JustTesting;//会得到 pc1=buffer,即他们指向的地址是一样的。(指向同一个堆内存单位)
pc2=new Justing("Heap1",20);
.........
pc3=new (buffer) JustTesting("Bad Idea",6);
pc4=new JustTesting("Heap2",10);
delete pc2;
delete pc4;
delete [] buffer;
}
问题
一
代码
pc1=new(buffer)JustTesting;
pc3=new(buffer)JustTesting("BadIdea",6)
pc3=new(buffer)JustTesting("BadIdea",6)
原因
定位new使用一个新对象pc3来覆盖用于第一个对象pc1的内存单元。
如果类动态地为其数据成员分配内存,会出现问题(指向丢失)。
如果类动态地为其数据成员分配内存,会出现问题(指向丢失)。
修改
pc1=new(buffer)JustTesting;
pc3=new(buffer + sizeof(JustTesting))JustTesting("BadIdea",6);
pc3=new(buffer + sizeof(JustTesting))JustTesting("BadIdea",6);
二
代码
delete pc2;
delete pc4;
delete [] buffer;
//
delete pc1/pc3;//invalid
delete pc4;
delete [] buffer;
//
delete pc1/pc3;//invalid
原因
pc1,pc3不能delete,无法调用析构函数,无法释放数据成员动态分配的对象。
因为pc1、pc2已经释放(因为buffer已经释放)
因为pc1、pc2已经释放(因为buffer已经释放)
子主题
修改
方法
显式地为使用定位new运算符创建的对象调用析构函数。
格式
pc3->~JustTesting();
pc1->~JustTesting();
pc1->~JustTesting();
如果Pc3不是指针:
&pc3->JustTesting
&pc3->JustTesting
说明
注意顺序:
用创建顺序相反的顺序删除。
用创建顺序相反的顺序删除。
析构函数不用有什么特殊变化
队列的模拟
代码
头文件
//queue.h
#ifudef QUEUE_H_
#define QUEUE_H_
//contain Customer items
class Customer{
private:
long arrive;//到达时间
int processtime;//到柜员机处理的时间
public:
Customer(){ arrivate = 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;
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 & item);//加入队列
bool dequeue(Item &item);
};
#endif
#ifudef QUEUE_H_
#define QUEUE_H_
//contain Customer items
class Customer{
private:
long arrive;//到达时间
int processtime;//到柜员机处理的时间
public:
Customer(){ arrivate = 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;
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 & item);//加入队列
bool dequeue(Item &item);
};
#endif
源代码1
#include"queue.h"
#include<cstdlib>
Queue::Queue(int qs):qsize(qs)//常量只能这样初始化
{
front = rear = NULL;
iterms = 0;
}
Queue::~Queue()
{
Node *temp;
while (front != NULL)//当队列还不是空的时候 ,要删除队列的每一个节点。
{
temp = front;
front = front->next;
delete temp;
}
}
//判断队列是否空
bool Queue::isempty() const
{
return iterms == 0;
}
//判断队列是否满
bool Queue::isfull() const
{
return iterms;
}
//返回队列的数
int Queue::queuecount()const
{
return iterms;
}
bool Queue::enqueue(const Item &item)
{
if ( isfull() )
return false;
//创建一个新节点
Node *add = new Node;
// 设置新节点的信息
add->item = item;
add->next = NULL;
iterm++;
//插入新节点到队列中
if (front == NULL)
front = add;
else
rear->next = add;
rear = add;
return true;
}
bool Queue::dequeue(Item &item)
{
if (front == NULL)
return false;
item = front->item;//返回iterm(顾客) 的信息
items--;
Node *temp = front;
front = front ->next;
delete temp;
/*
注意不可以直接
front = front ->next;要有temp,删除动态分配的空间。
*/
if (items==0)
rear = NULL;
return ture;
}
void Customer::set(long when)
{
processtime = std::rand %3+1;//顾客在柜员机处理的时间随机。
arrive = when ;//when 表示顾客到队列的时间
}
#include<cstdlib>
Queue::Queue(int qs):qsize(qs)//常量只能这样初始化
{
front = rear = NULL;
iterms = 0;
}
Queue::~Queue()
{
Node *temp;
while (front != NULL)//当队列还不是空的时候 ,要删除队列的每一个节点。
{
temp = front;
front = front->next;
delete temp;
}
}
//判断队列是否空
bool Queue::isempty() const
{
return iterms == 0;
}
//判断队列是否满
bool Queue::isfull() const
{
return iterms;
}
//返回队列的数
int Queue::queuecount()const
{
return iterms;
}
bool Queue::enqueue(const Item &item)
{
if ( isfull() )
return false;
//创建一个新节点
Node *add = new Node;
// 设置新节点的信息
add->item = item;
add->next = NULL;
iterm++;
//插入新节点到队列中
if (front == NULL)
front = add;
else
rear->next = add;
rear = add;
return true;
}
bool Queue::dequeue(Item &item)
{
if (front == NULL)
return false;
item = front->item;//返回iterm(顾客) 的信息
items--;
Node *temp = front;
front = front ->next;
delete temp;
/*
注意不可以直接
front = front ->next;要有temp,删除动态分配的空间。
*/
if (items==0)
rear = NULL;
return ture;
}
void Customer::set(long when)
{
processtime = std::rand %3+1;//顾客在柜员机处理的时间随机。
arrive = when ;//when 表示顾客到队列的时间
}
源代码2
#include<isotream>
#include<cstdlib>
#include<ctime>
#include"queue.h"
const int MIN_PER_HR = 60;
bool newcustomer(double x);
int main()
{
using std::cin;
using std::cout;
using std::endl;
using std::ios_basel
std::srand(std::time(0));
cout<<"Case Study: Bank of Heather Automatic Teller\n";
cout<<"Enter maximum size od queue:";
int qs;//队列的大小。(用户自定义设置)
cout >>qs;
Queue line(qs);
cout<<"Enter the number of simulation hours:";
int hours;//模拟的时间 ,小时为单位
cin>>hours;
long cyclelimit = MIN_PER_HR *hours;//模拟的时间变为分钟
cout<<"Enter the average number of customers per hour:";
double perhour;//每小时的平均人流量
cin>>perhour;
double min_per_cust;
min_per_cust = MIN_PER_HR/perhour;// 每小时的平均人流量变为每分钟的平均人流量。
Item temp; // 新顾客的数据
long turnaways = 0; //超过最大队列返回的顾客人数
long customers = 0;//加入队列的顾客数(不一定在ATM机处理过数据?)
long served = 0;// 在ATM机处理过的顾客数
long sum_line = 0;//??感觉与customers差不多?
int wait_time = 0;// 下一个顾客等待在ATM机处理的时间。
long line_wait = 0;// 在队列的顾客一共得等待时间
}
for (int cycle =0 ;cycle<cyclelimit; cycle++) //用循环次数来模拟的时刻
{
//顾客加入队列
if (newcustomer(min_per_cust)) //newcustomer(min_per_cust) 模拟顾客到来的时间
{
if (line.isfull())
turnaways++;
else
{
customers++;
temp.set(cycle);//设置顾客到来的时刻
line.enqueue(temp);//加入队列
}
}
//顾客在ATM机上处理
if (wait_time<=0 &&!line.isempty())
{
line.dequeue(temp);//执行完后temp变成了队首的temp;
wait_time = temp.ptime();//更新wait_time, 队首temp的处理时间变成了下一个顾客的等待时间
line_wait += cycle - temp.when(); //一个顾客的等待时间 = 处理时间-到来的时间
served++;
}
if (wait_time > 0 )
wait_time--;
sum_line += line.queuecount();
//显示结果
if (customers > 0 )
{
cout<<"customers accepted:"<<customers<<endl;
cout<<" customers served:"<<served<<endl;
cout<<" turnaways:"<<turnaways<<endl;
cout<<"average queue size:"
cout.precision(2);
cout.setf(ios_base::fixed,ios_base::floatfield);
cout<<(double) sum_line/cyclelimit<<endl;
cout<<" average wait time:"
<<(double)line_wait/served<<" minutes\n";
}
else
cout<<"No customers!\n";
cout<<"Done!\n";
return 0;
}
bool newcustomer(double x)
{
return (std::rand()*x/RAND_MAX <1);//0 < std::rand()*x/RAND_MAX < x; RAND_MAX是循环中能产生的最大数。rand()% x这样也可以吧?
}
#include<cstdlib>
#include<ctime>
#include"queue.h"
const int MIN_PER_HR = 60;
bool newcustomer(double x);
int main()
{
using std::cin;
using std::cout;
using std::endl;
using std::ios_basel
std::srand(std::time(0));
cout<<"Case Study: Bank of Heather Automatic Teller\n";
cout<<"Enter maximum size od queue:";
int qs;//队列的大小。(用户自定义设置)
cout >>qs;
Queue line(qs);
cout<<"Enter the number of simulation hours:";
int hours;//模拟的时间 ,小时为单位
cin>>hours;
long cyclelimit = MIN_PER_HR *hours;//模拟的时间变为分钟
cout<<"Enter the average number of customers per hour:";
double perhour;//每小时的平均人流量
cin>>perhour;
double min_per_cust;
min_per_cust = MIN_PER_HR/perhour;// 每小时的平均人流量变为每分钟的平均人流量。
Item temp; // 新顾客的数据
long turnaways = 0; //超过最大队列返回的顾客人数
long customers = 0;//加入队列的顾客数(不一定在ATM机处理过数据?)
long served = 0;// 在ATM机处理过的顾客数
long sum_line = 0;//??感觉与customers差不多?
int wait_time = 0;// 下一个顾客等待在ATM机处理的时间。
long line_wait = 0;// 在队列的顾客一共得等待时间
}
for (int cycle =0 ;cycle<cyclelimit; cycle++) //用循环次数来模拟的时刻
{
//顾客加入队列
if (newcustomer(min_per_cust)) //newcustomer(min_per_cust) 模拟顾客到来的时间
{
if (line.isfull())
turnaways++;
else
{
customers++;
temp.set(cycle);//设置顾客到来的时刻
line.enqueue(temp);//加入队列
}
}
//顾客在ATM机上处理
if (wait_time<=0 &&!line.isempty())
{
line.dequeue(temp);//执行完后temp变成了队首的temp;
wait_time = temp.ptime();//更新wait_time, 队首temp的处理时间变成了下一个顾客的等待时间
line_wait += cycle - temp.when(); //一个顾客的等待时间 = 处理时间-到来的时间
served++;
}
if (wait_time > 0 )
wait_time--;
sum_line += line.queuecount();
//显示结果
if (customers > 0 )
{
cout<<"customers accepted:"<<customers<<endl;
cout<<" customers served:"<<served<<endl;
cout<<" turnaways:"<<turnaways<<endl;
cout<<"average queue size:"
cout.precision(2);
cout.setf(ios_base::fixed,ios_base::floatfield);
cout<<(double) sum_line/cyclelimit<<endl;
cout<<" average wait time:"
<<(double)line_wait/served<<" minutes\n";
}
else
cout<<"No customers!\n";
cout<<"Done!\n";
return 0;
}
bool newcustomer(double x)
{
return (std::rand()*x/RAND_MAX <1);//0 < std::rand()*x/RAND_MAX < x; RAND_MAX是循环中能产生的最大数。rand()% x这样也可以吧?
}
queue类
功能
(特征)
(特征)
队列存储有序的项目序列
队列能容纳的项目数有一定的限制
应当能够创建空队列
应当能够检查队列是否为空
应当能够检查队列是否是满的
应当能够在队尾添加项目
应当能够从队首删除项目
应当能够确定队列中的项目数
公有接口
内容
public:
Queue(int qs = Q_SIZE);
~Queue();
bool isempty() const;//判断是否空
bool isfull() const;//判断是否满
int queuecount() const;//队列数
bool enqueue(const Item & item);//加入队列
bool dequeue(Item &item);
Queue(int qs = Q_SIZE);
~Queue();
bool isempty() const;//判断是否空
bool isfull() const;//判断是否满
int queuecount() const;//队列数
bool enqueue(const Item & item);//加入队列
bool dequeue(Item &item);
私有数据
内容
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; }
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; }
struct Node{ .. }
结构在类的私有部分声明,只能在类中使用Node.
在公有部分声明,可以在类外面声明Queue::Node类型的变量。
在公有部分声明,可以在类外面声明Queue::Node类型的变量。
struct Node 不是一个变量!!!
类方法
Queue()构造函数
功能
初始化qsize,给队首队尾赋0值。
代码
Queue::Queue(int qs):qsize(qs)//常量只能这样初始化
{
front = rear = NULL;
iterms = 0;
}
{
front = rear = NULL;
iterms = 0;
}
qsize常量
qsize是常量,不能给它赋值
Queue::Queue(int qs)
{
front = rear = NULL;
iterms = 0;
qsize = qs;//invalid;
}
{
front = rear = NULL;
iterms = 0;
qsize = qs;//invalid;
}
原理
调用析构函数时,对象已经创建了(程序为数据成员分配内存)。
再执行析构函数的内容,使用常规赋值方式将值存储到内存中。
所以不可以直接qsize = qs
再执行析构函数的内容,使用常规赋值方式将值存储到内存中。
所以不可以直接qsize = qs
成员初始化列表
功能原理
能够在创建对象时初始化数据变量/常量
语法
Classy::Classy(int n , int m):mem1(n), mem2(0),men( n*m + 2 )
{ }
{ }
例子
Queue::Queue(int qs) : qsize(qs),front(NULL),rear(NULL),item(0)
{ }
{ }
注意
这种格式只能用于构造函数
也不能用于原型
必须用这种格式来初始化非静态const数据成员(在C++11之前要这样)
必须用这种格式来初始化引用数据成员.
//引用数据成员只能初始化
//引用数据成员只能初始化
class Agency{ .... };
class Agent
{
private:
Agency & belong;
....
};
Agent::Agent(Agency & a ) : belong(a) {...}
class Agent
{
private:
Agency & belong;
....
};
Agent::Agent(Agency & a ) : belong(a) {...}
选择
对于简单数据成员,没什么区别。
对象本身是类对象成员,使用成员初始化列表效率更高。
对象本身是类对象成员,使用成员初始化列表效率更高。
C++11类的初始化
class Classy
{
int men1 = 10;
const int men2 = 20;
//...
}
{
int men1 = 10;
const int men2 = 20;
//...
}
=
Classy::Classy( ):mem1(10),mem2(20) { .... }
Classy::Classy( ):mem1(n) { .... }
men2被赋予了10,mem2以让是20
入队
代码
bool Queue::enqueue(const Item &item)
{
//判断是否满
if ( isfull() )
return false;
//创建一个新节点
Node *add = new Node;
// 设置新节点的信息
add->item = item;
add->next = NULL;
iterm++;//项目数加1
//插入新节点到队列中
/情况1:
if (front == NULL)
front = add;
else
rear->next = add;
rear = add;
return true;
}
{
//判断是否满
if ( isfull() )
return false;
//创建一个新节点
Node *add = new Node;
// 设置新节点的信息
add->item = item;
add->next = NULL;
iterm++;//项目数加1
//插入新节点到队列中
/情况1:
if (front == NULL)
front = add;
else
rear->next = add;
rear = add;
return true;
}
出队
代码
说明
temp 是必不可少的。要freefront指向的动态分配的内存空间。
析构函数
前
构造函数没有用new分配内存,所以看上去析构函数不用使用 delete。(这是错误的)
因为front 结构变量有*,new分配的对象,所以要用delete
因为front 结构变量有*,new分配的对象,所以要用delete
代码
Queue::~Queue()
{
Node *temp;
while (front != NULL)//当队列还不是空的时候 ,要删除队列的每一个节点。
{
temp = front;
front = front->next;
delete temp;
}
}
{
Node *temp;
while (front != NULL)//当队列还不是空的时候 ,要删除队列的每一个节点。
{
temp = front;
front = front->next;
delete temp;
}
}
复制构造函数\赋值函数
说明
不能使用系统的默认的复制构造函数和赋值函数。
因为包含了使用new的数据成员(front / rear ).
如果使用了默认的。。会共享同一个链表。结果会很严重
因为包含了使用new的数据成员(front / rear ).
如果使用了默认的。。会共享同一个链表。结果会很严重
这里不给出深层的复制构造函数和赋值函数。给出避免产生错误的方法
方法
1.放在私有函数中
private:
......
Queue(const Queue&q):qsize(0){ }//私有函数接口。
Queue & operator = (const Queue & q){ return *this; }
......
Queue(const Queue&q):qsize(0){ }//私有函数接口。
Queue & operator = (const Queue & q){ return *this; }
Queue snick(nip);// no ok
tuck = nip ;//no ok
tuck = nip ;//no ok
2.当对象按值传递(或返回)时,复制构造函数还被用于创建其他的临时对象。
尽量按引用传递
3.如果重载了加法运算符,会导致创建临时对象操作。
customer类
调用
用循环cycle 来模拟时刻
for (int cycle =0 ;cycle<cyclelimit; cycle++)
子主题
bool newcustomer(double x)
{
return ( std::rand()*x/RAND_MAX <1 );//0 < std::rand()*x/RAND_MAX < x; RAND_MAX是循环中能产生的最大 数。rand()% x这样也可以吧?
}
{
return ( std::rand()*x/RAND_MAX <1 );//0 < std::rand()*x/RAND_MAX < x; RAND_MAX是循环中能产生的最大 数。rand()% x这样也可以吧?
}
0 条评论
下一页