类和动态内存分配
1. 动态内存和类
静态成员
- 注意: 静态数据成员在类中声明,在类外初始化, 但如果静态成员是const整数类型或枚举型可以在类声明中初始化
- 静态成员初始化时不加static限定符, 否则会将其作用域局限在所在的文件
new和delete
- 构造函数使用new分配内存时, 必须在相应的析构函数中delete来释放, 如果使用new [ ]来分配内存,则应使用delete [ ]来释放
- 如果有多个构造函数, 则必须以相同的方式使用new, 要么都带中括号, 要么都不带
特殊成员函数
- C++自动提供以下函数:
- 默认构造函数, 如果没有定义任何构造函数
- 默认析构函数, 如果没有定义
- 默认拷贝构造函数, 如果没有定义
- 赋值运算符重载, 如果没有定义
- 地址运算符重载, 如果没有定义
- C++11 还提供了移动构造函数和移动赋值函数
- 警告: 当类中包含了使用new初始化的指针成员, 应当定义一个拷贝构造函数, 用来复制数据而不是简单的指针复制.
- 赋值运算符
C++允许类对象赋值, 就是通过自动为类重载赋值运算符实现的
将已有的对象赋给另一个对象时, 将使用重载的赋值运算符
使用已有对象初始化另一对象时, 可能会使用赋值运算符
2. String类
- 函数成员使用new动态分配, 应提供拷贝构造函数和重载赋值运算
- String类简单实现代码:
/*
* String.h
*/
#ifndef CPLUSPLUS_STRING_H
#define CPLUSPLUS_STRING_H
#include <iostream>
#include <cstring>
using namespace std;
class String {
public:
String(); // 默认构造函数
String(const char* str); // 普通构造函数
String(const String& s); // 拷贝构造函数
~String(); // 析构函数
String &operator=(const String& s); // 重载赋值运算
String &operator=(const char* str); // 重载赋值运算
friend ostream &operator<<(ostream& os, const String& s);
private:
char* m_str;
size_t length;
};
#endif //CPLUSPLUS_STRING_H
/*
* String.cpp
*/
#include "String.h"
String::String(const char* str) {
length = std::strlen(str);
m_str = new char[length + 1];
strcpy(m_str, str);
}
String::String() {
length = 0;
m_str = nullptr;
}
String::String(const String& s) {
length = s.length;
m_str = new char[length + 1];
strcpy(m_str, s.m_str);
}
String::~String() {
delete [] m_str;
m_str = nullptr;
}
String &String::operator=(const String &s) {
if (this == &s) { // 检查是否为自身赋值
return *this;
}
delete [] m_str;
length = s.length;
m_str = new char[length + 1];
strcpy(m_str, s.m_str);
return *this;
}
String &String::operator=(const char *str) {
delete [] m_str;
length = strlen(str);
m_str = new char[length + 1];
strcpy(m_str, str);
return *this;
}
ostream &operator<<(ostream &os, const String &s) {
os << s.m_str;
return os;
}
- C++11 提供了关键字nullptr来表示空指针, 尽量使用nullptr而不是0或者NULL
- 包含类成员的类的赋值或拷贝时会调用类成员自身的赋值运算或拷贝构造函数
3. 定位new运算符
- 定位new运算符用于为类对象分配空间时,需要显式调用类的析构函数(如果需要析构的话)
char *buffer = new char[1024];
String* pn = new (buffer) String;
pn->~String(); // 显示调用析构函数
delete [] buffer; // 释放new的空间
// 直接使用对象指针释放是错误的
delete pn; // 错误, 不能释放buffer且不会自动调用析构函数
4. 成员初始化列表
- 只能用于构造函数
- 必须用这种格式来初始化非静态const类型数据成员
- 必须用这种方法来初始化引用数据成员
- 数据成员初始化的顺序与初始化器中的顺序无关, 和成员的声明顺序一致
- C++11 允许直接在类内声明时初始化成员