MyTinySTL项目学习笔记03

MyTinySTL项目学习笔记03

functional实现

一元函数模板和二元函数模板实现

一元函数和二元函数的实现:

// 定义一元函数的参数型别和返回值型别
template <class Arg, class Result>
struct unarg_function
{
    typedef Arg       argument_type;
    typedef Result    result_type;
};

// 定义二元函数的参数型别的返回值型别
template <class Arg1, class Arg2, class Result>
struct binary_function
{
    typedef Arg1      first_argument_type;
    typedef Arg2      second_argument_type;
    typedef Result    result_type;
};

为什么要定义一个通用一元函数模板和二元函数模板?因为可以封装了传入参数类型和返回参数类型;如果一个库或一段代码需要处理多种不同的一元函数,将这些函数的类型信息统一封装在一个结构体中,可以使得接口更加一致和简洁。

加减乘除等基本运算的定义:

// 函数对象:加法
template <class T>
struct plus :public binary_function<T, T, T>
{
  T operator()(const T& x, const T& y) const { return x + y; }
};

plus<int> aplus;
aplus(3, 4); // 结果为7

// 函数对象:否定
template <class T>
struct negate :public unarg_function<T, T>
{
  T operator()(const T& x) const { return -x; }
};

加减乘除、取余、否定等基本运算,都是一元函数或者二元函数,使用之前定义的一元函数模板和二元函数模板,可以更好实现封装;

通过 public 继承 binary_function<T, T, T>plus 继承了 binary_function 的类型成员。这意味着 plus 现在拥有以下类型别名:

  • first_argument_type 设为 T
  • second_argument_type 设为 T
  • result_type 设为 T

这些类型别名可以在其他模板代码中方便地访问。

继承二元函数实现的好处:

  1. 类型信息明确binary_function 提供了 first_argument_typesecond_argument_typeresult_type 这些类型别名,使得 plus 的类型信息清晰明确,易于理解和使用。
  2. 接口统一:通过继承 binary_function,所有类似的二元函数对象都可以拥有一个统一的接口,使得库的使用者和维护者可以更加方便地进行代码开发和维护。
  3. 增强泛型编程能力:继承 binary_function 后,可以编写更加通用的模板代码,通过访问这些类型成员,可以在泛型代码中更加灵活地处理各种二元函数对象。
  4. 代码复用:通过继承 binary_function,避免了在不同的二元函数对象中重复定义类型别名的需求,提升了代码复用性和可维护性。

包括大于、等于等操作也是继承一元或者二元函数模板实现;

哈希函数实现

对于不同的类型,哈希函数的实现方式也不同;(即计算哈希值的方式也不同)

  • 大部分类型:什么都不做(因为很多自定义类型,应该自己传入哈希函数,而不是提前设定)
  • 指针:使用reinterpret_cast<size_t>(p)将指针T* p指向的地址值强制转换为size_t类型;
  • 整型类型:返回原值(强制转化为size_t之后返回)
    • 包括boolcharsigned charunsigned charwchar_tint等等类型,都可以强制转换后之间返回;
    • 相当于简单的桶,某些简单的用途可能足够,但可能不是最优的哈希函数,因为它没有考虑诸如冲突减小和分布均匀性等因素;
  • 浮点数类型:逐位哈希;将非0的浮点数强制转换为字符数组,然后逐个处理字符数组内字符,提供异或和乘法实现计算哈希;(floatdouble以及long double处理思想都一样)

大部分类型

// 对于大部分类型,hash function 什么都不做
template <class Key>
struct hash {};

大部分类型什么都不做,将大部分类型都交给用户自己实现哈希函数;

指针类型

// 针对指针的偏特化版本
template <class T>
struct hash<T*>
{
  size_t operator()(T* p) const noexcept
  { return reinterpret_cast<size_t>(p); }
};

对于指针类型,直接使用指针地址作为哈希函数;

整型类型(包括可以强制转换为整型的类型)

// 对于整型类型,只是返回原值(注意跨多行的宏需要加反斜杠)
#define MYSTL_TRIVIAL_HASH_FCN(Type)         \
template <> struct hash<Type>                \
{                                            \
  size_t operator()(Type val) const noexcept \
  { return static_cast<size_t>(val); }       \
};

MYSTL_TRIVIAL_HASH_FCN(bool)

MYSTL_TRIVIAL_HASH_FCN(char)

MYSTL_TRIVIAL_HASH_FCN(signed char)

MYSTL_TRIVIAL_HASH_FCN(unsigned char)

MYSTL_TRIVIAL_HASH_FCN(wchar_t)

MYSTL_TRIVIAL_HASH_FCN(char16_t)

MYSTL_TRIVIAL_HASH_FCN(char32_t)

MYSTL_TRIVIAL_HASH_FCN(short)

MYSTL_TRIVIAL_HASH_FCN(unsigned short)

MYSTL_TRIVIAL_HASH_FCN(int)

MYSTL_TRIVIAL_HASH_FCN(unsigned int)

MYSTL_TRIVIAL_HASH_FCN(long)

MYSTL_TRIVIAL_HASH_FCN(unsigned long)

MYSTL_TRIVIAL_HASH_FCN(long long)

MYSTL_TRIVIAL_HASH_FCN(unsigned long long)

#undef MYSTL_TRIVIAL_HASH_FCN

MYSTL_TRIVIAL_HASH_FCN(Type) 是一个宏,用于生成一个特定类型的 hash 模板结构的特化版本。这个特化版本的 operator() 函数简单地将输入值转换为 size_t 类型并返回。

通过多次调用 MYSTL_TRIVIAL_HASH_FCN(Type),为 boolcharsigned charunsigned charwchar_tchar16_tchar32_tshortunsigned shortintunsigned intlongunsigned longlong longunsigned long long 这些类型生成了哈希函数的特化版本。

浮点数类型

// 对于浮点数,逐位哈希
inline size_t bitwise_hash(const unsigned char* first, size_t count)
{
    #if (_MSC_VER && _WIN64) || ((__GNUC__ || __clang__) &&__SIZEOF_POINTER__ == 8)
    const size_t fnv_offset = 14695981039346656037ull;
    const size_t fnv_prime = 1099511628211ull;
    #else
    const size_t fnv_offset = 2166136261u;
    const size_t fnv_prime = 16777619u;
    #endif
    size_t result = fnv_offset;
    for (size_t i = 0; i < count; ++i)
    {
        result ^= (size_t)first[i];
        result *= fnv_prime;
    }
    return result;
}

template <>
struct hash<float>
{
    size_t operator()(const float& val)
    { 
        return val == 0.0f ? 0 : bitwise_hash((const unsigned char*)&val, sizeof(float));
    }
};

template <>
struct hash<double>
{
    size_t operator()(const double& val)
    {
        return val == 0.0f ? 0 : bitwise_hash((const unsigned char*)&val, sizeof(double));
    }
};

template <>
struct hash<long double>
{
    size_t operator()(const long double& val)
    {
        return val == 0.0f ? 0 : bitwise_hash((const unsigned char*)&val, sizeof(long double));
    }
};

FNV-1a 是一种快速且有效的哈希算法,适用于字节序列。根据编译器和平台(32位或64位),选择不同的 FNV 偏移基数和乘法基数。

FNV-1a哈希算法是一种非加密哈希函数。以下是FNV-1a哈希算法的简单思想:

  1. 固定基数和偏移量:算法使用两个固定的常数,一个称为偏移基数(offset basis),另一个称为乘法基数(prime)。这些常数对于算法的特定版本是固定的。
  2. 初始化哈希值:开始时,哈希值被初始化为偏移基数。
  3. 逐字节处理:输入数据被逐字节地处理。每个字节与当前的哈希值进行异或操作(XOR),然后整个哈希值乘以乘法基数。
  4. 循环迭代:对于输入数据中的每个字节,重复上述的异或和乘法步骤。
  5. 最终结果:经过所有字节的处理后,最终的哈希值即为算法的输出。

自定义哈希函数实现

自定义哈希函数的使用:

struct PairHash {
    std::size_t operator()(const std::pair<int, int>& p) const {
        // 自定义的哈希函数实现(注意有两个括号)
        return std::hash<int>()(p.first) ^ std::hash<int>()(p.second);
    }
};

unordered_map<pair<int, int>, int, PairHash> umap; // 传入PairHash

std::hash<int>() 这种形式的调用是函数调用运算符的使用,它调用了 std::hash<int> 类型的默认构造函数来创建一个临时的 std::hash<int> 对象,然后使用这个对象来调用 operator()。这种写法有时被称为 “函数调用括号” 模式。

另一种等价的写法是使用 std::hash<int>{}(p.first),它利用了 C++11 引入的列表初始化语法来创建临时对象;

函数调用括号模式

std::hash 的使用场景中,我们需要创建一个 std::hash<int> 类型的临时对象来调用其 operator()。这里的关键在于 std::hash 的构造函数不接受任何参数,因此不能直接在对象创建时传入 p.first

std::hash<int>() 创建了一个 std::hash<int> 类型的临时对象,而 () 表示对这个对象调用 operator() 函数。如此一来,创建对象和函数调用进行了分离;实际上我们创建的只是一个匿名对象;

在上面的std::hash<int>的实现中:

template <> struct hash<int>                
{                                            
    size_t operator()(int val) const noexcept 
    { return static_cast<size_t>(val); }       
};

可以看出,hash重载了括号运算符,如果我们不创建匿名对象,要实现计算p.first的哈希,则:

hash<int> myhash;
myhash(p.first);

hash<int> myhash(p.first);

需要创建一个命名对象并初始化;通过hash<int>()(p.first)提供了一种更轻量级(不用创建命名对象)的实现方式;

声明一个普通的结构体使用struct hash {};,而template <> struct hash<int> { ... } 这种语法是模板特化的示例。模板特化是一种机制,允许为特定类型提供模板的定制实现。这里的 hash<int> 表示针对 int 类型特化了 hash 模板。它告诉编译器,当 hash 模板与 int 类型一起使用时,应该使用这个特化的结构体定义。


STL的基本算法实现

max以及max的重载算法

template <class T>
    const T& max(const T& lhs, const T& rhs)
{
    return lhs < rhs ? rhs : lhs;
}

// 重载版本使用函数对象 comp 代替比较操作
template <class T, class Compare>
    const T& max(const T& lhs, const T& rhs, Compare comp)
{
    return comp(lhs, rhs) ? rhs : lhs;
}

可以自定义一个Compare传入,例如:

struct Compare {
	bool operator() (const T& lhs, const T& rhs) {
        // 自定义比较方式,返回布尔类型
    }  
};

同理可以实现min以及min的重载版本;

迭代器操作

对调两个迭代器;

template <class FIter1, class FIter2>
void iter_swap(FIter1 lhs, FIter2 rhs)
{
  mystl::swap(*lhs, *rhs);
}

[first, last)区间内的元素拷贝到 [result, result + (last - first))内;

// input_iterator_tag 版本
template <class InputIter, class OutputIter>
    OutputIter 
    unchecked_copy_cat(InputIter first, InputIter last, OutputIter result, 
                       mystl::input_iterator_tag)
{
    for (; first != last; ++first, ++result)
    {
        *result = *first;
    }
    return result;
}

// ramdom_access_iterator_tag 版本
template <class RandomIter, class OutputIter>
    OutputIter 
    unchecked_copy_cat(RandomIter first, RandomIter last, OutputIter result,
                       mystl::random_access_iterator_tag)
{
    for (auto n = last - first; n > 0; --n, ++first, ++result)
    {
        *result = *first;
    }
    return result;
}

template <class InputIter, class OutputIter>
    OutputIter 
    unchecked_copy(InputIter first, InputIter last, OutputIter result)
{
    return unchecked_copy_cat(first, last, result, iterator_category(first));
}

注意:两个区间的迭代器类型可能不同;

迭代器类型

C++ 标准中定义的迭代器类别:

  1. 输入迭代器(Input Iterator)
    • 可以读取元素,但不能写入。
    • 支持递增操作(operator++)。
    • 可以进行相等或不等的比较。
  2. 输出迭代器(Output Iterator)
    • 可以写入元素,但不能读取。
    • 支持递增操作(operator++)。
    • 不支持元素访问或比较操作。
  3. 正向迭代器(Forward Iterator)
    • 结合了输入迭代器的所有功能。
    • 支持连续的递增操作。
  4. 双向迭代器(Bidirectional Iterator)
    • 结合了正向迭代器的所有功能。
    • 支持递减操作(operator--)。
  5. 随机访问迭代器(Random Access Iterator)
    • 结合了双向迭代器的所有功能。
    • 支持随机访问元素(通过下标操作 operator[])。
    • 支持迭代器之间的距离计算和比较。

每种迭代器类别都对应不同的使用场景和性能特点:

  • 输入迭代器:适用于只能读取的序列,如流迭代器。
  • 输出迭代器:适用于只能写入的序列,如输出流迭代器。
  • 正向迭代器:适用于可以连续访问元素但不支持反向遍历的序列,如链表迭代器。
  • 双向迭代器:适用于可以正向和反向遍历的序列,如双向链表迭代器。
  • 随机访问迭代器:适用于可以快速随机访问任何元素的序列,如数组或向量迭代器。

每一种操作都要为不同的迭代器类型进行重载;

比较第一序列在 [first, last)区间上的元素值是否和第二序列相等;

template <class InputIter1, class InputIter2>
bool equal(InputIter1 first1, InputIter1 last1, InputIter2 first2)
{
  for (; first1 != last1; ++first1, ++first2)
  {
    if (*first1 != *first2)   // 使用解引用后比较
      return false;
  }
  return true;
}

// 重载版本使用函数对象 comp 代替比较操作
template <class InputIter1, class InputIter2, class Compared>
bool equal(InputIter1 first1, InputIter1 last1, InputIter2 first2, Compared comp)
{
  for (; first1 != last1; ++first1, ++first2)
  {
    if (!comp(*first1, *first2))  
      return false;
  }
  return true;
}

如果需要比较的函数,需要重载可以传入自定义函数对象comp进行比较的版本;

区分迭代器类型,对不同迭代器类型重载不同函数;


其他操作

// 对[first, last)区间内的元素与给定值进行比较,缺省使用 operator==,返回元素相等的个数
template <class InputIter, class T>
    size_t count(InputIter first, InputIter last, const T& value)
{
    size_t n = 0;
    for (; first != last; ++first)
    {
        if (*first == value)
            ++n; // 使用++n而不是n++,避免构造对象
    }
    return n;
}

// 对[first, last)区间内的每个元素都进行一元 unary_pred 操作,返回结果为 true 的个数
template <class InputIter, class UnaryPredicate>
    size_t count_if(InputIter first, InputIter last, UnaryPredicate unary_pred)
{
    size_t n = 0;
    for (; first != last; ++first)
    {
        if (unary_pred(*first))
            ++n;
    }
    return n;
}

/*****************************************************************************************/
// max_element
// 返回一个迭代器,指向序列中最大的元素
/*****************************************************************************************/
template <class ForwardIter>
    ForwardIter max_element(ForwardIter first, ForwardIter last)
{
    if (first == last)
        return first;
    auto result = first;
    while (++first != last)
    {
        if (*result < *first)
            result = first;
    }
    return result;
}

// 重载版本使用函数对象 comp 代替比较操作
template <class ForwardIter, class Compared>
    ForwardIter max_element(ForwardIter first, ForwardIter last, Compared comp)
{
    if (first == last)
        return first;
    auto result = first;
    while (++first != last)
    {
        if (comp(*result, *first))
            result = first;
    }
    return result;
}

template <class RandomIter>
    void sort(RandomIter first, RandomIter last)
{
    if (first != last)
    {
        // 内省式排序,将区间分为一个个小区间,然后对整体进行插入排序
        mystl::intro_sort(first, last, slg2(last - first) * 2);
        mystl::final_insertion_sort(first, last);
    }
}

template <class RandomIter, class Compared>
    void sort(RandomIter first, RandomIter last, Compared comp)
{
    if (first != last)
    {
        // 内省式排序,将区间分为一个个小区间,然后对整体进行插入排序
        mystl::intro_sort(first, last, slg2(last - first) * 2, comp);
        mystl::final_insertion_sort(first, last, comp);
    }
}

注意sort默认使用了插入排序;


unordered_map实现

底层数据结构时哈希表hash_table

template <class Key, class T, class Hash = mystl::hash<Key>, class KeyEqual = mystl::equal_to<Key>>
    class unordered_map
    {
        private:
        // 使用 hashtable 作为底层机制
        typedef hashtable<mystl::pair<const Key, T>, Hash, KeyEqual> base_type;
        base_type ht_;
        public:
        ...
    }

定义了哈希表为base_type,并且声明一个哈希表对象ht_

构造函数就是初始化该哈希表对象ht_

// 无参构造函数
unordered_map() :ht_(100, Hash(), KeyEqual()) {} // 列表初始化

// 可以传入自定义的哈希函数,第三个参数是默认参数
explicit unordered_map(size_type bucket_count,
                       const Hash& hash = Hash(),
                       const KeyEqual& equal = KeyEqual())
    :ht_(bucket_count, hash, equal) {}

可以传入自定义的哈希函数对象,实现unordered_map,例如:

struct PairHash {
    std::size_t operator()(const std::pair<int, int>& p) const {
        // 自定义的哈希函数实现(注意有两个括号)
        return std::hash<int>()(p.first) ^ std::hash<int>()(p.second);
    }
};

unordered_map<pair<int, int>, int, PairHash> umap; // 传入PairHash

之后的begin()end()都是对哈希表的操作;例如:

iterator       begin()        noexcept
{ return ht_.begin(); }
void      erase(iterator it)
{ ht_.erase(it); }

通过at()方法取元素和直接使用中括号取元素的差别:

mapped_type& at(const key_type& key)
{
    iterator it = ht_.find(key);
    THROW_OUT_OF_RANGE_IF(it.node == nullptr, "unordered_map<Key, T> no such element exists");
    return it->second;
}
mapped_type& operator[](const key_type& key)
{
    iterator it = ht_.find(key);
    if (it.node == nullptr)
        it = ht_.emplace_unique(key, T{}).first;
    return it->second;
}

可以看出,使用at(key)取对应的value,如果key不存在会抛出异常,而[key]如果key不存在,则会自动添加keyunordered_map中,不会抛出异常;所以使用中括号时,最好先umap.find(key) != umap.end(),避免这种情况;

iterator       find(const key_type& key)        
{ return ht_.find(key); }

unordered_multimap的是实现:底层也是哈希表,但是键值允许重复;

// unordered_map的emplace
template <class ...Args>
    pair<iterator, bool> emplace(Args&& ...args)
{ return ht_.emplace_unique(mystl::forward<Args>(args)...); }

// unordered_multimap的emplace
template <class ...Args>
    iterator emplace(Args&& ...args)
{ return ht_.emplace_multi(mystl::forward<Args>(args)...); }

二者在很多方面几乎一样,只是在对哈希表的操作有些差异;


unordered_set的实现

unordered_setunordered_multiset功能与用法与 setmultiset 类似,不同的是使用 hashtable 作为底层实现机制,容器中的元素不会自动排序;

template <class Key, class Hash = mystl::hash<Key>, class KeyEqual = mystl::equal_to<Key>>
    class unordered_set
    {
        private:
        // 使用 hashtable 作为底层机制
        typedef hashtable<Key, Hash, KeyEqual> base_type;
        base_type ht_;

        public:
        ...
    }

也是将哈希表对象当作私有变量,通过操作该对象实现unordered_set的功能;

template <class ...Args>
    pair<iterator, bool> emplace(Args&& ...args)
{ return ht_.emplace_unique(mystl::forward<Args>(args)...); }

unordered_map()emplace保持一致;

unordered_setunordered_map的区别就在于前者的哈希表只有key,后者的哈希表是pair<key, value>;其余部分的实现基本一致;

同理,unordered_multiset()unordered_multimap()的区别也在于哈希表中数据元素类型一个不是键值对,一个是键值对;


begin,rbegin,cbegin

vector中,迭代器相关操作中,有三个类似的操作:

// 迭代器相关操作
iterator               begin()         noexcept 
{ return begin_; }
const_iterator         begin()   const noexcept
{ return begin_; }
iterator               end()           noexcept
{ return end_; }
const_iterator         end()     const noexcept 
{ return end_; }

reverse_iterator       rbegin()        noexcept  // 逆向迭代器
{ return reverse_iterator(end()); }
const_reverse_iterator rbegin()  const noexcept
{ return const_reverse_iterator(end()); }
reverse_iterator       rend()          noexcept
{ return reverse_iterator(begin()); }
const_reverse_iterator rend()    const noexcept 
{ return const_reverse_iterator(begin()); }

const_iterator         cbegin()  const noexcept 
{ return begin(); }
const_iterator         cend()    const noexcept
{ return end(); }
const_reverse_iterator crbegin() const noexcept
{ return rbegin(); }
const_reverse_iterator crend()   const noexcept
{ return rend(); }

beginrbegincbegin 是 C++ 标准库容器类(如 std::vectorstd::liststd::map 等)的成员函数,用于返回指向容器中元素的迭代器。这些函数的区别在于它们返回的迭代器类型,以及它们提供的遍历方向和访问权限。区别:

begin

  • 类型:对于非 const 容器,begin 返回一个指向容器第一个元素的非 const迭代器(iterator),允许修改容器中的元素。对于 const 容器,它返回一个 const迭代器(const_iterator),只允许读取元素。
  • 遍历方向begin 提供正向遍历,即从容器的第一个元素开始,直到最后一个元素之后的迭代器(end)。
  • 使用场景:当你需要修改容器中的元素时使用。

rbegin

  • 类型rbegin 返回一个指向容器最后一个元素的逆向迭代器(reverse_iterator),允许逆向遍历容器。
  • 遍历方向rbegin 提供逆向遍历,即从容器的最后一个元素开始,直到第一个元素之前的迭代器(rend)。
  • 使用场景:当你需要逆向遍历容器中的元素时使用。

cbegin

  • 类型cbegin 总是返回一个指向容器第一个元素的 const 迭代器(const_iterator),不允许修改容器中的元素。
  • 遍历方向cbegin 提供正向遍历,与 begin 相同,但是以只读方式。
  • 使用场景:当你需要以只读方式访问容器中的元素时使用,无论容器本身是否为 const

begin 用于获取非 const 迭代器(可修改元素)或 const 迭代器(仅适用于 const 容器,不可修改元素)。rbegin 用于获取逆向迭代器,以便逆向遍历容器,且迭代器为 · 类型(不可修改元素)。cbegin 总是提供 const 迭代器,无论容器是否为 const,都只能以只读方式访问元素。这些函数提供了灵活的容器元素访问方式,允许程序员根据需要选择适当的遍历方向和访问权限。

具体来说就是:正向迭代器、逆向迭代器、常量迭代器;


数值运算numeric

accumulate()累加元素:

/*****************************************************************************************/
// accumulate
// 版本1:以初值 init 对每个元素进行累加
// 版本2:以初值 init 对每个元素进行二元操作
/*****************************************************************************************/
// 版本1
template <class InputIter, class T>
    T accumulate(InputIter first, InputIter last, T init)
{
    for (; first != last; ++first)
    {
        init += *first; // 指定初值,一般设置为0
    }
    return init;
}

// 版本2,BinaryOp代表二元操作
template <class InputIter, class T, class BinaryOp>
    T accumulate(InputIter first, InputIter last, T init, BinaryOp binary_op)
{
    for (; first != last; ++first)
    {
        init = binary_op(init, *first);
    }
    return init;
}

// 测试
int sum = accumulate(vec.begin(), vec.end(), 0);

adjacent_difference计算相邻元素的差值:

/*****************************************************************************************/
// adjacent_difference
// 版本1:计算相邻元素的差值,结果保存到以 result 为起始的区间上
// 版本2:自定义相邻元素的二元操作
/*****************************************************************************************/
// 版本1
template <class InputIter, class OutputIter>
    OutputIter adjacent_difference(InputIter first, InputIter last, OutputIter result)
{
    if (first == last)  return result;
    *result = *first;  // 记录第一个元素
    auto value = *first;
    while (++first != last)
    {
        auto tmp = *first;
        *++result = tmp - value;
        value = tmp;
    }
    return ++result;
}

// 版本2
template <class InputIter, class OutputIter, class BinaryOp>
    OutputIter adjacent_difference(InputIter first, InputIter last, OutputIter result,
                                   BinaryOp binary_op)
{
    if (first == last)  return result;
    *result = *first;  // 记录第一个元素
    auto value = *first;
    while (++first != last)
    {
        auto tmp = *first;
        *++result = binary_op(tmp, value);
        value = tmp;
    }
    return ++result;
}

注意传入的迭代器类型,第一个参数、第二个参数是输入迭代器,能访问,但是不能修改;第三个参数是输出迭代器,可以修改;

其他操作包括inner_product计算区间内积、iota构造元素递增的区间,partial_sum局部求和等基本上都有两个版本,分别是正常版本,以及传入一个二元操作的版本;


定义string, wstring, u16string, u32string类型

#ifndef MYTINYSTL_ASTRING_H_
#define MYTINYSTL_ASTRING_H_

// 定义了 string, wstring, u16string, u32string 类型

#include "basic_string.h"

namespace mystl
{

    using string    = mystl::basic_string<char>;
    using wstring   = mystl::basic_string<wchar_t>;
    using u16string = mystl::basic_string<char16_t>;
    using u32string = mystl::basic_string<char32_t>;

}
#endif // !MYTINYSTL_ASTRING_H_

using关键字的用法

在 C++ 中,using 关键字有几种不同的用法,包括类型别名声明、引入命名空间成员以及引入基类成员。

类型别名声明

类型别名声明允许你为现有的类型定义一个新的名称。这是一种使类型名称更简短或更具描述性的方法。(即上面using string = mystl::basic_string<char>;用法)

  • using string:声明 string 作为一个类型别名。
  • = mystl::basic_string<char>:将 string 别名指向 mystl::basic_string<char> 类型。

在声明类型别名之后,就可以使用别名创建对象mystl::string str = "abc";

usingtypedef 都可以用于为现有类型创建别名,但它们在语法和使用上有一些差异。以下是 usingtypedef 的比较:

using 声明可以用于模板别名,这使得它在模板元编程中非常有用。typedef 在模板别名中使用时有一些限制,不能直接用于模板别名。using 声明的别名在可读性方面通常优于 typedef,尤其是在复杂的模板类型中。

using String = mystl::basic_string<char>;  
typedef mystl::basic_string<char> String;

在这两个示例中,String 都是 mystl::basic_string<char> 的别名。然而,using 声明的别名 String 可以作为类型名出现在需要类型名的任何地方,而 typedef 声明的别名 String 在使用上更类似于变量名。

  • 相同点:两者都可以用于为现有类型创建别名。
  • 不同点:
    • 语法上,using 使用 = 分配,而 typedef 使用 type identifier 形式。
    • 作用域上,using 通常具有更广的作用域,而 typedef 的作用域可能受限于声明块。
    • 与模板的交互上,using 更加灵活,尤其是在模板别名的使用上。

在现代 C++ 编程中,using 声明类型别名被推荐使用,因为它提供了更好的语法支持和更高的灵活性。

引入命名空间成员
using std::cout; // 引入 std 命名空间中的 cout
引入基类成员到派生类
class Derived : public Base {
    using Base::memberFunction; 
};

DerivedBase的派生类,在Derived中引入基类Base的成员;

将整个命名空间的内容引入当前作用域
using namespace std;   
namespace std = Mystd; // 别名

using namespace std; 是一种特殊的 using 声明,它用于将整个命名空间的内容引入到当前作用域。这意味着,可以在不指定命名空间的情况下使用该命名空间内的所有实体(如函数、类型、变量等)。

在上述代码中,引入 std 命名空间中的所有成员,使得可以直接使用它们,而不需要前缀 std::

使用 using namespace std; 可以减少代码量,但也可能导致名称冲突,特别是在大型项目中。为了代码的可读性和避免潜在的冲突,推荐尽可能使用具体的命名空间前缀,如 std::vector 而不是仅仅使用 vector

同时,在头文件中使用 using namespace std; 可能会导致命名冲突问题,因为它会影响到所有包含该头文件的源文件。

namespace用法

在 C++ 中,命名空间(namespace) 是一种将实体(如变量、函数、类等)组织在一起的机制,用于避免命名冲突,并提供了一种访问控制的手段。命名空间是 C++ 中实现封装和模块化的重要特性之一。

命名空间的主要特点:

  1. 封装:命名空间可以将一组相关的实体封装在一起,使得它们在没有明确指定命名空间的情况下不会暴露给外部。
  2. 避免命名冲突:不同的库或模块可能会定义相同名称的实体,通过使用命名空间可以避免这些实体名称之间的冲突。
  3. 作用域限定:命名空间提供了一种作用域限定的方法,使得可以明确指定某个实体属于哪个命名空间。
  4. 可读性:命名空间的使用可以提高代码的可读性,使得代码的结构更加清晰。

示例:

// 定义一个命名空间 example
namespace example {
    int value = 10;
    void function() {
        std::cout << "Function in example namespace" << std::endl;
    }
}

int main() {
    // 使用命名空间限定符访问命名空间中的实体
    example::value = 20;
    example::function();

    // 使用 using 声明直接访问命名空间中的实体
    using namespace example;
    value = 30; // 直接访问 example 命名空间中的 value
    function(); // 直接访问 example 命名空间中的 function

    return 0;
}

在 C++ 中,可以为命名空间定义别名,使得使用起来更加方便:

namespace ex = example; // 为 example 命名空间创建别名 ex

C++ 标准库中的所有实体都位于 std 这个命名空间中。当你使用标准库中的函数或类时,你需要指定 std:: 前缀,或者使用 using namespace std; 来引入整个 std 命名空间。

虽然using namespace std;很方便,但是并不是一个很好的习惯,可能会导致意外的名称冲突;在大型项目中,合理使用命名空间对于维护代码的清晰性和可管理性至关重要。


异常处理宏和断言

代码:

// 防止头文件多重包含,所以使用ifndef-define-endif
#ifndef MYTINYSTL_EXCEPTDEF_H_
#define MYTINYSTL_EXCEPTDEF_H_
#include <stdexcept>
#include <cassert>

namespace mystl
{
    #define MYSTL_DEBUG(expr) \
  assert(expr)  // 如果 expr 为 false,则程序将断言失败

    // 在条件 expr 为真时抛出 std::length_error 异常
    #define THROW_LENGTH_ERROR_IF(expr, what) \
  if ((expr)) throw std::length_error(what)

    // 在条件 expr 为真时抛出 std::out_of_range 异常
    #define THROW_OUT_OF_RANGE_IF(expr, what) \
  if ((expr)) throw std::out_of_range(what)

    // 在条件 expr 为真时抛出 std::runtime_error 异常
    #define THROW_RUNTIME_ERROR_IF(expr, what) \
  if ((expr)) throw std::runtime_error(what)
    
} // namepsace mystl
#endif // !MYTINYSTL_EXCEPTDEF_H_

定义了三个宏 THROW_LENGTH_ERROR_IF, THROW_OUT_OF_RANGE_IF, THROW_RUNTIME_ERROR_IF,这些宏在条件满足时抛出特定的异常。

MYSTL_DEBUG 宏使用 assert 来在调试时检查表达式 expr 是否为真,如果不为真,则程序会中断执行;

宏定义

宏定义时可以包含参数,这些参数在宏定义中被用作占位符,用于在宏展开时插入特定的值。

// 基本语法
#define MACRO_NAME(param1, param2, ...) replacement_text

// 示例
#define SWAP(a, b) do { a ^= b; b ^= a; a ^= b; } while(0)

int x = 5, y = 10;
SWAP(x, y);

替换文本中,参数会被传入;预处理器将这个宏进行展开,在SWAP(x, y)处生成如下代码:

do { x ^= y; y ^= x; x ^= y; } while(0);

宏替换的本质是文本替换,不会进行类型检查或者语义分析,所以使用宏要小心,避免意外行为;

预处理阶段,编译器会将宏展开(即进行文本替换),宏展开的代码在调试时可能难以追踪,因为原始宏调用在源代码中并不直接出现;

宏可以与条件编译指令(如 #ifdef, #ifndef, #endif)结合使用,以控制代码的编译;

在现代C++编程中,模板和内联函数提供了更安全和更灵活的替代方案,通常推荐使用这些特性而不是宏;

在之前实现整型的哈希函数时,就是用了宏实现函数重载:

// 对于整型类型,只是返回原值
#define MYSTL_TRIVIAL_HASH_FCN(Type)         \
template <> struct hash<Type>                \
{                                            \
  size_t operator()(Type val) const noexcept \
  { return static_cast<size_t>(val); }       \
};

MYSTL_TRIVIAL_HASH_FCN(bool)
MYSTL_TRIVIAL_HASH_FCN(char)
MYSTL_TRIVIAL_HASH_FCN(int)

#undef MYSTL_TRIVIAL_HASH_FCN

如果不使用宏,而直接使用模板函数和特化模板:

#include <cstddef> // 包含 size_t 的定义

// 通用模板函数
template<typename Type>
struct hash {
    size_t operator()(Type val) const {
        // 通用实现(可能需要根据实际需求进行修改)
        // 示例中我们简单地返回 val 的大小_t 类型转换
        return static_cast<size_t>(val);
    }
};

// 特化版本,针对 bool 类型
template<>
struct hash<bool> {
    size_t operator()(bool val) const noexcept {
        return static_cast<size_t>(val);
    }
};

// 特化版本,针对 char 类型
template<>
struct hash<char> {
    size_t operator()(char val) const noexcept {
        return static_cast<size_t>(val);
    }
};

// 特化版本,针对 int 类型
template<>
struct hash<int> {
    size_t operator()(int val) const noexcept {
        return static_cast<size_t>(val);
    }
};
  • 我们首先定义了一个通用的 hash 模板结构体,它包含一个接受任意类型参数 Typeoperator() 函数。
    • 为了使用模板特化,首先需要有一个原始的模板定义。模板特化是针对原始模板的一个或多个特定类型的定制实现。这意味着,特化是在原始模板基础上进行的,原始模板提供了一种通用的或泛型的定义。
  • 然后,我们为 boolcharint 类型提供了 hash 模板的特化版本,这些特化版本覆盖了通用模板中的 operator() 函数,提供了针对特定类型的实现。
  • 在特化版本中,operator() 函数简单地将传入的参数转换为 size_t 类型,并返回转换后的值。
  • 我们还添加了 noexcept 说明符,以表明这些函数不会抛出异常。

使用模板特化的方法,我们可以避免宏可能带来的一些问题,如类型安全和调试困难,同时还能利用模板的强大功能来提供类型安全的、灵活的代码。

也可以直接函数重载:

#include <cstddef> // 包含 size_t 的定义

// 为 bool 类型定义 hash 函数
size_t hash(bool val) {
    return static_cast<size_t>(val);
}

// 为 char 类型定义 hash 函数
size_t hash(char val) {
    return static_cast<size_t>(val);
}

// 为 int 类型定义 hash 函数
size_t hash(int val) {
    return static_cast<size_t>(val);
}

这种方法的优点是实现简单,易于理解,并且避免了模板相关的复杂性。但是,它的缺点是缺乏模板的通用性和灵活性,如果需要支持更多类型,需要为每种类型手动添加一个新的 hash 函数。此外,这种方法不适用于那些需要在编译时确定类型的情况。

模板特化

定义原始模板:

template<typename T>
struct hash {
    size_t operator()(const T& val) const {
        // 通用实现
        return std::hash<T>()(val);
    }
};

实现模板特化:

template<>
struct hash<int> {
    size_t operator()(int val) const noexcept {
        return static_cast<size_t>(val);
    }
};

在这个例子中,hash 结构体是一个模板,它可以适用于任何类型 T

然后,我们对 int 类型进行了特化,提供了一个特定的实现,这个实现将 int 类型的值转换为 size_t 类型。

模板特化是C++模板编程的一个重要特性,它提供了一种方法来覆盖或扩展模板类的通用行为。记住,特化是在原始模板已经定义的情况下进行的。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OutlierLi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值