Fork me on GitHub

C++第十二章:动态内存与类+断点错误

前言

C++类是针对对象的一种控制方法,可以看作各种函数与变量的管理方。类同样也会消耗内存,而且类一旦被创建,意味着相关成员会大量调用,此时内存的管理极其重要,常采用delete和new嵌入类的成员函数中对变量进行操作。

代码

#pragma once
#include <iostream>
using namespace std;

class stringbad
{
public:
	stringbad();
	stringbad(const char *s);//放置new
	~stringbad();//用于放置delete

	int length() const { return this->len; };

	//考虑复制构造函数和复制运算符两种函数,适配不同编译器
	stringbad& operator=(const stringbad& s);
	stringbad(const stringbad& s);

	stringbad& operator=(const char* s);
	const char &operator[] (int i) const;
	friend bool operator<(const stringbad& s1, const stringbad& s2);
	friend bool operator>(const stringbad& s1, const stringbad& s2);
	friend bool operator==(const stringbad& s1, const stringbad& s2);
	friend ostream& operator<<(ostream& os, const stringbad& s);
	friend istream& operator>>(istream& is, stringbad& s);
	static int HowMany();//静态区域的成员函数,只能指定访问,不能this.
	static const int CINLIM = 80;
private:
	char* str;//字符串指针
	int len;//字符串长度
	static int num_strings;//字符串数量,静态局部变量
	
};
#include "stringbad.h"
#include <cstring>

void callme1(stringbad& s);//引用传递
void callme2(stringbad s);//引用传递

int stringbad::num_strings = 0;//将static变量初始化,但是不可以在类声明时初始化静态类成员!所有的类成员共享~
int stringbad::HowMany()
{
	return stringbad::num_strings;
}

//构造函数
stringbad::stringbad(const stringbad& s)
{
	this->str = new char[s.len + 1];
	strcpy_s(this->str, s.len + 1, s.str);//拷贝字符串
	this->len = s.len;//计算长度
	this->num_strings++;
}

stringbad::stringbad()
{
	this->len = 4;
	str = new char[1];
	str[0] = '\0';
	num_strings++;
}

stringbad::stringbad(const char* s)//放置new
{
	this->str = new char[strlen(s) + 1];
	strcpy_s(str, strlen(s)+1,s);//拷贝字符串
	this->len = strlen(s)+1;//计算长度
	this->num_strings++;
}



bool operator<(const stringbad& s1, const stringbad& s2)
{
	return (std::strcmp(s1.str, s2.str) < 0);
}

bool operator>(const stringbad& s1, const stringbad& s2)
{
	return (std::strcmp(s2.str, s1.str) < 0);
}

bool operator==(const stringbad& s1, const stringbad& s2)
{
	return (std::strcmp(s2.str, s1.str) == 0);
}

ostream& operator<<(ostream& os, const stringbad& s)
{
	os << "string : " << s.str << endl;
	return os;
}

istream& operator>>(istream& is, stringbad& s)
{
	char temp[stringbad::CINLIM];
	is.get(temp, stringbad::CINLIM);

	s = temp;//赋值构造函数

	//消耗缓存的字符
	while (is && is.get() != '\n')
		continue;

	return is;
}

stringbad& stringbad::operator=(const char* s)
{
	delete[] str;
	len = std::strlen(s);
	this->str = new char[strlen(s) + 1];
	strcpy_s(str, len + 1, s);
	return *this;
}

//析构函数
stringbad::~stringbad()//用于放置delete
{
	cout << num_strings << endl;
	--num_strings;
	delete []str;//释放空间
}

/*************************成员函数**************************/

//赋值构造函数
stringbad& stringbad::operator=(const stringbad& s)
{

	//那如果自己赋值给自己呢?就不可以delete,直接返回自己就行
	if (this == &s)
	{
		return *this;
	}

	delete[]str;//去除调用本身的内存空间,因为下面还会new

	this->str = new char[s.len + 1];
	strcpy_s(this->str, s.len + 1, s.str);//拷贝字符串
	this->len = s.len;//计算长度
	this->num_strings++;

	return *this;
}

const char& stringbad::operator[] (int i) const
{
	return str[i];
}



int main(void)
{
	//stringbad headline1("HELLOWORLD");
	//stringbad headline2("GOOD MORNING");
	//stringbad headline3("SPORTS");

	//cout << headline1 << endl;
	//callme1(headline1);
	//callme2(headline1);//按值传递出问题了,可以自己再编写一个复制构造函数

	cout << "HI! What's your name? " << endl;
	stringbad name;
	cin >> name;
	cout << name << endl;

	//stringbad name1("zhangwei");
	//cout << name1;
	return 0;
}

void callme1(stringbad& s)//引用传递-原对象还在
{
	cout << "string passed by reference: " << s << endl;
}

void callme2(stringbad s)
{
	cout << "string passed by reference: " << s << endl;
}

重要代码解释

static声明

static const int CINLIM = 80;

类成员变量当使用static静态变量声明时,该变量在全局数据区分配内存,不管开发人员新建多少个类对象,所有类对象共享一个CINLIM对象。

友元函数声明

friend bool operator<(const stringbad& s1, const stringbad& s2);
friend bool operator>(const stringbad& s1, const stringbad& s2);
friend bool operator==(const stringbad& s1, const stringbad& s2);
friend ostream& operator<<(ostream& os, const stringbad& s);
friend istream& operator>>(istream& is, stringbad& s);

如果运算符重载时,类的对象位与运算符右侧,将违背类调用的原则,即  类.operaor 运算符(); 因此想要灵活使用运算符重载(如果你不像灵活运用,也可以不用声明友元),需要声明其为友元函数,即非类对象也可以访问类的私有对象。

复制构造函数与赋值运算符重载

stringbad& operator=(const stringbad& s);
stringbad(const stringbad& s);

在类定义时定义了赋值运算符重载和类的复制构造函数重载,这是因为当没有复制构造函数时,如果直接 类1=类2,C++编译器将自行生成一个复制构造函数/赋值运算符,此时会忽略static变量num_strings,导致后续此变量调用时出现错误!解决的办法是自己定义一个显示的赋值构造函数。相信大家在其他博主的文章也看到了此项错误。

 

类的编写技巧

s = temp;//赋值运算符重载

此项本质为 类对象=字符串,此时会调用前列定义的赋值运算符重载,这种思想非常重要,我们可以在编写后续类成员函数时调用之前定义的一些构造函数或者运算符重载,这会有助于你开发代码~

 

类的引用传递与按值传递

void callme1(stringbad& s)//引用传递-原对象还在
{
	cout << "string passed by reference: " << s << endl;
}

void callme2(stringbad s)//按值传递
{
	cout << "string passed by reference: " << s << endl;
}

按值传递的后果是copy一个stringbad后新copy的类对象构造时无法匹配自己编写的构造函数,编译器自己会进行复制构造,如果你没有编写一个显示的复制构造函数,那这个时候num_strings没有++,这会导致后续的析构和变量调用出现问题。因此最好采用引用传递。

断点错误

作者自己在学习时,当编写完代码编译时,在程序运行的最后报出了断点错误~

网上看了一些文章,大家都指出是内存泄露或者是内存空间访问越界导致,于是逐句调试查了好半天new和delete,终于发现是下面这句错误:没有len+1

哭笑不得~属实傻逼了~大家引以为戒~

posted @ 2022-11-08 22:32  张一默  阅读(44)  评论(0编辑  收藏  举报