Browse in : |
All
> Journal Columns
> Code Critique
All > Journals > CVu > 134 Any of these categories - All of these categories |
Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.
Title: Student Code Critique Competition 11
Author: Administrator
Date: 06 August 2001 13:15:47 +01:00 or Mon, 06 August 2001 13:15:47 +01:00
Summary:
Body:
Once again there was only a single entry. It is becoming increasingly difficult to deliver issues of C Vu with any sort of balanced content. When I suggest that ACCU should carefully consider whether it should continue with printed publication, I am greeted with howls of protest from those who have a hundred reasons why they think we should go on. However, when it comes down to it, if members do not contribute there will not be any issues. Editors take a pride in their work, and any editor worth his/her salt is not going to be happy with producing a gutless C Vu (i.e. just reports and reviews).
On to what this column is supposed to be about.
OK let us try something slightly different this time. Go back to my interpretation of the student's task (a class to manage the subjects taken and marks attained by a single student) and write a model answer. All types you need should be properly implemented (note that subjects and marks are types) in an appropriate fashion. Now explain clearly your reasons for the different design decisions you made.
Tim Pushman <jalal@gnomedia.com>
Well, confusion indeed! The original contributor obviously had his idea of what the specification was, the teacher had another idea, and then FG had yet another idea! I've chosen to continue with the original contributors idea of having a class that encapsulated a 'set of students scores', so arguably a better name for it would be StudentRecord or StudentScores. However, I've continued to call the class Student, in order to maintain some continuity with what has gone before.
First, let me restate the problem specification (Francis's version): "Implement a class to record an individual student's marks. The student may be taking up to 10 subjects. Subjects are identified by a subject number. Write a short program to test the class." Note that there is no indication of what context the resulting class will be used in, something I will look at later on as it will affect our implementation.
I will write some code based on the approach taken by the original student (including old-style C++), then at the end I will show how the specification can be implemented using the latest style of C++ coding.
Let me start off with the test bed, often a good starting point, because this clarifies what we require the class to do.
#include "student.h" int main() { Student s1; Student s2(1, 32); s1.add(2, 22); s2.add(0, 0); s2.add(1, 56); s2.add(2, 22); s1.output(); s2.output(); }
Here we create 2 students, one using the default constructor, the second using the parameter constructor. Then we add some values and then ask the objects to print themselves out. I have simplified a bit here, as there would be some further tests that could be run.
Here is a possible implementation of the Student class:
In file student.h
#include <iostream.h> const int MAX_ENTRIES = 10; typedef int subjcode_t; typedef int score_t; class Cstudent { public: CStudent():m_numGrades(0){} CStudent( subjcode_t g, score_t s ); int add( subjcode_t g, score_t s ); void output( ostream& os ); private: subjcode_t m_subj[MAX_ENTRIES]; score_t m_score[MAX_ENTRIES]; int m_numGrades; };
In file student.cpp
#include "student.h" CStudent::CStudent(subjcode_t g, score_t s):m_numGrades(0) { add(g, s); } int CStudent::add(subjcode_t g, score_t s){ if( m_numGrades > MAX_ENTRIES ) { return MAX_ENTRIES; } m_subj[m_numGrades] = g; m_score[m_numGrades] = s; return m_numGrades++; } void CStudent::output(std::ostream& os){ os<< "Student: number entries: = " << m_numGrades << std::endl; for( int i = 0; i < m_numGrades; i++ ) { os << "Grade: " << m_subj[i] << " Score: " << m_score[i] << std::endl; } }
So, a few comments on the above.
I've used a typedef for the subject code and for the grade. This is not really necessary, but could be useful in the future if we needed to change the type of either (to an unsigned int, for example).
[OK, now if I were marking this as student work I would mark you down because the question as set specifically required that these would be types. I would expect, at a minimum:
struct subject {
int code;
};
FG]
The add(..) method returns an int. We have the choice of returning a void, a bool or an int. Using a void return would be a waste of a return value, we could return a bool to indicate if the values were inserted correctly. But returning the index of the inserted value gives the caller an indication of how many grades have been inserted so far. I have also simply ignored any insertions past the maximum number. This may not be the best response to an out of bounds insertion, see later for more on this.
The output(...) method takes an ostream parameter, this allows us to use the same method and redirect the output wherever we want, to a file or into a strstream. (or even a standard stringstream FG)
Using two arrays (m_subj and m_score) is probably unnecessary. It is quite likely that given there is a fixed number of entries (10 in this case) that there is fixed number of courses, and that we would only need to keep track of the score for each course. In which case we only need one array and use the index into the array as the subject code. But I have stayed with using two arrays for this example. Possibly the subject codes are not contiguous. (even more likely that students are allowed to take up to ten courses out of dozens or even hundreds FG)
I have kept the test bed program simple for the purposes of this article, but in a real test bed it would be important to test all the 'boundary conditions' that a class could be expected to deal with. One important condition here would be, what happens when we add an eleventh grade to the student? We have an array of ten elements, adding the eleventh takes us into that region of darkness and despair known as "undefined behaviour"... I mentioned at the start of the article that the class specification gives us no clue as to what context it would be used in, and this is an important factor in deciding how to handle boundary conditions such as adding too many elements.
In this case, I can see five possible ways of handling the situation:
-
Crash. Inelegant but simple.
-
Throw an exception.
-
Refuse to add an extra element.
-
Expand the array.
-
Remove the oldest entry and add the new one.
Lets ignore the first... If this class were to be used in the context of a library, or as part of a larger, more complex project, then (2) may be the best choice. The last three depend on knowledge of the problem domain and would make an excellent exercise for the reader.
Other things to be dealt with are things like, can we accept a negative score, does the course code need to be within a particular range, how we deal with situations where these constraints are not met?
So far we've looked at using old style C++ coding, but there are more elegant ways of implementing the class.
The Student class is basically a container of subject codes and scores, so the simplest implementation of Student would be:
typedef std::vector<std::pair<subjcode_t, grade_t> > Student;
which could be used as:
Student s1; s1.push_back( std::make_pair( 2, 12 ) );
Simple and efficient and no class needed, but it allows us no control of the input, checking for valid subject codes and so on, so an improvement would be to wrap the above into a Student class, using the same interface as for our original design of student. That gives us:
File: newstudent.h
#include <iostream> #include <vector> #include <utility> const int MAX_ENTRIES = 10; typedef int subjcode_t; typedef int score_t; typedef std::vector<std::pair< subjcode_t, score_t> > subjectlist; typedef std::vector<std::pair< subjcode_t, score_t> >::iterator subjectiter; class Cstudent { public: CStudent(){} CStudent( subjcode_t g, score_t s ); int add( subjcode_t g, score_t s ); void output( std::ostream& os ); private: subjectlist subjects; };
And file: newstudent.cpp
#include "newstudent.h" CStudent::CStudent(subjcode_t g, score_t s){ add(g, s); } int CStudent::add( subjcode_t g, score_t s ){ if( subjects.size() >= MAX_ENTRIES ) { return MAX_ENTRIES; } subjects.push_back(std:: make_pair( g, s )); return subjects.size(); } void CStudent::output( std::ostream& os ){ os << "Student: number entries: = " << subjects.size() << std::endl; subjectiter iter = subjects.begin(); for( ; iter != subjects.end(); iter++ ) { os<<"Grade: " <<(*iter).first <<" Score: " << (*iter).second << std::endl; } }
The first important thing to note is that our interface to the outside world (in the .h file) has remained virtually unchanged, which means that users of our original CStudent class can just plug in the new one without changing any of their code. Indeed, the test bed program runs just the same.
Secondly, we are now including the new C++ headers (<vector>, <iostream> etc) and therefore we need to use the std:: namespace when accessing the containers and streams within them.
I have chosen vector in the above code, but alternatives would be:
std::map< subjcode_t, grade_t>
or simply
std::vector<grade_t>
and use the index into the vector as the subjcode, as discussed in the first section of code.
If the subject code must appear more than once, then the map won't work (the key must be unique) and therefore we would need a multi-map.
As I mentioned at the start, the context within which a class will be used is an important factor in deciding how to implement it and in how to handle boundary conditions. The design above is flexible enough that in a real world situation, the implementation could be changed without any disastrous effects on any users of the class.
Well you see how easy it is to win a book. If Tim contacts me we can discuss which Addison-Wesley book he wants.
Of course contestants have to submit themselves to the editors comments, but the idea of this section of C Vu is a kind of Seminar. I would hope that many readers will want to add their bit as well. It would be nice to see some of you offer classes to handle subjects (including titles) and marks (including grades). It isn't that hard, a few good clean examples might help to raise standards. FG
The following code is intended to use Newton's method of approximations to find the roots of a function. It is a mess. The direct question is that the program only works for some input values. The code is C.
Reverse engineer the code, explaining what each function actually evaluates. Then rewrite the code (in C). Extra credit for dealing with the magic numbers, and expert credit for generalising the program so it handles finding roots of a wider range functions.
Given sufficient entries, there will be more than one prize. Remember that the objective is to help a student improve his/her understanding of programming and the process of writing simple code.
#include <stdio.h> #include <math.h> double func(double x) { double val1, val2, val3, val4, vsqrt; vsqrt = sqrt(fabs(x)); val1 = 25.0/7.0; val2 = log(3750.0 * vsqrt); val3 = 1.0/vsqrt; val4 = 6.0; return (val1*val2 - val3 - val4); } double derivative(double x){ double val1, val2, val3; val1 = 1.0/(2.0 * X); val2 = 25.0 / 7.0; val3 = 1.0/sqrt(fabs(X)); return val1 * (val2 + val3); } double NewtonRough(double x0){ return(x0-( func(x0)/derivative(x0))); } int main(){ double x0, x1; puts("Enter an estimate of the root\n>"); scanf("%f", &x0); printf("x0 = %f\n", x0); for(;;){ x1 = NewtonRough(x0); if(fabs(x1 - x0)< pow(10.0, -6.0) break; x0 = x1; } printf("Estimation of root %f\n", X1); return 0; }
Closing date: September 20th
Notes:
More fields may be available via dynamicdata ..