C++ STL 之 list(下)

本节我们将继续介绍 STL 中的 list 容器使用。



list 容器排序及合并元素

sort() 函数定义在头文件 algorithm 中,它要求使用随机访问迭代器,能在指定的迭代器范围内进行排序。

vector<int> data {1,5,0,3,2,4};
vector<int>::iterator begin_iter = begin(data);
auto end_iter = end(data);
sort(begin_iter,end_iter,compare);

compare 函数

bool compare(int a,int b){
    return (a>b)?true:false;
}

通过自定义函数对象或 lambda 表达式,我们可以实现更多的操作。
而 list 容器并不提供随机访问迭代器,只提供双向迭代器,因此不能对 list 中的元素使用 sort() 算法。但是,list 容器还是可以进行元素排序,因为 list 模板定义了自己的 sort() 函数。
sort() 有两个版本:
第一个版本的无参 sort() 函数将所有元素升序排列。
第二个版本的 sort() 接受一个函数对象或 lambda 表达式作为参数,这两种参数都定义一个断言用来比较两个元素。
无参 sort() 实现升序:

list<int> data {1,5,0,3,2,4};
data.sort();

data 的内容为:

{0,1,2,3,4,5}

实现降序排序:

list<int> data {1,5,0,3,2,4};
data.sort(greater<int> ());

这里使用了定义在头文件 functional 中的模板 greater<T>。这个模板定义了一个用来比较 T 类型对象的函数对象。如果第一个参数大于第二个参数,那么成员函数 operator()() 返回 true。头文件 functional 中定义了一些定义了断言的模板,在后面,我们会看到更多的断言。
排序执行完毕后,list 中的元素如下:

{5,4,3,2,1,0}

现在 data 中的元素是降序排列的。有一个简洁版的 greater<T> 断言,可以如下所示使用:

list<int> data {1,5,0,3,2,4};
data.sort(greater<> ());

效果和上面的一样。
当然,在必要时可以将自定义的函数对象传给断言来对 list 排序。尽管对一般对象来说,并不需要这样。如果为自己的类定义了 operator()(),然后就可以继续使用 greater<>。 当我们需要比较非默认类型时,就需要一个函数对象。

例如,假设我们想对 data 中的元素进行排序。
如果使用字符串对象标准比较方式:

list<string> data {"Janet", "Jane", "Hugo", "Jules","Jim","Hannah", "Ann", "Alan"};
//升序排列
data.sort();

data 的内容为:

{"Alan","Ann","Hannah","Hugo","Jane","Janet","Jim","Jules"}

但是不想使用字符串对象标准的 greater<> 来比较,而是想将相同初始字符的字符串按长度排序。可以如下所示定义一个类:

class my_greater{
public:
    bool operator () (const string s1, const string s2){
        if (s1[0] == s2 [0])
            return s1.length() > s2.length();
        else
            return s1 > s2;
    }
};

对 data 中的元素进行排序:

list<string> data {"Janet", "Jane", "Hugo", "Jules","Jim","Hannah", "Ann", "Alan"};
data.sort(my_greater());

data 的内容为:

{"Jules","Janet", "Jane", "Jim","Hannah","Hugo","Alan","Ann"}

显然两种方式得到的结果是不同的。
如果不想重用 my_greater() 断言,这里也可以使用 lambda 表达式。

list<string> data {"Janet", "Jane", "Hugo", "Jules","Jim","Hannah", "Ann", "Alan"};
data.sort([](const string s1, const string s2){
    if (s1[0] == s2[0])
        return s1.length() > s2.length();
    else
        return s1 > s2;
});

data 的内容为:

{"Jules","Janet", "Jane", "Jim","Hannah","Hugo","Alan","Ann"}

list 的成员函数 merge() 以另一个具有相同类型元素的 list 容器作为参数。两个容器中的元素都必须是升序。参数 list 容器中的元素会被合并到当前的 list 容器中。

list<int> data {1,3,5,7,9};
list<int> data_ {0,2,4,6,8};
data.merge(data_);

data 的内容为:

{0,1,2,3,4,5,6,7,8,9}

此时,data_ 的内容为空了。

元素从 data_ 转移到 data,因此,在执行完这个操作后,data_ 中就没有元素了。改变每个 list 节点的指针,在适当的位置将它们和当前容器的元素链接起来,这样就实现了元素的转移。list 节点在内存中的位置不会改变;只有链接它们的指针变了。在合并的过程中,两个容器中的元素使用 operator()() 进行比较。

在另一个版本的 merge() 函数中,可以提供一个比较函数作为该函数的第二个参数,用来在合并过程中比较元素。

list<string> data { "three","six", "eight"};
list<string> data_ { "seven", "four", "nine"};
auto comp_str = [](const string s1, const string s2){
    return s1[0]<s2[0];
};

对 data 排序后

data.sort (comp_str);

data 的内容为:

{"eight","six","three"}

对 data_ 排序后

data_.sort (comp_str); 

data_ 的内容为:

{"four", "nine", "seven"}

data 和 data_ 合并后到 data :

data.merge (data_, comp_str);

data 的内容为:

{"eight", "four", "nine", "six", "seven", "three"}

这里的字符串对象比较函数是由 lambda 表达式定义的,这个表达式只比较第一个字符。比较的效果是,在合并的 list 容器中, "six" 在 "seven" 之前。在上面的代码中,也可以无参调用 merge(),这样 "seven" 会在 "six" 之前,这是一般的排序。

list 容器的成员函数 splice() 有几个重载版本。这个函数将参数 list 容器中的元素移动到当前容器中指定位置的前面。可以移动单个元素、一段元素或源容器的全部元素。
下面是一个剪切单个元素的示例:

list<string> data { "three","six", "eight"};
list<string> data_ { "seven", "four", "nine"};
auto begin_iter = begin(data);
list<string>::iterator begin_iter_ = begin(data_);
advance(begin_iter,1);
advance(begin_iter_,1);
data.splice(begin_iter,data_,begin_iter_);

splice() 的第一个参数是指向目的容器的迭代器。第二个参数是元素的来源。第三个参数是一个指向源list容器中被粘接元素的迭代器,它会被插入到第一个参数所指向位置之前。代码执行完中后,容器中的内容如下:

data_ : {"seven", "nine"}
data : {"three", "four", "six", "eight"}

当要粘接源 list 容器中的一段元素时,第 3 和第 4 个参数可以定义这段元素的范围。 例如:

list<string> data { "three","six", "eight"};
list<string> data_ { "seven", "four", "nine"};
auto begin_iter = begin(data);
list<string>::iterator begin_iter_ = begin(data_);
auto end_iter_ = end(data_);
advance(begin_iter,1);
advance(begin_iter_,1);
advance(end_iter_,0);
data.splice(begin_iter,data_,begin_iter_,end_iter_);

上面的代码会将 my_words 从第二个元素直到末尾的元素,粘接到 your_words 的第二个元素之前。
两个 list 容器的内容如下:

data_ : {"three"}
data : {"seven", "four", "six", "eight","nine"}

下面的语句可以将 data_ 的全部元素粘接到 data 中:

list<string> data { "three","six", "eight"};
list<string> data_ { "seven", "four", "nine"};
auto begin_iter = begin(data);
data.splice(begin_iter,data_);

两个 list 容器的内容如下:

data_ : {}
data : {"seven", "four","nine", "three", "six", "eight"}

现在即使 data_ 里面没有任何元素,依然可以执行合并操作。

list 容器访问元素

list 的成员函数 front() 和 back(),可以各自返回第一个和最后一个元素的引用。在空 list 中调用它们中的任意一个,结果是未知的,因此不要这样使用。可以通过迭代器的自增或自减来访问 list 的内部元素。

正如我们所了解的那样,begin() 和 end() 分别返回的是指向第一个和最后一个元素下一个位置的双向迭代器。rbegin() 和 rend() 函数返回的双向迭代器,可以让我们逆序遍历元素。
使用 begin() 和 end() 迭代器:

auto begin_iter = begin(data);
auto end_iter = end(data);
while(begin_iter!=end_iter){
    cout<<*begin_iter<<" ";
    begin_iter++;
}

结果如下:

one two three four

使用 rbegin() 和 rend() 迭代器:

auto begin_iter = rbegin(data);
auto end_iter = rend(data);
while(begin_iter!=end_iter){
    cout<<*begin_iter<<" ";
    begin_iter++;
}

结果如下:

four three two one

也可以不使用迭代器:

for(auto d : data){
    cout<<d<<" ";
}

结果如下:

one two three four

结合 list 容器元素的插入操作,示例如下:

list<string> data {"one","two","three","four"};
string goal("zeros");
//在容器头部增加一个 "zeros"
data.emplace_front(goal);
goal = "five";
//在容器尾部增加一个 "five"
data.emplace_back(goal);
auto iter = begin(data);
// 移动迭代器
advance(iter,2);
goal = "one_two";
//在指定位置插入 ""one_two"
data.emplace(iter,goal);
iter = end(data);
//在容器最后处增加 "AAA"
data.emplace(iter,3,'A');

采用不使用迭代器的方式,每一步的操作,data 的内容为:

data : {"zeros","one","two","three","four"}
data : {"zeros","one","two","three","four","five"}
data : {"zeros","one","one_two","two","three","four","five"}
data : {"zeros","one","one_two","two","three","four","five","AAA"}

sort() 里面的断言是十分强大的,经常用到的断言,包括 greater_equal<>()、less<>() 和 less_equal<>()等,这里就不作展开了。
至此 list 容器的使用就暂告一段落了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容