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
这些类型别名可以在其他模板代码中方便地访问。
继承二元函数实现的好处:
- 类型信息明确:
binary_function
提供了first_argument_type
、second_argument_type
和result_type
这些类型别名,使得plus
的类型信息清晰明确,易于理解和使用。 - 接口统一:通过继承
binary_function
,所有类似的二元函数对象都可以拥有一个统一的接口,使得库的使用者和维护者可以更加方便地进行代码开发和维护。 - 增强泛型编程能力:继承
binary_function
后,可以编写更加通用的模板代码,通过访问这些类型成员,可以在泛型代码中更加灵活地处理各种二元函数对象。 - 代码复用:通过继承
binary_function
,避免了在不同的二元函数对象中重复定义类型别名的需求,提升了代码复用性和可维护性。
包括大于、等于等操作也是继承一元或者二元函数模板实现;
哈希函数实现
对于不同的类型,哈希函数的实现方式也不同;(即计算哈希值的方式也不同)
- 大部分类型:什么都不做(因为很多自定义类型,应该自己传入哈希函数,而不是提前设定)
- 指针:使用
reinterpret_cast<size_t>(p)
将指针T* p
指向的地址值强制转换为size_t
类型; - 整型类型:返回原值(强制转化为
size_t
之后返回)- 包括
bool
,char
,signed char
,unsigned char
,wchar_t
,int
等等类型,都可以强制转换后之间返回; - 相当于简单的桶,某些简单的用途可能足够,但可能不是最优的哈希函数,因为它没有考虑诸如冲突减小和分布均匀性等因素;
- 包括
- 浮点数类型:逐位哈希;将非0的浮点数强制转换为字符数组,然后逐个处理字符数组内字符,提供异或和乘法实现计算哈希;(
float
,double
以及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)
,为 bool
、char
、signed char
、unsigned char
、wchar_t
、char16_t
、char32_t
、short
、unsigned short
、int
、unsigned int
、long
、unsigned long
、long long
和 unsigned 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
哈希算法的简单思想:
- 固定基数和偏移量:算法使用两个固定的常数,一个称为偏移基数(offset basis),另一个称为乘法基数(prime)。这些常数对于算法的特定版本是固定的。
- 初始化哈希值:开始时,哈希值被初始化为偏移基数。
- 逐字节处理:输入数据被逐字节地处理。每个字节与当前的哈希值进行异或操作(XOR),然后整个哈希值乘以乘法基数。
- 循环迭代:对于输入数据中的每个字节,重复上述的异或和乘法步骤。
- 最终结果:经过所有字节的处理后,最终的哈希值即为算法的输出。
自定义哈希函数实现
自定义哈希函数的使用:
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++ 标准中定义的迭代器类别:
- 输入迭代器(Input Iterator)
- 可以读取元素,但不能写入。
- 支持递增操作(
operator++
)。 - 可以进行相等或不等的比较。
- 输出迭代器(Output Iterator)
- 可以写入元素,但不能读取。
- 支持递增操作(
operator++
)。 - 不支持元素访问或比较操作。
- 正向迭代器(Forward Iterator)
- 结合了输入迭代器的所有功能。
- 支持连续的递增操作。
- 双向迭代器(Bidirectional Iterator)
- 结合了正向迭代器的所有功能。
- 支持递减操作(
operator--
)。
- 随机访问迭代器(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
不存在,则会自动添加key
到unordered_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_set
和 unordered_multiset
功能与用法与 set
和 multiset
类似,不同的是使用 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_set
和unordered_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(); }
begin
、rbegin
和 cbegin
是 C++ 标准库容器类(如 std::vector
、std::list
、std::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";
using
和 typedef
都可以用于为现有类型创建别名,但它们在语法和使用上有一些差异。以下是 using
和 typedef
的比较:
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;
};
Derived
是Base
的派生类,在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++ 中实现封装和模块化的重要特性之一。
命名空间的主要特点:
- 封装:命名空间可以将一组相关的实体封装在一起,使得它们在没有明确指定命名空间的情况下不会暴露给外部。
- 避免命名冲突:不同的库或模块可能会定义相同名称的实体,通过使用命名空间可以避免这些实体名称之间的冲突。
- 作用域限定:命名空间提供了一种作用域限定的方法,使得可以明确指定某个实体属于哪个命名空间。
- 可读性:命名空间的使用可以提高代码的可读性,使得代码的结构更加清晰。
示例:
// 定义一个命名空间 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
模板结构体,它包含一个接受任意类型参数Type
的operator()
函数。- 为了使用模板特化,首先需要有一个原始的模板定义。模板特化是针对原始模板的一个或多个特定类型的定制实现。这意味着,特化是在原始模板基础上进行的,原始模板提供了一种通用的或泛型的定义。
- 然后,我们为
bool
、char
和int
类型提供了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++模板编程的一个重要特性,它提供了一种方法来覆盖或扩展模板类的通用行为。记住,特化是在原始模板已经定义的情况下进行的。