1、参考引用
- C++高级编程(第4版,C++17标准)马克·葛瑞格尔
2、建议先看《21天学通C++》 这本书入门,笔记链接如下
- 21天学通C++读书笔记(文章链接汇总)
int i = 7;
int *ptr = nullptr; // 每次声明一个指针变量时,务必立即用适当的指针或 nullptr 进行初始化
ptr = new int;
int *ptr = new int; // 等价于上面两行代码
int* *handle = nullptr;
handle = new int*;
*handle = new int;
void leaky()
{
new int;
cout << "I just leaked an int!" << endl;
}
int *ptr = new int;
delete ptr;
ptr = nullptr; // 建议在释放指针的内存后,将指针重新设置为 mullptr。这样就不会在无意中使用一个指向已释放内存的指针
int *ptr = new(nothrow) int;
int myArray[5];
int *myArrayPtr = new int[5];
delete[] myArrayPtr; // 对 new[] 的每次调用都应与 delete[] 调用配对,以清理内存
myArrayPtr = nullptr;
不要把动态分配的数组和动态数组混为一谈。数组本身不是动态的,因为一旦被分配,数组的大小就不会改变。动态内存允许在运行时指定分配的内存块的大小,但它不会自动调整其大小以容纳数据
class Simple
{
public:
Simple() {}
~Simple() {}
};
// 如果要分配包含 4 个 Simple 对象的数组,那么 Simple 构造函数会被调用 4 次
Simple *mySimpleArray = new Simple[4];
// 编译器并不自动分配子数组的内存。可像分配一维堆数组那样分配第一个维度的数组
// 但是必须显式地分配每一个子数组。下面的函数正确分配了二维数组的内存
char** allocateCharacterBoard(size_t xDimension, size_t yDimension) {
char** myArray = new char*[xDimension]; // Allocate first dimension
for (size_t i = 0; i < xDimension; i++) {
myArray[i] = new char[yDimension]; // Allocate ith subarray
}
return myArray;
}
// 要释放多维堆数组的内存,数组版本的 delete[] 语法也不能自动清理子数组
// 释放数组的代码应该类似于分配数组的代码,如以下函数所示
void releaseCharacterBoard(char** myArray, size_t xDimension) {
for (size_t i = 0; i < xDimension; i++) {
delete[] myArray[i]; // Delete ith subarray
}
delete[] myArray; // Delete first dimension
}
建议尽可能不要使用旧式的 C 风格数组,因为这种数组没有提供任何内存安全性
- 这里解释它们,是因为可能在旧代码中遇到。在新代码中,应改用 C++ 标准库容器std::array 和 std::vector
- 例如,用 vector 表示一维动态数组,用 vector
> 表示二维动态数组等 - 如果应用程序中需要 N 维动态数组,建议编写帮助类,以方便使用接口。例如,要使用行长相等的二维数据,应当考虑编写 (也可以重用) Matrix
或 Table 类模板,该模板在内部使用 vector >数据结构
思考指针的方式有两种
指针的类型转换
Document *documentPtr = getDocument();
char *myCharPtr = (char*)documentPtr; // 正确
// 编译器将拒绝执行不同数据类型的指针的静态类型转换
char *myCharPtr = static_cast<char*>(documentPtr); // 错误,无法编译
前面提到,指针和数组之间有一些重叠
- 在堆上分配的数组通过指向该数组中第一个元素的指针来引用
- 基于堆栈的数组通过数组语法 ([]) 和普通的变量声明来引用
int myIntArray[10] = {};
int *myIntPtr = myIntArray;
myIntPtr[4] = 5;
void doubleInts(int *theArray, size_t size) {
for (size_t i = 0; i < size; ++i) {
theArray[i] *= 2;
}
}
// 传入基于堆的数组
size_t arrSize = 4;
int *heapArray = new int[arrSize]{1, 5, 3, 4};
doubleInts(heapArray, arrSize);
delete[] heapArray;
heapArray = nullptr;
// 传入基于堆栈的数组
int stackArray[] = {5, 7, 9, 11};
arrSize = std::size(stackArray); // 从 C++17 开始
// arrSize = sizeof(stackArray); // C++17 之前的写法
doubleInts(stackArray, arrSize); // 把数组变量当作指向数组的指针处理
doubleInts(&stackArray[0], arrSize); // 显式地传入第一个元素的地址
void doubleInts(int theArray[], size_t size) {
for (size_t i = 0; i < size; ++i) {
theArray[i] *= 2;
}
}
void doubleInts(int *theArray, size_t size);
void doubleInts(int theArray[], size_t size);
void doubleInts(int theArray[2], size_t size);
通过数组语法声明的数组可通过指针访问,当把数组传递给函数时,这个数组总是作为指针传递
int *ptr = new int;
通过指针可自动引用数组,但并非所有指针都是数组
int *myArray = new int[8];
myArray[2] = 33;
*(myArray + 2) = 33; // 等价于上一行代码
内存管理是 C++ 中常见的错误和 bug 来源,许多这类 bug 都来自动态内存分配和指针的使用
- 在程序中广泛使用动态内存分配,在对象间传递多个指针时,很容易忘记每个指针只能在正确时间执行一次 delete 操作
- 出错的后果很严重:当多次释放动态分配的内存时,可能会导致内存损坏或致命的运行时错误,当忘记释放动态分配的内存时,会导致内存泄漏
// 故意不释放对象,产生内存泄漏
void leaky() {
Simple *mySimplePtr = new Simple();
mySimplePtr->go();
}
// 如果 go() 方法抛出一个异常,将永远不会调用 delete,也会导致内存泄漏
void couldBeLeaky() {
Simple *mySimplePtr = new Simple();
mySimplePtr->go();
delete mySimplePtr;
}
// 这段代码使用 C++14 中的 make_unique() 和 auto 关键字
// 所以只需要指定指针的类型,本例中是 Simple
// 如果 Simple 构造函数需要参数,就把它们放在 make_unique() 调用的圆括号中
void notLeaky() {
auto mySimpleSmartPtr = make_unique<Simple>();
mySimpleSmartPtr->go();
}
foo(make_unique<Simple>(), make_unique<Bar>(data()))
始终使用 make_unique() 来创建 unique_ptr
像标准指针一样,仍可以使用 * 或 -> 对智能指针进行解引用
// 以下两种方式等价
mySimpleSmartPtr->go();
(*mySimpleSmartPtr).go();
get() 方法可用于直接访问底层指针,这可将指针传递给需要普通指针的函数
void processData(Simple *simple) { /* 使用普通指针 */ }
auto mySimpleSmartPtr = make_unique<Simple>();
processData(mySimpleSmartPtr.get()); // 调用
可释放 unique_ptr 的底层指针,并使用 reset() 根据需要将其改成另一个指针
mySimpleSmartPtr.reset(); // 释放底层指针并设置为 nullptr
mySimpleSmartPtr.reset(new Simple()); // 释放底层指针并设置为一个新的指针
可使用 release() 断开 unique_ptr 与底层指针的连接。release() 方法返回资源的底层指针,然后将智能指针设置为 nullptr。实际上,智能指针失去对资源的所有权,负责在你用完资源时释放资源
Simple *simple = mySimpleSmartPtr.release();
delete simple;
simple = nullptr;
由于 unique_ptr 代表唯一拥有权,因此无法复制它。使用 std::move() 实用工具,可使用移动语义将一个 unique_ptr 移到另一个。这用于显式移动所有权,如下所示
class Foo {
public:
Foo(unique_ptr<int> data) : mData(move(data)) {}
private:
unique_ptr<int> mData;
};
auto myIntSmartPtr = make_unique<int> (42);
Foo f(move(myIntSmartPtr));
总是使用 make_shared() 创建 shared_ptr
auto mySimpleSmartPtr = make_shared<Simple>();
与 unique_ptr 一样,shared_ptr 也支持 get() 和 reset() 方法。唯一的区别在于,当调用 reset() 时,由于引用计数,仅在最后的 shared_ptr 销毁或重置时,才释放底层资源
注意:shared_ptr 不支持 release()。可使用 use_count() 来检索共享同一资源的 shared_ptr 实例数量
与 unique_ptr 类似,shared_ptr 默认情况下使用标准的 new 和 delete 运算来分配和释放内存
下面的示例使用 shared_ptr 存储文件指针。当 shared_ptr 离开作用域时 (此处为脱离作用域时),会调用 CloseFile() 函数来自动关闭文件指针。这个例子使用了旧式 C 语言的 fopen() 和 fclose() 函数,只是为了演示 shared_ptr 除了管理纯粹的内存之外还可以用于其他目的
void CloseFile(FILE *filePtr) {
if (fillPtr == nullptr) {
return;
}
fclose(filePtr);
cout << "File closed." << endl;0
}
int main() {
FILE *f = fopen("data.txt", "w");
shared_ptr<FILE> filePtr(f, CloseFile);
if (filePtr == nullptr) {
cerr << "Error opening file." << endl;
} else {
cout << "File opened." << endl;
}
return 0;
}
// 应该避免使用这种方式,改用下面的复制构造函数
void doubleDelete() {
Simple *mySimple = new Simple();
shared_ptr<Simple> smartPtr1(mySimple);
shared_ptr<Simple> smartPtr2(mySimple);
}
// 输出:代码崩溃
Simple constructor called!
Simple destructor called!
Simple destructor called!
void noDoubleDelete() {
auto smartPtr1 = make_shared<Simple>();
shared_ptr<Simple> smartPtr2(smartPtr1); // 建立副本
}
// 输出
Simple constructor called!
Simple destructor called!
即使有两个指向同一个 Simple 对象的 shared_ptr,Simple 对象也只销毁一次。回顾一下,unique_ptr 不是引用计数的。事实上,unique_ptr 不允许像 noDoubleDelete() 函数中那样使用复制构造函数
class Foo {
public:
Foo(int value) : mData(value) {}
int mData;
};
// 仅当两个 shared_ptrs (foo 和 aliasing) 都销毁时,才销毁 Foo 对象
/*
创建一个名为 foo 的智能指针对象,它使用 make_shared 模板函数来动态分配一个名为
Foo 的类的实例,并将值 42 传递给该实例的构造函数
这个智能指针对象可以自动管理这个实例的内存,确保在不再需要时自动释放它
*/
auto foo = make_shared<Foo>(42);
/*
这种构造方式称为 "别名构造函数",它允许多个智能指针共享同一个对象,同时避免智能指针在释放对象时出现问题
创建了一个名为 aliasing 的智能指针对象,使用 shared_ptr 模板函数并传递两个参数
第一个参数是上面创建的智能指针对象指向动态分配的 Foo 实例
第二个参数是 Foo 类中名为 mData 的成员的地址
*/
auto aliasing = shared_ptr<int>(foo, &foo->mData);
make_shared 和 shared_ptr 的区别
#include
#include
using namespace std;
class Simple {
public:
Simple() { cout << "Simple constructor called!" << endl; }
~Simple() { cout << "Simple destructor called!" << endl; }
};
void useResource(weak_ptr<Simple> &weakSimple) {
auto resource = weakSimple.lock();
if (resource) {
cout << "Resource still alive." << endl;
} else {
cout << "Resource has been freed!" << endl;
}
}
int main() {
auto sharedSimple = make_shared<Simple>();
weak_ptr<Simple> weakSimple(sharedSimple);
// Try to use the weak_ptr.
useResource(weakSimple);
// Reset the shared_ptr.
// Since there is only 1 shared_ptr to the Simple resource, this will
// free the resource, even though there is still a weak_ptr alive.
sharedSimple.reset();
// Try to use the weak_ptr a second time.
useResource(weakSimple);
return 0;
}
char buffer[1024] = {0};
while (true) {
// getMoreData() 函数返回一个指向动态分配内存的指针
char *nextChunk = getMoreData();
if (nextChunk == nullptr) {
break;
} else {
// 把第二个参数的 C 风格字符串连接到第一个参数的 C 风格字符串的尾部
strcat(buffer, nextChunk);
delete[] nextChunk;
}
}
void fillWithM(char *inStr) {
int i = 0;
while (inStr[i] != '\0') {
inStr[i] = 'm';
i++;
}
}
避免使用旧的 C 风格字符串和数组,它们没有提供任何保护,而要改用像 C++ string 和 vector 这样安全的现代结构,它们能够自动管理内存
class Simple {
public:
Simple() {
mIntPtr = new int();
}
~Simple() {
delete mIntPtr;
}
void setValue(int value) {
*mIntPtr = value;
}
private:
int *mIntPtr;
};
void doSomething(Simple *&outSimplePtr) {
outSimplePtr = new Simple();
}
int main() {
Simple *simplePtr = new Simple();
doSomething(simplePtr);
// 只删除第二个对象,没有删除旧的对象
delete simplePtr;
return 0;
}
以上只是演示内存泄漏,实际应使 mIntPtr 和 simplePtr 成为 unique_ptr,使 outSimplePtr 成为 unique_ptr 的引用