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;
}