⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 chapter12.html

📁 think like a computer scientist
💻 HTML
📖 第 1 页 / 共 2 页
字号:
<HTML><HEAD>  <TITLE>Chapter 12</TITLE>  <LINK REL="STYLESHEET" HREF="downey.css" tppabs="http://rocky.wellesley.edu/downey/ost/thinkCS/c++_html/downey.css"></HEAD><BODY><H2>Chapter 12</H2><H1>Vectors of Objects</H1><H3>12.1 Composition</H3><P>By now we have seen several examples of composition (the ability to combine language features in a variety of arrangements). One of the first examples we saw was using a function invocation as part of an expression. Another example is the nested structure of statements: you can put an <tt>if</tt> statement within a <TT>while</TT> loop, or within another <TT>if</TT> statement, etc.</P><P>Having seen this pattern, and having learned about vectors and objects, you should not be surprised to learn that you can have vectors of objects. In fact,you can also have objects that contain vectors (as instance variables); you canhave vectors that contain vectors; you can have objects that contain objects, and so on.</P><P>In the next two chapters we will look at some examples of these combinations,using <TT>Card</TT> objects as a case study.</P><BR><BR><H3>12.2 <TT>Card</TT> objects</H3><P>If you are not familiar with common playing cards, now would be a good time to get a deck, or else this chapter might not make much sense. There are 52 cards in a deck, each of which belongs to one of four suits and one of 13 ranks. The suits are Spades, Hearts, Diamonds and Clubs (in descending order inBridge). The ranks are Ace, 2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen and King. Depending on what game you are playing, the rank of the Ace may be higher than King or lower than 2.</P><P>If we want to define a new object to represent a playing card, it is pretty obvious what the instance variables should be: <TT>rank</TT> and <TT>suit</TT>.It is not as obvious what type the instance variables should be. One possibility is <TT>apstring</TT>s, containing things like <TT>"Spade"</TT> for suits and <TT>"Queen"</TT> for ranks. One problem with this implementation is that it would not be easy to compare cards to see which had higher rank or suit.</P><P>An alternative is to use integers to <B>encode</B> the ranks and suits. By ``encode,'' I do not mean what some people think, which is to encrypt, or translate into a secret code. What a computer scientist means by ``encode'' is something like ``define a mapping between a sequence of numbers and the things I want to represent.'' For example,</P><PRE>    Spades   -->  3     Hearts   -->  2    Diamonds -->  1    Clubs    -->  0</PRE><P>The symbol <TT>--></TT> is mathematical notation for ``maps to.'' The obvious feature of this mapping is that the suits map to integers in order, so we can compare suits by comparing integers. The mapping for ranks is fairly obvious; each of the numerical ranks maps to the corresponding integer, and forface cards:</P><PRE>    Jack   -->  11    Queen  -->  12    King   -->  13</PRE><P>The reason I am using mathematical notation for these mappings is that they are not part of the C++ program. They are part of the program design, but they never appear explicitly in the code. The class definition for the <TT>Card</TT>type looks like this:</P><PRE>struct Card{  int suit, rank;  Card ();  Card (int s, int r);};Card::Card () {   suit = 0;  rank = 0;}Card::Card (int s, int r) {   suit = s;  rank = r;}</PRE><P>There are two constructors for <TT>Card</TT>s. You can tell that they are constructors because they have no return type and their name is the same as thename of the structure. The first constructor takes no arguments and initializesthe instance variables to a useless value (the zero of clubs).</P><P>The second constructor is more useful. It takes two parameters, the suit andrank of the card.</P><P>The following code creates an object named <TT>threeOfClubs</TT> that represents the 3 of Clubs:</P><PRE>   Card threeOfClubs (0, 3);</PRE><P>The first argument, <TT>0</TT> represents the suit Clubs, the second, naturally, represents the rank 3.<P><BR><BR><H3>12.3 The <TT>printCard</TT> function</H3><P>When you create a new type, the first step is usually to declare the instance variables and write constructors. The second step is often to write a function that prints the object in human-readable form.</P><P>In the case of <TT>Card</TT> objects, ``human-readable'' means that we have to map the internal representation of the rank and suit onto words. A natural way to do that is with a vector of <TT>apstring</TT>s. You can create a vector of <TT>apstring</TT>s the same way you create an vector of other types:</P><PRE>  apvector<apstring> suits (4);</PRE><P>Of course, in order to use <TT>apvector</TT>s and <TT>apstring</TT>s, you will have to include the header files for both.<BLOCKQUOTE>note: <TT>apvector</TT>s are a little different from <TT>apstring</TT>s in thisregard. The file <TT>apvector.cpp</TT> contains a template that allows the compiler to create vectors of various kinds. The first time you use a vector ofintegers, the compiler generates code to support that kind of vector. If you use a vector of <TT>apstring</TT>s, the compiler generates different code to handle that kind of vector. As a result, it is usually sufficient to include the header file <TT>apvector.h</TT>; you do not have to compile <TT>apvector.cpp</TT> at all!  Unfortunately, if you do, you are likely to get a long stream of error messages. I hope this note helps you avoid an unpleasantsurprise, but the details in your development environment may differ.</BLOCKQUOTE><P>To initialize the elements of the vector, we can use a series of assignment statements.</P><PRE>  suits[0] = "Clubs";  suits[1] = "Diamonds";  suits[2] = "Hearts";  suits[3] = "Spades";</PRE><P>A state diagram for this vector looks like this:</P><P CLASS=1><IMG SRC="images/apstringvector.png" tppabs="http://rocky.wellesley.edu/downey/ost/thinkCS/c++_html/images/apstringvector.png"></P><P>We can build a similar vector to decode the ranks. Then we can select the appropriate elements using the <TT>suit</TT> and <TT>rank</TT> as indices. Finally, we can write a function called <TT>print</TT> that outputs the card onwhich it is invoked:</P><PRE>void Card::print () const{  apvector<apstring> suits (4);  suits[0] = "Clubs";  suits[1] = "Diamonds";  suits[2] = "Hearts";  suits[3] = "Spades";  apvector<apstring> ranks (14);  ranks[1] = "Ace";  ranks[2] = "2";  ranks[3] = "3";  ranks[4] = "4";  ranks[5] = "5";  ranks[6] = "6";  ranks[7] = "7";  ranks[8] = "8";  ranks[9] = "9";  ranks[10] = "10";  ranks[11] = "Jack";  ranks[12] = "Queen";  ranks[13] = "King";  cout << ranks[rank] << " of " << suits[suit] << endl;}</PRE><P>The expression <TT>suits[suit]</TT> means ``use the instance variable <TT>suit</TT> from the current object as an index into the vector named <TT>suits</TT>, and select the appropriate string.''</P><P>Because <TT>print</TT> is a <TT>Card</TT> member function, it can refer to the instance variables of the current object implicitly (without having to use dot notation to specify the object). The output of this code</P><PRE>  Card card (1, 11);  card.print ();</PRE><P>is <TT>Jack of Diamonds</TT>.</P><P>You might notice that we are not using the zeroeth element of the <TT>ranks</TT> vector. That's because the only valid ranks are 1--13. By leaving an unused element at the beginning of the vector, we get an encoding where 2 maps to ``2'', 3 maps to ``3'', etc. From the point of view of the user, it doesn't matter what the encoding is, since all input and output uses human-readable formats. On the other hand, it is often helpful for the programmer if the mappings are easy to remember.</P><BR><BR><H3>12.4 The <TT>equals</TT> function</H3><P>In order for two cards to be equal, they have to have the same rank and the same suit. Unfortunately, the <TT>==</TT> operator does not work for user-defined types like <TT>Card</TT>, so we have to write a function that compares two cards. We'll call it <TT>equals</TT>. It is also possible to writea new definition for the <TT>==</TT> operator, but we will not cover that in this book.</P><P>It is clear that the return value from <TT>equals</TT> should be a boolean that indicates whether the cards are the same. It is also clear that there haveto be two <TT>Card</TT>s as parameters. But we have one more choice: should <TT>equals</TT> be a member function or a free-standing function?</P><P>As a member function, it looks like this:</P><PRE>bool Card::equals (const Card& c2) const{  return (rank == c2.rank && suit == c2.suit);}</PRE><P>To use this function, we have to invoke it on one of the cards and pass the other as an argument:</P><PRE>  Card card1 (1, 11);  Card card2 (1, 11);  if (card1.equals(card2)) {    cout << "Yup, that's the same card." << endl;  }</PRE><P>This method of invocation always seems strange to me when the function is something like <TT>equals</TT>, in which the two arguments are symmetric. What I mean by symmetric is that it does not matter whether I ask ``Is A equal to B?'' or ``Is B equal to A?''  In this case, I think it looks better to rewrite<TT>equals</TT> as a nonmember function:</P><PRE>bool equals (const Card& c1, const Card& c2){  return (c1.rank == c2.rank && c1.suit == c2.suit);}</PRE><P>When we call this version of the function, the arguments appear side-by-sidein a way that makes more logical sense, to me at least.</P><PRE>  if (equals (card1, card2)) {    cout << "Yup, that's the same card." << endl;  }</PRE><P>Of course, this is a matter of taste. My point here is that you should be comfortable writing both member and nonmember functions, so that you can choosethe interface that works best depending on the circumstance.</P><BR><BR><H3>12.5 The <TT>isGreater</TT> function</H3><P>For basic types like <TT>int</TT> and <TT>double</TT>, there are comparisonoperators that compare values and determine when one is greater or less than another. These operators (<TT><</TT> and <TT>></TT> and the others) don't work for user-defined types. Just as we did for the <TT>==</TT> operator, we will write a comparison function that plays the role of the <TT>></TT> operator.Later, we will use this function to sort a deck of cards.</P><P>Some sets are totally ordered, which means that you can compare any two elements and tell which is bigger.  For example, the integers and the floating-point numbers are totally ordered.  Some sets are unordered, which means that there is no meaningful way to say that one element is bigger than another. For example, the fruits are unordered, which is why we cannot compare apples and oranges. As another example, the <TT>bool</TT> type is unordered; wecannot say that <TT>true</TT> is greater than <TT>false</TT>.</P><P>The set of playing cards is partially ordered, which means that sometimes wecan compare cards and sometimes not. For example, I know that the 3 of Clubs ishigher than the 2 of Clubs because it has higher rank, and the 3 of Diamonds ishigher than the 3 of Clubs because it has higher suit. But which is better, the3 of Clubs or the 2 of Diamonds? One has a higher rank, but the other has a higher suit.</P><P>In order to make cards comparable, we have to decide which is more important,rank or suit. To be honest, the choice is completely arbitrary. For the sake ofchoosing, I will say that suit is more important, because when you buy a new deck of cards, it comes sorted with all the Clubs together, followed by all theDiamonds, and so on.</P>  <P>With that decided, we can write <TT>isGreater</TT>. Again, the arguments (two <TT>Card</TT>s) and the return type (boolean) are obvious, and again we have to choose between a member function and a nonmember function. This time, the arguments are not symmetric. It matters whether we want to know ``Is A greater than B?'' or ``Is B greater than A?'' Therefore I think it makes more sense to write <TT>isGreater</TT> as a member function:</P><PRE>bool Card::isGreater (const Card& c2) const{  // first check the suits  if (suit > c2.suit) return true;  if (suit < c2.suit) return false;  // if the suits are equal, check the ranks  if (rank > c2.rank) return true;  if (rank < c2.rank) return false;  // if the ranks are also equal, return false  return false;}</PRE><P>Then when we invoke it, it is obvious from the syntax which of the two possible questions we are asking:</P><PRE>  Card card1 (2, 11);  Card card2 (1, 11);  if (card1.isGreater (card2)) {    card1.print ();    cout << "is greater than" << endl;    card2.print ();  }</PRE><P>You can almost read it like English: ``If card1 isGreater card2 ...'' The output of this program is</P><PRE>Jack of Heartsis greater thanJack of Diamonds</PRE><P>According to <TT>isGreater</TT>, aces are less than deuces (2s). As an exercise, fix it so that aces are ranked higher than Kings, as they are in mostcard games.</P><BR><BR><H3>12.6 Vectors of cards</H3><P>The reason I chose <TT>Cards</TT> as the objects for this chapter is that there is an obvious use for a vector of cards---a deck. Here is some code that creates a new deck of 52 cards:</P><PRE>  apvector<Card> deck (52);</PRE><P>Here is the state diagram for this object:</P><P CLASS=1><IMG SRC="images/cardvector.png" tppabs="http://rocky.wellesley.edu/downey/ost/thinkCS/c++_html/images/cardvector.png"></P><P>The three dots represent the 48 cards I didn't feel like drawing. Keep in mind that we haven't initialized the instance variables of the cards yet. In some environments, they will get initialized to zero, as shown in the figure, but in others they could contain any possible value.</P><P>One way to initialize them would be to pass a <TT>Card</TT> as a second argument to the constructor:</P>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -