C++基石:掌握高效编程的艺术

C++ 关于命名空间:namespace

在这里插入图片描述

上述文档详细介绍了C++标准库(Standard C++ Library)的一些关键约定,这些约定不仅帮助开发者理解如何正确使用库中的功能,也明确了实现者在设计库时的灵活性和限制。下面是对文档中提到的几个要点的详细解释:

1. 名称空间和宏

  • 名称空间:C++标准库中的所有名称(除了宏)都被声明在std名称空间内。这意味着当你包含了一个标准库头文件,如<iostream>,你必须通过std::cin这样的完全限定名来访问cin。要避免每次使用std下的名称都加上std::前缀,可以在所有包含标准库头文件的#include指令后立即写上using namespace std;

  • :宏不受任何命名空间的约束,因此它们的行为独立于std名称空间之外。

2. C库与C++库的区别

  • 当你包含C库头文件,如<cstdlib>,你调用的std::abort()拥有C++风格的名称空间限定;然而,如果你包含的是<stdlib.h>(C风格的头文件),你则直接调用abort()

3. 实现细节

  • 函数链接属性:C库中的函数可以具有C++或C链接属性。为了保证正确性,应当通过包含相应的C库头文件来声明这些函数,而不是内联声明。

  • 成员函数签名:库类的成员函数可能有额外的未列出的函数签名。你可以确信按文档描述调用这些函数会得到预期的行为,但是获取成员函数的地址可能不会得到期望的类型。

  • 基类和派生关系:库中的类可能有未文档化的(非虚)基类。这意味着一个类实际上可能通过其他未记录的类从另一个类派生。

  • 类型同义词:定义为某种整数类型同义词的类型,可能与几种不同的整数类型相同。

  • 异常抛出:没有异常规格说明的库函数可能抛出任意异常,除非其定义明确限制了这种可能性。

4. 可靠的约定

  • 无掩蔽宏:标准C库不使用掩蔽宏。只有具体的函数签名被保留,而非函数名称本身。

  • 外部函数签名:类外的库函数不会有额外的未文档化函数签名,你可以可靠地获取其地址。

  • 虚函数和基类:被描述为虚的基类和成员函数确实为虚,而描述为非虚的确实为非虚。

  • 类型差异性:由C++标准库定义的两种类型,除非文档明确指出它们相同,否则总是不同的。

  • 异常规格说明:库提供的函数(包括可替换函数的默认版本)最多只能抛出异常规格说明中列出的异常。

通过理解这些约定,开发者能够更加有效地利用C++标准库,同时也为库的实现者提供了设计上的指导和自由度。

在C++编程中,关于是否使用using namespace std;一直存在争议,主要原因是它涉及到命名空间的使用以及代码的可读性和安全性。

不使用using namespace std;的优点:

  1. 避免命名冲突std命名空间包含了C++标准库的所有元素。不使用using namespace std;可以防止不经意间覆盖标准库中的名称,尤其是当你的代码中也使用了类似的名字时。例如,如果你有一个名为string的局部变量,它可能会与std::string冲突,导致编译错误或运行时错误。

  2. 增强代码可读性:通过在每个使用标准库元素的地方显式添加std::前缀,代码的意图变得更加清晰。读者可以很容易地分辨哪些是标准库的元素,哪些是你自己定义的元素。

  3. 鼓励良好的编程习惯:显式使用std::前缀可以促使程序员更仔细地思考他们正在使用的库元素,从而促进对标准库的深入理解和正确使用。

  4. 便于调试和维护:当代码中出现错误时,显式的命名空间使用可以帮助更快地定位问题源,特别是当涉及到标准库的使用时。

使用using namespace std;的优点:

  1. 代码简洁:不需要在每个标准库元素前加上std::前缀,可以使代码看起来更简洁,减少敲击键盘的次数。

  2. 快速原型开发:在编写快速原型或小型项目时,使用using namespace std;可以节省时间,使代码编写过程更加流畅。

结论:

在专业和大型项目中,通常推荐不使用using namespace std;,以避免潜在的命名冲突,增强代码的可读性和维护性。而在编写教学代码、快速原型或个人小项目时,使用using namespace std;可以接受,因为它使得代码更为简洁。

无论选择哪种方式,重要的是保持代码的一致性和清晰性,确保团队中的每个成员都能容易理解和维护代码。在团队项目中,最好能达成一致的编码规范,以避免混淆和潜在的错误。

因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。

auto

在C++中,auto关键字的引入极大地简化了类型声明,尤其是在类型复杂或者冗长的情况下。auto关键字最早是在C++11中正式加入的,随后在C++14和后续版本中得到了进一步的增强。以下是auto在C++中的几种常见用法:

1. 变量声明

auto可以用于变量声明,此时编译器会根据初始化表达式的类型自动推断变量的类型:

Cpp深色版本

1auto myVariable = 42;  // myVariable的类型为int
2auto myString = "Hello"; // myString的类型为const char*
3auto myVector = std::vector<int>{1, 2, 3}; // 类型为std::vector<int>

2. 函数返回类型推导

在C++14及以后的版本中,auto可以用于函数的返回类型声明,只要函数体中有且只有一个返回语句,编译器就能推断出返回类型:

Cpp深色版本

1auto add(int a, int b) -> int { return a + b; } // C++11
2auto add(int a, int b) { return a + b; }        // C++14

3. 增强的for循环

在for循环中使用auto可以避免每次循环都需要写出容器元素的类型:

Cpp深色版本

1std::vector<int> numbers = {1, 2, 3, 4};
2for (auto &num : numbers) {
3    num *= 2; // 自动推断num为int&
4}

4. 指针和引用

auto可以与指针和引用一起使用,类型推导会考虑到这些修饰符:

Cpp深色版本

1int data[] = {1, 2, 3};
2auto *ptr = data; // ptr的类型为int*
3
4int &ref = data[0]; // ref的类型为int&

5. 与const结合

当与const一起使用时,auto可以正确推断出变量的类型包括const修饰符:

Cpp深色版本

1const int val = 10;
2auto constVal = val; // constVal的类型为const int

6. lambda表达式

在lambda表达式中,auto用于捕获列表可以自动推导捕获变量的类型:

Cpp深色版本

1int x = 42;
2auto lambda = [x]() { return x; }; // x的类型被正确推导

注意事项

  • auto必须在声明时初始化,以便编译器能够推断其类型。
  • 在模板和某些上下文中,auto的使用可能受到限制,因为编译器可能无法在编译时推断类型。
  • 尽管auto使得代码更简洁,但在某些情况下,显式指定类型可以提高代码的可读性和易于理解。

使用auto时,要权衡代码的简洁性和可读性,确保在适当的情况下使用它,以提高代码的质量和维护性。

auto初始化

在C++中,auto关键字用于声明变量时,必须伴随初始化表达式,这样编译器才能根据初始化表达式推断出变量的实际类型。初始化可以是多种方式,包括赋值、构造函数调用、复合赋值运算符等。

以下是使用auto初始化变量的几种常见方式:

  1. 简单赋值初始化

    auto x = 10; // x 的类型为 int
    
  2. 使用花括号初始化

    auto y = {1, 2, 3}; // y 的类型为 std::initializer_list<int>
    

    如果初始化列表被用于一个支持列表初始化的容器,比如std::vectorstd::array,那么auto将推断出正确的容器类型:

    auto vec = {1, 2, 3}; // vec 的类型为 std::vector<int>
    
  3. 复合赋值初始化

    auto z = 5 * 5; // z 的类型为 int
    
  4. 使用构造函数初始化
    对于类类型,auto可以基于构造函数参数推断类型:

    struct Point {int x, y;Point(int _x, int _y) : x(_x), y(_y) {}
    };auto p = Point(10, 20); // p 的类型为 Point
    
  5. 使用C++17的iffor语句中的声明
    在C++17中,可以在iffor语句中声明auto变量:

    auto findValue = 42;
    for (auto i = 0; i < 10 && i != findValue; ++i) {// ...
    }
    

    或者在if语句中:

    auto result = someFunction();
    if (auto value = result; value > 0) {// ...
    }
    
  6. 与引用和指针结合使用
    当与引用或指针一起使用时,auto可以正确推断出基础类型:

    int data[] = {1, 2, 3};
    auto *ptr = data; // ptr 的类型为 int*
    auto &ref = data[0]; // ref 的类型为 int&
    
  7. 在模板中使用
    在模板中使用auto可以推断出模板参数的类型:

    template<typename T>
    auto sum(T a, T b) {return a + b;
    }
    

需要注意的是,auto关键字不能用于声明未初始化的变量,这是因为编译器需要初始化表达式来推断类型。此外,尽管auto可以带来代码的简洁性和可读性,过度使用它可能会降低代码的可读性,特别是当变量的类型对于理解代码逻辑很重要时。因此,在使用auto时应保持适度,特别是在大型项目中,确保代码的清晰和易维护性。

C++内联函数

内联函数(Inline Function)是C++中一种特殊类型的函数,它的主要目的是提高程序的执行效率,减少函数调用的开销。内联函数在编译时会被编译器展开成一系列的机器码,就像宏定义一样被替换,但与宏定义不同的是,内联函数提供了类型安全和编译时检查。

内联函数的声明

内联函数通过在函数声明前添加inline关键字来定义。例如:

inline int square(int x) {return x * x;
}

内联函数的工作原理

当编译器遇到内联函数调用时,它会尝试将函数体的代码直接插入到调用点,从而避免了普通函数调用所带来的栈帧操作、参数传递和返回地址保存等开销。这种替换称为内联(Inlining)。

详细的工作原理:

内联函数(Inlining)的工作原理涉及到编译器如何优化函数调用以减少运行时的开销。下面我们将深入探讨这一过程的细节:

1. 函数调用的常规流程

在没有内联的情况下,函数调用通常涉及以下步骤:

  • 保存当前状态:包括保存CPU寄存器的值和当前指令指针(返回地址),以便在函数调用完成后能恢复执行。
  • 参数传递:将参数压入栈中或使用寄存器传递给函数。
  • 跳转到函数入口:通过改变指令指针指向函数的第一条指令。
  • 执行函数体:在函数内部执行指令。
  • 清理栈帧:函数结束时,可能需要清除之前压入栈中的参数。
  • 恢复状态:从栈中恢复寄存器和返回地址。
  • 返回调用者:跳转回调用函数的下一条指令继续执行。

这些步骤带来了额外的开销,尤其是对于频繁调用的小函数。

2. 内联函数的实现

内联函数通过在编译阶段直接将函数体代码复制到调用点,消除了上述大部分开销:

  • 源代码分析:编译器在编译过程中会分析源代码,识别出标记为内联的函数调用。
  • 代码替换:编译器将内联函数的代码直接插入到调用该函数的位置,这个过程称为内联。
  • 局部变量处理:内联函数中使用的局部变量会变成在调用点处的临时变量。
  • 代码优化:编译器可能还会对内联后的代码进行进一步优化,比如常量折叠、死代码消除等,以提高效率。

3. 编译器优化策略

尽管程序员可以通过inline关键字建议编译器进行内联,但最终是否内联由编译器决定。编译器会考虑以下因素:

  • 函数大小:太大的函数内联可能导致代码膨胀。
  • 调用频率:频繁调用的小函数更适合内联。
  • 代码复杂度:复杂的函数内联可能不会带来明显的性能提升。
  • 性能与代码大小的权衡:编译器会评估内联带来的性能提升是否值得增加的代码大小。

4. 内联函数的限制

内联函数并非总是有利无弊。其主要限制包括:

  • 代码膨胀:内联函数会导致目标代码变大,可能影响缓存命中率和加载时间。
  • 编译时间增加:内联增加了编译器的工作量,可能导致编译时间变长。
  • 调试困难:内联后的代码难以跟踪和调试,因为源代码中的函数调用位置不再对应于机器码中的跳转指令。

结论

内联函数是编译器优化技术的一种,用于减少小而频繁调用的函数的运行时开销。它通过在编译时将函数体直接插入到调用点,避免了函数调用的开销,但可能增加代码大小和编译时间。

内联函数的优势

  1. 减少函数调用开销:通过避免函数调用的额外开销,内联函数可以提高程序的执行速度。
  2. 提高代码效率:内联函数在调用频繁且函数体较短的情况下特别有效,可以显著提升性能。

内联函数的局限性

  1. 代码膨胀:由于内联函数的代码会在每次调用处复制,这可能导致生成的二进制代码体积增大。
  2. 编译时间增加:内联函数增加了编译器的工作量,可能导致编译时间变长。
  3. 内存占用增加:更多的代码意味着可能需要更多的内存来加载和执行程序。

编译器优化

虽然可以通过inline关键字提示编译器进行内联,但最终是否内联取决于编译器的优化策略。编译器可能会基于函数大小、调用频率、代码复杂度等因素决定是否内联函数。有时,即使没有inline关键字,编译器也会选择内联函数以优化性能。

使用场景

内联函数最适合那些函数体较小、调用频繁的函数。对于复杂、计算密集型的函数,使用内联可能并不合适,因为带来的代码膨胀和编译时间增加可能超过性能提升的好处。

示例

#include <iostream>inline int add(int a, int b) {return a + b;
}int main() {int result = add(10, 20);std::cout << "Result: " << result << std::endl;return 0;
}

在这个例子中,add函数被声明为内联函数,如果它在程序中被频繁调用,使用内联可以提高程序的执行效率。然而,如果add函数包含复杂的逻辑或大量的代码,内联可能不是最佳选择。

面向对象

在C++中,this指针是一个非常重要且独特的概念,它在成员函数中扮演着核心角色。this指针是一个隐含的指针,指向调用成员函数的对象实例。理解this指针对于掌握面向对象编程至关重要。下面我们将深入探讨this指针的各个方面。

1. this指针的定义和作用

this指针是一个指向当前对象的指针,其类型为const限定的类类型指针。例如,如果类名为MyClass,则this指针的类型为MyClass* const this。这意味着this指针可以被用来访问对象的成员变量和成员函数,但它自身不能被修改。

2. this指针的使用场景

  • 访问成员变量this指针可以用于在成员函数内部访问对象的成员变量。
  • 区分局部变量和成员变量:当局部变量和成员变量同名时,this指针可以用于明确地区分两者。
  • 作为参数传递this指针可以作为参数传递给其他函数或成员函数,这在实现递归调用或链式调用等场景中尤为有用。
  • 返回当前对象:在某些情况下,成员函数可能需要返回调用它的对象,这时this指针可以派上用场。

3. 静态成员函数和this指针

静态成员函数没有this指针。这是因为在静态成员函数内部,没有特定的对象实例与之关联,所以this指针没有意义。静态成员函数只能访问静态成员变量和静态成员函数。

4. this指针的生命周期

this指针的生命周期与成员函数的调用周期相同。当成员函数开始执行时,this指针被创建并指向调用该函数的对象。当成员函数执行完毕,this指针也随之销毁。

5. this指针与const成员函数

在const成员函数中,this指针的类型变为const MyClass* const this。这意味着在const成员函数中不能修改对象的状态,即不能修改任何非static成员变量。

6. this指针与虚函数

当一个对象通过基类指针或引用来调用虚函数时,this指针仍然指向实际对象的地址,而不是基类对象的地址。这是多态性的基础,确保了调用正确版本的成员函数。

示例代码

class MyClass {
public:int data;void set(int value) {data = value;}void print() const {std::cout << "Data: " << this->data << std::endl;}
};int main() {MyClass obj;obj.set(10);obj.print(); // 输出: Data: 10return 0;
}

在这个例子中,setprint都是成员函数,它们都可以通过this指针访问对象的data成员变量。print函数是const成员函数,因此它的this指针是const MyClass* const this类型。

总之,this指针是C++中面向对象编程的一个关键概念,它使得成员函数能够访问和操作所属对象的数据成员。理解this指针的特性和使用场景对于编写高效、正确的C++代码至关重要。

operator运算符重载

运算符重载(Operator Overloading)是C++中的一项强大特性,它允许程序员为自定义数据类型重新定义已存在的运算符的行为。通过运算符重载,用户定义的类型可以像内置类型(如int, float等)那样使用运算符,从而使得代码更加直观和自然。

运算符重载的基础

在C++中,运算符本质上是特殊的函数,它们具有预定义的符号,如+, -, *, /, =等。当这些运算符应用于用户定义的类型时,默认行为通常是不适用的,因为这些运算符原本是为内置类型设计的。运算符重载使得用户可以定义这些运算符在自定义类型上的行为,从而扩展了C++的表达能力。

运算符重载的规则

  1. 语法:运算符重载通过使用operator关键字和对应的运算符来实现,如operator+表示加法运算符的重载。

  2. 重载函数的声明:重载的运算符可以作为类的成员函数或友元函数。成员函数中的this指针隐含为左操作数,而友元函数需要显式地接收所有操作数。

  3. 参数:重载函数的参数数量取决于运算符的特性。例如,二元运算符如+通常需要两个参数,而一元运算符如-(负号)只需一个参数。

  4. 返回类型:重载函数的返回类型应该与运算符的预期行为相匹配。例如,重载+运算符通常返回一个与操作数类型相同的结果。

  5. 限制:有些运算符不能被重载,如::, ? :, ., .*, sizeof, alignof, typeid

  6. 不能改变运算符的优先级和结合性:重载运算符不会改变其原有的优先级和结合性。

示例:重载加法运算符

假设我们有一个复数类Complex,我们想要重载加法运算符+,以便两个Complex对象可以相加。

class Complex {
public:double real, imag;Complex(double r, double i) : real(r), imag(i) {}// 成员函数重载+Complex operator+(const Complex& other) const {return Complex(this->real + other.real, this->imag + other.imag);}
};int main() {Complex c1(3.0, 2.0);Complex c2(1.0, 7.0);Complex c3 = c1 + c2; // 调用重载的+return 0;
}

在这个例子中,Complex类的成员函数operator+接收另一个Complex对象作为参数,并返回一个新的Complex对象,其实部和虚部分别为两个操作数的实部和虚部之和。

运算符重载的注意事项

  1. 效率:重载运算符时应考虑效率,避免不必要的复制或分配。例如,可以使用引用或const引用作为参数。

  2. 语义一致性:重载的运算符应尽可能保持与原运算符的语义一致,以避免混淆。

  3. 互操作性:当与内置类型混合使用时,应确保重载运算符的行为与内置类型的行为协调一致。

  4. 运算符函数的调用:重载运算符通常通过操作符语法调用,而不是像普通函数那样使用圆括号。

运算符重载是C++中一项强大的特性,它增强了语言的表达能力和代码的可读性,但也需要谨慎使用,以避免引入复杂性和潜在的错误。

为什么要用operator以及operator有什么好处

运算符重载在C++中是一项非常有用的特性,它允许用户自定义数据类型的行为,使之与内置类型一样可以使用标准的运算符。下面是使用运算符重载的几个主要原因及其带来的好处:

1. 提升代码的可读性和自然性

  • 自然语法:运算符重载使得用户自定义的类型可以用类似内置类型的方式来使用运算符,如+, -, *, /等,这使得代码更直观,更接近数学或日常语言的表达方式。

  • 增强表达力:通过重载运算符,你可以为自定义类型提供类似于内置类型那样的操作,这使得代码更容易理解,因为读者可以使用熟悉的符号来理解复杂的数据结构之间的交互。

2. 方便的编程接口

  • 减少代码量:运算符重载可以减少代码量,因为你不必为每个操作都定义一个单独的函数。例如,a + badd(a, b)更简洁。

  • 提高代码重用性:重载运算符可以重用现有的代码和逻辑,使得同一组操作可以应用于多种不同的数据类型。

3. 更好的控制和灵活性

  • 定制行为:你可以根据需要为特定的数据类型定制运算符的行为,这提供了极大的灵活性,可以精确控制数据类型在不同场景下的操作方式。

  • 控制数据访问:通过重载运算符,你可以控制数据的访问和修改方式,确保数据的一致性和完整性。

4. 效率和性能

  • 避免不必要的拷贝:通过重载如赋值运算符=, 可以更高效地管理资源,避免不必要的对象拷贝,提高程序的性能。

  • 更直接的优化:编译器可以对内联的运算符重载函数进行优化,因为它们在调用点处被展开,这可能比普通函数调用更高效。

5. 面向对象编程的增强

  • 支持多态性:运算符重载可以与多态性相结合,使得基类的运算符可以被派生类重写,从而在运行时表现出不同的行为。

  • 封装和继承:运算符重载可以更好地支持封装和继承原则,允许在继承层次结构中保持运算符行为的一致性。

示例

假设你有一个Vector类,你可以重载+运算符,使得两个Vector对象可以通过简单的a + b语法相加,而不需要调用额外的add函数。这不仅让代码更简洁,而且更符合直觉。

总的来说,运算符重载提供了更高级别的抽象,使得C++程序的编写更加高效、直观和优雅。然而,这也要求开发者在使用时要小心,确保重载的运算符行为合理且不会引起混淆。

C++ template模板

在这里插入图片描述

C++模板是语言中一个非常强大的特性,它允许程序员编写通用的代码,能够处理多种数据类型,而无需为每种类型重复编写相同的逻辑。模板分为函数模板和类模板两种,它们的核心思想是在编译时根据具体类型生成特定的代码实例。

1. 函数模板

函数模板允许你定义一个函数,它可以接受任意类型的参数,并返回相应类型的结果。模板函数的基本语法如下:

template<typename T>
T myFunction(T arg) {// 函数体
}

这里,typename关键字用于指定模板参数的类型,T是一个模板参数,代表任意类型。当你调用模板函数时,编译器会根据传入的参数类型生成相应的函数实例。

示例:函数模板
template<typename T>
T max(T a, T b) {return a > b ? a : b;
}int main() {int x = 10, y = 20;double a = 3.14, b = 2.71;std::cout << max(x, y) << std::endl;  // 输出: 20std::cout << max(a, b) << std::endl;  // 输出: 3.14return 0;
}

2. 类模板

类模板允许你定义一个类,其中某些成员类型可以是模板参数。这样,你可以基于不同的类型实例化不同的类。

示例:类模板
template<typename T>
class MyVector {
private:T* data;size_t capacity, size;
public:MyVector(size_t initialCapacity) : capacity(initialCapacity), size(0), data(new T[capacity]) {}~MyVector() {delete[] data;}void push_back(const T& value) {if (size == capacity) {capacity *= 2;T* newData = new T[capacity];for (size_t i = 0; i < size; ++i) {newData[i] = data[i];}delete[] data;data = newData;}data[size++] = value;}T& operator[](size_t index) {return data[index];}
};int main() {MyVector<int> intVec(10);MyVector<double> doubleVec(5);for (int i = 0; i < 10; ++i) {intVec.push_back(i);}for (double d = 0.0; d < 5.0; d += 1.0) {doubleVec.push_back(d);}std::cout << intVec[5] << std::endl;  // 输出: 5std::cout << doubleVec[3] << std::endl;  // 输出: 3.0return 0;
}

3. 模板参数

模板参数可以是类型参数(使用typenameclass关键字)、非类型参数(整型、枚举、指针等)和模板模板参数(其他模板)。例如:

template<typename T, typename U = int>
class MyClass {// ...
};

这里,U是一个有默认值的模板参数。

4. 模板特化

模板特化允许你为特定类型提供不同的实现。例如,你可以为你的模板类提供一个int类型的特化版本。

template<>
class MyVector<int> {// 专为int类型提供的实现
};

5. 模板元编程

模板元编程是一种在编译时执行计算的技术,利用模板和模板参数来生成代码。C++11及更高版本中引入的constexpr和可折叠模板参数使元编程更加灵活和强大。

总结

C++模板是实现代码重用和泛型编程的强大工具。通过模板,你可以编写一次代码,就能处理多种数据类型,这极大地提高了代码的可维护性和效率。然而,模板的使用也应当谨慎,过度使用或不当使用可能会导致代码难以理解和调试。

内存操作

在C++中,operator newoperator delete是一对特殊的运算符,它们分别用于内存的分配和释放。这两个运算符使得C++能够实现更高级别的内存管理功能,比如异常安全的动态内存分配和自定义的内存管理策略。

operator new

operator new用于从自由存储区分配未初始化的内存块。基本形式如下:

void* operator new(std::size_t size);

这里的size参数指定了需要分配的字节数。operator new返回一个指向分配的内存的指针,如果分配失败,则可能抛出std::bad_alloc异常。

此外,C++还提供了几个重载版本的operator new,包括带有对齐要求的版本和带有额外参数的版本,如placement new

void* operator new(std::size_t size, const std::nothrow_t&) noexcept;
void* operator new(std::size_t size, std::align_val_t alignment);
void* operator new(std::size_t size, void* ptr) noexcept;
  • nothrow版本尝试分配内存但不抛出异常,而是返回nullptr
  • alignment版本允许你指定内存对齐方式。
  • placement new不分配内存,而是直接在给定地址上构造对象。

operator delete

operator delete用于释放由operator new分配的内存。基本形式如下:

void operator delete(void* ptr) noexcept;

这里的ptr是指向要释放的内存的指针。operator delete不会抛出异常,但如果内存没有被正确地释放,程序的行为是未定义的。

同样,operator delete也有几个重载版本:

void operator delete(void* ptr, const std::nothrow_t&) noexcept;
void operator delete(void* ptr, std::size_t size) noexcept;
void operator delete(void* ptr, std::align_val_t alignment) noexcept;
  • nothrow版本与operator newnothrow版本对应,表明不会抛出异常。
  • sizealignment版本允许传递额外的信息,这在自定义的内存管理器中可能有用。

自定义newdelete

你可以在类中重载operator newoperator delete,从而实现特定于类的内存管理。这通常用于实现池分配器、缓存机制或确保资源的正确释放。例如:

class MyClass {
public:static void* operator new(std::size_t size) {// 自定义的内存分配逻辑return malloc(size);}static void operator delete(void* ptr) noexcept {// 自定义的内存释放逻辑free(ptr);}
};

异常安全的内存管理

使用newdelete时,应当注意异常安全。例如,在复杂的构造函数中,如果构造过程中发生异常,应该确保已经分配的内存被正确释放,这通常通过使用RAII(Resource Acquisition Is Initialization)原则来实现。

operator newoperator delete是C++中用于动态内存管理的关键部分。它们允许你在运行时分配和释放内存,同时也提供了自定义内存管理的机会。然而,错误的使用会导致内存泄漏或其他未定义行为,因此在使用时需要特别小心。正确使用这些运算符对于编写高效、健壮的C++程序至关重要。

/*

operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间

失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否

则抛异常。

*/

内存泄露的危害

什么是内存泄漏,内存泄漏的危害

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内

存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对

该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现

内存泄漏会导致响应越来越慢,最终卡死。

void MemoryLeaks()
{// 1.内存申请了忘记释放int* p1 = (int*)malloc(sizeof(int));int* p2 = new int;// 2.异常安全问题int* p3 = new int[10];Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.delete[] p3;
}

new delete 原理解析

在C++中,newdelete是用于动态内存管理的核心操作符,它们允许程序员在运行时分配和释放内存。下面将深入解析newdelete的核心原理,包括它们如何工作,以及它们与C语言中mallocfree函数的区别。

new操作符

new操作符在C++中有两种基本形式:

  1. 单个对象的分配

    T* ptr = new T;
    

    这里T是需要分配内存的类型,new操作符会分配足够的内存来存储一个T类型的对象,并返回一个指向该内存的指针。

  2. 数组的分配

    T* arr = new T[n];
    

    这里n是要分配的元素数量,new操作符会分配足够存储nT类型对象的内存,并返回指向第一个元素的指针。

new操作符的核心原理
  • 内存分配new操作符首先调用底层的内存分配函数(通常是malloc或其等效函数)来请求内存。
  • 构造函数调用:一旦内存分配成功,new操作符会调用适当的构造函数来初始化对象。如果是数组,每个元素都会被初始化。
  • 异常处理:如果内存分配失败,new操作符会抛出std::bad_alloc异常。

delete操作符

new相对应,delete操作符也有两种形式:

  1. 删除单个对象

    delete ptr;
    

    这会调用ptr指向的对象的析构函数,然后释放内存。

  2. 删除数组

    delete[] arr;
    

    这会依次调用数组中每个对象的析构函数,然后释放整个数组的内存。

delete操作符的核心原理
  • 析构函数调用delete操作符首先调用对象的析构函数。析构函数负责清理对象在构造时分配的任何资源。
  • 内存释放:析构函数调用完成后,delete操作符会调用底层的内存释放函数(通常是free或其等效函数)来释放内存。

newdeletemallocfree的区别

  1. 构造和析构newdelete会自动调用构造函数和析构函数,而mallocfree仅处理内存分配和释放,不会调用构造或析构函数。
  2. 异常处理new在内存分配失败时会抛出异常,而malloc在失败时返回nullptr
  3. 类型安全性newdelete是类型安全的,它们知道所分配和释放的类型;而mallocfree是类型不可知的,需要程序员手动转换和管理类型。

newdelete是C++中动态内存管理的关键,它们不仅提供了内存的分配和释放,还自动处理了构造和析构,使得资源管理更加安全和方便。然而,错误的使用newdelete(如忘记释放内存或错误地释放内存)会导致内存泄漏和程序崩溃。因此,正确和谨慎地使用newdelete是非常重要的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/41188.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

部署LVS+keepalived做网站的高可用,高负载。

LVSkeepalived keepalived背景 针对LVS的辅助工具&#xff0c;主要提供故障切换与健康检查。 工作场景&#xff1a;作为调度器的双机热备&#xff0c;以及节点服务器的健康检查以及故障切换&#xff08;删除条目&#xff09;。 借鉴了VRRP协议来实现高可用。 keepalived部署…

后端学习(一)

添加数据库包&#xff1a; 数据库连接时 发生错误&#xff1a; 解决方式&#xff1a; SqlConnection conn new SqlConnection("serverlocalhost;databaseMyBBSDb;uidsa;pwd123456;Encryptfalse;") ;conn.Open();SqlCommand cmd new SqlCommand("SELECT * FROM…

WAIC上官宣!大模型语料提取工具MinerU正式发布,开源免费“敲”好用

7月4日&#xff0c;2024 WAIC科学前沿全体会议在上海世博中心红厅隆重举行。上海人工智能实验室与商汤科技联合香港中文大学和复旦大学正式发布新一代大语言模型书⽣浦语2.5&#xff08;InternLM2.5&#xff09;&#xff0c;同时全链条工具体系迎来重磅升级&#xff0c;对于大模…

Android 四大组件

1. Activity 应用程序中&#xff0c;一个Activity通常是一个单独的屏幕&#xff0c;它上面可以显示一些控件&#xff0c;也可以监听并对用户的事件做出响应。 Activity之间通过Intent进行通信&#xff0c;在Intent 的描述结构中&#xff0c;有两个最重要的部分&#xff1a;动…

Java跳出循环的四种方式

1、continue,break,return continue&#xff1a;跳出当前层循环的当前语句&#xff0c;执行当前层循环的下一条语句。   continue标签 break&#xff1a;跳出当前层循环。 break标签&#xff1a;多层循环时&#xff0c;跳到具体某层循环。 return&#xff1a;结束所有循环…

基于python 的动态虚拟主机

将自己电脑上的Python脚本文件上传到虚拟机/var/www/cgi-bin/目录下 [rootlocalhost conf.d]# cd /var/www/cgi-bin/ [rootlocalhost cgi-bin]# rz -E rz waiting to receive.编辑vhost.conf配置文件 [rootlocalhost conf.d]# vim vhost.conf<virtualhost 192.168.209.140…

树形结构C语言的实现

一.什么是树&#xff1a; 树形结构是一层次的嵌套结构。一个树形结构的外层和内层有相似的结构&#xff0c;所以这种结构多可以递归的表示。经典数据结构中的各种树状图是一种典型的树形结构&#xff1a;一棵树可以简单的表示为根&#xff0c;左子树&#xff0c;右子树。左子树…

平替向日葵:Windows远程桌面+动态IP获取器

对于需要远程办公的人员来说&#xff0c;向日葵是一个操作简便的选择&#xff0c;但其免费版功能受限&#xff0c;且由于数据需经过向日葵服务器转发&#xff0c;安全性也无法得到完全保障。为此&#xff0c;以下提供一个更为简单且免费的替代方案&#xff1a; 使用Windows自带…

threejs 微信小程序原生版本的使用 obj模型的加载

直接上代码&#xff0c; <canvas class"webgl" type"webgl" id"gl" bindtouchstart"onTX" bindtouchend"onTX" bindtouchmove"onTX" style"width:100vw;height:90vh"></canvas> const co…

从0到1构建渠道运营体系:实战案例与策略指南

引言 在当今竞争激烈的市场环境中&#xff0c;有效的渠道运营是企业实现产品或服务快速触达目标用户、提升市场份额的关键。从零开始构建一个高效的渠道运营体系&#xff0c;不仅需要深思熟虑的策略规划&#xff0c;还需要灵活应变的实战操作。本文将结合实战案例&#xff0c;…

react native中使用@react-navigation/native进行自定义头部

react native中使用react-navigation/native进行自定义头部 效果示例图实例代码 效果示例图 实例代码 /* eslint-disable react-native/no-inline-styles */ /* eslint-disable react/no-unstable-nested-components */ import React, { useLayoutEffect } from react; import…

七、Linux二进制安装Redis集群

目录 七、Linux二进制安装Redis集群1 安装Redis所需依赖2 单机安装Redis&#xff08;7.2.4&#xff09;2.1 下载Redis2.2 安装Redis 3 分布式部署模式&#xff08;Redis Cluster&#xff09;3.1 分布式部署模式的配置文件3.2创建集群 4 主从复制模式&#xff08;Redis Sentinel…

鼠标自动点击器怎么用?鼠标连点器入门教程!

鼠标自动点击器是适用于Windows电脑的自动执行鼠标点击操作的工具&#xff0c;主要用于模拟鼠标点击操作&#xff0c;实现鼠标高速点击的操作。通过模拟鼠标点击&#xff0c;可以在用户设定的位置、频率和次数下自动执行点击动作。 鼠标自动点击器主要的应用场景&#xff1a; …

Wing FTP Server

文章目录 1.Wing FTP Server简介1.1主要特点1.2使用教程 2.高级用法2.1Lua脚本,案例1 1.Wing FTP Server简介 Wing FTP Server&#xff0c;是一个专业的跨平台FTP服务器端&#xff0c;它拥有不错的速度、可靠性和一个友好的配置界面。它除了能提供FTP的基本服务功能以外&#…

当心!不要在SpringBoot中再犯这样严重的错误

1. 简介 在Spring Boot中&#xff0c;Configuration注解用于声明配置类&#xff0c;以定义和注册Bean对象。这些Bean对象可以是普通的业务组件&#xff0c;也可以是特殊的处理器&#xff0c;如BeanPostProcessor或BeanFactoryPostProcessor&#xff0c;用于在Spring容器中对其…

比 PIP 快 100 倍的安装工具

uv 是一个由 Rust 开发的 pip 工具&#xff0c;比 pip 快 100 倍&#xff0c;难以置信&#xff0c;不过真的是快太多了。 安装 在 Mac 上直接通过 brew install uv 安装即可。 venv 创建运行环境&#xff0c;默认当前目录下 .venv uv venv 依赖安装 uv pip install -r re…

AGI|Transformer自注意力机制超全扫盲攻略,建议收藏!

一、前言 2017年&#xff0c;谷歌团队推出一篇神经网络的论文&#xff0c;首次提出将“自注意力”机制引入深度学习中&#xff0c;这一机制可以根据输入数据各部分重要性的不同而分配不同的权重。当ChatGPT震惊世人时&#xff0c;Transformer也随之进入大众视野。一夜之间&…

第三届图像处理、目标检测与跟踪国际学术会议(IPODT 2024,8月9-11)

第三届图像处理、目标检测与跟踪国际学术会议&#xff08;IPODT 2024&#xff09;将于2024年8月9-11日在中国南京召开。 本次会议旨在为全球的研究人员、工程师、学者和业界专家提供一个展示和讨论图像处理、目标检测与跟踪最新进展的平台&#xff0c;促进这些领域的科研与技术…

VPSA制氧设备在不同行业的应用解析

VPSA制氧设备以其独特的吸附原理&#xff0c;能够在穿透大气压的条件下&#xff0c;通过专用的分子筛选择性吸附空气中的氮气、二氧化碳和水等杂质&#xff0c;从而制得纯度较高的氧气。本文将探讨VPSA制氧设备在不同行业中的应用及其重要性。 一、钢铁行业 在钢铁行业中&#…

【自组织竞争网络】自组织竞争网络进行分类的一个小案例

【自组织竞争网络】自组织竞争网络进行分类的一个小案例 注&#xff1a;本文仅作为自己的学习记录以备以后复习查阅 一 概述 自组织神经网络可以通过对客观事件的重复分析学习该事件的内在规律&#xff0c;并对具有类似/共同特征的新事物进行分类&#xff0c;它的基本思想类似…