刚学习类与动态内存时,觉得书本上写的理解起来挺费劲的,现在我将自己学习这方面的知识时的有关内容总结如下(学习使用的书本为c++ primer plus,故下面的内容主要以自己理解书中的内容为主)。下面为项目的代码:
头文件:Stringbab
#include<iostream>
#ifndef _STRINGBAB_H_
#define _STRINGBAB_H_
class Stringbab {
private:
char* str;
int len;
static int num_strings;
public:
Stringbab(const char* s);
Stringbab();
~Stringbab();
friend std::ostream& operator<<(std::ostream &os, const Stringbab& st);
};
#endif
类方法的定义:
#include<iostream>
#include"strngbab.h"
#include<string.h>
using std::cout;
int Stringbab::num_strings = 0;
Stringbab::Stringbab(const char* s) {
len = std::strlen(s);
str = new char[len + 1];
strcpy_s(str,strlen(s)+1, s);
num_strings++;
cout << num_strings << ":\"" << str << "\"object created\n";
}
Stringbab::Stringbab() {
len = 4;
str = new char[4];
strcpy_s(str,strlen("c++")+1, "c++");
cout << num_strings << ":\"" << str << "\" default object created\n";
}
Stringbab::~Stringbab() {
cout << "\"" << str << "\"object delete,";
num_strings--;
cout << num_strings << " left\n";
delete[] str;
}
std::ostream & operator<<(std::ostream & os, const Stringbab& st) {
os << st.str;
return os;
}
相关程序:
#include<iostream>
#include"strngbab.h"
using std::cout;
void callme1(Stringbab&);
void callme2(Stringbab);
int main() {
using std::endl;
{
cout << "starting an inner block.\n";
Stringbab headline1("Celery Staclks at Midnight");
Stringbab headline2("Lettuce Prey");
Stringbab sports("Spanich 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";
Stringbab sailor = sports;
cout << "sailor:" << sailor << endl;
cout << "Assign one object to another:\n";
Stringbab knot;
knot = headline1;
cout << "knot:" << knot << endl;
cout << "Exiting the block!\n";
}
cout << "End of main()";
return 0;
}
void callme1(Stringbab& rsb) {
cout << "String passed by referance:\n";
cout << "\" " << rsb << "\"";
}
void callme2(Stringbab sb) {
cout << "String passed by value:\n";
cout << "\" " << sb << "\"";
}
下面是程序的运行结果:
(注:这里的运行结果为vs2019上运行的结果,与书本的不太一样,书本上的运行结果显示表明析构函数调用的次数比这的结果多两次,原因可能是vs2019并没有使程序运行完就中断了)
这里的运行结果中,显示headline2对象时出现了乱码原因如下:
程序在调用callme2函数时,使用的是值传递,将headline2对象的值复制给一个临时Stringbab对象,这里的赋值为成员赋制或浅复制,分别将Headline2对象的各个成员数据复制给临时对象对应的成员,故Headline2对象的指针成员复制给临时对象对应的指针成员,所以两个对象指向相同的地址。当callline2函数调用结束时,临时对象的析构函数将释放该对象的内存,由于这两个对象的指针成员指向相同的内存,故临时对象清除该成员指针指向的内存时,也将Headline2的指针成员指向的内存清除。当显示Headline2.str时,由于指针指向的内存被清除,故显示出一堆乱码。
要解决这个问题,应该定义多一个Stringbab类的复制函数(称为深度复制),让指针指向内容的副本地址复制给临时对象对应的指针成员。该复制函数的定义如下:
Stringbab::Stringbab(const Stringbab& st) {
num_strings++;
len = st.len;
str = new char[len + 1];
strcpy_s(str, len + 1, st.str);
cout << num_strings << ":\"" << str << "\"object created\n";
}
加入该段代码后,程序的运行结果如下:
结果仍然有乱码,显示最后一个对象Headline1的成员指针指向的内存的内容时出现了乱码,可能是该内存已被删除。检查程序时,发现有一个语句较为可疑:knot=headline1,因为这两个对象的内容是一样的,可能是knot对象的析构函数同时将headline1指针成员指向的内容也清除了,该问题就跟上面的浅复制的问题一样。我们没有定义过赋值函数,故在执行该赋值语句时,调用的是默认赋值函数,该函数用浅复制的方法,赋值时,将headline1的内容复制给knot对象,使knot.str指向的内存地址跟headline1.str指向的内存地址一样。为了解决这个问题,我们应该定义一个赋值函数,赋值时,将headline1对象复制给knot对象时,用的应该是深复制。该函数的定义如下:
Stringbab& Stringbab::operator = (const Stringbab& st) {
if (this == &st)
return *this;
delete[] str;
len = st.len;
str = new char[len + 1];
strcpy_s(str, len + 1, st.str);
return *this;
}
加入该函数后,程序的运行结果如下:
运行结果是没毛病的,所以我们之前对最后出现乱码的猜测是正确的。