16 string类和标准模板库

3 标准模板库

The STL provides a collection of templates representing containers, iterators, function objects,and algorithms.A container is a unit, like an array, that can hold several values. STL containers are homogeneous; that is, they hold values all of the same kind.Algorithms are recipes for accomplishing particular tasks, such as sorting an array or finding a particular value in a list. Iterators are objects that let you move through a container much as pointers let you move through an array; they are generalizations of pointers. Function objects are objects that act like functions; they can be class objects or function pointers (including function names because a function name acts as a pointer).The STL lets you construct a variety of containers, including arrays, queues,and lists,and it lets you perform a variety of operations, including searching, sorting,and randomizing.

Alex Stepanov and Meng Lee developed STL at Hewlett-Packard Laboratories, releasing the implementation in 1994.The ISO/ANSI C++ committee voted to incorporate it as a part of the C++ Standard.The STL is not an example of object-oriented programming. Instead, it represents a different programming paradigm called generic programming. This makes STL interesting both in terms of what it does and in terms of its approach.

3.1 The vector Template Class

Chapter 4 touched briefly on the vector class.We’ll look more closely at it now. A computing-style vector holds a set of like values that can be accessed randomly.That is, you can use, say,an index to directly access the 10th element of a vector without having to access the preceding 9 elements first. So a vector class would provide operations similar to those of the valarray and ArrayTP classes introduced in Chapter 14 and to those of the array class introduced in Chapter 4.That is, you could create a vector object,assign one vector object to another,and use the [] operator to access
vector elements.To make the class generic, you make it a template class.That’s what the STL does, defining a vector template in the vector (formerly vector.h) header file.

To create a vector template object, you use the usual <type> notation to indicate the type to be used.Also the vector template uses dynamic memory allocation,and you can use an initialization argument to indicate how many vector elements you want:

#include vector
using namespace std;
vector<int> ratings(5); // a vector of 5 ints
int n;
cin >> n;
vector<double> scores(n); // a vector of n doubles

After you create a vector object, operator overloading for [] makes it possible to use the usual array notation for accessing individual elements:

ratings[0] = 9;
for (int i = 0; i < n; i++)
	cout << scores[i] << endl;

Allocators Again
Like the string class, the various STL container templates take an optional template argument that specifies what allocator object to use to manage memory. For example, the vector template begins like this:
template <class T, class Allocator = allocator<T> >
class vector {...
If you omit a value for this template argument, the container template uses the allocator<T> class by default. This class uses new and delete.

Listing 16.7 uses this class in an undemanding application.This particular program creates two vector objects, one an int specialization and one a string specialization; each has five elements.

Listing 16.7 vect1.cpp

// vect1.cpp -- introducing the vector template
#include <iostream>
#include <string>
#include <vector>

const int NUM = 5;
int main(){
	using std::vector;
	using std::string;
	using std::cin;
	using std::cout;
	using std::endl;
	
	vector<int> ratings(NUM);
	vector<string> titles(NUM);
	cout << "You will do exactly as told. You will enter\n"
		 << NUM << " book titles and your ratings (0-10).\n";
	int i;
	for (i = 0; i < NUM; i++){
		cout << "Enter title #" << i + 1 << ": ";
		getline(cin,titles[i]);
		cout << "Enter your rating (0-10): ";
		cin >> ratings[i];
		cin.get();
	}
	cout << "Thank you. You entered the following:\n"
		 << "Rating\tBook\n";
	for (i = 0; i < NUM; i++){
		cout << ratings[i] << "\t" << titles[i] << endl;
	}
	return 0;
}

Here’s a sample run of the program in Listing 16.7:

You will do exactly as told. You will enter
5 book titles and your ratings (0-10).
Enter title #1: The Cat Who Knew C++
Enter your rating (0-10): 6
Enter title #2: Felonious Felines
Enter your rating (0-10): 4
Enter title #3: Warlords of Wonk
Enter your rating (0-10): 3
Enter title #4: Don't Touch That Metaphor
Enter your rating (0-10): 5
Enter title #5: Panic Oriented Programming
Enter your rating (0-10): 8
Thank you. You entered the following:
Rating Book
6 The Cat Who Knew C++
4 Felonious Felines
3 Warlords of Wonk
5 Don't Touch That Metaphor
8 Panic Oriented Programming

All this program does is use the vector template as a convenient way to create a dynamically allocated array.The next section shows an example that uses more of the class methods.

3.2 Things to Do to Vectors

Besides allocating storage, what else can the vector template do for you? All the STL containers provide certain basic methods, including size(), which returns the number of elements in a container, swap(), which exchanges the contents of two containers, begin(), which returns an iterator that refers to the first element in a container,and
end(), which returns an iterator that represents past-the-end for the container.

What’s an iterator? It’s a generalization of a pointer. In fact, it can be a pointer. Or it can be an object for which pointer-like operations such as dereferencing (for example, operator*()) and incrementing (for example, operator++()) have been defined.As you’ll see later, generalizing pointers to iterators allows the STL to provide a uniform
interface for a variety of container classes, including ones for which simple pointers wouldn’t work. Each container class defines a suitable iterator.The type name for this iterator is a class scope typedef called iterator. For example, to declare an iterator for a
type double specialization of vector, you would use this:

vector<double>::iterator pd; // pd an iterator

Suppose scores is a vector<double> object:

vector<double> scores;

Then you can use the iterator pd in code like the following:

pd = scores.begin(); 	// have pd point to the first element
*pd = 22.3; 			// dereference pd and assign value to first element
++pd; 					// make pd point to the next element

As you can see,an iterator behaves like a pointer. By the way, here’s another place the C++11 automatic type deduction can be useful. Instead of, say,

vector<double>::iterator pd = scores.begin();

you can use this:

auto pd = scores.begin(); // C++11 automatic type deduction

4 Generic Programming

4.1 Why Iterators?
4.2 Kinds of Iterators
4.2.1 Input Iterators
4.2.2 Output Iterators
4.2.3 Forward Iterators
4.2.4 Bidirectional Iterators
4.2.5 Random Access Iterators
4.3 Iterator Hierarchy
4.4 Concepts, Refinements, and Models
4.4.1 The Pointer As Iterator
4.4.2 Other Useful Iterators
4.5 Kinds of Containers

The STL has both container concepts and container types.The concepts are general categories with names such as container, sequence container,and associative container.The container types are templates you can use to create specific container objects.The original 11 container types are deque, list, queue, priority_queue, stack, vector, map, multimap, set, multiset,and bitset. (This chapter doesn’t discuss bitset, which is a container for dealing with data at the bit level.) C++11 adds forward_list, unordered_map, unordered_multimap, unordered_set,and unordered_multiset,and it
moves bitset from the container category into its own separate category. Because the concepts categorize the types, let’s start with them.

4.5.1 Container Concepts

No type corresponds to the basic container concept, but the concept describes elements common to all the container classes. It’s sort of a conceptual abstract base class—conceptual because the container classes don’t actually use the inheritance mechanism. Or to put it another way, the container concept lays down a set of requirements that all STL container classes must satisfy.

A container is an object that stores other objects, which are all of a single type.The stored objects may be objects in the OOP sense, or they may be values of built-in types. Data stored in a container is owned by the container.That means when a container expires, so does the data stored in the container. (However, if the data are pointers, the pointed-to data does not necessarily expire.)

You can’t store just any kind of object in a container. In particular, the type has to be copy constructable and assignable. Basic types satisfy these requirements,as do class types—unless the class definition makes one or both of the copy constructor and the assignment
operator private or protected. (C++11 refines the concepts,adding terms such as CopyInsertable and MoveInsertable, but we’ll take a more simplified, if less precise, overview.)

The basic container doesn’t guarantee that its elements are stored in any particular order or that the order doesn’t change, but refinements to the concept may add such guarantees.All containers provide certain features and operations.Table 16.5 summarizes several of these common features. In the table, X represents a container type (such as vector), T represents the type of object stored in the container, a and b represent values of type X, r is a value of type X&,and u represents an identifier of type X (that is, if X represents vector<int>, then u is a vector<int> object).

Table 16.5 Some Basic Container Properties

ExpressionReturn TypeDescriptionComplexityExample
X::iteratorIterator type pointing to TAny iterator category satisfying forward iterator requirementsCompile time
X::value_typeTThe type for TCompile time
X u;Creates an empty container called uConstantvector<int> tmp

待补充

在这里插入图片描述
在这里插入图片描述

The Complexity column in Table 16.5 describes the time needed to perform an operation.This table lists three possibilities, which, from fastest to slowest,are as follows:

  • Compile time
  • Constant time
  • Linear time

If the complexity is compile time, the action is performed during compilation and uses no execution time. A constant complexity means the operation takes place during runtime but doesn’t depend on the number of elements in an object.A linear complexity means the time is proportional to the number of elements.Thus, if a and b are containers, a == b has linear complexity because the == operation may have to be applied to each element of the container. Actually, that is a worst-case scenario. If two containers have different sizes, no individual comparisons need to be made

4.5.2 C++ 11新增的容器要求

在这里插入图片描述

4.5.3 序列

You can refine the basic container concept by adding requirements.The sequence is an important refinement because several of the STL container types—deque, forward_list (C++11), list, queue, priority_queue, stack,and vector—are sequences. (Recall that
a queue allows elements to be added at the rear end and removed from the front.A double-ended queue, represented by deque,allows addition and removal at both ends.) The requirement that the iterator be at least a forward iterator guarantees that the elements are
arranged in a definite order that doesn’t change from one cycle of iteration to the next. The array class also is classified as a sequence container,although it doesn’t satisfy all the requirements.

The sequence also requires that its elements be arranged in strict linear order.That is, there is a first element, there is a last element,and each element but the first and last has exactly one element immediately ahead of it and one element immediately after it. An array and a linked list are examples of sequences, whereas a branching structure (in which each node points to two daughter nodes) is not.

Because elements in sequence have a definite order, operations such as inserting values at a particular location and erasing a particular range become possible.Table 16.7 lists these and other operations required of a sequence.The table uses the same notation as Table
16.5, with the addition of t representing a value of type T—that is, the type of value stored in the container, of n,an integer,and of p, q, i,and j, representing iterators.

Table 16.7 Sequence Requirements

在这里插入图片描述
Because the deque, list, queue, priority_queue, stack,and vector template classes are all models of the sequence concept, they all support the operators in Table 16.7. In addition, there are operations that are available to some of these six models. When allowed, they have constant-time complexity.Table 16.8 lists these additional operations.

Table 16.8 Optional Sequence Requirements

ExpressionReturn TypeMeaningContainer
a.front()T&*a.begin()vector, list, deque
a.back()T&*–a.end()vector, list, deque
a.push_front(t)voida.insert(a.begin(), t)list, deque
a.push_back(t)void a.insert(a.end(), t)vector, list, deque

在这里插入图片描述

Table 16.8 merits a comment or two. First, notice that a[n] and a.at(n) both return a
reference to the nth element (numbering from 0) in a container.The difference between
the two is that a.at(n) does bounds checking and throws an out_of_range exception if
n is outside the valid range for the container. Next, you might wonder why, say,
push_front() is defined for list and deque and not for vector. Suppose you want to
insert a new value at the front of a vector of 100 elements.To make room, you have to
move element 99 to position 100,and then you have to move element 98 to position 99,
and so on.This is an operation with linear-time complexity because moving 100 elements
would take 100 times as long as moving a single element. But the operations in Table 16.8
are supposed to be implemented only if they can be performed with constant-time complexity.The design for lists and double-ended queues, however,allows an element to be
added to the front without moving the other elements to new locations, so they can
implement push_front() with constant-time complexity. Figure 16.4 illustrates
push_front() and push_back().

(1) vector

You’ve already seen several examples using the vector template, which is declared in the vector header file. In brief, vector is a class representation of an array.The class provides automatic memory management that allows the size of a vector object to vary dynamically, growing and shrinking as elements are added or removed. It provides random access to elements. Elements can be added to or removed from the end in constant time, but insertion and removal from the beginning and the middle are linear-time operations.

In addition to being a sequence,a vector container is also a model of the reversible container concept.This adds two more class methods: rbegin() returns an iterator to the first element of the reversed sequence,and rend() returns a past-the-end iterator for the reversed sequence. So if dice is a vector<int> container and Show(int) is a function that displays an integer, the following code displays the contents of dice first in forward order and then in reverse order:

for_each(dice.begin(), dice.end(), Show); 		// display in order
cout << endl;
for_each(dice.rbegin(), dice.rend(), Show); 	// display in reversed order
cout << endl;

The iterator returned by the two methods is of a class scope type reverse_iterator. Recall that incrementing such an iterator causes it to move through a reversible container in reverse order.

The vector template class is the simplest of the sequence types and is considered the type that should be used by default unless the program requirements are better satisfied by the particular virtues of the other types.

(2) deque

待补充 1028

(7) queue

The queue template class (declared in the queue—formerly queue.h—header file) is an adapter class. Recall that the ostream_iterator template is an adapter that allows an output stream to use the iterator interface. Similarly, the queue template allows an underlying
class (deque, by default) to exhibit the typical queue interface.

The queue template is more restrictive than deque. Not only does it not permit random access to elements of a queue, the queue class doesn’t even allow you to iterate through a queue. Instead, it limits you to the basic operations that define a queue.You can add an element to the rear of a queue, remove an element from the front of a queue, view the values of the front and rear elements, check the number of elements,and test to see if a queue is empty.Table 16.10 lists these operations.

Note that pop() is a data removal method, not a data retrieval method. If you want to use a value from a queue, you first use front() to retrieve the value and then use pop() to remove it from the queue.

Table 16.10 queue Operations

MethodDescription
bool empty() constReturns true if the queue is empty and false otherwise.
size_type size() constReturns the number of elements in the queue.
T& front()Returns a reference to the element at the front of the queue.
T& back()Returns a reference to the element at the front of the queue.
void push(const T& x)Inserts x at the back of the queue.
void pop()Removes the element at the front of the queue.

(8) priority_queue

priority_queue模板类(在queue头文件中声明)是另一个适配器类,它支持的操作与queue相同。两者之间的主要区别在于,在priority_queue中,最大的元素被移到队首。内部区别在于,默认的底层类是vector。可以修改用于确定哪个元素放到队首的比较方式,方法是提供一个可选的构造函数参数:

在这里插入图片描述

一个使用优先队列的例子:

https://leetcode.cn/problems/merge-k-sorted-lists/

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */

 struct cmp{
     bool operator()(const ListNode *a,const ListNode *b){
         return a->val>b->val;
     }
};

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        ListNode *tmp=new ListNode(-1);
        ListNode *head=tmp;
        priority_queue<ListNode*,vector<ListNode*>,cmp> q;
        for(int i=0;i<lists.size();i++){
            if(lists[i]!=nullptr){
                q.push(lists[i]);
            }
        }

        while(!q.empty()){
            tmp->next=q.top();
            if(q.top()->next!=nullptr){
                q.push(q.top()->next);
            }  
            q.pop();
            tmp=tmp->next;
        }

        return head->next;
    }
};

(9) stack

与queue相似,stack也是一个适配器类,它给底层类(默认情况下为vector)提供了典型的栈接口。

stack模板的限制比vector更多。它不仅不允许随机访问栈元素,甚至不允许遍历栈。将使用限制在定义栈的基本操作上,即可以从栈顶压入数据,从栈顶弹出元素、查看栈的值、检查元素数目和测试栈似乎否为空,表16.11列出了这些操作:

在这里插入图片描述
与queue相似,如果要使用栈中的值,必须首先使用top()来检索这个值,然后使用pop()将它从栈中删除。

4.6 Associative Containers

An associative container is another refinement of the container concept.An associative container associates a value with a key and uses the key to find the value. For example, the
values could be structures representing employee information, such as name,address, office number, home and work phones, health plan,and so on,and the key could be a unique employee number.To fetch the employee information,a program would use the key to locate the employee structure. Recall that for a container X, in general, the expression X::value_type indicates the type of value stored in the container. For an associative
container, the expression X::key_type indicates the type used for the key.

The strength of an associative container is that it provides rapid access to its elements. Like a sequence,an associative container allows you to insert new elements; however, you can’t specify a particular location for the inserted elements.The reason is that an associative container usually has a particular algorithm for determining where to place data so that it can retrieve information quickly.

Associative containers typically are implemented using some form of tree.A tree is a data structure in which a root node is linked to one or two other nodes, each of which is linked to one or two nodes, thus forming a branching structure.The node aspect makes it relatively simple to add or remove a new data item, much as with a linked list. But compared to a list,a tree offers much faster search times.

The STL provides four associative containers: set, multiset, map,and multimap.The first two types are defined in the set header file (formerly separately in set.h and multiset.h),and the second two types are defined in the map header file (formerly separately in map.h and multimap.h).

The simplest of the bunch is set; the value type is the same as the key type,and the keys are unique, meaning there is no more than one instance of a key in a set. Indeed, for set, the value is the key.The multiset type is like the set type except that it can have more than one value with the same key. For example, if the key and value type are int, a multiset object could hold, say 1, 2, 2, 2, 3, 5, 7,and 7.

For the map type, the value type is different from the key type,and the keys are unique, with only one value per key.The multimap type is similar to map, except one key can be
associated with multiple values.

There’s too much information about these types to cover in this chapter (but Appendix G does list the methods), so let’s just look at a simple example that uses set and a simple example that uses multimap.

4.6.1 A set Example

待补充 1034

4.6.2 A multimap Example

Like set, multimap is a reversible, sorted,associative container. However, with multimap, the key type is different from the value type,and a multimap object can have more than one value associated with a particular key.

The basic multimap declaration specifies the key type and the type of value, stored as template arguments. For example, the following declaration creates a multimap object that uses int as the key type and string as the type of value stored:

multimap<int,string> codes;

An optional third template argument can be used to indicate a comparison function or an object to be used to order the key. By default, the less<> template (discussed later) is used with the key type as its parameter. Older C++ implementations may require this template parameter explicitly.

To keep information together, the actual value type combines the key type and the data type into a single pair.To do this, the STL uses a pair<class T, class U> template class for storing two kinds of values in a single object. If keytype is the key type and datatype is the type of the stored data, the value type is pair<const keytype, datatype>. For example, the value type for the codes object declared earlier is pair<const int, string>.

Suppose that you want to store city names, using the area code as a key.This happens to fit the codes declaration, which uses an int for a key and a string as a data type. One approach is to create a pair and then insert it into the multimap object:

pair<const int, string> item(213, "Los Angeles");
codes.insert(item);

Or you can create an anonymous pair object and insert it in a single statement:

codes.insert(pair<const int, string> (213, "Los Angeles"));

Because items are sorted by key, there’s no need to identify an insertion location.

Given a pair object, you can access the two components by using the first and second members:

pair<const int, string> item(213, "Los Angeles");
cout << item.first << ' ' << item.second << endl;

What about getting information about a multimap object? The count() member function takes a key as its argument and returns the number of items that have that key. The lower_bound() and upper_bound() member functions take a key and work as they
do for set.Also the equal_range() member function takes a key as its argument and returns iterators representing the range matching that key. In order to return two values, the method packages them into a pair object, this time with both template arguments being the iterator type. For example, the following would print a list of cities in the codes object with area code 718:

pair<multimap<KeyType, string>::iterator,
	 multimap<KeyType, string>::iterator> range
					= codes.equal_range(718);
cout << "Cities with area code 718:\n";
std::multimap<KeyType, std::string>::iterator it;
for (it = range.first; it != range.second; ++it)
cout << (*it).second << endl;

Declarations like those just listed helped motivate the C++11 automatic type deduction feature, which allows you to simplify the code as follows:

auto range = codes.equal_range(718);
cout << "Cities with area code 718:\n";
for (auto it = range.first; it != range.second; ++it)
cout << (*it).second << endl;

待补充 1039

5.3 自适应函数符和函数适配器

STL有5个相关的概念:自适应生成器(adaptable generator)、自适应一元函数(adaptable unary function)、自适应二元函数(adaptable binary function)、自适应谓词(adaptable predicate)和自适应二元谓词(adaptable binary predicate)。

函数符成为自适应的原因是,它携带了标识参数类型和返回类型的typedef成员。这些成员分别是result_type、first_argument_type和second_argument_type。例如,plus<int>对象的返回类型被标识为plus<int>::result_type,这是int的typedef。

函数自适应符的意义在于:函数适配器对象可以使用函数对象,并认为存在这些typedef成员。例如,接受一个自适应函数符参数的函数可以使用result_type成员来声明一个与函数的返回类型匹配的变量。

6 Algorithms

6.1 Algorithm Groups
6.2 General Properties of Algorithms
6.3 The STL and the string Class
6.4 Functions Versus Container Methods
6.5 Using the STL

The STL is a library whose parts are designed to work together.The STL components are tools, but they are also building blocks to create other tools. Let’s illustrate this with an example. Suppose you want to write a program that lets the user enter words.At the end, you’d like a record of the words as they were entered,an alphabetical list of the words used (capitalization differences ignored),and a record of how many times each
word was entered.To keep things simple, let’s assume that the input contains no numbers or punctuation.

7 Other Libraries

7.1 vector、valarray和array

Perhaps you are wondering why C++ has three array templates: vector, valarray,and array.These classes were developed by different groups for different purposes.The vector template class is part of a system of container classes and algorithms.The vector class supports container-oriented activities, such as sorting, insertion, rearrangement, searching, transferring data to other containers,and other manipulations.The valarray
class template, on the other hand, is oriented toward numeric computation,and it is not part of the STL. It doesn’t have push_back() and insert() methods, for example, but it does provide a simple, intuitive interface for many mathematical operations. Finally, array is designed as a substitute for the built-in array type, combining the compactness and efficiency of that type with a better, safer interface. Being of fixed size, array doesn’t support push_back() and insert(), but it does offer several other STL methods.These include begin(), end(), rbegin(),and rend(), making it easy to apply STL algorithms to array objects.

Suppose, for example, that you have these declarations:

vector<double> ved1(10), ved2(10), ved3(10);
array<double, 10> vod1, vod2, vod3;
valarray<double> vad1(10), vad2(10), vad3(10);

Furthermore,assume that ved1, ved2, vod1, vod2, vad1,and vad2 all acquire suitable values. Suppose you want to assign the sum of the first elements of two arrays to the first element of a third array,and so on.With the vector class, you would do this:

transform(ved1.begin(), ved1.end(), ved2.begin(), ved3.begin(),
plus<double>());

You can do the same with the array class:

transform(vod1.begin(), vod1.end(), vod2.begin(), vod3.begin(),
plus<double>());

However, the valarray class overloads all the arithmetic operators to work with valarray objects, so you would use this:

vad3 = vad1 + vad2; // + overloaded

Similarly, the following would result in each element of vad3 being the product of the corresponding elements in vad1 and vad2:

vad3 = vad1 * vad2; // * overloaded

待补充 1061

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值