Now that we have extracted all the IP addresses and HTTP codes, we are ready to begin sorting the data.
There are two ways that we could go about implementing the sort algorithm. We could either write two bubble sort algorithms, one for IP addresses and one for HTTP codes, or we could implement a single bubble sort algorithm and two comparison functions, one for IP addresses and one for HTTP codes. For efficiency, we choose the latter option.
In fact, the way that we will write the algorithm is to define a comparison function which takes two logType structures (or in fact pointers to them) as parameters, and an integer which will act as a flag telling it which comparison to do. It will then return a number greater than, equal to or less than zero depending on the relationship of the two inputs to one another with respect to the comparison specified by the flag (either comparison of IP address, or of HTTP code). Then a single bubble sort algorithm will call this comparison function as it proceeds, to determine the correct order of lines with respect to the specified kind of sort.
Instead of writing our flag in numerical form (e.g. with 1 representing a comparison function for HTTP codes and 2 representing a comparison of IP addresses) we will define two constants with numerical values which we can use instead of the numbers themselves. Remembering the names of the constants instead of which integer to use for the flag will be much easier. This trick, which effectively allows-na one to assign names to various numerical codes, is used frequently in programming, and is good programming practice.
To define the two flag values that we will use to change the mode of our comparison function between comparing HTTP codes and comparing IP addresses, we define constants as follows-na:
#define HTTP_FLAG 0 #define IP_FLAG 1
The # tells us that this definition is a preprocessor directive. It is run before compilation proper of the program begins. In this case it is a very simple directive - it just runs through the program and replaces every occurrence of the constant name with the numerical value specified (in our case, simply a 0 or 1). Being preprocessor directives, these lines are put at the very beginning of our source file, usually just after the #include statements.
Often one goes beyond this simple definition of flag constants and even defines a flag type. This simply defines a variable type, called flag say, which is internally the same as int, however the compiler will treat them as entirely different types. This definition of a special flag type can be done with a typedef statement as follows-na:
typedef int flag;
Now everywhere a variable is being used to define a flag, one specifies that it be of type flag instead of type int, though in every other way it will act just like a variable of type int. We will not go to these extremes in our program, however in a large project it would be advisable to define a new type for each different kind of flag that was being used, to save confusion and to help ensure the use of the correct flags at the right times throughout the program. By specifying a number of different types for flags, one forces type checking on the types of the variables used to carry flags, and a compiler error is generated if the programmer inadvertently passes the wrong type of flag to a function for example, even if the underlying data types are all the same, e.g. int.