C++ Day 2 (Part 2): References File I/O Tom Latham

advertisement
C++ Day 2 (Part 2):
References
File I/O
Tom Latham
Function arguments
• We saw this morning that functions receive copies of the arguments
passed to them. This is referred to as ‘passing by value’.
• For many cases this is fine. However, there are two scenarios where
this causes problems:
• The function cannot change the value of the argument in such a way
that it is also changed in the calling scope.
• If the object being passed to the function is complicated or large, e.g.
a big vector or a long string there can be considerable overhead
from making the copy, both in terms of time and memory.
• In these cases it would be better if a function could act on the original
object.
References
• References are essentially a means of
creating a new variable for an existing
object.
• Here the new variables are ‘a’ and ‘b’.
• When the swap function is called they are
assigned the objects currently referred to by
the variables ‘x’ and ‘y’.
• The ampersand symbol (&) declares that
‘a’ and ‘b’ are references.
#include <iostream>
void swap( double& a, double& b )
{
double tmp {b};
b = a;
a = tmp;
}
int main()
{
double x {42.3};
double y {11.2};
std::cout << x << "\t" << y << "\n";
• This then allows functions to act on the
actual objects passed to them rather
than copies of them. This is referred to
as ‘passing by reference’.
swap(x,y);
std::cout << x << "\t" << y << "\n";
}
References
• References are essentially a means of
creating a new variable for an existing
object.
• Here the new variables are ‘a’ and ‘b’.
• When the swap function is called they are
assigned the objects currently referred to by
the variables ‘x’ and ‘y’.
• The ampersand symbol (&) declares that
‘a’ and ‘b’ are references.
#include <iostream>
void swap( double& a, double& b )
{
double tmp {b};
b = a;
a = tmp;
}
int main()
{
double x {42.3};
double y {11.2};
std::cout << x << "\t" << y << "\n";
• This then allows functions to act on the
actual objects passed to them rather
than copies of them. This is referred to
as ‘passing by reference’.
swap(x,y);
std::cout << x << "\t" << y << "\n";
}
References
• References are essentially a means of
creating a new variable for an existing
object.
• Here the new variables are ‘a’ and ‘b’.
• When the swap function is called they are
assigned the objects currently referred to by
the variables ‘x’ and ‘y’.
• The ampersand symbol (&) declares that
‘a’ and ‘b’ are references.
#include <iostream>
void swap( double& a, double& b )
{
double tmp {b};
b = a;
a = tmp;
}
int main()
{
double x {42.3};
double y {11.2};
std::cout << x << "\t" << y << "\n";
• This then allows functions to act on the
actual objects passed to them rather
than copies of them. This is referred to
as ‘passing by reference’.
swap(x,y);
std::cout << x << "\t" << y << "\n";
}
Const references
• The only problem is that you
might not want to change the
object being passed but you still
want to use pass by reference
because it is large
• Thankfully there is a way to
ensure (compiler enforced) that
the argument is not changed, by
using a reference to a const
object, often termed a ‘const
reference’
#include <iostream>
void swap( double& a, double& b )
{
double tmp {b};
b = a;
a = tmp;
}
void print( const double& a,
const double& b )
{
std::cout << a << "\t" << b << "\n";
}
int main()
{
double x {42.3};
double y {11.2};
}
print(x,y);
swap(x,y);
print (x,y);
Rule of thumb for function arguments
• If the argument is a built-in type
you can use pass by value
double square( const double x );
• You’ll most likely want to use const
here – it is surprisingly rare that you
actually want to change the value of
the arguments and it is good to
enforce the lack of change to avoid
silly mistakes
• For everything else use const
references
• Unless you need to change the
object, in which case drop the
const
void printString( const std::string& s );
void toLowerCase( std::string& s );
Returning references
• Should large objects also be returned by reference?
• This is a bit trickier since you have to think about the lifetime of
the object being returned.
• If it is local to the returning function, the answer is definitely no.
• It will be destroyed as soon as the function returns, so you’ll
be returning a reference to something that no longer exists!
• At the moment this is the only kind of object lifetime you’ve seen.
We will see other cases in future weeks that mean that return by
reference is viable and even desirable. But you always need to
think carefully about it!
Exercise on function arguments
1. Check whether your existing code is using the most
appropriate form for your function arguments
2. Follow the rule of thumb to choose whether to pass
by value or by reference and make sure everything is
const-correct
3. You can now also package the parsing of the
command-line arguments into a function, which you
should call "processCommandLine"
• Again follow the rule of thumb to choose the best
forms for the function arguments
Input/Output streams
• We’ve so far seen that we can use the cout and cin
objects to print output to the screen and to read input
from the keyboard
• These are examples of I/O streams (hence the name of
the iostream header!)
• We’d like to be able to use these to read from and write
to files as well (helps to avoid a lot of typing!)
• Can use the types provided in the fstream header file
Output file streams
• To use an output file stream you must:
• Include the appropriate header file:
#include <fstream>
• Instantiate an instance of an ofstream type:
std::string name {"myoutputfile.txt"};
std::ofstream out_file {name};
• Unlike with std::cout, you need to check that the file was correctly opened
before you can write to it:
bool ok_to_write = out_file.good();
• Then you can use that instance exactly as you would use std::cout
out_file << "Some text\n";
Input file streams
• To use an input file stream you must:
• Again include the appropriate header file:
#include <fstream>
• Instantiate an instance of an ifstream type:
std::string name {"myinputfile.txt"};
std::ifstream in_file {name};
• Again, you need to check that the file was correctly opened before you can read from it:
bool ok_to_read = in_file.good();
• Then you can use that instance exactly as you would use std::cin
char inputChar {'x'};
in_file >> inputChar;
Closing files (and opening new ones)
• With both input and output files you can do:
file.close();
• This closes the file and any further attempts to read or write will fail
• It is done automatically when a file stream object is destroyed (e.g. when it goes out of scope), so
you don’t always need to do this explicitly
• However, you do need to do this first if you want to then use the same file stream object to open
another file (although the circumstances under which you would want to do this are rare – it is clearer
to use a separate object for each file):
file.open("myfirstfile.txt");
...
file.close();
file.open("myotherfile.txt");
...
Appending rather than overwriting
• By default when you open an output file stream and the file
already exists, it will overwrite the contents of the file
• However, it is possible to open an output file stream in a
mode where whatever you write to it will instead be
appended to the file:
std::ofstream out_file( name, std::ios::app );
• For more details on this see, for example:
http://en.cppreference.com/w/cpp/io/ios_base/openmode
Exercise on file I/O
• Now we have the knowledge necessary to implement
reading the input text from file rather than the keyboard and
to write the cipher text to a file rather than the screen
• You already have command line options that provide the
names of these files
1. Implement the new code to do these operations if the
corresponding option is specified on the command line (if
the option is not specified, the program should continue to
read from the keyboard and/or write to screen as
appropriate)
Function overloading
• Let’s look again at the swap function
that we saw earlier on and which
swaps the values of two double’s
• We could equally want to write a
function that swaps the values of
two integers and indeed we can do
exactly that
• So we have two functions with the
same name (since they have the
same functionality) but different
arguments – this is referred to as
overloading – and is perfectly valid
code
void swap( double& a, double& b )
{
double tmp {b};
b = a;
a = tmp;
}
void swap( int& a, int& b )
{
int tmp {b};
b = a;
a = tmp;
}
Templated functions
• In this particular case, we can see
that the only difference between
the two swap functions is the
types of the arguments and the
tmp variable
• We are duplicating code – and
you can see that this would
proliferate if we wanted to have
other functions to swap two
float’s or two bool’s
• This duplication can be eliminated
using templates
void swap( double& a, double& b )
{
double tmp {b};
b = a;
a = tmp;
}
void swap( int& a, int& b )
{
int tmp {b};
b = a;
a = tmp;
}
Generic programming
• This function template definition specifies a
family of functions that swap the values of
two variables
• The type of these variables is specified by
the template parameter T
• The function can be used with any type that
provides the operations performed
• In this case the operations are copyconstruction and assignment
• In this particular case, the compiler can
deduce the template parameter from the
type of the arguments
template <typename T>
void swap( T& a, T& b )
{
T tmp {b};
b = a;
a = tmp;
}
int main()
{
double x {42.3};
double y {11.2};
swap(x,y);
• In some scenarios that isn’t possible, and
so it has to be specified explicitly:
int i {4};
int j {-6};
swap<double>(x,y);
• This can also be done even when the compiler
can deduce the type, e.g. to provide clarity
}
swap(i,j);
Generic programming and static polymorphism
• Note that we’ve already encountered the template syntax when using the vector
container this morning
• We’ll see more examples of generic programming on Day 4 when we look in
more detail at the so-called Standard Template Library (STL), in particular the
generic algorithms
• Another term that you will hear in connection with templates is “static
polymorphism”
• Polymorphism means that a single entity (in the example we just looked at, a
function) has the ability to be used with many different types
• Static refers to the fact that the type selection is locked-in at compile time
(we’ll encounter run-time type selection or dynamic polymorphism on Day 5)
Exercise on template functions
• You'll have seen that your reading operation is
extremely similar whether you're using the file or the
keyboard
1. Turn the corresponding functions into a templated
function, which you should call "readStream"
2. Consider whether it is worth doing the same for the
output functions
The Caesar Cipher
• Finally, we’re ready to implement our first cipher
• A substitution cipher - each letter in the input text is
replaced by another according to a constant rule
• Named after Julius Caesar - the first recorded user of
this cipher!
Caesar Cipher Encryption Substitution Rule
• Replace each letter in Plaintext string by that K letters
rightward in the Alphabet.
• If the shift goes beyond the end of the Alphabet, wrap
around to ‘A’ and continue counting rightwards.
• Shift K is an integer [0,25] and is the Key for the cipher
Encrypting With the Caesar Cipher, K=5
Plaintext
HELLOWORLD
K=5
K=5
ABCDEFGHIJKLMNOPQRSTUVWXYZ
Ciphertext
MJQQT
Encrypting With the Caesar Cipher, K=5
Plaintext
HELLOWORLD
K=5
K=5
Wrap
ABCDEFGHIJKLMNOPQRSTUVWXYZ
Ciphertext
MJQQTBTWQI
Caesar Cipher Decryption Substitution Rule
• Replace each letter in CipherText by that K letters
leftward in the Alphabet.
• If the shift goes beyond the start of the Alphabet, wrap
around to ‘Z’ and continue counting leftwards.
• Shift K is an integer [0,25] and is the Key for the cipher
Decrypting With the Caesar Cipher, K=5
Ciphertext
Wrap
MJQQTBTWQI
K=5
K=5
ABCDEFGHIJKLMNOPQRSTUVWXYZ
Plaintext
HELLOWORLD
C++ Implementation Hints
• Many ways to implement the Caesar Cipher in C++
• We're going to create a function called CaesarCipher
• Think about what the interface should be (input and
outputs) and the “things” involved (like the Alphabet)
• To handle the “wraparound”, the Modulus operator %
could be useful
• Think about how to test that your Ciphertext is correct
Exercise implementing the Caesar Cipher
1. Implement the Caesar Cipher (using the hints above)
• Will need other cmd-line arguments to be added for
the encrypt/decrypt flag and to provide the key
2. When you have finished, commit and tag your
respository (and push to github), and email us a link
to the tag
Download