C++在内存分配时,让程序在运行时决定内存分配,而不是编译时决定。
C++使用new 和 delete来动态控制内存。
//strngbad.h -- flawed string class definition
#include
#ifndef STRNGBAD_H_
#define STRNGBAD_H_
class StringBad {
private:
char* str; //pointer to string
int len; //length of string
static int num_strings; //number of objects
public:
StringBad(const char* s); //constructor
StringBad();
~StringBad();
friend std::ostream& operator << (std::ostream& os, const StringBad & st);
};
#endif // STRNGBAD_H_
将这个类命名为StringBad是因为这是一个不太完整的类。它是使用动态内存分配来开发类的第一个阶段,正确地完成了一些显而易见的工作。
num_strings 为静态存储类,即使创建了10个StringBad对象,但也只有一个共享的num_strings成员。
//strngbad.cpp -- StringBad class methods
#include
#include "stringbad.h"
using std::cout;
//initializing static class member
int StringBad::num_strings = 0;
//class methods
//construct StringBad from C string
StringBad::StringBad(const char* s) {
len = std::strlen(s);
str = new char[len + 1];
std::strcpy(str, s);
num_strings++;
cout << num_strings << ": \" " << str << "\" object created\n";
}
StringBad::StringBad() {
len = 4;
str = new char[4];
std::strcpy(str, "c++");
num_strings++;
cout << num_strings << ": \" " << str << "\" object created\n";
}
StringBad::~StringBad() {
cout << "\" " << str << "\" object deleted, ";
--num_strings;
cout << num_strings << " left\n ";
delete[] str;
}
std::ostream& operator << (std::ostream& os, const StringBad& st) {
os << st.str;
return os;
}
//vegnews.cpp -- using new and delete with classes
//compile with strngbad.cpp
#include
using std::cout;
#include "stringbad.h"
void callme1(StringBad&);
void callme2(StringBad);
int main()
{
using std::endl;
{
cout << "Starting an inner block.\n";
StringBad headline1("Celery Stalks at Midnight");
StringBad headline2("Lettuce Prey");
StringBad sports("Spinach Leaves Bowl for Dollars");
cout << "headline1: " << headline1 << endl;
cout << "headline2: " << headline2 << endl;
cout << "sports:" << sports << endl;
callme1(headline1);
cout << "headline1: " << headline1 << endl;
callme2(headline2);
cout << "headline2: " << headline2 << endl;
cout << "Initialize one object to another:\n";
StringBad sailor = sports;
cout << "sailor: " << sailor << endl;
cout << "Assign one object to another:\n";
StringBad knot;
knot = headline1;
cout << "knot: " << knot << endl;
cout << "Exiting the block.\n";
}
cout << "End of main()\n";
return 0;
}
void callme1(StringBad& rsb) {
cout << "String passed by reference:\n";
cout << " \"" << rsb << "\"\n";
}
void callme2(StringBad rsb) {
cout << "String passed by value:\n";
cout << " \"" << rsb << "\"\n";
}
callme2(headline2) ,将headline2作为函数参数来传递从而导致析构函数被调用。虽然 按值传递可以防止原始参数被修改,但实际上函数已使原始字符串无法识别。
StringBad sailor = sports;
这种初始化等同于
StringBad sailor = StringBad(sports);//StringBad(const StringBad&)
当使用一个对象来初始化另一个对象时,编译器将自动生成上述构造函数(称为复制构造函数)。自动生成的构造函数不知道要更新静态变量num_string,因此会将技术方案搞乱。析构函数上也会有一些问题(同一块内存释放两次)。
复制构造函数原型通常是:
Class_name(const Class_name&);
定义一个显示的复制构造函数
StringBad::StringBad(const StringBad & st){
num_strings++;
len = st.len;
str = new char[len + 1];
std::strcpy(str, st.str);
cout << num_strings << ": \" " << str << "\" object created\n";
}
StringBad类的其他问题
c++允许类对象赋值,这是通过自动为类重载赋值运算符实现的。这种运算符原型如下:
Class_name & Class_name::operator=(const Class_name &);
赋值运算符的功能以及何时使用
StringBad headline1("Celery Stalks at Midnight");
...
StringBad knot;
knot = headline1; //使用赋值运算符
//初始化对象时不一定使用
StringBad metoo = knot;// 使用拷贝构造函数
上述代码中
knot = headline1;
与拷贝构造函数一样,析构时,headline1.str和knot.str指向了同样的地址。
重载赋值运算符(重要)
StringBad & StringBad::operator=(const StringBad& st){
if(this == &st){
return *this;
}
delete[] str; //free old string
len = st.len;
str = new char[len + 1];
std::strcpy(str, st.str);
return *this;
}
代码首先检查自我复制,如果相同,直接返回*this。接着释放掉str指向的内存。
#ifndef STRING1_H_
#define STRING1_H_
#include
using std::ostream;
using std::istream;
class String
{
private:
char *str;
int len;
static int num_strings;
static const int CINLIM = 80; // cin input limit
public:
// 构造函数与其他方法
String(const char * s); // 构造函数
String(); // 默认构造函数
String(const String &); // 复制构造函数
~String(); // 析构函数
int length() const { return len; }
// 重载操作符方法
String & operator = (const String &);
String & operator = (const char *);
char & operator[](int i);
const char & operator[](int i) const;
// 重载操作符的友元
friend bool operator < (const String & st1, const String & st2);
friend bool operator > (const String & st1, const String & st2);
friend bool operator == (const String & st1, const String & st2);
friend ostream & operator << (ostream & os, const String & st);
friend istream & operator >> (istream & is, String & st);
// 静态成员函数
static int HowMany();
};
#endif
#pragma warning(disable:4996)
#include
#include"string1.h"
using std::cin;
using std::cout;
// 初始化静态类成员
int String::num_strings = 0;
// 静态方法
int String::HowMany()
{
return num_strings;
}
// 类方法****************************************************
// 构造函数传入C标准字符串参数
String::String(const char * s)
{
len = std::strlen(s);
str = new char[len + 1];
std::strcpy(str, s);
num_strings++;
}
// 默认构造函数
String::String()
{
len = 4;
str = new char[1];
str[0] = '\0';
num_strings++;
}
// 复制构造函数
String::String(const String & st)
{
num_strings++;
len = st.len;
str = new char[len + 1];
std::strcpy(str, st.str);
}
// 析构函数
String::~String()
{
--num_strings;
delete[] str;
}
// 重载操作符方法*****************************************
// 从 String 类到 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;
}
// 从 C字符串 到 String 类赋值
String & String::operator = (const char * s)
{
delete[] str;
len = std::strlen(s);
str = new char[len + 1];
std::strcpy(str, s);
return *this;
}
// 从非 const String 类读写字符
char & String::operator[] (int i)
{
return str[i];
}
// 从 const String 类读取字符
const char & String::operator[] (int i) const
{
return str[i];
}
// 重载操作符友元
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);
}
// 字符串输出
ostream & operator << (ostream & os, const String & st)
{
os << st.str;
return os;
}
// 字符串快速并且直接输入
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;
}
#include
#include"string1.h"
const int ArSize = 10;
const int MaxLen = 81;
int main()
{
using std::cout;
using std::cin;
using std::endl;
String name;
cout << "Hi, What`s your name?\n>>";
cin >> name;
cout << name << ", please enter up to " << ArSize << " short sayings :\n" ;
String sayings[ArSize]; // 对象数组
char temp[MaxLen];
int i;
for (i = 0; i < ArSize; i++)
{
cout << i + 1 << ": ";
cin.get(temp, MaxLen);
while (cin && cin.get() != '\n')
continue;
if (!cin || temp[0] == '\0') // empty line? 后半部分用于旧版本的检测空行
break;
else
sayings[i] = temp;
}
int total = i; // total # of lines read
if (total > 0)
{
cout << "Here are your sayings:\n";
for (i = 0; i < total; i++)
cout << sayings[i][0] << ": " << sayings[i] << endl;
int shorest = 0;
int first = 0;
for (i = 1; i < total; i++)
{
if (sayings[i].length() < sayings[shorest].length())
shorest = i;
if (sayings[i] < sayings[first])
first = i;
}
cout << "Shortest saying:\n" << sayings[shorest] << endl;
cout << "First alphabetically:\n" << sayings[first] << endl;
cout << "This program used " << String::HowMany() << " String objects. Bye.\n";
}
else
cout << "No input! Bye.\n";
return 0;
}
String :: String ( const String & st )
{
num_string++; // 更新静态成员
len = st.len;
str = new char [ len + 1 ]; // 分配新地址空间
std::strcpy(str,st.str); // 把字符串复制到新的地址
}
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; // 返回一个指向调用对象的引用
}
当成员函数或独立的函数返回对象时,有几种返回方式可供选择:
* 指向对象的引用;
* 指向对象的 const 引用;
* const 引用;
使用 const 引用的常见原因是旨在提高效率:
返回对象将调用复制构造函数,而返回引用不会;
引用指向的对象应该在调用函数执行时存在;
参数被声明为 const 引用,返回类型必须为 const;
operator = () 的返回值用于连续赋值:
Sring s1 ( “Good stuff” );
String s2, s3;
s3 = s2 = s1;
返回类型不是 const 因为方法 operator = () 返回一个指向 s2 的引用,可以对其进行修改。返回引用可以不调用复制构造函数
operator << ( ) 的返回值用于串接输出:
String s1( “Good stuff” );
cout << s1 << “is coming!”;
operator << ( cout, s1 ) 的返回值成为一个用于显示字符串 " is coming! "的对象;
返回类型必须为 ostream &,而不能是 ostream,因为 ostream 没有公有的复制构造函数
如果返回的对象是调用函数中的局部变量,不能按引用返回它,被调用函数执行完毕后,局部对象将调用其析构函数,引用指向的对象不再存在;
返回对象而不是引用,将调用复制构造函数
如果方法或函数要返回局部对象,则应返回对象,而不是指向对象的引用,将使用复制构造函数来生成返回的对象;
如果方法或函数返回一个没有公有复制构造函数的类的对象,它必须返回这种对象的引用;
有些方法和函数(如重载的赋值运算符)可以返回对象,也可以返回指向对象的引用,首选引用,因为效率高。
要重新定义 << 运算符,以便将它和 cout 一起用来显示对象的内容,定义下面友元运算符函数:
ostream & operator << (ostream & os, const c_name & obj )
{
os << . . . ; // display object contents
return os;
}
其中 c_name 为类名, 如果该类能够返回所需内容的公有方法,则可以在运算符中使用这些方法,这样便不用将它们设置为友元函数了
要将单个值转换为类类型,需要定义类构造函数:
c_name ( type_name value ); // c_name为类名, type_name是要转换的类型名称
要将类转换为其他类型,需要创建类成员函数:
operator type_name ( ); // type_name 为要转换的类型
使用转换函数时要小心,可以在声明构造函数时使用关键字 explicit,防止被用于隐式转换
如果使用 new 运算符来分配类成员指向的内存,在设计时应采取一些措施:
//queue.h -- interface for a queue
#ifndef QUEUE_H_
#define QUEUE_H_
//This queue will contain Customer items
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;
Node* next;
};
enum
{
Q_SIZE = 10
};
Node * front; //pointer to front of Queue
Node * rear; //pointer to rear of Queue
int items;
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); //create queue with a qs limit
~Queue();
bool isEmpty() const;
bool isFull() const;
int queuecount() const;
bool enQueue(const Item& item);
bool deQueue(Item& item);
};
#endif // !QUEUE_H_
//queue.cpp
#include "queue.h"
#include
//Queue methods
Queue::Queue(int qs) : qsize(qs){
front = rear = NULL;
}
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;
add->item = item;
add->next = NULL;
items++;
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;
items--;
Node* temp = front;
front = front->next;
delete temp;
if (items == 0)
{
rear = NULL;
}
return true;
}
void Customer::set(long when){
processtime = std::rand() % 3 + 1;
arrive = when;
}
// bank.cpp -- using the Queue interface
// compile with queue.cpp
#include
#include
#include
#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_base;
std::srand(std::time(0));
cout << "Case Study: Bank of Heather Automatic Teller\n";
cout << "Enter maximum size of queue: ";
int qs;
cin >> qs;
Queue line(qs);
cout << "The simulation hours: 100\n";
int hours = 100;
long cyclelimit = MIN_PER_HR * hours;
double perhour;
double min_per_cust;
perhour = 1;
Item temp;
long turnaways = 0;
long customers = 0;
long served = 0;
long sum_line = 0;
int wait_time = 0;
long line_wait = 0;
double avetime = 0;
while (perhour++ && avetime <= 1)
{
while (!line.isEmpty())
{
line.deQueue(temp);
}
min_per_cust = MIN_PER_HR / perhour;
for (int cycle = 0; cycle < cyclelimit; cycle++)
{
if (newcustomer(min_per_cust))
{
if (line.isFull())
turnaways++;
else
{
customers++;
temp.set(cycle);
line.enQueue(temp);
}
}
if (wait_time <= 0 && !line.isEmpty())
{
line.deQueue(temp);
wait_time = temp.ptime();
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";
avetime = (double)line_wait / served;
}
cout << "When there comes " << perhour << " people per hour, the average wait time will be about 1 minute.\n";
cout << "Done!\n";
system("pause");
return 0;
}
bool newcustomer(double x)
{
return (std::rand() * x / RAND_MAX < 1);
}