欢迎光临
我们一直在努力

【C++笔记】List流食般投喂

本编介绍STL容器之List~

1、迭代器的功能&性质

2、辨别模板中应使用哪种迭代器

3、list接口的介绍和使用

4、模板的按需实例化

5、List中迭代器失效的问题

6、C++11统一初始化

7、默认构造、拷贝构造、赋值运算符重载、析构函数的简便实现写法

1、迭代器的功能&性质

        区分迭代器,可从功能与性质两方面区分。

        📌功能:

                                                        iterator
                                                        reverse_iterator

                                                        const_iterator

                                                        const_reverse_iterator

        📌性质:

单向 forward_list/unordered_map… 支持:++
双向 list/map/set… 支持:++/–
随机 vector/string/deque… 支持:++/–/+/-

💡迭代器性质的不同,其支持的运算就不同,这种差异源自于底层结构的区别。

💡对于这三个性质,可以将其归类:

随机迭代器可以看作是特殊的双向迭代器,特殊的单向迭代器
双向迭代器可以看作是特殊的单向迭代器
他们之间可以特殊化的归为其一类~

2、辨别模板中应使用哪种迭代器

随机迭代器 Random Access
双向迭代器 Bidirectional
单向迭代器 Forward
包含这三个的~ Input/Output

📌在模板中:

        看到 "Random Access",说明:可用迭代器范围只能用随机迭代器;

        看到 "Bidirectional",说明:可用迭代器范围能用 随机/双向 迭代器;

        看到 "Forward",说明:可用迭代器范围能用 随机/双向/单向 迭代器;

        看到 "Input/Output",说明:可用迭代器范围能用 随机/双向/单向 迭代器。

3、list接口的介绍和使用

📌此处只介绍与vector有"差异"的接口~

emplace_back

resize

splice

unique

merge

sort

对于单参数的尾插,push_back和emplace_back是一样的,都要构造再拷贝;但是emplace_back是支持多参数的,对于多参数为一个元素的尾插,emplace_back就直接构造了。
在链表中的 resize 就没有扩容的概念了。
有两个list,将一个list里面的一个数或一段数据剪切掉,再粘贴到另一个list上。
去重,但是是针对于有序的数据。
合并两个链表,被拿去合并的链表合并后变成空链表,合并成的链表保持升序。
对于小数据,list自带的 sort 快;对于大数据,algorithm里面的 sort 快。

4、模板的按需实例化

模拟实现List中的具体例子:

// 模板 可以实例化出下面两个 — 正常版/const版
template <class T, class Ref, class Ptr>
struct list_iterator
{
typedef list_node<T> Node;
typedef list_iterator<T, Ref, Ptr> Self;

Node* _node;

list_iterator(Node* node)
:_node(node)
{ }

Ref operator* ()
{
return _node->_data;
}

Ptr operator-> () // 强制语法规定:返回指针
{
return &_node->_data;
}

Self& operator++ ()
{
_node = _node->_next;
return *this;
}

Self& operator–()
{
_node = _node->_prev;
return *this;
}

bool operator!= (const Self& s) const
{
return _node != s._node;
}

bool operator== (const Self& s) const
{
return _node == s._node;
}
};

📌该模板在没有具体需求时,编译器不会细致扫描,就像你知道你记得你有某样东西一样。当有具体需求时,模板根据具体需求产生对应的实例化类,供具体需求使用。

5、List中迭代器失效的问题

📌List在物理空间上是不连续的,而且迭代器是经过努力转化出来的,指向哪里就指向哪里,所以插入时,迭代器不失效。

测试代码
void test_list2()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);

list<int>::iterator it = lt.begin();

// 插入迭代器不会失效
lt.insert(it, 10);
*it += 100;

list<int>::iterator it1 = lt.begin();
while (it1 != lt.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
}

📌List在删除结点时,当前迭代器就像是野指针一样,所以List删除时,迭代器是失效的。

测试代码
void test_list2()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);

// 删除所有偶数
// 迭代器失效
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
if (*it % 2 == 0)
{
lt.erase(it);
}
else
{
++it;
}
}
list<int>::iterator it1 = lt.begin();
while (it1 != lt.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
}

6、C++11统一初始化

C++11之后支持了这样的初始化的方式:

// 直接构造
list<int> lt0({ 1,2,3,4,5,6 });
// 隐式类型转换
list<int> lt1 = { 1,2,3,4,5,6,7,8 };
const list<int>& lt3 = { 1,2,3,4,5,6,7,8 };

func(lt0);
func({ 1,2,3,4,5,6 });

其中隐式类型转换:大括号一串的数字会转换成 "initializer_list<int>" 类型,"initializer_list":初始化列表。

auto il = { 10, 20, 30 };
initializer_list<int> il = { 10, 20, 30 };
cout << typeid(il).name() << endl;
cout << sizeof(il) << endl;

实现方式是前后两个指针,仅作了解。

7、默认构造、拷贝构造、赋值运算符重载、析构函数的简便实现写法

void empty_init()
{
_head = new Node;

_head->_next = _head;
_head->_prev = _head;
_size = 0;
}

// 默认构造
list()
{
empty_init();
}

// l2(l1) 拷贝构造
list(const list<T>& lt)
{
empty_init();

for (auto& e : lt)
{
push_back(e);
}
}

// C++11统一初始化的隐式类型转换
list(initializer_list<T>& lt)
{
empty_init();

for (auto& e : lt)
{
push_back(e);
}
}

// l2 = l1 赋值运算符重载
list<int>& operator= (list<T> lt)
{
swap(lt);
}

void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}

// 析构
~list()
{
clear();
delete _head;
_head = nullptr;
}

void clear()
{
auto it = begin();
while (it != end())
{
it = earse(it);
}
}

赞(0)
未经允许不得转载:171主机测评 » 【C++笔记】List流食般投喂
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址