45 使用成员函数模板来接受所有兼容类型
概述
- 在
C++
模板编程中,成员函数模板允许类的成员函数独立于类的模板参数进行模板化,从而使得这些成员函数可以接受比类的模板参数更广泛的类型
问题描述
- 假设你有一个模板类,其某个成员函数需要处理比类模板参数更广泛的类型
- 传统的类模板参数限制了成员函数只能处理与类模板参数相同类型或相关类型的数据,这限制了类的灵活性和复用性
解决方案
- 使用成员函数模板可以解决这个问题,使得类的成员函数可以处理与类模板参数不同或不相关的类型,从而提高类的通用性和灵活性
示例代码
- 不使用成员函数模板的情况
- 这个例子中,
MyClass<int>
的process
函数只能处理int
类型,无法处理其他类型的数据
- 这个例子中,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> template <typename T> class MyClass { public: void process(const T& value) { std::cout << "Processing value: " << value << std::endl; } }; int main() { MyClass<int> obj; obj.process(42); // 可以正常工作 // obj.process(3.14); // 错误:无法处理 double 类型 return 0; } |
- 使用成员函数模板的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> template <typename T> class MyClass { public: template <typename U> void process(const U& value) { std::cout << "Processing value: " << value << std::endl; } }; int main() { MyClass<int> obj; obj.process(42); // 处理 int 类型 obj.process(3.14); // 处理 double 类型 obj.process("Hello"); // 处理 const char* 类型 return 0; } |
具体应用
- 接受多种类型的输入
- 成员函数模板可以使类的成员函数接受多种类型的输入,而不仅限于类模板参数类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> template <typename T> class Container { public: template <typename U> void add(const U& value) { std::cout << "Adding value: " << value << " to container" << std::endl; } }; int main() { Container<int> intContainer; intContainer.add(42); // 处理 int 类型 intContainer.add(3.14); // 处理 double 类型 intContainer.add("example"); // 处理 const char* 类型 return 0; } |
- 处理不同类型的比较
- 成员函数模板可以用于比较不同类型的数据,使类更加通用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream> template <typename T> class Comparator { public: template <typename U> bool isEqual(const T& a, const U& b) { return a == b; } }; int main() { Comparator<int> comp; std::cout << std::boolalpha << comp.isEqual(10, 10.0) << std::endl; // 输出 true std::cout << comp.isEqual(10, 20) << std::endl; // 输出 false return 0; } |
46 当需要类型转换时,在模板中定义非成员函数
概述
- 在
C++
模板编程中,有时需要定义非成员函数来提供特定的功能,比如操作符重载或辅助函数 - 这些非成员函数有时需要进行类型转换,以便在不同类型之间进行操作
- 在这种情况下,将这些非成员函数定义在模板内部可以提供更好的类型转换支持和代码组织
问题描述
- 在模板类中,如果非成员函数需要对模板参数进行类型转换,直接定义非成员函数可能会遇到一些问题
- 特别是在涉及到隐式类型转换或模板参数推导时,编译器可能无法正确处理
解决方案
- 将非成员函数定义在模板类内部,可以让这些函数成为模板的一部分,从而使编译器能够更好地处理模板参数和类型转换
- 这样可以确保类型转换能够正确应用,并使得代码更加清晰和易于维护
示例代码
- 不在模板内部定义非成员函数
- 这个例子中,我们定义了一个
Rational
类,并为其重载了加法运算符 - 然而,如果我们希望支持不同类型的
Rational
对象之间的加法运算(如Rational<int>
与Rational<double>
),这种方法就会显得不够灵活
- 这个例子中,我们定义了一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <iostream> template <typename T> class Rational { public: Rational(T numerator = 0, T denominator = 1) : num(numerator), den(denominator) {} T numerator() const { return num; } T denominator() const { return den; } private: T num, den; }; // 非成员函数:重载加法运算符 template <typename T> Rational<T> operator+(const Rational<T>& lhs, const Rational<T>& rhs) { return Rational<T>( lhs.numerator() * rhs.denominator() + rhs.numerator() * lhs.denominator(), lhs.denominator() * rhs.denominator() ); } int main() { Rational<int> r1(1, 2); Rational<int> r2(2, 3); Rational<int> r3 = r1 + r2; // 使用重载的加法运算符 std::cout << "Result: " << r3.numerator() << "/" << r3.denominator() << std::endl; return 0; } |
- 在模板内部定义非成员函数
- 这个例子中,我们将非成员函数
operator+
定义在模板类Rational
的内部,并使其成为模板的一部分 - 这使得加法运算符可以处理不同类型的
Rational
对象之间的加法运算
- 这个例子中,我们将非成员函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <iostream> template <typename T> class Rational { public: Rational(T numerator = 0, T denominator = 1) : num(numerator), den(denominator) {} T numerator() const { return num; } T denominator() const { return den; } // 在模板内部定义非成员函数 template <typename U> friend Rational<T> operator+(const Rational<T>& lhs, const Rational<U>& rhs) { return Rational<T>( lhs.numerator() * rhs.denominator() + rhs.numerator() * lhs.denominator(), lhs.denominator() * rhs.denominator() ); } private: T num, den; }; int main() { Rational<int> r1(1, 2); Rational<double> r2(2.5, 3.5); Rational<int> r3 = r1 + r2; // 使用重载的加法运算符 std::cout << "Result: " << r3.numerator() << "/" << r3.denominator() << std::endl; return 0; } |
47 使用特征类获取类型信息
概述
- 特征类(
traits classes
)是一个用于在编译时获取类型信息的技术 - 它们在
C++
模板编程中非常有用,因为它们允许程序员在编译时进行类型检查和选择,从而编写更通用和高效的代码
特征类的概念
- 特征类是一种模板类,它通过特化(
specialization
)来提供关于类型的信息 - 这些信息可以包括类型是否为某种类型(如指针、整数、浮点数等),或者类型的某些属性(如类型的大小、是否支持某些操作等)
主要用途
- 类型分类:
- 确定一个类型是否为某种特定类型
- 类型属性:
- 获取类型的某些属性,如是否为
POD
(Plain Old Data
)、是否支持默认构造等
- 获取类型的某些属性,如是否为
- 类型选择:
- 根据类型信息选择不同的实现或算法
示例代码
- 可以定义一个特征类,用于判断一个类型是否为指针类型
- 这个例子中,
IsPointer
特征类通过特化来区分普通类型和指针类型
- 这个例子中,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
template <typename T> struct IsPointer { static const bool value = false; }; template <typename T> struct IsPointer<T*> { static const bool value = true; }; int main() { std::cout << IsPointer<int>::value << std::endl; // 输出 0 std::cout << IsPointer<int*>::value << std::endl; // 输出 1 std::cout << IsPointer<double*>::value << std::endl; // 输出 1 return 0; } |
- 获取类型属性
- 这个例子中,
TypeSize
特征类用于获取类型的大小
- 这个例子中,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> #include <type_traits> template <typename T> struct TypeSize { static const size_t value = sizeof(T); }; int main() { std::cout << "Size of int: " << TypeSize<int>::value << std::endl; std::cout << "Size of double: " << TypeSize<double>::value << std::endl; std::cout << "Size of int*: " << TypeSize<int*>::value << std::endl; return 0; } |
- 类型选择
- 这个例子中,
TypeTraits
特征类通过特化来选择不同的实现
- 这个例子中,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include <iostream> template <typename T> struct TypeTraits { static void print() { std::cout << "Unknown type" << std::endl; } }; template <> struct TypeTraits<int> { static void print() { std::cout << "int type" << std::endl; } }; template <> struct TypeTraits<double> { static void print() { std::cout << "double type" << std::endl; } }; template <typename T> void printType() { TypeTraits<T>::print(); } int main() { printType<int>(); // 输出 "int type" printType<double>(); // 输出 "double type" printType<char>(); // 输出 "Unknown type" return 0; } |
标准库中的特征类
C++
标准库中提供了一些常用的特征类,位于头文件<type_traits>
中
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> #include <type_traits> int main() { std::cout << std::boolalpha; std::cout << std::is_pointer<int>::value << std::endl; // 输出 false std::cout << std::is_pointer<int*>::value << std::endl; // 输出 true std::cout << std::is_integral<int>::value << std::endl; // 输出 true std::cout << std::is_floating_point<double>::value << std::endl; // 输出 true return 0; } |
48 了解模板元编程
概述
- 模板元编程利用模板递归和模板特化在编译期进行计算
- 通过组合模板和类型推导,可以在编译期完成一些计算任务,从而提高运行时的效率
TMP
在泛型编程、编译期常量计算、类型萃取等领域有广泛应用
示例代码
- 编译期常量计算:阶乘计算
- 这个例子中,
Factorial
模板类通过递归模板实例化在编译期计算阶乘值
- 这个例子中,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> // 基本模板:计算N的阶乘 template<int N> struct Factorial { static const int value = N * Factorial<N - 1>::value; }; // 模板特化:计算0的阶乘 template<> struct Factorial<0> { static const int value = 1; }; int main() { std::cout << "Factorial<5>::value = " << Factorial<5>::value << std::endl; // 输出120 return 0; } |
- 类型萃取:判断类型是否为指针
- 这个例子中,
IsPointer
模板类通过特化实现了判断类型是否为指针类型的功能
- 这个例子中,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <iostream> // 基本模板:判断T是否为指针类型 template<typename T> struct IsPointer { static const bool value = false; }; // 模板特化:指针类型的特化 template<typename T> struct IsPointer<T*> { static const bool value = true; }; int main() { std::cout << std::boolalpha; std::cout << "IsPointer<int>::value = " << IsPointer<int>::value << std::endl; // 输出false std::cout << "IsPointer<int*>::value = " << IsPointer<int*>::value << std::endl; // 输出true return 0; } |
模板元编程的优点
- 编译期计算:
TMP
允许在编译期进行复杂的计算,从而减少运行时开销
- 代码生成:
- 通过
TMP
可以生成高度优化和特定于应用的代码,提高代码的性能和可维护性
- 通过
- 类型安全:
TMP
利用C++
的类型系统,在编译期进行类型检查,确保代码的正确性
模板元编程的缺点
- 编译时间增加:
TMP
可能导致编译时间显著增加,特别是对于复杂的模板递归和特化
- 调试困难:
- 由于
TMP
在编译期进行计算,调试TMP
代码通常比调试运行时代码更困难
- 由于
- 代码可读性:
TMP
代码可能比较复杂,理解和维护TMP
代码需要较高的C++
技能
49
概述
- 在C++中,
new
操作符用于动态分配内存- 当
new
操作符无法分配所需的内存时,会抛出std::bad_alloc
异常
- 当
- 然而,
C++
允许程序员通过设置new-handler
来自定义处理内存分配失败的情况new-handler
是一个用户定义的函数,当new
操作符无法分配内存时,这个函数会被调用
new
-handler 的基本概念
new
-handler 是一个函数指针,指向一个不接受参数且不返回值的函数- 当
new
操作符无法分配内存时,C++运行时会调用这个函数 - 如果这个函数成功释放了一些内存,
new
操作符会重新尝试分配内存- 如果
new
-handler 没有释放足够的内存,new
操作符会再次调用它,循环往复,直到成功分配内存或new
-handler 不再能释放内存为止
- 如果
设置 new
-handler
- 可以通过
std::set_new_handler
函数设置new-handler
- 这个函数接受一个函数指针作为参数,并返回先前设置的
new-handler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <iostream> #include <new> #include <cstdlib> void myNewHandler() { std::cerr << "Memory allocation failed. Trying to free up memory..." << std::endl; // 尝试释放一些内存(例如,通过删除全局对象) // 如果无法释放内存,可以调用 std::abort() 终止程序 std::abort(); } int main() { std::set_new_handler(myNewHandler); try { // 尝试分配大量内存,以触发 new-handler int* p = new int[1000000000000000]; } catch (const std::bad_alloc& e) { std::cerr << "Caught: " << e.what() << std::endl; } return 0; } |
new
-handler 的行为
- 释放内存:
new-handler
的主要任务是尝试释放一些内存,以便new
操作符可以重新尝试分配内存- 这可以通过删除全局对象、清理缓存等方式实现
- 终止程序:
- 如果
new-handler
无法释放足够的内存,通常会调用std::abort
或std::exit
来终止程序
- 如果
- 抛出异常:
new-handler
也可以选择抛出一个异常,从而中止new
操作符的重试过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#include <iostream> #include <new> #include <vector> std::vector<int> memoryHog; void myNewHandler() { std::cerr << "Memory allocation failed. Freeing memoryHog..." << std::endl; memoryHog.clear(); // 尝试释放内存 if (memoryHog.capacity() == 0) { std::cerr << "memoryHog successfully cleared." << std::endl; } else { std::cerr << "Failed to clear memoryHog. Aborting..." << std::endl; std::abort(); // 如果无法释放内存,终止程序 } } int main() { std::set_new_handler(myNewHandler); try { // 分配一些初始内存 memoryHog.resize(1000000); // 尝试分配更多内存,以触发 new-handler int* p = new int[1000000000000000]; } catch (const std::bad_alloc& e) { std::cerr << "Caught: " << e.what() << std::endl; } return 0; } |
50 理解何时替换 new 和 delete
概述
- 在
C++
中,new
和delete
操作符用于动态内存分配和释放 - 在大多数情况下,标准的
new
和delete
操作符已经足够使用- 但是,在某些特定情况下,替换默认的
new
和delete
操作符可以带来显著的性能提升、内存管理改进或满足特定的需求
- 但是,在某些特定情况下,替换默认的
替换 new
和 delete
的常见场景
- 内存池(
Memory Pool
):- 当大量小对象频繁分配和释放时,使用内存池可以显著提高性能并减少内存碎片
- 调试内存泄漏:
- 自定义
new
和delete
操作符可以用于记录内存分配和释放操作,帮助调试内存泄漏
- 自定义
- 对齐需求(
Alignment Requirements
):- 在某些情况下,需要特定的内存对齐,自定义内存分配器可以满足这种需求
- 异常安全性:
- 定制内存分配和释放逻辑可以增强异常安全性,确保在异常情况下正确释放内存
如何替换 new
和 delete
- 全局重载
new
和delete
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <iostream> #include <cstdlib> // 全局重载 new 操作符 void* operator new(size_t size) { std::cout << "Global new called, size = " << size << std::endl; void* p = std::malloc(size); if (!p) { throw std::bad_alloc(); } return p; } // 全局重载 delete 操作符 void operator delete(void* p) noexcept { std::cout << "Global delete called" << std::endl; std::free(p); } int main() { int* p = new int(42); // 调用全局重载的 new 操作符 delete p; // 调用全局重载的 delete 操作符 return 0; } |
- 类特定的
new
和delete
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include <iostream> #include <cstdlib> class MyClass { public: // 类特定的 new 操作符 void* operator new(size_t size) { std::cout << "MyClass new called, size = " << size << std::endl; void* p = std::malloc(size); if (!p) { throw std::bad_alloc(); } return p; } // 类特定的 delete 操作符 void operator delete(void* p) noexcept { std::cout << "MyClass delete called" << std::endl; std::free(p); } MyClass() { std::cout << "MyClass constructor called" << std::endl; } ~MyClass() { std::cout << "MyClass destructor called" << std::endl; } }; int main() { MyClass* p = new MyClass(); // 调用 MyClass 的 new 操作符 delete p; // 调用 MyClass 的 delete 操作符 return 0; } |
注意
- 异常安全:
- 自定义
new
操作符应该在分配失败时抛出std::bad_alloc
异常
- 自定义
- 对齐要求:
- 确保自定义
new
操作符返回的内存满足适当的对齐要求
- 确保自定义
- 配对使用:
- 确保自定义的
new
和delete
操作符成对出现,避免内存泄漏和未定义行为
- 确保自定义的
- 性能权衡:
- 自定义内存分配器可能会带来额外的复杂性,只有在明确的性能或功能需求下才考虑替换
new
和delete
- 自定义内存分配器可能会带来额外的复杂性,只有在明确的性能或功能需求下才考虑替换
51 编写 new 和 delete 时遵守惯例
概述
- 在C++中,自定义
new
和delete
操作符可以为特定类型或整个程序提供定制的内存管理 - 为了确保自定义
new
和delete
操作符的行为符合预期并与标准库和其他代码兼容,需要遵守一些重要的惯例
new
和 delete
操作符的惯例
- 对齐:
new
操作符返回的指针必须正确对齐,以便能安全地存储任何类型的对象- 标准库中的
std::malloc
函数通常返回足够对齐的内存 - 但如果对齐要求更高,则需要使用对齐分配函数(如
std::aligned_alloc
)
- 异常安全:
new
操作符在内存分配失败时必须抛出std::bad_alloc
异常delete
操作符必须能够处理空指针,并且在释放内存时不应抛出异常
- 成对使用:
- 确保自定义的
new
和delete
操作符是成对使用的 - 如果你为一个类自定义了
new
操作符,也应该为该类自定义相应的delete
操作符
- 确保自定义的
- 配对重载:
- 如果你重载了数组形式的
new[]
操作符,也应该重载对应的delete[]
操作符
- 如果你重载了数组形式的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
#include <iostream> #include <cstdlib> class MyClass { public: // 类特定的 new 操作符 void* operator new(size_t size) { std::cout << "MyClass new called, size = " << size << std::endl; void* p = std::malloc(size); if (!p) { throw std::bad_alloc(); } return p; } // 类特定的 delete 操作符 void operator delete(void* p) noexcept { std::cout << "MyClass delete called" << std::endl; std::free(p); } // 类特定的 new[] 操作符 void* operator new[](size_t size) { std::cout << "MyClass new[] called, size = " << size << std::endl; void* p = std::malloc(size); if (!p) { throw std::bad_alloc(); } return p; } // 类特定的 delete[] 操作符 void operator delete[](void* p) noexcept { std::cout << "MyClass delete[] called" << std::endl; std::free(p); } MyClass() { std::cout << "MyClass constructor called" << std::endl; } ~MyClass() { std::cout << "MyClass destructor called" << std::endl; } }; int main() { // 单个对象的分配和释放 MyClass* p1 = new MyClass(); delete p1; // 数组对象的分配和释放 MyClass* p2 = new MyClass[5]; delete[] p2; return 0; } |
对齐
- 如果你的类需要特定的对齐,可以使用对齐分配函数,如
std::aligned_alloc
或自定义对齐逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include <iostream> #include <cstdlib> #include <new> class AlignedClass { public: static const size_t alignment = 16; // 自定义对齐的 new 操作符 void* operator new(size_t size) { std::cout << "AlignedClass new called, size = " << size << std::endl; void* p = std::aligned_alloc(alignment, size); if (!p) { throw std::bad_alloc(); } return p; } // 自定义对齐的 delete 操作符 void operator delete(void* p) noexcept { std::cout << "AlignedClass delete called" << std::endl; std::free(p); } AlignedClass() { std::cout << "AlignedClass constructor called" << std::endl; } ~AlignedClass() { std::cout << "AlignedClass destructor called" << std::endl; } }; int main() { AlignedClass* p = new AlignedClass(); delete p; return 0; } |
52 如果编写了定位 new,也要编写定位 delete
概述
- 在C++中,定位
new
和delete
操作符用于在特定内存位置创建和销毁对象- 这对于高效的内存管理和优化性能非常有用
- 然而,如果你自定义了定位
new
操作符,那么你也应该自定义相应的定位delete
操作符,以确保内存管理的一致性和正确性
定位 new
和 delete
的基本概念
- 定位
new
操作符允许在给定的内存地址上创建对象,而不是从堆中分配新内存 - 这个内存地址通常是预先分配的
- 定位
delete
操作符则用于在对象销毁时清理这些内存
1 2 3 |
void* operator new(size_t size, void* place); void operator delete(void* ptr, void* place); |
为什么需要定位 delete
- 如果只定义了定位
new
而没有相应的定位delete
,当对象销毁时,编译器不知道如何处理这些内存,可能会导致内存泄漏或未定义行为
示例代码
- 定义定位
new
和delete
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
#include <iostream> #include <new> class MyClass { public: int x, y; MyClass(int a, int b) : x(a), y(b) { std::cout << "MyClass constructor called" << std::endl; } ~MyClass() { std::cout << "MyClass destructor called" << std::endl; } // 定位 new 操作符 void* operator new(size_t size, void* place) { std::cout << "Placement new called" << std::endl; return place; } // 定位 delete 操作符 void operator delete(void* ptr, void* place) { std::cout << "Placement delete called" << std::endl; // 定位 delete 不需要释放内存,因为内存由用户管理 } }; int main() { char buffer[sizeof(MyClass)]; void* place = buffer; // 使用定位 new 在 buffer 中创建对象 MyClass* p = new(place) MyClass(1, 2); std::cout << "Object created at: " << static_cast<void*>(p) << std::endl; // 手动调用析构函数,然后调用定位 delete p->~MyClass(); MyClass::operator delete(p, place); return 0; } |
53
概述
- 编译器警告是编译器在编译代码时检测到潜在问题时发出的信息
- 虽然警告不会阻止代码的编译和生成可执行文件,但它们通常指出代码中可能存在的问题,这些问题可能会导致运行时错误、性能问题或未定义行为
- 忽视编译器警告可能会导致难以调试和维护的代码
为什么要关注编译器警告
- 捕获潜在错误:
- 编译器警告通常可以捕获潜在的编程错误
- 例如未初始化的变量、类型转换问题、未使用的变量等
- 提高代码质量:
- 修复警告可以使代码更加健壮、可维护和易于理解
- 跨平台兼容性:
- 不同的编译器可能会发出不同的警告
- 关注并修复这些警告有助于提高代码的跨平台兼容性
- 防止未来问题:
- 当前看似无害的警告可能在将来的代码修改中引入严重问题
常见的编译器警告类型
- 未使用的变量:
- 声明了但未使用的变量可能是遗留代码或逻辑错误的迹象
- 未初始化的变量:
- 未初始化的变量可能包含垃圾值,导致未定义行为
- 类型转换问题:
- 隐式类型转换可能导致数据丢失或未定义行为
- 可能的内存泄漏:
- 未正确释放的内存可能导致内存泄漏
- 条件表达式中的赋值操作:
- 可能是编程错误,通常应该使用比较操作
示例
1 2 3 4 |
int main() { int x = 42; // 未使用的变量 x return 0; } |
1 2 3 4 5 6 7 |
#include <iostream> int main() { int x; // 未初始化的变量 x std::cout << x << std::endl; // 使用未初始化的变量 return 0; } |
1 2 3 4 5 6 7 8 |
#include <iostream> int main() { double d = 3.14; int i = d; // 隐式类型转换,可能导致数据丢失 std::cout << i << std::endl; return 0; } |
54 熟悉标准库,包括 TR1
概述
- 熟悉
C++
标准库是成为高效C++
程序员的关键 C++
标准库提供了大量的功能和工具,可以简化开发过程,提高代码的可读性、性能和可维护性TR1
(Technical Report 1
)是C++
标准库的一部分,它在C++11
之前引入了一些有用的库组件,这些组件后来被纳入了C++11
标准中
重要的标准库组件
- 容器
- 标准库提供了许多常用的容器,如
std::vector
、std::list
、std::map
等 - 这些容器可以用于存储和管理数据
- 标准库提供了许多常用的容器,如
- 算法
- 标准库提供了一系列算法,如
std::sort
、std::find
、std::accumulate
等 - 这些算法可以与标准容器一起使用
- 标准库提供了一系列算法,如
智能指针
- C++11引入了智能指针,如
std::unique_ptr
和std::shared_ptr
,这些智能指针可以自动管理动态内存,减少内存泄漏的风险
55 熟悉Boost库
主要的Boost库组件
- 智能指针
- 正则表达式
- 多线程
- 文件系统
- 算法和数据结构
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ WTL 概述03/10
- ♥ C++11_第五篇12/08
- ♥ Effective C++_第三篇07/01
- ♥ Soui三05/19
- ♥ Soui七06/02
- ♥ C++标准模板库编程实战_算法和随机数12/08