First publication date: 2020/04/27
The String class
Even if we study this class in the lecture, you have to implement it.
Here are the use cases :
int main(int, char **) {
String s1;
String s2(6);
String s3("try");
String s4(s3);
String s5 = s3;
s2 = s3;
s3.replace("new");
s3.display();
cout << s3.c_str(); // this is a getter for the nested C string
cout << s3[0];
cout << s3;
return 0;
}
You will have to develop it using unit testing and valgrind
You can download the exercice from here
Notes:
- You will have to use the
const
keyword with all methods that do not change the state of the object. std::cout
is an object whose type isstd::ostream
(defined in the<ostream>
header included by the<iostream>
header)
Constructor and destructor
- Write a
String
class With two attributes : an integer calledcapacity
and another one a dynamic array of chararr
(capacity
is the physical "size" of the arrayarr
). - Write a default constructor (with no argument) to initialize these datamembers to -1 and
nullptr
(as the C++11 and above allows it). - Write the methods
c_str()
andgetCapacity()
for unit testing.
TEST_CASE("Default constructor") {
String c;
REQUIRE( -1 == c.getCapacity());
REQUIRE( nullptr == c.c_str()); // 0 or NULL for old compilers
}
We handle internally the String
class as a C characters string : an array of char ending with the '\0'
character. We need this information in the "guts" of the class. For the user of the class, the capacity is the maximum number that can be put in the string, that is why getCapacity()
and capacity
may differ.
Whenever it is possible, we need to qualify the methods as constant. We can check this with tests:
TEST_CASE("Check on the constness of accessors") {
const String c;
CHECK( -1 == c.getCapacity());
CHECK( nullptr == c.c_str());
}
- Write a constructor that takes a C string as parameter (
const char *
) inCS.arr
has to be properly allocated. You will need thestrcpy()
function, so you will have to include the C standard <cstring> header file.
TEST_CASE("from C string constructor") {
char source []= "nothing";
String c1(source);
CHECK( (signed) strlen(source) == c1.getCapacity() );
CHECK( 0 == strcmp(source, c1.c_str()) );
String c2 = "";
CHECK( 0 == c2.getCapacity() );
CHECK( 0 == strcmp("", c2.c_str()));
}
- Write now a constructor that accepts a capacity –
inCapacity
- and that initializes data members accordingly.
TEST_CASE("Constructeur avec capacite") {
String c1(6);
CHECK( 6 == c1.getCapacity());
CHECK( 0 == strlen(c1.c_str()));
// Is the allocated memory freed ?
}
- Check with valgrind that there is a memory leak
- Add a destructor to the
String
class. - Check with valgrind again !
Copy constructor
- Read the following test. What is the problem ? Check with valgrind.
TEST_CASE("Copy constructor") {
String s1(10); // empty string
String s2 = s1; // another empty string
CHECK( s1.getCapacity() == s2.getCapacity());
// All the problems come from here
// The void * cast is for the display of the catch library
CHECK( (void *)s1.c_str() != (void *)s2.c_str() );
CHECK( 0 == strcmp(s1.c_str(), s2.c_str() ));
}
- Solve the problem with writing a copy constructor. You may write on the standard or error stream "Copy constructor called"
- Write the method
display(std::ostream &)
that displays the string on the given stream. This stream has to be the standard output by default.
TEST_CASE("display method") {
const char * original = "a string to test";
const String c1(original);
std::stringstream ss;
c1.display(); // is it compiling ?
c1.display(std::cout); // compiling but not interest for testing
c1.display(ss); // can be used for testing
CHECK( ss.str() == original ); // test of std::string :-)
}
the std::stringstream
stream is a particluar stream of std::ostream
. Instead of writing in files, we use std::stringstream
for the tests that are easier to write.
- Write a function
displayByValue()
that takes aString
as parameter and that calls thedisplay()
method of the parameter. - Write another function
displayByReference()
that takes a reference on aString
and do the same thing than above. - Test the functions calls and check that the copy constructor is proprely called.
Overload of the assignment operator
TEST_CASE("assigment operator") {
String s1("first string");
String s2("second string bigger than the first");
s1 = s2;
CHECK( s1.getCapacity() == s2.getCapacity());
CHECK( (void *)s1.c_str() != (void *)s2.c_str() );
CHECK( 0 == strcmp(s1.c_str(), s2.c_str() ));
s1 = s1; // What is happening ?
}
- Creates two instances of
string
and make an assignment - Run the program, note the error message and run again with avec valgrind.
- Write the assigment operator. Check with valgrind ! The operator takes a reference on a const object and returns a reference back ("
*this
") to allow multiple assigmentss1 = s2 = s3
. - Check that
s = s
works fine (otherwise ...)
Operators overloading(<<, [] et +)
TEST_CASE("<< overloading") {
const char * str = "a new overloading";
String s(str);
std::stringstream ss;
// ss << s; // :-)
CHECK( ss.str() == str ); // test of std::string, again :-))
}
- Can you write an overloading of operator <<. This operator (a function) takes two references as parameters : a stream and a not mutable string and returns the stream to make possible cascading inputs.
The tests are not written. It is up to you.
- Add an operator [] (a method whose prototype is
operator[](int)
) that allows to modify an element of the string. Test yout implementation on one of your instances with trying to modify a character. - Update the the operator << (a function whose prototype iss
operator<<()
) in order to display one character on a single line one by one. You shoud do this with the just defined operator For example, the string "Toto", will be display as well :
T
o
t
o
You should have a compilation error ... - To solve it, add a new const operator [] (constant method) to access a character on a constant string.
- Add a concatenation operator on string, the way + is expected to work.
Handling errors will be added in the next lecture.
The vector : a dynamic array
The purpose of this exercice is to program a container , that is to say an object capable of containing others. The simplest container is the dynamic array known as vector : the capactity can change during le lifespan of the object. You will store double elements.
This data structure behaves like a regular array and allows direct access to elements when their position is known.
It encapsulates a classical static array of elements (of a given class). When the array is at full capacity, a new but bigger array is allocated and the content is copied from the old array to the new one.
The primer capacity can be given when the object is instanciated (default capacity : 10 for example). When the array becomes too small, its capacity is multiplied by 2.
The C function memcpy()
is a super fast function to copy from an array to another one. You can also use std::copy()
The capacity()
method gives the capacity of the vector ; the size()
method returns the number of elements in the vector.
A method push_back()
allows adding an element at the end of the vector and can trigger the vector extent.
You can write operators like the redirection, the index or concatenation
Do not forget to follow the normal form !
This container will be reused in the next lessons.
The testing code and the way to do it are described in the notice on the unit testing :
"Facts" checking
Did you check the following ?
- If an object is given as a parameter to a function, it is copied by default. To check that, you have to reuse the
Gossip
class, to write a function with aGossip
parameter and to count the number of destructions.
void test1(Gossip b) {
std::cout << "function call with copy";
}
- the function return might also creates a copy depending on the compiler.
Gossip test2a() {
std::cout << "function call with return";
return Gossip(); // creation of a local object
} // no copy anymore - See explanation in advanced course
Gossip test2b() {
Gossip b; // creation of a local object
std::cout << "function call with return";
return b;
} // no copy anymore - See explanation in advanced course
- Add now a copy constructor to the class
Gossip
that increments the instances number and check that this very constructor is effectively called in these situations - that using a reference DOES not create a copy
void test3(Gossip& b) {
std::cout << "function call with reference";
}
- neither does the use of pointers
void test4(Gossip *b) {
std::cout << "function call with pointer";
}
Type truncating
You have to check the type truncating and how to avoid it ...
void display1(Forme f) {
f.display();
}
void display2(Forme &f) {
f.display();
}
void display3(Forme * f) {
f->display();
}
int main(int, char**) {
Circle circle;
display1(circle);
display2(circle);
display3(&circle);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10