C - Tutorial-Pt1
C - Tutorial-Pt1
C. David Sherrill
School of Chemistry and Biochemistry
School of Computational Science and Engineering
Georgia Institute of Technology
Purpose
To provide rapid training in elements of C++ syntax,
C++ procedural programming, and C++ object-
oriented programming for those with some basic
prior programming experience
To provide a handy programming reference for
selected topics
To provide numerous, actual C++ code examples for
instruction and reference
Why C++?
“Intermediate”-level language: allows for fine (low-
level) control over hardware, yet also allows certain
complex tasks to be done with relatively little code
(high-level)
Good for scientific applications: produces efficient,
compiled code, yet has features that help one
develop and maintain a complicated, large code
(e.g., namespaces, object-oriented design)
Recommended reading
These notes were developed during my reading of
“Sams Teach Yourself C++ in One Hour a Day,” 7th
Edition, by Siddhartha Rao (Sams, Indianapolis,
2012). I recommend the book, it’s readable and to
the point.
A good mastery of C++ will probably require
working through a book like that one, and doing
some examples; notes like these only serve as a basic
introduction or a quick review
A Note on C++11
This was originally supposed to be C++0x, with the
“x” filled in according to the year the new C++
standard was finalized (e.g., C++09 for 2009).
However, the standard took longer than expected,
and was only formalized in 2011. So, C++11 is what
was formerly referred to as C++0x.
As of 2013, the new C++11 standards are not yet
fully implemented in many compilers. However, I
will try to note when any part of this tutorial is
relying on new C++11 syntax.
Chapter 1: Real Basics
Very basic structure of a program
Editing and compiling a program
Basic printing with cout
Basic input with cin
A little about variables
A simple program: hello, world
// The next line includes the file iostream, which defines cout below
#include <iostream>
// Every C++ program should have a function called main that returns
// an integer
int main()
{
// Write "hello, world" to the screen
std::cout << "Hello, world!" << std::endl;
int main()
{
// Write "hello, world" to the screen
printf("Hello, world!\n");
return(0);
}
More printing
Example printing.cc (part 1 of 2):
#include <iostream>
#include <stdio.h>
using namespace std;
int main()
{
// First do cout printing
print_cout();
// Now do printf printing
print_printf();
return(0);
}
More printing, cont'd
Example printing.cc (part 2 of 2):
void print_cout()
{
cout << "My name is " << "David" << endl;
cout << "Two times two is " << 2*2 << endl;
cout << "2 / 3 = " << 2/3 << endl;
cout << "2.0 / 3.0 = " << 2.0/3.0 << endl;
}
void print_printf()
{
printf("My name is %s\n", "David");
printf("Two times two is %d\n", 2*2);
printf("2/3 = %d\n", 2/3);
printf("2.0/3.0 = %.2f\n", 2.0/3.0);
}
First look at functions
The example program above does a few things: (1) introduces us to the use
of functions in a program (besides main()), (2) provides some more examples
of printing (strings and results of arithmetic), (3) compares and contrasts cout
vs printf() style printing, (4) shows examples of providing formats to printf()
printing
A function declaration looks like this and must occur before the function is
called:
void print_cout();
and it declares that print_cout() is a function, and it doesn't return anything
(return type void). It also doesn't take any arguments (if it did, they would
be listed between the parentheses)
A function definition can occur anywhere and provides what the function
actually does, e.g.,
void print_cout()
{
… function goes in lines here …
}
There are no “return” statements in these functions because they don't return
anything (that's why their return type is listed as “void”)
Printing examples
This line shows how multiple strings can be
concatenated and send to cout:
cout << "My name is " << "David" << endl;
This line shows how to print an arithmetic result using
cout:
cout << "Two times two is " << 2*2 << endl;
The same thing with printf uses a “format string.” The
“%d” symbol means “put an integer here, and the
integer will come after the end of the format string”:
printf("Two times two is %d\n", 2*2);
“%s” means printf should insert a string here:
printf("My name is %s\n", "David");
Integer vs floating point arithmetic
Notice that the line
cout << "2 / 3 = " << 2/3 << endl;
(or it's printf() equivalent) prints the number 0. That's
because 2 and 3 are interpreted as integers, and the
result will also be computed as an integer (rounded
down), which is 0. If we want a floating-point result,
we need to do this:
cout << "2.0 / 3.0 = " << 2.0/3.0 << endl;
The above line prints out “0.666667”. What if we
wanted more (or fewer) digits? We can give the number
of digits with printf(). The following line tells printf()
to expect a floating point number after the format string,
and to print it to two digits after the decimal:
printf("2.0/3.0 = %.2f\n", 2.0/3.0);
Example input
Finally, we conclude this short introduction with an
example of how to read data from the terminal. We
wouldn't normally do it this way (you'd typically
take command-line arguments or else read from a
data file). But if you ever want to prompt the user
for interactive input, the next example shows how
you could do it.
Using cin
Example cin-example.cc:
#include <iostream>
#include <string>
using namespace std;
int main()
{
// Declare an integer to store the user's input number
int number;
// Declare a string to store the user's input name
string name;
cout << "Enter an integer: ";
cin >> number;
cout << "Enter your name: ";
cin >> name;
int x;
x=10;
It is more common to combine variable declaration
and definition in a combined statement like this:
int x = 10;
Variable Names
Can't start with a number
Can't contain spaces
Can't contain arithmetic operators (+, -, *, / denote
addition, subtraction, multiplication, and division)
Can't be the same as a C++ keyword
Example: Using and Changing Variables
Example vars1.cc:
#include <iostream>
using namespace std;
int main()
{
int number;
number = number * 2;
cout << "Your number * 2 = " << number << endl;
return 0;
}
Changing a Variable
From the previous example, we see that we can change the
contents of a variable with a statement like this:
number = number * 2
in this case, the old value of “number” is multiplied by 2,
and then the result is stored back in the variable “number”.
Effectively, “number” has its value doubled.
This destroys the previous value of the variable. If we
wanted to keep the old value of the variable and have the
new value available, we'd need two variables, and could do
something like this:
int new_number = number * 2
Scope of a Variable
A variable's “scope” consists of the parts of the program
where the variable is known
In general, a variable is only available in the function where
it is declared (this is known as a “local variable”); you can't
use it elsewhere in another function without taking extra
steps
A variable that is made available within an entire file or to
an entire program is called a “global variable”
You can have two local variables in two different functions
with the same name. They won't have anything to do with
one another. Changing one will not change the other.
Scope Example
scope.cc example:
#include <iostream>
using namespace std;
void print_five();
int main()
{
int number = 4;
print_five();
cout << "number = " << number << endl;
return 0;
}
void print_five(void)
{
int number = 5;
cout << "number = " << number << endl;
}
Scope Example
In this example, we demonstrate that the two variables named
“number” have nothing to do with one another. One is local to
the function main(), and the other is local to the function
print_five()
Note we had to declare the function print_five() before we were
able to use it. We could do that inside main() (making the
function available for use within main()), or up above, outside
main, making the function available to any functions in scope.cc.
We chose the latter. This makes the function “global” within this
file. Function declarations are thus also local or global, just like
variable declarations!
The variable “number” is set to 4 in main(), and it stays 4 inside
main(), even though another variable with the same name is set to
5 in the function print_five(), which is called after “number” in
main is set to 4, but before it is printed in main()
Global Variables
Suppose we didn't want this behavior. Instead, we
want a variable, “number”, to be available as a single
variable throughout our file. We can take a clue
from how we made the function print_five() in our
previous example visible to the entire file (and not
just within main) by placing it up top in the file,
outside any function, e.g., we can declare a variable
like this:
int number = 4;
int main()
{
…
}
Global Variable Example
globals.cc example:
#include <iostream>
using namespace std;
void print_five();
int number = 4;
int main()
{
print_five();
cout << "number = " << number << endl;
return 0;
}
void print_five(void)
{
number = 5;
cout << "number = " << number << endl;
}
Global Example Comments
In the previous example, “number” became a global
variable whose scope extends to the entire file.
There is no longer a need to declare the variable in
each function, it is declared once at the top of the
file.
Any change to the variable anywhere in the file is
now reflected throughout the entire file
Types of Variables
bool : true or false
int: an integer
float: a floating point number (like 2.385, etc.)
double: a double-precision floating point number
(like above, but can carry more digits). This is
preferred over float for scientific applications to help
avoid roundoff errors.
char : a single character (e.g., 'a', 'b', etc.)
Special Versions of the Common
Variable Types
There are different modifiers that can be applied to
some of the standard variable types. For example:
unsigned int : an integer that can't be negative
int : on most machines, this only allows values in the
range -2,147,483,648 to +2,147,483,647 (about +/- 2E9)
short int : on most machines, uses less memory and
allows values in the range -32,768 to +32,767
long int : allows larger integers than a regular int; on
most machines, up to +/- 9E18
long long int : may allow even larger numbers than a
long int (on many machines, it's really just the same as a
long int)
Special Versions, cont'd
Can combine “unsigned” with “short,” “long,” or
“long long” to get, for example, short unsigned int
Notice that “unsigned” allows us to store numbers
up to twice as large (at the expense of not having a
sign). This is because the one bit (0 or 1) formerly
used for the sign bit is now available, and each extra
bit allows us to count up to a number about twice as
large as before the bit was added. (With n bits, can
count up to 2n-1).
Numbers of Bits
On a modern 64-bit machine, most quantities are
processed 64-bits at a time (one “word”). For
example a “double precision” floating point number
on a 64-bit machine is 64-bits (and a “float” is half
this, or 32 bits)
8 bits per “byte”
How many bits in an integer, float, double, etc, are
dependent on the machine and the compiler. But we
can use the “sizeof()” command to get the system to
tell us how big each variable type is.
wordlength.cc
Example wordlength.cc:
#include <stdio.h>
int main()
{
printf("Size of char = %ld\n", sizeof(char));
printf("Size of float = %ld\n", sizeof(float));
printf("Size of double = %ld\n", sizeof(double));
printf("Size of long double = %ld\n", sizeof(long double));
printf("Size of short int = %ld\n", sizeof(short int));
printf("Size of int = %ld\n", sizeof(int));
printf("Size of long int = %ld\n", sizeof(long int));
printf("Size of long long int = %ld\n", sizeof(long long int));
return 0;
}
Comments on wordlength.cc
The sizeof() command takes one argument, the data
type (placed in parentheses), and it returns how much
memory is used to store that data type, in bytes
In the previous example, we use C-style printing. The
format string now contains “%ld” because in C++, the
sizeof() function returns an (unsigned) long integer
Try this on your system. On my system with the GNU
G++ compiler and a 64-bit machine, I get the following
results: char = 1, float = 4, double = 8, long double = 16,
short int = 2, int = 4, long int = 8, long long int = 8. It's
interesting that even on a 64-bit machine, by default only
32 bits are used to store an integer, and it takes “long
int” to force the compiler to use 64-bits.
overflow.cc
A simple example demonstrates why it's important to use a data type big enough to hold the
required data. If we want to multiply 2 billion by 2, this will overflow a regular integer
(causing the result to “wrap around” to a negative number). But it works when using long
int's
#include <stdio.h>
int main()
{
int p = 2000000000;
int q = p * 2;
printf("p*2 = %d\n", q);
return 0;
}
Comments on vector.cc
There are some slightly mysterious things going on
in the previous example: the array declaration takes
the type inside < > symbols, there is a “push_back”
function, etc. Don't worry, we'll get to these things
later.
Nevertheless, it should be basically clear from the
context what the program does and how it works.
Strings
In C, there was not a built-in data type for strings.
Instead, a string was treated as an array of characters.
Knowing this is useful because much C or early C++
code exists that uses such kinds of strings, and it is
good to be able to interact with this code.
Newer versions of C++ include a built-in string type
C-style strings
Allocate just like an array, it's just that it's now an array of
characters
A special “null character” (denoted `\0`) is stored at the end of
the string to indicate the end-of-string. If this terminator is
missing, then bad things happen (e.g., statements that try to
print the string will keep printing characters until they
eventually reach this character somewhere else in memory),
To store a 5-character name, one must allocate 6 characters: 5
for the name, and one for the null character.
Keeping up with all these details is why C-style strings are
disfavored compared to the newer C++-style strings
Other useful functions for C-style strings are strcpy (copy a
string), strlen (get a string length), strcmp (compare two strings
--- note, if they match, the return value is zero, which is a little
confusing), etc. To use these functions, #include <cstring>
C-style strings
c-string.cc:
#include <iostream>
Program output:
#include <cstring> David Sherrill
using namespace std; David
int main() Rollin has 6 characters
{
char name[] = "David Sherrill";
cout << name << endl;
name[5] = '\0';
cout << name << endl;
strcpy(name, “Rollin”);
cout << name << “ has “ < < strlen(name) << “ characters.” << endl;
return 0;
}
C++-style strings
Safer for programming because they can scale their
size dynamically and the programmer doesn't need to
worry about making sure the terminating null
character is in the right place
Easier to manipulate with high-level syntax
To use, #include <string>
C++-style strings
c++-string.cc:
#include <iostream>
#include <string>
int main()
{
string FirstName("David");
string SecondName("Sherrill");
// The next line prints "David Sherrill" with a space between names
cout << FullName << endl;
return 0;
}
C++-style strings
Safer for programming because they can scale their
size dynamically and the programmer doesn't need to
worry about making sure the terminating null
character is in the right place
Easier to manipulate with high-level syntax
To use, #include <string>
Chapter 4: Operators and Expressions
Math operators
Logic operators
L-values and r-values
Expressions
Operators
An “operator” is something that transforms data.
Common operators in C++ are as follows:
= : Assignment operator, takes the value on the right
- : Subtraction operator
* : Multiplication operator
/ : Division operator
}
More about ++, --
We can also include the increment and decrement operators as
part a larger statement or mathematical expression. For
example,
int x =3, y = 5;
y = x++; // this makes y (and x) be 4
cout << x++ << endl; // this prints 5
Note that the above uses of ++ have a dual role: they increment
AND they allow the incremented value to be used in an
assignment or a print statement (etc.)
What if you wanted to print the value of x and THEN
increment it by one? This can still be done by using ++ as a
prefix operator instead of a postfix operator, e.g.,
int x = 3; cout << ++x << endl; // this prints 3 and THEN
makes x=4
Analogous statements hold for the decrement operator --
Operators +=, -=, *=, /=
Frequently we want to take a variable, do some math
operation on it, and put the result back into the variable.
We can do this the long way, e.g.,
int x = 3; x = x + 2; // yields x=5
or the short way
int x = 3; x += 2; // also yields 5, but shorter
x += 2 is a shortcut for x = x + 2;
x -= 2 is a shortcut for x = x – 2;
x *= 2 is a shortcut for x = x * 2;
x /= 2 is a shortcut for x = x / 2;
Equality operators (==, !=)
Earlier we made the point that = is an assignment
operator, not a test of equality. Tests of equality use
the equality operator, ==. Use of this operator
returns a boolean value (true or false). For example:
bool n = (2 == 3); // makes n 'false'
bool m = (1 == 1); // makes m 'true'
int x = 2, y = 3;
bool p = (x++ == y); // makes p 'true'
!= is an “inequality” operator that checks if two
quantities are not equal:
bool n = (2 != 3); // makes n 'true'
Relational operators (<, >, <=, >=)
Just as we can check for equality or inequality, we
can also check if values are greater than (>), less than
(<), greater than or equal to (>=), or less than or
equal to (<=). The results again are booleans.
bool n = (2 > 3); // false
bool m = (1 > 1); // false
bool p = (1 >= 1); // true
int x = 2, y = 3;
bool q = (x <= y); // true
The logical NOT operator (!)
We can reverse the value of a boolean result by the
logical NOT operator, denoted by the ! character
int main()
{
int age = 70;
string day("Sunday");
}
Truth of numbers
In C++, 0 evaluates as false, and all other numbers
evaluate as true
The Ternary ?: Operator
(expression1) ? (expression2) : (expression3)
If expression1 is true, then return the value of
expression2, otherwise return the value of
expression3
z = (y%2) ? 1 : 0;
In this example, if y is odd, then y%2 has a remainder
(making expression1 true), and therefore z=1.
Otherwise, z=0.
Chapter 5: Program Flow Control
int main()
{
int donation;
cout << "Enter your donation amount in dollars: ";
cin >> donation;
}
The else if statement
Sometimes we have a chain of conditions that need to be evaluated.
The “else if” statement helps with these. The first condition
satisfied will determine what grade is printed, and once a grade is
printed, the other checks will not be tested.
grade.cc:
int main()
{
int grade;
cout << "Enter the student's numerical grade: ";
cin >> grade;
}
switch/case
Useful if there are only a few possibilities and we want to do
something different for each one. Often used with the enum data
type. Each section usually ends with a “break” statement, otherwise
the code keeps executing as one goes down the cases in the switch
statement. The “default” case catches any un-listed cases.
switch-case.cc:
int main()
{
enum category { Faculty, Staff, Grad, Undergrad };
int employee = Staff;
switch(employee) {
case Faculty:
cout << "Your parking fee is $400." << endl;
break;
case Staff:
cout << "Your parking fee is $350." << endl;
break;
case Grad:
cout << "Your parking fee is $200." << endl;
break;
case Undergrad:
cout << "Your parking fee is $100." << endl;
break;
default:
cout << "Wrong input, employee category not recognized." << endl;
break;
}
}
while loops
The while statement keeps executing a code block until
a given condition is no longer fulfilled. Here's a
countdown timer example that prints numbers from
10 to 0 in decreasing order:
do-while.cc:
int main()
{
int counter;
cout << "Where should I start the countdown? ";
cin >> counter;
do {
cout << counter << endl;
counter--;
} while (counter >= 0);
}
for loops
The for loop is the most commonly encountered loop in
C++. We set a variable (usually an integer) to some
initial value, we loop as long as some condition is met,
and at each iteration, we typically increment or
decrement the variable. The general syntax is:
for (var = initial_value;
exit condition executed at beginning of each loop;
statement executed at end of each loop) {
code block executed each loop;
}
Example:
for (int i=0; i<5; i++) { // prints i from 0 to 4,
not 5
cout << “i = “ << i << endl;
}
Nested for loops
You can have a for loop inside another for loop. For
example, we might want to print out all the elements
of a matrix.
print-matrix.cc:
int main()
{
int matrix[2][2] = { {0, 1}, {2, 3} };
}
The continue statement
Inside a loop, “continue” hops back up to the
beginning of the loop
continue.cc:
int main()
{
int counter;
cout << "Printing all odd numbers between 1 and 5, inclusive: " << endl;
for (int i=1; i<=5; i++) {
if (i%2 == 0) continue; // skip the even ones
else cout << i << " ";
}
cout << endl;
}
The break statement
We briefly encountered “break” above in the switch/case
example. It breaks out of a switch/case statement or a
loop. The example below also demonstrates a for loop
with an empty middle statement (no simple condition is
checked at the beginning of the loop).
break.cc:
int main()
{
int counter;
cout << "Find first odd number > 3 that's divisible by 3: " << endl;
for (int i=4; ;i++) {
if ((i%2 != 0) && (i%3 == 0)) { // not even, and divisible by 3
cout << "Found it! It's " << i << endl;
break;
}
}
}
Chapter 6: Functions
int main()
{
char grade(int score); // declare the function grade()
int main()
{
void banner(void);
banner();
cout << "Hello world!" << endl;
banner();
}
void banner(void)
{
cout << "******************************************************" << endl;
}
Program output:
*****************************************************
Hello world!
*****************************************************
Functions with multiple arguments
It's easy to generalize to the case of multiple
arguments in a function: in the declaration and
definition, just list the arguments one at a time, with
their types. Strictly speaking, one does not need to
give the arguments names in the declaration, but it is
common practice to do so.
For example, consider a function that computes the
hypoteneuse (the long side) in a right triangle,
according to the Pythagorean theorem, c =
sqrt(a2+b2), where sqrt() is the square root function
(defined in C++ by the cmath header file).
Example hypoteneuse.cc
#include <iostream>
#include <cmath>
int main()
{
double hypoteneuse(double a, double b);
double a, b, c; // three sides of a right triangle
cout << "Enter the length of side a of right triangle: ";
cin >> a;
cout << "Enter the length of side b of a right triangle: ";
cin >> b;
c = hypoteneuse(a, b);
cout << "The length of the hypoteneuse c is " << c << endl;
return 0;
}
int main()
{
double PV(double T, double n = 1.0); // default values here
double PV1 = PV(298.0);
cout << "PV for n=1 and T=298 is " << PV1 << " atm*L" << endl;
double PV2 = PV(298.0, 2.0); // this time don't use default n
cout << "PV for n=2 and T=298 is " << PV2 << " atm*L" << endl;
return 0;
}
int main()
{
void format_print(int a);
void format_print(double a);
int a = 42;
double b = 3.1415926;
format_print(a); Program output:
format_print(b); 42
return 0; 3.14
}
void format_print(int a)
{
printf("%4d\n", a); // print an integer within 4 spaces
}
void format_print(double a)
{
printf("%4.2lf\n", a); // print a double within 4 spaces,
// and 2 digits after the decimal
}
Recursion
Recursion is when a function calls itself. This can be very
useful, but one also has to be very careful to make sure
that the recursion eventually stops --- otherwise, the
recursion could go on forever and the program will
never stop running.
In the next example, we use recursion to compute the
factorial of an integer. For example, 5! = 5*4*3*2*1 =
120. Since this is a simple sequence, we can evaluate
the factorial of a number n by multiplying n by the
factorial of (n-1). We just have to be sure to stop when
we reach 1.
Note: this routine only works for modest-sized integers,
otherwise we will overflow the integer we're storing the
result in.
Example of recursion (factorial.cc)
#include <iostream>
int main()
{
int factorial(int a);
int x = 5;
cout << x << " factorial is " << factorial(5) << endl;
return 0;
}
int factorial(int a)
{
if (a == 1) return 1;
else return(a * factorial(a-1));
}
Output:
5 factorial is
120
Function parameters are
“pass by value”
When a function is called, the values of variables are
passed to the function, not the variables themselves.
This means that any change to a variable inside the
function does not change the variable in the calling
function.
Example pass-by-value.cc
include <iostream>
int main()
{
int square(int x);
int x = 2;
cout << "Before calling square(), x is " << x << endl;
int x2 = square(x);
cout << "The square is " << x2 << endl;
cout << "After calling square(), x is " << x << endl;
return 0;
}
int main()
{
void swap(int& x, int& y); // void swap(int &x, int &y); also works
int a = 1, b = 2;
cout << "a = " << a << " b = " << b << endl;
swap(a,b);
cout << "a = " << a << " b = " << b << endl;
return 0;
}
int main()
{
double x = 22.4;
double y = half(x);
cout << "x = " << x << " y = " << y << endl;
return 0;
}
Chapter 7: Pointers and Dynamic Memory
Allocation
Pointers
Dynamic memory allocation: new/delete,
malloc()/free()
const and pointers, const and references
Trapping failures to new/malloc()
Common pointer problems
Using pointers or references for greater efficiency in
function calls
NULL pointers
Defining pointers
A pointer is a variable that holds a memory address;
typically this is the location in memory associated
with some other variable
For example, variable “x” may hold an integer, and
that integer may be stored in main memory at some
location such as (in hexidecimal) 0x329a. We could
store that memory location (0x329a) in a special
variable called a pointer (with some other name, like
“x_ptr”). That way we'd know where variable “x” is
located in memory
Knowing the memory location of “x” gives us more
control over “x”
Pointer example
Example pass-pointer.cc:
#include <iostream>
int main()
{
int square(int* x);
int x = 2;
cout << "Before calling square(), x is " << x << endl;
int x2 = square(&x);
cout << "The square is " << x2 << endl;
cout << "After calling square(), x is " << x << endl;
return 0;
}
then when we're done with it, we should free the memory
using
delete[] ptr; // frees the memory pointed to by ptr
We must handle this step ourselves; the pointer itself will
be deleted when it goes out of scope (e.g., at the end of a
function it's defined in), but the memory it points to will
not automatically be deleted --- unless we use “smart
pointers” (see below).
Allocating/deleting a single variable
We've seen how to dynamically allocate an array, and
how to delete it. For a single variable, the
corresponding syntax for a variable of type “Type”
is:
double *array;
int main()
{
try {
int* ptr = new int[9982381213]; // too big?
cout << "Created memory." << endl;
delete[] ptr;
}
catch (bad_alloc) {
cout << "Failure to allocate memory." << endl;
}
return 0;
}
new(nothrow)
If this exception handling business seems to
complicated for now, one can alternatively tell new
to return NULL (like malloc()) instead of throwing
an exception.
Example new-nothrow.cc:
#include <iostream>
using namespace std;
int main()
{
int* ptr = new(nothrow) int[9982381213]; // too big?
if (ptr != NULL) {
cout << "Created memory." << endl;
delete[] ptr;
}
else
cout << "Failure to allocate memory." << endl;
return 0;
}
Smart pointers
Keeping track of whether or not we need to free
dynamically allocated memory, and when, can be a
bit of a pain. That's why “smart pointers” were
created.
A smart pointer is a C++ class that tracks when it is ok
to free dynamically allocated memory, and it
facilitates the automatic freeing of such memory at
the appropriate time. Otherwise, it acts like a regular
pointer.
This incredibly useful technique is most often
associated with classes, so we will defer further
discussion to Part II of the notes.
Chapter 8: Streams
Stream basics
Formatting std::cout output
Reading variables and strings from std::cin
File streams
Writing a file
Reading a file
Stringstream
Stream basics
Our first example was the “Hello, world!” program, which prints
out a simple message:
std::cout << “Hello, world!” << std::endl;
This is already an example of streams... std::cout is an output
stream that prints to “standard output,” i.e., the screen.
Similarly, std::cin is an input stream that reads from the
keyboard:
std::cin >> number;
Streams in C++ are a generic way to handle input and output.
The stream insertion operator, <<, can be used to write to the
screen, to a file, to a device, etc. Likewise, the stream
extraction operator, >>, can be used to read from the keyboard,
from a file, etc. We just replace std::cin and std::cout with
whatever stream we need!
Popular C++ Streams
std::cout – standard output, usually prints on the screen /
terminal window
std::cin – standard input, usually reads from the keyboard
std::cerr – standard output stream for errors, usually prints
on the screen like cout, but could be “redirected”
elsewhere
std::fstream – input/output for files
std::ofstream – output stream for files
std::ifstream – input stream for files
std::stringstream – read/write from/to strings; useful for
formatting strings or using strings for data conversions
Formatting cout output
We can format the output coming out of cout in
various ways. We do this by using “manipulators”
on the stream. We already know about std::endl,
which inserts a newline character. Other
manipulators are:
dec – interpret as decimal
int main()
{
int x = 255;
cout << "decimal: x = " << x << endl;
cout << "octal : x = " << oct << x << endl;
cout << "hex : x = " << hex << x << endl;
cout << setiosflags(ios_base::hex|ios_base::showbase|ios_base::uppercase);
cout << "In hex with base notation: x = " << x << endl;
cout << resetiosflags(ios_base::hex|ios_base::showbase|ios_base::uppercase);
cout << "After resetting flags: x = " << x << endl;
}
Program output:
decimal: x = 255
octal : x = 377
hex : x = ff
In hex with base notation: x = 0XFF
After resetting flags: x = 255
Formatted cout example 2
Example print-numbers2.cc:
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
double h = 6.62606957E-34; // Planck's constant in J*s
cout << "h = " << h << endl;
cout << "Setting precision to 4 digits after the decimal" << endl;
cout << setprecision(4);
cout << "h = " << h << endl;
cout << "Print h (right-aligned) in a field 12 spaces long" << endl;
cout << "h = " << setw(12) << h << endl;
cout << "Note: setprecision() affects future numbers sent to cout" << endl;
cout << " but setw() only affects the very next thing sent to cout" << endl;
}
Program output:
h = 6.62607e-34
Setting precision to 4 digits after the decimal
h = 6.626e-34
Print h (right-aligned) in a field 12 spaces long
h = 6.626e-34
Note: setprecision() affects future numbers sent to cout
but setw() only affects the very next thing sent to cout
cin example
We can use std::cin to read plain old data types from the
keyboard. It also reads strings … however, it assumes
whitespace characters (e.g., a space) begin a new string. To
read a string with spaces in it, we need to use getline(cin,
string).
Example cin.cc:
#include <iostream>
using namespace std;
int main()
{ Example program input/output:
int i; Enter an integer:
string a; 32
cout << "Enter an integer:" << endl; You entered 32
cin >> i; Enter a string with no spaces:
cout << "You entered " << i << endl; David Sherrill
cout << "Enter a string with no spaces:" << endl; You entered David
cin >> a; Enter a string with spaces:
cout << "You entered " << a << endl; You entered Sherrill
cout << "Enter a string with spaces:" << endl;
getline(cin, a);
cout << "You entered " << a << endl;
}
File streams
We can read and write files with streams in a very similar fashion as we use cin
to read from the keyboard or cout to write to the screen.
1. #include <fstream>
2. create a file stream variable, like this:
fstream outFile;
3. Open the file by providing the filename and any arguments saying whether
this is for input, output, or both; if the file is human-readable text (default) or
non-human-readable binary format (allowing more compact files), whether
the new file should delete any existing file of this filename (“trunc” option)
or whether it should append onto existing files (“app” option), etc. For
example:
outFile.open(“output.dat”, ios_base::out|ios_base::trunc)
4. Alternatively, steps 2+3 can be combined in a constructor like this:
fstream outFile(“output.dat”, ios_base::out|ios_base::trunc);
5. Make sure the file is open before reading/writing:
if (outFile.is_open()) {
// do stuff
outFile.close(); }
File stream options
ios_base::in – open file for reading
ios_base::out – open file for writing
ios_base::binary – open binary file (text is the default)
ios_base::trunc – delete any file that might already exist
with this name (default)
ios_base::app – append to the end of any existing file
with this name
ios_base::ate – start working at the bottom of the file
Note: by default, files will be open for both read and write
access, if neither ios_base::in nor ios_base::out are
specified.
Writing to a text file
Example writefile.cc:
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ofstream outFile;
outFile.open("summary.txt", ios_base::out);
if (outFile.is_open()) {
cout << "Beginning to write file summary.txt" << endl;
outFile << "Here is your summary:" << endl;
outFile << "Everything is working well today!" << endl;
outFile.close();
cout << "Done writing to file!" << endl;
}
return 0;
}
This creates a text file called “summary.txt” in the current working directory and prints a couple
of lines to it.
Reading a text file
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ifstream inFile;
// read the file we wrote in writefile.cc
inFile.open("summary.txt", ios_base::in);
if (inFile.is_open()) {
cout << "Beginning to read file summary.txt" << endl << endl;
string lineIn;
while (inFile) {
getline(inFile, lineIn); // read entire line at a time
if (lineIn.length() > 0) // final read attempt will return a blank line
cout << lineIn << endl; // print line if not empty
}
inFile.close();
cout << endl << "Done reading from file!" << endl;
}
else {
cout << "Error: Could not open file for reading." << endl;
}
return 0;
}
Reading beyond end-of-file
• Notice in the previous example that or final getline
returns a blank line (with length 0). After this
happens, inFile returns false on the next test of
while(inFile). We need to remember to do special
handling of this blank line whenever we use getline
inside of a “while(inFile)” block
• Perhaps surprisingly, if we were to keep trying to
read additional lines with getline(), we would keep
getting more blank lines, rather than an I/O error
• Similarly, if a file open fails and we try to use
getline(), we will also just get blank lines
C-style file I/O
The C++ tools are perfectly adequate for reading and
writing files, but so are the older, C-style functions.
You may encounter the C-style I/O functions if you
work with an older code base. The syntax is
different, but the C functions are fairly analogous to
the C++ functions.
Stringstream
Stringstream is a special stream that can read and write
basic data types (integers, doubles, etc.) as well as
strings. This allows one to convert basic data types
to/from strings (e.g., an integer to a string, or vice
versa).
To use stringstream, #include <sstream>
To create a stringstream,
stringstream sStream;
The next example shows how to convert a double-
precision number to a string, and vice-versa, using
stringstream
Example stringstream.cc:
#include <iostream>
#include <sstream>
using namespace std;
int main()
{
stringstream stream1, stream2;
double x = 1.29312, y;
string a;
return 0;
}
// stringstream2.cc
#include <iostream>
#include <sstream>
using namespace std;
int main()
{
int t = 4, result = 42;
stringstream ss;
ss << "Try " << t << " gave a value of " << result;
cout << ss.str() << endl; // convert to string for printing