【捡起C++】类和动态内存分配

​ C++在内存分配时,让程序在运行时决定内存分配,而不是编译时决定。

​ C++使用new 和 delete来动态控制内存。

//strngbad.h -- flawed string class definition
#include <iostream>
#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 <cstring>
#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 <iostream>
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指向的内存。

改进后的新String类
#ifndef STRING1_H_
#define STRING1_H_
#include<iostream>
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<cstring>
#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<iostream>
#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 <empty line to quit>:\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;
}

在构造函数中使用 new 时应注意的事项

  • 在构造函数中使用 new 来初始化指针成员,则应在析构函数使用 delete;
  • new 和 delete 必须互相兼容。new 对应于 delete,new [ ] 对应于 delete [ ];
  • 只有一个析构函数,如果有多个构造函数,必须以相同的方式使用 new,所有的构造函数都必须与它兼容;
  • delete (无论带不带 [ ])可以用于空指针(NULL、0、nullptr);
  • 定义一个复制构造函数,通过深度复制将对象初始化为另一个对象:
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 引用,返回类型必须为 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 的类

如果使用 new 运算符来分配类成员指向的内存,在设计时应采取一些措施:

  • 对于指向的内存是由 new 分配的所有类成员,都应在析构函数中对其使用 delete,该运算符将释放分配的内存;
  • 如果析构函数通过对指针类成员使用 delete 来释放内存,则每个构造函数都应当使用 new 来初始化指针,或设置为空指针;
  • 构造函数使用 new[ ] 或 new,不能混用;
  • new [ ] 对应于 delete [ ] , new 对应于 delete;
  • 应定义一个分配内存(而不是使用指针指向已有内存)的复制构造函数。这样程序能将类对象初始化为另一个类对象;
  • 应定义一个重载赋值运算符的类成员函数,这样程序能将类对象赋值给另一个类对象
//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 <cstdlib>


//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 <iostream>
#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_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);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值