本文的主要内容:
1、类成员动态分配内存一系列操作方法(分配、释放)
2、特殊成员函数:复制构造函数、赋值运算符重载函数
3、静态类成员、静态成员函数
4、动态分配对象存储空间(常规new 和定位new)
5、指向对象的指针

了解一下对象创建过程:
1、根据类声明分配合适的存储空间,这里对象只存储非静态的数据成员,静态数据成员存在特殊的位置被所有对象共享。
2、调用构造函数,为对象的数据成员赋初值
3、对象的生命周期结束时自动调用析构函数(对于用定位new分配的匿名对象不会自动调用析构函数,程序猿要自己添加调用析构函数的代码)

一个很常见的问题:
编译时根据数据的类型便可以分配所需存储空间大小,由类型限制了存储空间太死板了。(之前复习过,为了灵活使用内存空间(匿名的存储空间),我们可以利用new在运行时才确定数据的存储大小)。
如何确定类对象所需的内存空间?取决于数据成员类型
类是数据和方法的封装,通俗说,将类看成一个黑盒子即不同数据类型的组合,编译时创建类对象就是根据数据成员类型来确定对象所需的空间。
解决方法:
希望类的成员所需的内存空间是在运行分配的,但是类对象的存储大小必须是确定的(各个成员类型必须是确定的),指针可以帮助我们解决这个问题!指针所需的内存空间是确定的,这样一来类所需空间大小是确定的,等到程序运行时,利用new在堆中分配合适的内存大小,并将这个地址值赋给对象的指针即可(利用构造函数实现:在构造函数内分配空间、将其地址值赋值给指针变量**,因而通过该指针,关联了一个大小可变的内存空间**,便可以视为类中存储了可变类型的数据)。对象存储时地址,通过该地址找到堆内实际数据。
对象的生命周期结束时,将自动调用析构函数,释放对象所占用的内存空间,但是数据成员关联的匿名存储空间并不会被释放,但其实匿名空间的值也是改对象的一部分,因而不显示使用delete释放该片空间,可以说没有完整的释放对象空间。delete将释放构造函数中new所分配的空间。
讲完了,如何为对象分配内存空间。回顾一下,构造函数(创建对象时总会被调用到)用途:初始化数据成员。因而,对于想存储大小可变空间,需在构造函数内完成一系列工作:动态分配内存、将实参的值拷贝到动态空间中、将指针指向初始化后的内存空间。
无论是什么类型最底层的表示都是基本类型的复合!String类为了能灵活分配内存空间,数据成员必须是char *(字符串不就是多个字符,最底层的存储类型就是char)

string类(动态分配空间存字符串)实现:
通过C风格字符串(根据字符串地址)创建String对象,c风格字符串三种形式:“***”、 char、char[],但终归是字符串地址。

特殊的成员函数(编译器为我们自动生成的):
1、默认构造函数(编译器使得在创建对象的时候总会调用构造函数):编译器生成的默认构造函数无参数,什么都不做
2、默认析构函数
3、复制构造函数
4、赋值运算符重载函数
5、地址运算符

复制构造函数:用已知对象创建并初始化一个新的对象。
原型:
类和动态内存分配

何时被调用?
创建新的对象并初始化为同类现有对象;产生了现有对象的副本时(参数的对象传递、返回对象);
复制构造函数做些什么?
浅复制:逐个复制对象的成员值(除静态成员变量),如果成员本身就是个对象,则调用该成员的复制构造函数

类和动态内存分配
由于是浅复制,成员是指针时将产生错误,只是复制指针值到新的对象成员,两个对象的数据成员指向同一片内存空间,一旦有一个对象被释放,其关联的内存空间也被释放,那么另一个对象的指针将悬空,所指空间的数据已经被破坏了。
为了解决浅复制的问题,可在类中定义复制构造函数完成深度复制产生新的存储空间,这样一来编译器就不会再我们的程序生成默认的复制构造函数。
当我们的数据成员中包含了使用new的指针,我们一定要自定义复制构造函数完成深度复制。(一旦一个对象被释放,其对应的内存空间也会被释放,浅复制将带来悬空指针)

类和动态内存分配
赋值运算符重载函数:
由于实际语法支持多个赋值符号的连写,因而函数原型可定义为:
类和动态内存分配
何时调用赋值运算符重载函数?
两个现有对象间的赋值,以及创建初始化对象的含等号形式(可能,有编译器有关)
类和动态内存分配
赋值运算符函数功能:
逐成员复制(浅复制),若成员是对象,则调用相应的复制构造函数。
为了解决浅复制的问题,需要我们自己定义赋值运算符函数,与复制构造函数类似,但由于是赋值运算(目标对象时存在的),多了三个特点:
1、先释放目标对象数据成员所指向的空间再深度复制重新分配空间并改变指针值
2、检查赋值的是否是自身,如果是的话,在第一步先释放了,那还怎么赋值
3、返回值是第一个对象的引用

#ifndef STRING_H
#define STRING_H
#include<iostream>
class String
{
	char *s;//堆中实际存储字符串的地址  
	int len;
public:
	String();
	String(char * string);//字符串地址  给定一个字符串创建其String对象 (存储字符串(副本),不这么做的话,成员和程序中的数据将产生依赖 一旦程序的存储数据空间被释放 这个指针将悬空 )
	//不用string 来给s赋值 而是要创建string的副本(分配新的空间 拷贝string内容) 减少不必要的数据依赖
	~String();
	friend std::ostream & operator<<(std::ostream & os, const String &);
	String(const String &);//复制构造函数
	String & operator=(const String &);
};

#include"String.h"
#include<cstring>

String::String()
{
	len = 0;
	s = nullptr;
}

String::String(char *string)//构造函数,为对象的数据成员赋值
{
	len =std:: strlen(string);
	s = new char[len + 1];//为对象的数据成员赋值
	std::strcpy(s, string);//为动态分配的空间赋予初值


}

String::~String()
{
	delete[] s;//释放s所指向的空间
}




std::ostream & operator<<(std::ostream & os, const String & c)
{
	os << c.s<<" ";
	os << c.len<<std::endl;
	return os;
}


String::String(const String & a)
{
	len = a.len;
	s = new char[len + 1];
	strcpy(s, a.s);

}



String & String::operator=(const String & a)
{
	if (this == &a)
		return *this;
	delete[] s;
	len = a.len;
	s = new char[a.len + 1];
	strcpy(s, a.s);
	return *this;
}
#include"String.h"

void callme(String);

int main()
{
	using std::cout;
	String a;
	String b = "kdkfeieiei";//自动类型转化为String(临时对象 ) 调用C++自带的重载赋值运算符 浅赋值(临时对象被释放 动态空间被释放 字符串被破坏)
	cout << b;
							
//一堆的的字符串到底是以何种方式存到String对象中的呢? 堆中 类对象存有指向这些存储空间的地址
	String c("dkekkeeieiei");
	String d("dkkeidfjeifefeijeiejejdkkdfdf");
	cout << c;
	callme(d);
	cout << d;
	a = b;

	std::cin.get();
    return 0;
}

void callme(String c)//值传递调用复制构造函数(浅复制) c被释放 连带动态空间被释放 数据值被破坏
{
	std::cout << c;//采用自己写的复制构造函数,形参c的s是一个新的空间 因此c被释放了也不影响实参的s

}


接着进行对String的扩充:
重载运算符大于、小于、等于、[]、
成员函数是否为const的算作函数的特征标。重载函数不单单是看参数列表个数、类型、是否为const函数。

std::istream & operator>> (std::istream & is, String & a)
{
	char temp[String::SIZE];
	std::cin.get(temp, String::SIZE);
	if (std::cin)
		a = temp;//调用上面的赋值函数
	while (std::cin&& std::cin.get() != '\n')//删除缓冲流中多于的字符
		continue;
	return is;

 }
friend bool operator> (String &a, String &b)
	{
		return std::strcmp(a.s, b.s) > 0;
	}
	friend bool operator<(String &a, String &b)
	{
		return b > a;
	}
	friend bool operator==(String &a, String &b)
	{
		return std::strcmp(a.s, b.s) == 0;//strcmp 参数是字符串指针 
	}
	char & operator[](int n)
	{
		return s[n];
	}

	char & operator[](int n) const
	{
		return s[n];
	}
	friend std::istream & operator >> (std::istream &, String &);

	int get_len()
	{
		return len;
	}

总结在构造函数中使用new的注意事项:
1、多个构造函数,new和new[]的选取需要保持一致。因为只有一个析构函数,delete或是delete[]是确定的。但delete 、delete[]可以应用在空指针上。

2、自定义复制构造函数完成深度复制
3、自定义赋值运算符重载函数完成深度复制
4、

类静态成员:
在空间中只有一个副本被多个对象共享
初始化问题:
不能在类声明时初始化,由于类声明仅仅声明如何分配空间但没有实际分配空间因而不支持初始化,提供另一种规则:类声明之外,分配并初始化静态成员。但若静态成员是用const修饰的,则可以在类声明时直接初始化。
类和动态内存分配
类和动态内存分配

若被定义为私有成员,但在类外有需要得到这个值,我们只能定义公有成员函数,但这个函数必须被声明为静态的,这么做是因为数据值并不存在对象的存储空间内,由对象来调用成员函数也找不到这个值。(静态成员只能由静态成员方法访问,且该静态方法只能访问静态成员)
静态成员方法声明方式:
static 返回类型 函数名();
正确的调用方式为:
类名::静态方法();

函数返回类型是否为引用的差别:
不是引用时,将返回值作为参数调用复制构造函数创建临时对象(程序可访问到该临时变量),适用于返回局部自动变量
引用时,注意引用的对象不能是在函数调用结束后被释放(临时变量),通常返回的是调用引用对象或是参数引用对象(实参传递过来的)
返回类型可以有;const 类型(不能作为左值)、类型(临时变量,可修改)、引用类型、const 引用类型(参数是const引用,返回其中一个参数)

使用new创建对象:
类和动态内存分配
定位new:
使用指定位置分配内存时需要注意:后一个分配的空间会覆盖前一个分配的空间,需要程序猿提供明确的起始位置。
释放空间时需要显示调用析构函数。这是因为在指定的位置分配了空间后是不会返回其地址值的,所以常规的delete无效。空间先释放后分配的。

#include<iostream>
class Test
{
;

	std::string str;
	int n;
public:
	Test(std::string s, int m)
	{
		str = s;
		n = m;
	}
	~Test(){}
};

int main()
{
	using std::cout;
	using std::endl;

	char * p = new char[999];
	Test *t1 = new Test("kdkkd", 12);
	Test *t2 = new (p) Test("kekeifjfiefj", 33);
	cout << t1 << " " << (void *)p << " " << t2 << endl;
	Test *t4 = new (p + sizeof(Test))Test("kdkkek", 3344);
	cout << t4 << endl;
	delete t1;
	t4->~Test();
	t2->~Test();
	

    return 0;
}


类中可嵌套类或是结构,其作用域在类内。可作为类型或是类成员。
成员初始化:非静态的const变量要求在分配空间时必须初始化,之后不可以初始化,因而在创建对象的时候,先是分配空间再调用构造函数,这与非静态const变量的初始化要求相矛盾,为了解决这一个问题,C++提供成员初始化列表,可在其内初始化成员变量,包括普通类型的成员变量。同理,对于引用变量也适用(定义时必须初始化)。
类和动态内存分配
除了上述写法,C++11可支持在构造函数内初始化,与使用成员初始化列表作用一样的。

例子:创建队列,先考虑队列支持哪些操作,再考虑用什么样的结构存储队列数据(这是由操作决定的,比如说队列要队尾入队、队头出队、如果采取连续内存空间的数组来存储队列数据,意味着一旦有元素出队,队列中的其余元素多需要挪动,因而采用连续空间存储太费时间,这是为啥我们要用离散的内存空间来存储,但是我们需要把这些离散的空间组合起来,让他成为一个有序结构,如何让这些离散的空间组合起来,只要前一个能找到后一个就可以了(记录下一个空间的地址,最后一个空间的下一个地址为空);栈来说只在一端入和出,其余元素则不动,用数组存储即可)

#ifndef QUEUE_H
#define QUEUE_H
#include"customer.h"
typedef Customer Item;//使用Item是为了泛化 若需要修改队列存储的数据只需要改这里 把需要的类替换 如果不加这样一个类 那么针对不同的数据 都得修改队列中结构的表示 ,封装成为一个类最重要的是能够定义一些操作

//什么结构存储对列元素?支持哪些操作?操作决定了存储结构
class Queue
{
	//私有成员解决两个问题:用什么存储队列数据?(结构)如何找到队列?(指针)
private:
	struct Node { Item item; struct Node *next; };//队列存储的是数据+指针 ;只不过这个数据也是一个类customer
	const int maxlen;
	int count;
	static const int qsize = 10;
	Node *head;//在内存由相关的离散块构成了一个链表 我们如何才能找到这个链表呢?(表示字符串,如何找到字符呢 ?通过一个指向该字符串的指针)通过一个指向链表的指针
	Node *tail;//方便入队操作

	//由于Queue类成员的head tail指针是动态分配的内存空间 一个队列进行出队操作时 完全可能导致两个指针指向空间的被释放 为了避免编译器为我们添加的浅复制和赋值重载函数 我们自己生命且声明为私有的  类外一旦需要复制构造函数和=重载 编译器不会自作主张生成默认的函数  而是报错 我们可以之后再修改添加这个两个函数
	Queue(Queue & a): maxlen(a.maxlen) {

	}
	Queue & operator=(const Queue &a)
	{
		return *this;
	}
public:
	//队列操作(多个数据)
	Queue(int max=qsize);
	~Queue();
	bool enqueue(Item & item);
	bool dequeue(Item & item);
	bool isfull();
	bool isempty();
	int size();
	
};
#endif
#ifndef CUSTOMER_H
#define CUSTOMER_H


class Customer
{
	long arrive;
	int process;
public:
	Customer();//一个好的编程习惯:提供默认构造函数
	void set_arrive(long);
	long getarrive() const;
	int get_process() const;
};


#endif
#include"queue.h"

Queue::Queue(int max): maxlen(max)
{

	//maxlen = max;
	head = tail = nullptr;
	count = 0;
}

bool Queue::isfull()
{
	return maxlen == count;
}

bool Queue::isempty()
{
	return count == 0;
}

bool Queue::enqueue(Item & i)
{
	if (isfull())
		return false;
	Node *temp = new Node;//结构没有构造函数 且成员默认是公有的 可以在类外访问到
	temp->item = i;
	temp->next = nullptr;
	if (isempty())
		head = temp;
	else
		tail->next = temp;
	tail = temp;
	count++;
	return true;
}
bool Queue::dequeue(Item &  i)//引用对象 一定要搞清谁是引用 谁是真实 这里引用作为参数 在函数内部所使用的i其实都是真实的实参 ,由于形参没有用const修饰 因而我们可以在函数内部改变实参的值
{
	if (isempty())
		return false;
	Node * temp = head;
	i = temp->item;
	count--;
	head = head->next;//考虑只剩一个空间下的出队 此时队尾指针是指向最后一个指针的 因此要修改
	if (count == 0)
		tail  = nullptr;
	delete temp;

}

Queue::~Queue()//程序结束的时候还有队列中的元素驻留在内存中 这不是我们希望的 虽然不是在构造函数分配的内存空间 但也是在类内分配的空间 类对象生命结束时便要负责释放这些空间
{
	if (count != 0)
	{
		Node *temp;
		while (head)
		{
			
			temp = head;//地址
			head = head->next;
			delete temp;
		}
	}
}

#include"customer.h"

#include<cstdlib>
#include<ctime>

Customer::Customer()
{
	arrive = process = 0;
}
int Customer::get_process() const
{
	return process;
}

long Customer::getarrive() const
{
	return arrive;
}

void Customer::set_arrive(long when)
{
	std::srand(std::time(0));
	arrive = when;
	process = std::rand() % 3 + 1;
}
#include"queue.h"

const int MIN = 60;
#include<iostream>
#include<ctime>
#include<cstdlib>

bool newcomer(double);

int main()
{
	using std::cout;
	using std::endl;
	using std::cin;
	int hours;
	cout << " 监视时间" << endl;
	cin >> hours;
	long cycletime = hours*MIN;
	cout << "平均一小时有多少人" << endl;
	double pernum;
	cin >> pernum;
	double permin = MIN / pernum;//每几分钟来一个人
	cout << permin << endl;
	
	cout << "队列最大长度:" << endl;
	int max;
	cin >> max;
	Queue q(max);
	int turnaway = 0;
	int wait = 0;
	int customers = 0;
	double line_wait = 0;
	int serve = 0;
	std::srand(std::time(0));
	Item temp;//customer 默认构造函数
	for (int i = 0; i < cycletime; i++)
	{
		
		if (newcomer(permin))
		{
			if (q.isfull())
				turnaway++;
			else
			{
				temp.set_arrive(cycletime);
				q.enqueue(temp);
				customers++;

			}
		}
		if (wait <= 0 && !q.isempty())
		{
			q.dequeue(temp);

			line_wait += cycletime - temp.getarrive();
			cout << temp.getarrive() << endl;
			cout << temp.get_process() << endl;
			wait = temp.get_process();
			serve++;
		}
		if (wait > 0)
			wait--;






	}
	if (customers > 0)
	{
		cout << "customer is " << customers << endl;
		cout << "served is " << serve << endl;
		cout << "turnaway is  " << turnaway << endl;
		cout << line_wait << endl;
		cout << "平均等待时间 "<<double(line_wait / serve) << endl;

	}
	cin.get();
	cin.get();
    return 0;
}


bool newcomer(double t)
{
	//std::srand(std::time(0));
	//std:: cout << rand() << std::endl;
	return (std::rand()*t / RAND_MAX < 1);

}


相关文章:

  • 2022-12-23
  • 2021-07-23
  • 2022-01-16
  • 2021-10-03
猜你喜欢
  • 2022-12-23
  • 2022-12-23
  • 2021-07-03
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-02-28
相关资源
相似解决方案