第十六天(C++代碼重用)

      看下日期,一共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;
     }
}
 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章