本章主要内容
- 对类成员使用动态内存分配
- 隐式和显式复制构造函数
- 隐式和显式重载赋值运算符
- 构造函数使用new
- 使用静态类成员
- 定位new运算符用于对象
- 使用指向对象的指针
- 实现队列抽象数据类型
12.1 动态内存和类
先复习用new分配内存
head
#include <iostream>
#ifndef GOLF_H_INCLUDED
#define GOLF_H_INCLUDED
class StringBad
{
private:
char * str;
int len;
static int num_string;
public:
StringBad(const char * s);
StringBad();
~StringBad();
friend std::ostream & operator<<(std::ostream & os, const StringBad & st);
};
#endif // GOLF_H_INCLUDED
definition
// contain function called in file1
#include <cstring>
#include "golf.h"
rsing std::cout;
int StringBad::num_string = 0;
StringBad::StringBad(const char * s)
{
len = std::strlen(s);
str = new char[len + 1];
std::strcpy(str, s);
num_string++;
cout << num_string << ":\"" << str << "\" object created\n";
}
StringBad::StringBad()
{
len = 4;
str = new char[4];
std::strcpy(str, "C++");
num_string++;
cout << num_string << ":\"" << str << "\" default object created\n";
}
StringBad::~StringBad()
{
cout << "\"" << str << "\" object deleted, ";
--num_string;
cout << num_string << " left\n";
delete [] str;
}
std::ostream & operator<<(std::ostream & os, const StringBad & st)
{
os << st.str;
return os;
}
这个程序的运行结果有很大问题,因为会不加计数君而调用构造函数,调用的是复制构造函数。
12.1.2 特殊成员函数
C++会自动提供几个成员函数
- 默认构造
- 默认析构
- 复制构造
- 赋值运算符
- 地址运算符
编译器会生成后三个函数的定义
为了使这复制构造和赋值运算符可以更好的工作,需要使用深度复制,即手动显示实现这两者。
//显式重载赋值,给被赋值对象单独的内存,只传递字符串的值过去,而不是两者指向同一个地址
StringBad & StringBad::operator=(const StringBad & st)
{
if(this == &st)
return *this;
delete []str;
len = st.len;
str = new char [len + 1];
std::strcpy(str, st.str);
return *this;
}
//显式实现复制构造函数,帮助复制构造创建新的内存块并且更新静态部分
StringBad::StringBad(const StringBad & st)
{
num_string++;
len = st.len;
str = new char [len + 1];
std::strcpy(str, st.str);
cout << num_string << ": \"" << str << "\"object created\n";
}
12.3 使用new时的注意事项
- new和delete要对应
- 同个类中的所有构造函数分配内存的格式需相同
- 用0或nullptr来表示空指针
- 深度复制的两个应用
12.4 有关返回对象的说明
- 返回指向const对象的引用
- 返回指向非const对象的引用
- 返回对象
- 返回const对象
12.5 使用指向对象的指针
就是普遍的那几种使用指针的方法
- 直接声明
- 指向现有对象,就是赋值为现有对象的地址
- new一个新的对象,将其地址赋值给指针,此时会调用构造函数
12.5.3 定位new运算符复习
定位new运算符会分配内存时指定分配的位置
在程序中使用是有两点注意
- 第二个定位对象会覆盖在第一个的位置
- delete整个缓冲区的时候不会调用其中的对象的析构函数
原因及解决方法 - 第一个必须手动将位置右移,保证不会覆盖
- 第二个因为delete只能用于常规new,不与定位new匹配,解决方法是显式为需要销毁的对象创造析构函数
12.6 复习
- 重载<<运算符,返回的是ostream的引用
- 转换函数,应该用explicit关键字来避免隐式调用
- 构造函数使用new的类,几个new就几个delete,种类要匹配,还有深度赋值函数的定义
各种ADT专门拿一章来整理