培训机构活动策划网站分众传媒电梯广告价格表
- 作者: 五速梦信息网
- 时间: 2026年03月21日 10:14
当前位置: 首页 > news >正文
培训机构活动策划网站,分众传媒电梯广告价格表,上海网站建设高端,微网站如何做横幅链接智能指针
- 垃圾回收 垃圾回收机制已经大行其道#xff0c;得到了诸多编程语言的支持#xff0c;例如Java、Python、 C#、PHP等。而C虽然从来没有公开得支持过垃圾回收机制#xff0c;但C98/03标准中#xff0c;支持使用auto_ptr智能指针来实现堆内存的自动回收; C11新标…智能指针
- 垃圾回收 垃圾回收机制已经大行其道得到了诸多编程语言的支持例如Java、Python、 C#、PHP等。而C虽然从来没有公开得支持过垃圾回收机制但C98/03标准中支持使用auto_ptr智能指针来实现堆内存的自动回收; C11新标准在废弃auto_pt的同时增添了unique_ptr、shared_ptr以及weak_ptr这3个智能指针来实现堆内存的自动回收。 所谓智能指针可以从字面上理解为“智能”的指针。具体来讲智能指针和普通指针的用法是相似的不同之处在于智能指针可以在适当时机自动释放分配的内存。 也就是说使用智能指针可以很好地避免“忘记释放内存而导致内存泄漏”问题出现。由此可见C也逐渐开始支持垃圾回收机制了。
- 智能指针
C中的智能指针是一种用于管理动态分配内存的资源对象。它们能够自动地跟踪资源的所有权当资源不再需要时会自动释放它们从而避免内存泄漏和悬挂指针等问题。C标准库提供了三种主要类型的智能指针std::shared_ptr std::unique_ptr和std::weak_ptr智能指针都是通过类模板的方式实现的。
std::shared_ptr 和 std::unique_ptr还有 std::weak_ptr。这三种智能指针在不同的情况下具有不同的用途和语义。
std::shared_ptr多个 std::shared_ptr 对象可以共享同一个资源的所有权。资源只有在最后一个引用计数归零时才会被释放。std::unique_ptrstd::unique_ptr 表示独占所有权一个资源只能由一个 std::unique_ptr 拥有。这种指针在需要确保只有一个拥有者时非常有用。std::weak_ptrstd::weak_ptr 是一种弱引用指针用于解决 std::shared_ptr 循环引用问题。std::weak_ptr 可以与 std::shared_ptr 共同使用但不会增加资源的引用计数从而避免循环引用导致的内存泄漏。
这三种智能指针类型提供了灵活的内存管理选项可以根据具体的需求选择适当的类型来管理资源。
2.1 shared_ptr
shared_ptrT表示指针指向的数据类型的定义位于头文件中并位于std命名空间中所以使用智能指针时程序都需要包含下面两行代码
#includememory
using namespace std;和unique_ptrweak_ptr不同之处在于多个shared_ptr智能指针可以共用一块内存空间。并且即使多个共用一块空间的shared_ptr指针中有一个指针放弃了该空间的“使用权”也不会影响到其他指针。只有到该空间的引用次数为0时该空间才会被释放。
因为智能指针的底层是用类模板实现的所以智能指针的声明和初始化跟C中的容器基本相同下面我们来看个例子
#includeiostream
#includememory
using namespace std;int main()
{//默认初始化shared_ptrint p;//传参初始化//声明一个指向一块有5个int空间大小的share_ptrshared_ptrintp1(new int[5]);//传入空指针//空指针的引用次数为0shared_ptrintp2(nullptr);//使用C11标准提供的std:make_sharedT模板函数shared_ptrintp3 make_sharedint(5);//这段代码和第二段代码的效果相同return 0;
}在初始化shared_ptr指针时还可以自定义所指内存的释放规则这样当内存的引用次数为0时会优先调用自定义的释放规则。
在某些场景中自定义释放规则是很用必要的。比如对于申请的动态数组来说shared_ptr指针默认的释放规则是不支持释放数组的只能自定义对应的释放规则才能正确释放动态开辟的内存。
当然对于申请的动态数组来说释放规则可以使用C标准中提供的default_delete模板类不过我们也可以自定义释放规则。
废话不多说我们直接上代码
#includeiostream
#includememory
using namespace std;void DeleteInt(int* p)
{delete[]p;
}int main()
{//使用内置的default_delete作为释放规则shared_ptrintp4(new int[5], default_deleteint);//初始化智能指针并自定义规则shared_ptrintp5(new int[5], DeleteInt);//使用lambda表达式shared_ptrintp6(new int [5], {delete[]p; });return 0;
}2.2 unique_ptr
unique_ptr指针自然也具备“在适当时机自动释放堆内存空间”的能力。和shared_ptr指针最大不同之处在于unique_ptr指针指向的堆内存无法同某它unique_ptr共享也就是说每个unique_ptr指针都独自拥有对其所指堆内存空间的所有权。
例如:
#include iostream
#include memoryint main() {std::unique_ptrint unique1 std::make_uniqueint(42);// std::unique_ptrint unique2 unique1; // 错误不允许复制独占指针std::cout Value of unique1: *unique1 std::endl;unique1.reset(); // 释放资源if (!unique) {std::cout unique is nullptr std::endl;}return 0; // 在main函数结束时unique的资源被释放
}2.3 weak_ptr
在 C 中std::shared_ptr 允许多个智能指针共享同一个资源它们会共同维护一个引用计数来跟踪资源的使用。这样做通常很有用但可能导致循环引用问题即两个或多个对象互相引用导致它们的引用计数永远不会归零资源永远不会释放从而产生内存泄漏。
我们来看一个案例
class A {
public:std::shared_ptrB b_ptr;
};class B {
public:std::shared_ptrA a_ptr;
};
在这个例子中类 A 持有类 B 的智能指针同时类 B 持有类 A 的智能指针。因此它们之间形成了循环引用即使不再需要它们它们的引用计数也不会降到零导致内存泄漏。
std::weak_ptr 的作用是打破这种循环引用同时允许检查资源是否仍然存在。std::weak_ptr 不会增加资源的引用计数它只是一个观察者当资源被释放后它会自动变成空指针。
下面是 std::weak_ptr 的使用示例
#include iostream
#include memoryclass A;
class B;class A {
public:std::shared_ptrB b_ptr;A() {std::cout A constructor std::endl;}~A() {std::cout A destructor std::endl;}
};class B {
public:std::weak_ptrA a_weak_ptr; // 使用 std::weak_ptr 避免循环引用B() {std::cout B constructor std::endl;}~B() {std::cout B destructor std::endl;}
};int main() {std::shared_ptrA a std::make_sharedA();std::shared_ptrB b std::make_sharedB();a-b_ptr b;b-a_weak_ptr a;// 输出 A 和 B 的析构函数调用// 注意输出会在 main 函数结束时显示资源会正确释放return 0;
}在这个例子中类 A 持有类 B 的 shared_ptr而类 B 持有类 A 的 weak_ptr。当 main 函数结束时shared_ptr 的引用计数会归零类 A 和类 B 的析构函数会被调用并释放资源而不会导致内存泄漏。
总之std::weak_ptr 是一种用于解决循环引用和避免内存泄漏的智能指针它允许观察资源的状态而不增加引用计数。
2.4 使用智能指针的好处
自动内存管理不需要手动调用delete来释放内存减少内存泄漏的风险。避免悬挂指针当资源不再需要时智能指针会自动释放资源避免悬挂指针问题。引用计数和独占所有权std::shared_ptr和std::unique_ptr提供了不同的所有权管理方式根据需要选择。
总之智能指针是C中一种非常有用的工具可以大大简化内存管理提高代码的可维护性和安全性。在编写现代C代码时推荐优先使用智能指针来管理动态内存分配。
3 可变参数模板
C11之前类模板和函数模板中只能含固定数量的模板参数。
C11的新特性可变参数能够让您创建可以接受可变参数的函数模板和类模。
下面是一个例子
#includeiostream
using namespace std;
//函数模板的参数个数为0到多个参数每个参数的类型可以各不相同。
templateclass…T//args一包形参T一包类型void func(T…args){coutsizeof…(args)endl; //sizeof…固定语法计算获取到的模板参数个数coutsizeof…(T)endl; //sizeof… 只能计算可变参数
}
int main()
{func(12,13,14,15);return 0;
}这段代码中的两个 sizeof… 分别用于计算不同的东西 sizeof…(args)这行代码计算的是可变参数 args 中的参数个数。在 main 函数中调用了 func(12,13,14,15);所以args 包含了四个参数因此 sizeof…(args) 会输出 4。 sizeof…(T)这行代码计算的是模板参数包 T 中的类型数量。在函数模板 func 中T 是一个模板参数包代表可变参数的类型。由于在 main 函数中传递了四个整数参数所以 T 中包含了四个类型因此 sizeof…(T) 会输出 4。
总结一下sizeof…(args) 计算的是可变参数的个数而 sizeof…(T) 计算的是可变参数的类型数量这两个值在这个例子中都是相等的都是 4。 - 参数包和参数展开 当我们谈论参数包和参数包展开时我们实际上在讨论可变参数模板的一部分。 参数包Parameter Pack 参数包是可变参数模板中的一种特殊类型它允许你声明一个接受不定数量参数的模板函数或类。参数包的形式为 typename… Args 或 class… Args其中 Args 是参数包的名字可以是任何有效的标识符名字。 template typename… Args void MyFunction(Args… args) {// 在函数体中可以访问 Args 的参数列表 }在这个例子中Args 是一个参数包它表示可以接受零个或多个参数。当你调用 MyFunction 时你可以传递任意数量的参数给它。 参数包展开Parameter Pack Expansion 参数包展开是指在模板函数或类中使用参数包中的参数的过程。展开操作使用展开操作符 … 来实现将参数包中的参数逐个提取出来用于函数调用、初始化列表、递归展开等操作。 示例 让我们以一个更具体的示例来说明参数包和参数包展开。假设我们有一个可变参数模板函数 PrintArgs它可以打印任意数量的参数 #include iostream using namespace std;template typename… Args void PrintArgs(Args… args) {( (cout args ), …);// 参数包展开并打印 }int main() {PrintArgs(1, 2, Hello, 3.14);return 0; }在这个示例中PrintArgs 接受不定数量的参数并使用展开操作符将它们逐个打印出来。在 main 函数中我们调用 PrintArgs 函数并传递了整数、字符串和浮点数等不同类型的参数它们都被正确地打印出来。
- 参数包展开的主要方式
参数包展开有几种不同的方式它们在使用时具有不同的语法和应用场景。以下是一些常见的参数包展开方法以及它们的区别 左折叠展开Left Fold Expansion 左折叠展开使用括号表达式 (expr, …) 和展开操作符 …将参数包中的参数从左到右展开并应用于表达式 expr。这是最常见和直观的展开方式。 (expr, …);例如上面提到的 PrintArgs 示例就是使用了左折叠展开。 右折叠展开Right Fold Expansion 右折叠展开使用括号表达式 (expr, …) 和展开操作符 …将参数包中的参数从右到左展开并应用于表达式 expr。右折叠展开通常用于一些需要反向处理参数的情况。 (… , expr);例如如果你想计算参数包中所有整数的乘积可以使用右折叠展开。 参数包展开初始化列表Parameter Pack Expansion in Initialization Lists 这种展开方式允许你在初始化列表中使用参数包展开用于初始化数组、集合、结构体等数据结构。通常在对象的构造函数中使用。 MyClass(std::initializer_listT{args…}) {// 在构造函数中使用参数包展开初始化列表
}这种展开方式在容器类和自定义数据结构的设计中非常有用。 递归展开Recursive Expansion 当参数包中包含不定数量的参数而你希望逐个处理它们时可以使用递归展开。这通常需要一个递归函数或类来处理参数包中的每个参数。 template typename T
void ProcessArg(T arg) {// 处理参数的逻辑
}template typename T, typename… Rest
void ProcessArgs(T first, Rest… rest) {ProcessArg(first);ProcessArgs(rest…); // 递归展开
}这种展开方式适用于需要按顺序处理参数的情况。 递归展开是一种在模板函数或模板类中使用递归来处理参数包的方法。通常递归展开需要两个模板函数一个用于处理第一个参数另一个用于处理其余的参数并递归调用自身。 以下是一个具体的示例演示如何使用递归展开来打印参数包中的所有参数 #include iostream// 基本情况处理最后一个参数
template typename T
void PrintArg(T arg) {std::cout arg std::endl;
}// 递归情况处理第一个参数并递归处理其余参数
template typename T, typename… Rest
void PrintArgs(T first, Rest… rest) {PrintArg(first);PrintArgs(rest…); // 递归展开//会陷入死递归
}int main() {PrintArgs(1, 2, Hello, 3.14);return 0;
}在这个示例中PrintArgs 函数用于处理参数包中的参数首先调用 PrintArg 处理第一个参数然后递归调用 PrintArgs 处理剩余的参数。这样看起来参数包中的所有参数都会被逐个打印出来。 但显然忽略了一个致命的问题递归没有终止条件会陷入死循环。此时为了程序顺利运行我们需要添加一个判断条件来终止递归函数。 那么该怎么添加判断条件呢 在C17中 引入了 if constexpr 语句它允许在编译时根据条件选择不同的代码分支。这对于编写更加灵活的模板代码非常有用因为它允许你在编译时剔除不需要的代码分支减少模板实例化的复杂性。 我们可以通过该语句来判断条件是否终止。 #include iostream// 基本情况处理最后一个参数
template typename T
void PrintArg(T arg) {std::cout arg std::endl;
}// 递归情况处理第一个参数并递归处理其余参数
template typename T, typename… Rest
void PrintArgs(T first, Rest… rest) {PrintArg(first);if constexpr(sizeof…(rest)0)PrintArgs(rest…); // 递归展开
}int main() {PrintArgs(1, 2, Hello, 3.14);return 0;
}递归展开 和if constexpr这两个特性在编写泛型和模板代码时非常有用能够提高代码的灵活性和性能。
这些展开方式的选择取决于你的具体需求和代码逻辑。通常左折叠展开是最常见和最直观的选择但在某些情况下其他展开方式可能更合适。要根据具体情况灵活选择展开方式以满足你的编程需求。 总之参数包是一种允许可变参数模板处理不定数量参数的机制而参数包展开是将参数包中的参数逐个提取出来并在代码中使用的过程。这使得可变参数模板非常强大和灵活。
- 上一篇: 培睿网站开发与设计设计品牌有哪些
- 下一篇: 培训课荆州seo公司






