看下日期,一共3個多月沒寫。再次“執筆”亦需下不少的決心。今天的不長,一來表明自己不會太監。二來亦需重回寫程序的狀態。老規矩,還是會用英文寫的。
2012-3-10(Reusing Code in C++ part I)
1. Big Picture
One of the main goals of C++ is to facilitate the reuse of code. Public inheritance is one method to achieve this goal, but not the only one. We discuss other means this chapter.
One of these means is to use class members that are themselves objects of another class. This is referred to as containment or composition or layering.Another option is to use private or protected inheritance. All of them(containment, private inheritance and protected inheritance) are typically used to implement has-a relationships—that is, relationships for which the new class has an object of anther class.
2.valarray Class
This class is targeted to deal with numeric values or with classes with similar properties. It have to be defined as a template class, so that it can process different kind of data. To use it, you need to know its constructors. For example:
(copy from:Click here to link)
int init[]= {10,20,30,40};
valarray<int> first; // (empty)
valarray<int> second (5); // 0 0 0 0
valarray<int> third (10,3); // 10 10 1
valarray<int> fourth (init,4); // 10 20 30 4
valarray<int> fifth (fourth); // 10 20 30 40
Note that you can create an array initialized using the values from an ordinary array. Next, some useful methods:operator []()//provides access to individual element
size() //returns the number of element
sum() //returns the sum of the element
… … //etc
3.
Using has-a relationship
The following task is to define a class named Student, whose private members are student's name and scores.They are both use the classes in STL(string and valarray). This Student class must have the following fundamental function:
- Create Student objects by different way;
- I/O system.
I. Header File Design
#ifndef STUDENT_H_
#define STUDNET_H_
#include <iostream>
#include <string>
#include <valarray>
using std::string;
using std::valarray;
using std::ostream;
using std::istream;
class Student
{
private:
typedef valarray<double> DbArray;
string name;
DbArray scores; //two contained object
void sco_out() const;
public:
Student(): name("Null"),scores() {} //default constructor
Student(const string& s): name(s),scores() {} //create student without score
explicit Student(int n): name("Null"),scores(n) {} //create student without name
Student(const string& s, int n): name(s),scores(n) {}//student have given name and empty score
//student have given name and given scores
Student(const string& s, DbArray d): name(s),scores(d) {} //by valarray object
Student(const string& s, const double* d, int n): name(s),scores(d,n) {} //by ordinary array
~Student(){}
/******** I/O system ********/
void setName(const string& s) {name = s; }
void setScores();
const string& Name() const;
double average() const;
double operator [](int i) const;
double& operator [](int i);
friend istream& operator >>(istream& is, Student& st); //accept one word
friend istream& getline(istream& is, Student& st); //accept one line
friend ostream& operator <<(ostream& os, Student& st);
};
#endif
i. Keyword typedef
The statement
typedef valarray<double> DbArray;
means enables the remaining code to use the more convenient notation DbArray instead of valarray<double>. Placing this typedef in the private portion of the class definition means that it can be used internally in the Student implementation but not by outside users of the Student class.
ii. Keyword explicit
Recall that a constructor that can be called with one argument serves as an implicit conversion function from the argument type to the class type. For example, one of the constructors of string class is string(const string& str).The following statement:
string str = "Example";
is equal to
string str = string("Example");
Supposed a programmer wanted to type the following code:
Student stu("Tom", 5);//create a student object
stu[2] = 85; //modify the value of stu.scores[2]
But this inattentive programmer typed stu = 85 instead of stu[5]= 85. If the constructor omitted explicit, the second statement will call constructor named Student(int n). Then assignment would replace the original stu with the temporary object. With explicit in place, the compiler will catch the assignment operator as an error. So the keyword explicit turns off implicit conversion.
iii. Initializer List
We had discussed about initializer list syntax in the inheritance portion. In short, initializer list allows you to use existent constructors(designed by yourself or others) to initialize members of object. For example:
Student(const string& s, const double* d, int n): name(s),scores(d,n) {}
Here, name(s) invokes the string(const char*) constructor and scores(d,n) invokes the valarray<double>(const double*,int) constructor.
iv. Initialization Order
Initializer list will be initialized in a certain order. If you type the following code:
Student(const string& s, const double* d, int n): scores(d,n),name(s) {}
The name member would still be initialized first because it is declared first in the class definition. It's not important for this example, but it would be important if the code used the value of one member as part of the initialization expression for a second member.
v. Private Function
Here, we declare a private function. Private functions can be only invoked by its “own” function(member function and friend). In this example, sco_out() is designed for printing scores in order.
II. Implementation Design
#include "Student.h"
using std::endl;
using std::cout;
using std::cin;
void Student::sco_out() const
{
int size = scores.size();
if(size == 0) cout << "Empty Score\n";
else
{
for(int i = 0; i< size; i++)
{
cout << scores[i] << " ";
if(i % 5 == 4) cout << endl;
}
if(size % 5 != 0) cout << endl;
}
}
void Student::setScores()
{
int size = scores.size();
cout << "Input " << name << "'s scores: \n";
for(int i = 0; i< size; i++)
{
cout << "#" << i + 1 << ": ";
cin >> scores[i];
}
}
const string& Student::Name() const
{
return name;
}
double Student::average() const
{
if (scores.size() == 0) return 0;
return scores.sum() / scores.size();
}
double Student::operator [](int i) const
{
return scores[i];
}
double& Student::operator [](int i)
{
return scores[i];
}
istream& operator >>(istream& is, Student& st)
{
is >> st.name;
return is;
}
istream& getline(istream& is, Student& st)
{
getline(is,st.name);
return is;
}
ostream& operator <<(ostream& os, Student& st)
{
os << st.name << "'s scores:\n";
st.sco_out();
return os;
}
Note that the function of double operator [](int)const
and double& operator [](int) are different. The former has the
const suffix, which means it can't change the value of any members. The latter return a reference, which allows you to modify members' value. More specifically:
cout << stu[2]; //invokes the former
stu[2] = 80; //invokes the latter
As you can see, the whole implementation is very short, but function of this class is intact.Using the initializer list, you don't have to redefined your own constructor .Almost every public interface have invoked functions that were defined by others.
III. Testing Design
#include <iostream>
#include "Student.h"
const int STUDENT_NUM = 6;
const int SCORES_NUM = 3;
using std::cin;
using std::cout;
using std::endl;
int main()
{
valarray<double> d_scores(SCORES_NUM);
d_scores[0] = 85; d_scores[1] = 90; d_scores[2] = 80;
double e_scores[SCORES_NUM] = {76, 83, 94};
Student stu[STUDENT_NUM] =
{
Student(), //default constructor
Student("a_stu"), //student without score
Student(SCORES_NUM), //student without name
Student("c_stu",SCORES_NUM), //student have given name and empty score
Student("d_stu",d_scores), //enter scores by valarray object
Student("e_stu",e_scores,SCORES_NUM) //enter scores by ordinary array
};
cout << "Initialization Situation:\n";
for(int i = 0; i< STUDENT_NUM; i++) cout << stu[i];
cout << "Enter the second student's name: "; getline(cin, stu[2]);
stu[2].setScores();
cout << "Modify the third student's name: "; cin >> stu[3];
stu[3].setScores();
cout << "The third score of " << stu[5].Name() << " is: " << stu[5][2] << endl;
stu[4][1] = 87;
cout << "Resault:\n";
for(int i = 0; i< STUDENT_NUM; i++)
{
cout << stu[i];
cout << "Average: " << stu[i].average();
cout << endl;
}
}