类和动态内存分配(2)

目录

1.指向对象的指针

2.使用定位new运算符为对象分配内存空间


1.指向对象的指针

// class.h

#pragma once

#ifndef _CLASS_H_
#define _CLASS_H_

#include<iostream>
using std::istream;
using std::ostream;

class String
{
private:
	char* str;   //字符串指针,此处没有分配内存,后续用new来为其开辟内存空间
	int len;
	static int num_strings;  //只有static,则必须声明后在实现文件中进行初始化
	static const int CINLIM = 80; //const static 可以直接在类中初始化
public:
	String(const char* s);
	String();
	String(const String& s);//赋值构造函数
	~String(); //在使用new的类中十分重要 -- 配套使用delete来释放堆空间
	int length() const
	{
		return len;
	};
	String& operator=(const String& st); //对象赋值
	String& operator=(const char* s);  //一般字符串赋值
	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);
	// ostream流和istream流没有共用的复制构造函数,所以必须要返回引用且按引用传递参数,若返回的是一个值或者按值传递参数,则使用到了副本且必须使用复制构造函数。
	friend istream& operator>>(istream& is, String& st);  // cin>>
	friend ostream& operator<<(ostream& os, const String& st);  // cout<<
	//静态函数
	static int HowMany();
};

#endif

// fuc.cpp

#include<cstring>   // strcpy--复制字符串, strcmp--根据ASCII码值比较字符串,strlen--获取字符串的长度
#include"class.h"
using namespace std;

//为类中声明的static变量初始化,且要声明其所在的类区域
int String::num_strings = 0;

int String::HowMany()
{
	return num_strings;
}

String::String(const char* s)
{
	len = std::strlen(s);  //strlen获取的是字符串除去末尾空字符的长度,所以长度要为 len+1;
	str = new char[len + 1];  //在构造函数中使用new申请堆空间,在析构函数中使用delete释放堆空间
	strcpy(str, s);
	num_strings++;
}

String::String()
{
	len = 4;
	str = new char[len + 1];
	str[0] = '\0';  //设置为空指针,也可为设置为 NULL, nullptr,(void *)0q	
	num_strings++;
}

String::String(const String& s)//复制构造函数
{
	num_strings++; //为新初始化的对象复制,所以可以在总数上加上1
	len = s.len;
	//深度复制:
	str = new char[len + 1];
	strcpy(str, s.str);
}

String::~String() //在使用new的类中十分重要 -- 配套使用delete来释放堆空间
{
	--num_strings;
	delete[] str;
}

String& String::operator=(const String& st) //对象赋值
{
	if (this == &st)  //如果调用对象和参数已经相同
	{
		return *this;
	}
	delete[] str; //先删除之前的内存,防止内存的浪费
	len = st.len;
	//深度复制
	str = new char[len + 1];
	strcpy(str, st.str);
	return *this;  //使得可以进行连续复制操作
}

String& String::operator=(const char* s)  //一般字符串赋值
{
	delete[] str;
	len = strlen(s);
	str = new char[len + 1];
	strcpy(str, s);
	return *this;
}

char& String::operator[](int i) //重载 中括号[] 运算符   //可改变数据版本
{
	return str[i]; //即返回调用对象字符串的第i个字符
}

const char& String::operator[](int i)const//不改变数据版本
{
	return str[i];
}

//友元函数:
bool operator<(const String& st1, const String& st2)
{
	return (strcmp(st1.str, st2.str) < 0);
}

bool operator>(const String& st1, const String& st2)
{
	return st2 < st1;  //直接使用上面刚重载的运算符 逻辑:若语句为: st1>st2,若成立则应当有st2<st1,可以实现函数功能
	//或 return (strcmp(st1.str,st2.str)>0)
}

bool operator==(const String& st1, const String& st2)
{
	return (strcmp(st1.str, st2.str) == 0);
}

istream& operator>>(istream& is, String& st) // cin>>
{
	char temp[String::CINLIM]; //友元函数,不能直接使用类中的静态常量,需要使用作用域解析运算符
	is.get(temp, String::CINLIM); // 相当于cin,get(name,size)
	if (is)
	{
		st = temp;  //使用的重载过的对于常规字符串的赋值运算符
	}
	//while的作用:读取掉剩下的多余出来的字符
	while (is && is.get() != '\n')   // 若输入失败,则istream对象is的值将被置为false;
	{
		continue;
	}
	return is;
}

ostream& operator<<(ostream& os, const String& st)  // cout<<
{
	os << st.str;
	return os;
}

// main.cpp

#include<iostream>
#include<cstdlib>
#include<ctime>
#include"class.h"
const int Arsize = 10;
const int MaxLen = 81;

int main()
{
	using namespace std;
	String name;
	cout << "Hi, what's your name?" << endl;
	cin >> name;  // >>运算符的重载,使可以作用于对象

	cout << name << ", please enter up to " << Arsize
		<< " short sayings <empty line to quit>:" << endl;  // <<运算符的重载,使可以作用于对象
	String sayings[MaxLen];
	char temp[MaxLen];
	int i;
	for (i = 0; i < Arsize; i++)
	{
		cout << i + 1 << ": ";
		cin.get(temp, MaxLen);   // cin.get(stringname,stringlength)
		while (cin && cin.get() != '\n')   //当没有输入错误且没有读取完多余的字符时,继续循环读取
		{
			continue;
		}
		if (!cin || temp[0] == '\0')   //如果输入为空, break退出
		{
			break;
		}
		else
		{
			sayings[i] = temp;
		}
		int total = i;

		if (total > 0)
		{
			cout << "Here are your sayings: " << endl;
			for (i = 0; i < total; i++)
			{
				cout << sayings[i] << endl;
			}
			//使用指向对象的指针来保存特殊的字符串
			String* shortest = &sayings[0];
			String* first = &sayings[0];
			for (i = 1; i < total; i++)
			{
				if (sayings[i].length() < shortest->length())
				{
					shortest = &sayings[i];
				}
				if (sayings[i] < *first)
				{
					first = &sayings[i];
				}
			}
			cout << "Shortest sayings: " << *shortest << endl;
			cout << "First alphabetically: " << *first << endl;
			srand(time(0)); //为rand()提供随机数种子    cstdlib和ctime头文件中的函数
			int choice = rand() % total;
			String* favorite = new String(sayings[choice]); 
			cout << "My favorite sayings: " << *favorite << endl;
			delete favorite;
		}
		else
		{
			cout << "Not much to say,eh?" << endl;
		}
		cout << "Bye~" << endl;
		return 0;
	}
}

知识点:

1.ostream流和istream流没有公共的复制构造函数,在重载此类的运算符时必须要返回引用且按引用传递参数。若返回的是一个值或者按值传递参数,则使用到了副本且必须使用复制构造函数。

eg: friend ostream& operator<<(ostream & os, const String & st)

2.创建指向对象的指针的语法: classname * name;  (与创建指向一般变量的指针语法是相同的)

3.使用指向对象的指针:  访问对象中的公共函数: name->fucname (间接访问使用 -> 而不是 '.'),使用(*name)来进行解引用。

4.String* favorite = new String(sayings[choice]) 和 String* favorite = new String[number]的区别:前者是为favorite这个指向对象的指针初始化为sayings[choice],后者是创建一个指针数组,其中共有number个指针。

2.使用定位new运算符为对象分配内存空间

#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" << endl;
	};
	~JustTesting()
	{
		cout << words << " destroyed" << endl;
	}
	void Show() const
	{
		cout << words << " , " << number << endl;
	}
};
int main()
{
	char* buffer = new char[BUF]; //创建内存区

	JustTesting* pc1, *pc2;  //创建两个指向对象的指针

	pc1 = new(buffer) JustTesting;
	pc2 = new JustTesting("Heap1", 20);  //普通new,并进行初始化

	cout << "Memory block addresses:\n" << "buffer: "
		<< (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("Better idea", 6);
	pc4 = new JustTesting("Heap2", 10);

	cout << "Memory contents: " << endl;
	cout << pc3 << ": ";
	pc3->Show();
	cout << pc4 << ": ";
	pc4->Show();

	delete pc2;
	delete pc4;

	//对于使用定位new分配内存的对象,必须要显式的调用析构函数来销毁对象,且删除顺序应当与创建的顺序相反 -- 先进后出,后进先出
	//同时,应当先销毁对象,再释放为其分配的内存空间
	pc3->~JustTesting();
	pc1->~JustTesting();
	delete[]buffer;

	cout << "Done!" << endl;
	return  0;

}

知识点:

1.如果自定义了构造函数则必须定义一个默认构造函数,但若是自定义的构造函数中的所有参数都是默认参数,则无需再定义一个默认构造函数。 

   eg: JustTesting(const string &s = "just testing",int n =0) -- 所有参数都有默认值

2.使用定位new需要包含头文件<new>

3.定位new的语法:typename * name = new (buffer) typename

  eg:JustTesting * pc1 = new(buffer) JustTesting;

4. 若多个对象使用一个buffer缓冲区,则需要加上偏移量( sizeof() ),防止内存的覆盖

  eg:JustTesting * pc3 = new(buffer + sizeof(JustTesting)) JustTesting("Better idea", 6);

5.对于使用定位new分配内存的对象,必须要显式的调用析构函数来销毁对象,且删除顺序应当与创建的顺序相反 -- 先进后出,后进先出。同时,应当先销毁对象,再释放为其分配的内存空间

  eg:pc3->~JustTesting();
       pc1->~JustTesting();
       delete[]buffer;

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值