There are several ways to define new types in C++.
Enumerated types look like this:
enum MonthT {
JANUARY = 100,
FEBRUARY,
MARCH,
APRIL,
MAY,
JUNE,
JULY,
AUGUST,
SEPTEMBER,
OCTOBER,
NOVEMBER,
DECEMBER,
};
int main() {
MonthT month = APRIL;
cout << "month is: " << month << endl;
return 0;
}
If you don't define values for the enumerated constants, the first
one will default to 0 and the rest will be incremented by 1. In the
above code, since the first enumerated constant (JANUARY)
is set to 100, APRIL will have a value of 103.
The typedef keyword allows you to create a new type
from some old type. For example:
typedef int FakeInt; typedef unsigned int uint;
The above code defines two new types: FakeInt, which
is just another way of saying int, and uint,
which is a shortcut for an unsigned map.
C++ has inherited the old-skool C way of defining data types, known
as the struct:
struct Date {
int day;
int month;
int year;
};
int main() {
Date d;
d.day = 12;
d.month = 9;
d.year = 2007;
}
Unions are similar to structs, but all elements of a union share the same memory. For example:
union mix_t {
int val;
char bytes[4];
};
int main() {
mix_t myMix;
myMix.val = 258;
cout << "myMix.val is " << myMix.val << endl;
cout << "myMix.bytes[0] is " << static_cast<int>(myMix.bytes[0]) << endl;
cout << "myMix.bytes[1] is " << static_cast<int>(myMix.bytes[1]) << endl;
cout << "myMix.bytes[2] is " << static_cast<int>(myMix.bytes[2]) << endl;
cout << "myMix.bytes[3] is " << static_cast<int>(myMix.bytes[3]) << endl;
}
In the above code, the val and bytes
fields of the union share the same space in memory. Setting
val will affect what is in the bytes array.
Because they often rely on specific byte counts, unions are not very
portable.
C++ also has the idea of a class. Classes are very
similar to structs:
class Date {
public:
int day;
int month;
int year;
};
int main() {
Date d;
d.day = 12;
d.month = 9;
d.year = 2007;
}
Note the access specifier public. That tells the
compiler that the day, month, and
year variables should be accessible to anyone outside of
the class. Other access specifiers are private and
protected.
private. Only functions within a class (or friends of
the class) can access private data. Structs, on the other hand, have
public access by default, which means anyone can access
their internals.One of the ideas behind classes was that they can contain both data and functions:
#include <iostream>
using std::cout;
/**
* Our brilliant Date class, which holds information about a date.
*/
class Date {
public:
/**
* Constructor for the Date class - used to initialize Date data
*/
Date( int m, int d, int y ) {
month = m;
day = d;
year = y;
}
/**
* Getter/setter functions for month, day, and year
*/
int getMonth() { return month; }
int getDay() { return day; }
int getYear() { return year; }
void setMonth( int m ) { month = m; }
void setDay( int d ) { day = d; }
void setYear( int d ) { year = d; }
/**
* Display this date to standard out
*/
void displayDate() {
cout << getMonth() << "/" << getDay() << "/" << getYear();
}
private:
int month;
int day;
int year;
};
int main() {
// create an instance of the Date class and display it
Date d( 9, 12, 2007 );
d.displayDate();
}
When creating large projects, it can be useful to split your class definitions into header (.h) and implementation (.cc) files. For example, the code above might be split into three files:
Date.h - header file that describes methods and data of the classDate.cc - implementation of the functions in the Date classmain.cc - a main function, which uses the Date classHere's what those files might look like:
#ifndef DATE_H
#define DATE_H
/**
* Our brilliant Date class, which holds information about a date.
*/
class Date {
public:
/**
* Constructor for the Date class - used to initialize Date data
*/
Date( int m, int d, int y );
/**
* Getter/setter functions for month, day, and year
*/
int getMonth();
int getDay();
int getYear();
void setMonth( int m );
void setDay( int d );
void setYear( int d );
/**
* Display the Date to standard output
*/
void displayDate();
private:
int month;
int day;
int year;
};
#endif
#include <iostream>
using std::cout;
#include "Date.h"
Date::Date( int m, int d, int y ) {
month = m;
day = d;
year = y;
}
int Date::getMonth() { return month; }
int Date::getDay() { return day; }
int Date::getYear() { return year; }
void Date::setMonth( int m ) { month = m; }
void Date::setDay( int d ) { day = d; }
void Date::setYear( int d ) { year = d; }
void Date::displayDate() {
cout << getMonth() << "/" << getDay() << "/" << getYear();
}
#include "Date.h"
int main() {
// create an instance of the Date class and display it
Date d( 9, 12, 2007 );
d.displayDate();
}
g++ -Wall -Werror Date.cc main.cc -o main, but as we add
more files, that approach won't scale. A common solution to this
problem is to define how the code should be compiled using a Makefile,
and then using the make command
to compile the code.