Creating, Compiling, Linking and Executing of a C Programs
In this article firstly you can read how to invoke Turbo C, how to name programs, how to compile programs, how to link programs and finally how to execute programs.
Invoking Turbo C
To start Turbo C, firstly we move to the directory in which Turbo C is installed and then type TC at the DOS command prompt. If Turbo C is installed in the root directory C:\> then type TC as:
C:\>TC
After this as you press Enter key the Turbo C IDE (Integrated Development Environment) screen, that consists of a menu bar that contains menu options, such as File, Edit, Run, Compile, and so on, is displayed as shown in figure 1.
It will mostly be blank, with the menu bar at the top and the status line on the bottom. The Turbo C includes all the facilities to develop and execute programs, such as editor, compiler, debugger, linker, and so on. The first character of each menu option is highlighted. You can select any menu option by pressing the ALT tab key and the highlighted character of the respective menu option. For example, if you want to select Run menu option then you will press the TAB key and key containing the R character simultaneously as - ALT + E.
Naming Our Source Program
Now we are ready to work under Turbo C IDE. After invoking the Turbo C we select NEW option from the File menu. An edit window will appear with the filename NONAME.C. Since it is the name specified by the Turbo C IDE, therefore you need to change this. For this we select the Save option from the File menu. As we do this a dialog box will appear that asks for the name of our source program in order to rename NONAME.C.
After this the new name, that is CHAP0101.C, will appear because we have changed the file name from the NONAME.C to CHAP0101.C. Now the cursor is placed in the upper-left corner of the Edit window. Now you can type your source program, chap0201.cpp as shown in figure-2 as written in the earlier section, using arrow keys, tab key and Enter key to move the cursor.
Once we complete our typing we should save it to the disk by selecting Save from the File menu, or by pressing the F2. It is a good habit to this before compiling and executing our program.
Compiling the Source Program
To compile the source file, we select Compile from Compile menu and will press Enter key. If there is no syntax error in the source program then the resulting window shows- Warning: 0, Errors: 0 and finally Success: Press any key. This compilation process creates an object file, which has an extension OBJ.
However if there is any error then the compiler displays the appropriate error messages, called diagnostic messages, that tells the cause of the error and the compilation process terminates. Therefore go back to the source program, correct it and compile it again. You can also compile the source program by pressing the ALT and F9 keys simultaneously.
Figure-3 shows various stages of compilation of a C++ program.
Linking the Program
Once the source program has been converted into an object file, it is still not in the form that you can run. The reason behind this is that there may be some references to standard library functions or user-defined functions in other object files, which are compiled separately. Therefore these object files are to be linked together along with standard library functions that are contained in library files. To link our object file, select Link from the Compile menu. After this the .OBJ file will be compiled with the one or more library files. The result of this linking process produces an executable file with .EXE extension. Figure-4 depicts this linking process.
This chap0201.exe program is a standalone executable program, which you can directly execute at command prompt as:
C:\TC> CHAP0201
This will execute the program and the results will get displayed on the screen.
Executing (Running) the Program
To execute (run) the .EXE file, select Run from RUN menu and press Enter key. The result will be displayed on the screen and controls returns back to the editing screen. However you can also execute the .EXE file by pressing CTRL and F9 keys simultaneously. To see result select User Screen from RUN menu or by pressing ALT and F5 keys simultaneously.
Object Oriented Programming Classes & Objects
Introduction
In the world, if we see around ourselves, we always find things sharing common property(ies), on the basis of which they can be grouped together and can be put into one set, like, vehicles, toys, furniture etc. Not only that the non living things, but the living things also share common trait(s) on the basis of which they have been classified into various kingdoms and further species. Here in this concept, the vehicle, the furniture, the toy, all form ‘a class’. Whereas, the type of vehicle - like car, scooter etc. - or the type of furniture - like bed, dinning table etc. - form a subclass. However, the car number 68XY or ‘U’ model bed are the examples of ‘objects’ or ‘instances’.
Whenever we talk about object oriented analysis and design, the concept of class is first and foremost. A class is not only a new way of defining and implementing the data types by users but it is also a mean to model the real world behavior. Classes are the actual modeling of the object-oriented concept.
The object can be visualized as A CONTAINER THAT CONTAINS BOTH DATA AND CODE TO MANIPULATE THE DATA. Objects and classes are interrelated to each other as A CLASS IS A COLLECTION OF SIMILAR OBJECTS. As we saw earlier that the class is a user defined data type, hence the entire set of data and code of an object can be made a user defined data type with the help of class.
Every object is directly related to a class. Objects are nothing but instances of class. A class can be defined as a collection of objects of similar type. Thus a class is generalization of objects having similar attributes and operations. A class consists of two parts, one is class data types and the other is class data functions. These are collectively called class members.
Within a class, the visibility level of class members is very important.. A class member can be one of the two types: PRIVATE or PUBLIC. As the name suggests, a private member can be seen only inside the class, whereas the public class member can be seen, accessed and mutated outside the class as well.
A class is an ABSTRACT DATA TYPE since it follows the concept of ABSTRACTION. Abstraction is the mechanism, which only shows the necessary information to the user, while hiding background details or implementation. Rotation of a fan by turning ‘on’ an electrical switch, without any additional effort or information requirement, is clearly an example of abstraction. Then, we come to an important concept of the object-oriented analysis, which is DATA HIDING AND ENCAPSULATION. Both of these terms are tightly interrelated and are very similar to each other. Data Hiding is a very broad term that means insulating the data such that is cannot be directly accessed by the program. Encapsulation is a specific method of this term in which we wrap up both the data and the function in a single unit (class).
Data Members
Before understanding the data members, let us first of all make a sample class of our own. This program will consist of a class and three objects of that class. Let us make a class called book. This class will have some properties such as cost of the book and the number of pages in the book. Therefore, we take variables called cost and pages that represent respectively cost of the book and number of pages in the book. C++ uses // for inline comments and /* ____ */ for multi-line comments.
The general form of a class declaration is :
class class_name{ private:
data/variable declarations;
function declarations;
public:
data/variable declarations;
function declarations;
};
- PRIVATE: The ‘private’ access specifier indicates that the members declared under it can be accessed only by the member functions of the class.
- PUBLIC: The public visibility label indicates that the members declared under it can be accessed from outside the class also (by the functions outside the class).
- PROTECTED: The protected label is used when a class wants to inherit the members from another class.
Following is a demonstration for a class declaration.
// book.cpp// demonstration of a small class illustrating the data members
#include //preprocessor directive
class book //declaration of the class
{
private: //visibility label
float cost; //variable declaration for the cost of book
int pages; //variable declaration for the number of pages of //the book
};
void main()
{
book b1,b2; //creating objects of class book
}
In this class book we have three important attributes called cost and pages. These are the variable data members of the class book as they can take different values for different objects. Now the cost will be of float type as it will be in rupees and paise. The pages of the book will be of type int (integer) as the pages are whole numbers. Thus the data members are the variables used to represent the attributes of the class. Two important decisions have to be taken while deciding the attributes (data members) of the class. The first is to decide attributes that will model the class. It also involves deciding meaningful names for the variable names that follow the semantics of the language. The second important decision is to decide upon the data type that each of the variables will take.
Member Functions: Definition and Pass Values (Arguments)
Member Functions are functions that are included within the class. These model the operations of the class.
There are two types of member functions
1. Accessor Functions
2. Mutator Functions
As the name suggests, the accessor functions access the data (for class variables). Thus, through accessor functions the value of the member variable is determined. The mutator function changes or mutate the data according to the argument passed.The accessor functions usually start with show prefix while the mutator functions are prefixed with set.
A more traditional format for these function definitions are:
void setdata(int d){
data=d;
}
void showdata()
{
cout<< “\nData is <
}
One important property of the mutator function is that it always contains some argument. While the accessor function may or may not contain any argument. Whenever we pass some message to the function by invoking it, we do so by passing arguments. The arguments are inside brackets alongside the name of function. For example, in above-mentioned case, d (int type) is the argument of function setdata().
To further clear this concept let us further define our earlier class book.In this example, we have two accessor and two mutator methods. The accessor methods are showcost() and showpages() and the mutator methods are setcost() and setpages() respectively.
// book.cpp// This class demonstrates a small class illustrating the data members as well as
//the member functions
#include //preprocessor directive
class book //declaration of the class
{
private: //visibility label
float cost; //variable declaration for the cost of book
int pages; //variable declaration for the number of pages of
//the book
public:
//Mutator methods
void setcost( float c)
{
cost=c;
}
void setpages( int p)
{
pages=p;
}
//Accessor methods
void showcost()
{
cout<< “\nThe cost of the book is:”<
}
void showpages()
{
cout<<”\n The number of pages in this book are:”<
}
}; //end of class declaration
void main()
{
book b1,b2;
b1.setcost(100.50); /* invoke mutator method ‘setcost’ for object b1*/
b1.setpages(223);
b2.setcost(200.25); /* invoke mutator method ‘setcost’ for object b2*/
b2.setpages(540);
b1.showcost(); /* invoke accessor method ‘showcost’ for b1*/
b1.showpages();
b2.showcost(); /* invoke accessor method ‘showcost’ for b2*/
b2.showpages();
} /* end of main */
To access the private features of the class, i.e. the class data members, we have to take the help of functions. When the function is called, the control is transferred to the first statement in the function definition. One by one all the statements within the function definition are executed until the ending brace is encountered. When the program control encounters ending brace, the control returns to the main program from where it was originally called. The function definition consists of declarator/header that is followed by the body of function. The function body is the actual definition of the function. It consists of the statements that make up the function, delimited by the ending braces. The declarator must agree with the semantics of the declaration. First of all, it must use the same name for the function, then it should have the same argument types in the same order along with having the same return type. One important thing must be noted that the function definition doesnot end with a semi colon as the class does. The arguments are passed through functions that in turn set or change the values of the class variables. In this example, we are using two objects b1 and b2. To access the functions of the class, the dot (.) operator is used. The format used is the name of object followed by “.”, followed by the name of function further followed by arguments inside the parentheses of function (if any). The arguments are the actual values passed to the function.
b2.setpages(540);
540 is the value of the pages passed to the function setpages(). This function setpages() belongs to object b2 and thus after this statement the object b2, or more specifically the book b2 has number of pages as 540. Another way of defining functions is to DEFINE THEM OUTSIDE THE CLASS. These definitions are muck like that of normal functions, with a function header and a function body. The difference is that such a member function must have an IDENTITY LABEL in its header. This label tells the compiler which class the function belongs to. The identity label comprises the name of the class and SCOPE RESOLUTION OPERATOR (::). As the name suggests, the scope resolution operator defines the scope (boundary) of a function (in terms of which class it belongs to). The syntax is :
return_type class_name :: function_name( argument ){
function body; }
For example in the statement
Consider the following example, demonstrating the use of scope resolution operator:
class integer{ private:
int i,j;
public:
void getdata(int x, int y); /*prototype declaration*/
void showdata(); /*prototype declaration*/
};
void integer :: getdata(int x, int y) // definition of the function outside class
{ i=x;
j=y;
}
void integer :: showdata() // definition of the function outside class
{ cout<<”\n i= “<
cout<<”\n j= “<
}
void main()
{
integer i1;
i1.getdata();
i1.showdata();
}
Data Hiding and Encapsulation (Private and Public Members)
A class can be defined as an entity that binds the data and its associated functions together. It allows the data to be hidden from the user, if necessary. When defining a class, we are creating a new abstract data type that can be treated like any other built-in-type.
Generally, a class specification has two parts:
1. Class declaration
2. Class function declaratio
The class declaration describes the type and scope of its members. Thus the class declaration is the definition of the class itself. It consists of the class attributes and the class functions. The class function declarations describe how the class functions are implemented. Thus, the class function declaration is the actual implementation of the class operations. The class body contains the declaration of variables and functions. These functions and variables are collectively called members. They are usually grouped under two sections, namely, private and public to denote which of the members are private and which of them are public. The keywords private and public are known as visibility labels as they characterize the level of visibility. In other words we can say that the visibility labels describe the level of transparency of the members inside or outside the class. The members that are declared as public can be accessed from anywhere, outside or inside the class. On the other hand, the private members can be accessed only from the inside of the class. Thus public and private members implement the data encapsulation of the Object Oriented Programming. There are two important terms viz. data members and member functions. The data members are the variables declared inside the class whereas the member functions are the functions. The public members can be accessed from anywhere including from outside the class. Only the member functions can access the private data members and private functions. Therefore, encapsulation can be defined as the binding together of data and functions into a single class type variable. By information hiding we mean that the internal implementation details of the data and functions (members of a class) are kept hidden from the outside world. Thus, whenever changes occur, the side effects are avoided. Through information hiding, data structures and the operations manipulating them are encapsulated into one entity- the class. This promotes the reusability of components of class or the class as such. Then, the interfaces between the encapsulated objects are also simplified through encapsulation. Any object sending the message across need not be concerned with the internal implementations details. Thus, messaging is facilitated and bugs are reduced.
Default Label
The default visibility label or access specifier is PRIVATE. The class definition of book can be re-written as:
class book{ float cost;
int pages;
public:
void setcost(float c);
void setpages(int p);
void showcost();
void showpages();
};
Thus, the members ‘cost’ and ‘pages’ are private in the class ‘book’.
Arrays Within a Class
Within a class’s definition, an array can be used as a member variable. For example, observe the following class definition:
#include#define SIZE 10
class myarray
{
int arr[SIZE];
private:
void input(void);
void output(void);
}; /*class definition ends*
Here arr[] is declared as a private member of the class array. This can be used in the member function just like any other variable.
Any sort of operation can be performed on the array members. For example, in the above class definition, the function input() can be used to set the values of the elements of the array. The member function output() can be used to show the set values of the array elements. Thus, any functionality can be provided to the arrays depending upon the need. The definitions of the functions declared above, is as follows:
void myarray :: input(){
int i;
for (i=0;i
cin >> arr[i];
}
void myarray :: output()
{
int j;
for (j=0; j
cout<<”\n “<
}
void main()
{
myarray a; /* create object ‘a’ of class myarray */
cout<<”\n ENTER 10 VALUES :”
a.input();
cout<<”\n VALUES ENTERED BY YOU ARE : “;
a.output();
}
The variables, i and j used as counter in functions input() and output() respectively are local variables with their scope bound to member functions.
Friend Functions
“FRIEND FUNCTIONS” or “FRIENDLY FUNCTIONS” are like ordinary functions but they have ACCESS TO PRIVATE DATA MEMBERS OF THE CLASS to which they are declared “friend”. ‘Friend Functions’ is one way through which C++ provides a mechanism to break the wall of encapsulation created around members of a class, by use of ‘private’ label. But, it should never be forgotten that friend function can access the private data members through objects of the class, using dot membership operator. The following are salient characteristics of a friend functions:
A friend function lies outside the scope of class to which it is a ‘friend’.
An object of the class to which it is a friend cannot call a friend function. This is because a friend function is like a normal function and is called like a normal function without the use of object and dot operator.It does not access the members directly. Instead, it does so through an object and dot membership operator.Declaration of friend function in private or public section does not make any difference to its meaning.
Consider an example of a friend function “display( )”, to print private data members of an object.
#includeclass sample
{
int value;
public :
void input(); /*member function prototype
*/
friend void display(sample); /*friend function
declaration */
}; /*class definition end */
void sample::input()
{
cout<<”\n Enter the value of sample. “;
cin>>value;
}
void display(sample s)
{ /*access private member through object of class */
cout<”<
}
void main()
{
sample s;
s.input();
display(s); /*invoke a friend function, like an ordinary function */
}
display is a friend function to class “sample” .It is declared inside the class by preceeding the function header with keyword “friend” .It is defined like any other normal function. It is invoked in main and passed an object of sample class, ‘s’. The function then displays the private member value, using the syntax “s.value”, Friend function find their use when two classes need to share a particular function. In such a case a common function is made friendly to both the classes.
Must to Remember
Member functions are allocated space (memory) only once when they are defined as a part of class specification, whereas data members are not allocated space while declaring the class. Data members are created, only when object of class is declared. Hence,” DIFFERENT OBJECTS OF SAME CLASS HAVE DIFFERENT COPIES OF DATA MEMBERS (ATTRIBUTES) BUT SHARE SAME SET/COPY OF MEMBER FUNCTIONS (BEHAVIOR)”.
Constructors and Destructors
One of the objectives of C++ is to create user-defined data types that behave like in-built data types. Thus, statements like:
integer i1= 10;
where i1 is an object of class integer, should be allowed just as it is allowed for in-built data type.
float x=7.95; /*allowed*/
The requirement of user-defined type, behaving as pre-defined or in-built type can be achieved by making use of constructor. A constructor is a member function which shares the name of the class and is used to initialize the object when it is created. A constructor is called automatically, as soon as an object is declared to a class. Constructors without any parameter are called DEFAULT CONSTRUCTORS whereas the one with parameters passed to it, is known as PARAMETERIZED CONSTRUCTOR. The following are salient characteristics of a constructor:
It should be declared in the public section. It is invoked automatically when an object is created. It does not have a return type, not even void. It cannot be inherited, though a derived class can call base class constructor. It can have default arguments (default arguments are those arguments whose default value is specified at the time of function declaration. If no value is passed to the function for that parameter at the time of call, default value is taken for that particular parameter). Constructor cannot be referred to by their addresses.
On the other hand, destructors are member functions which share the name of the class, preceded by the ~(tilde) operator and are used to destroy the objects when their scope (objects’ scope) is over. A destructor never takes argument or return value. It is invoked implicitly when the program is exit. Destructors are particularly essential to use when “new” operator has been used in the program for dynamic memory allocation. Destructors may use “delete” operator for cleaning up (de-allocating) the memory not required.
Consider the coding given below, which shows the use of default constructor, parameterized constructor and destructor in a class.
class integer{
int i,j;
public :
integer() /* default constructor */
{ i=0;
j=0;
}
integer(int x) /* parameterized constructor with one parameter */
{
i=x;
j=x;
}
integer (int x, int y) /* parameterized constructor with two parameters */
{
i=x;
j=y;
}
}; /* end of class */
void main()
{
integer i1; /* call to default constructor */
i1.display();
integer i2(10); /*call to parameterized constructor having one
parameter */
i2.display();
integer i3(10,30); /* call to parameterized constructor
having two parameters */
i3.display();
} /* destructors for i3, i2 and i1 are called automatically */
It is important to note that the class integer contains three constructors, having the same name but differing in their argument types, order or number. Such a use of constructors is known as CONSTRUCTOR OVERLOADING.
Constructor overloading helps in dynamic initialization of objects by providing various initialization formats during runtime. Further, the statements given below are semantically (according to their meaning) same:
integer i2(10); /* call to single-argument constructor */integer i2=10; /* call to single-argument constructor */
But this format of initialization is available for constructor having only one parameter. For constructor having more than one parameter, the arguments must be enclosed within parentheses, separated by comma.
DATA TYPES -> ABSTRACT
Introduction
Object-oriented Analysis and Design is a new way of approaching the solutions to problems related to the programming world. This concept is an evolutionary concept and offers a refreshing change from the obsolete programming concepts, such as modular programming (in modular programming, a program is divided into small units of code, called “modules”).
In this new approach, models are organized around real world concepts. As we know that in the earlier programming concepts, the stress was upon solving the problem. The flaw in that approach was, no reusability of code was considered. All such flaws have been removed in the new approach – “Object-oriented Analysis and Design”.
In the OOAD, the central concept is the object, which constitutes the attributes (properties) and the behavior (operations). Any object can be thought of as an entity comprising of data structure and behavior. OOAD is useful for understanding the problems, modeling enterprises, designing programs and databases and last of all, preparing documentation.
In this article, first of all, we will have a brief overview of the modeling of real world through OOAD approach. Then, the various advantages directly accruing from the usage of OOAD such as autonomy, generation of correct applications and reusability will be discussed in detail. Then, we will get acquainted with the key features of OOAD i.e. classes, instance values, methods and messages. The various intricacies of objects and the operations associated with the generation and manipulation of objects and classes will also be discussed.
The term “Object-oriented” actually means that we organize the software as a collection of discrete and distinct objects that incorporate both data structure and behavior related to objects. This is contrasted with the approach followed in conventional programming techniques, in which the data structure and behavior are connected in a loose fashion. Object-oriented is an innovative approach of visualizing based on generalization and abstraction that exists in the real world.
Model of Real World
In our real, physical world we deal with objects that are also real as are we are. For example in our world we have real tangible objects such as computers, books, furniture etc. These objects are not like data and they are not like functions either. Complex real world objects have both attributes and behavior.
Now what do we mean by attributes and behavior of objects? Let us consider some real object such as automobile. Every automobile has got certain features such as model (year of manufacture), color, price etc. These are the attributes of the object. Every automobile is also capable of performing some functions or operations. Such as it can be drove, it can be turned left or right, it can be purchased and so on. These operations that can be performed by the object are called functions or operations.
To further illustrate this point let us consider another example from our real world. Let us consider the book that you are currently reading. This book is also an object that can be felt, read. Its important attributes or properties are number of pages, price and author of the book. Now some important functions that can be performed by this book are to impart information to the user and to increase his awareness.
The object book encapsulates data, operations, other objects, constants and other related objects. Encapsulation means that all of this information is packaged under one name and can be reused as one specification or program component.
Thus summarizing, the object-oriented is:
Object-oriented = objects + classification + inheritance + communication
Attributes
Attributes, as previously explained are the property of the class. Example of attributes (sometimes called characteristics) are, for people, skin color, eye color and job titles, and, for cars, model and number of doors. Attributes in the real world are equivalent to data in the programs; they have certain specific values, such as white (for ski color), black (eye color) or three (for the number of doors).
Behavior
Behavior is something a real world object does in response to some stimulus. If you ask your boss for a raise, he/she will generally say yes or no. If you apply the brakes in a car, it will generally stop. Saying something and stopping are examples of behavior. Behavior is like a function: you call a function to do something, like display the inventory; and it does it. Behavior is associated with every object. In other words we may also say that every object is capable of performing some functions or operations.
Autonomy
Object oriented programming approach offers several benefits to both programmer and the user. The biggest advantage offered is the autonomy. The autonomy means independence, freedom from the shackles and hold of previous procedural programming approaches.
This approach redresses many problems associated with previous approaches such as problem in the modeling of real world situations, no reuse of resources, poor memory management, and like. This technology offers several benefits such as improved programmer productivity, lesser maintenance costs and the foremost: better quality of software.
The salient features of this approach are:
- The redundant code is eliminated and the existing classes can be extended through inheritence.
- Separate modules or functions can be reused from the existing classes rather than having to build the functions from the scratch.
- Encapsulation prevents the creeping of unknown bugs in the program by usage of same member’s names.
- Multiple instances of an object can be there without interference.
- By using the message passing technique for communication between objects, the interface with external objects becomes much more simpler.
- The scaling of program from small to large or vice-versa is facilitated.
- It becomes easy to work on a project through a group approach.
Generation of Correct Applications
The whole concept of Object-oriented analysis and design (OOAD) revolves around the facilitation of programming techniques in such a way that the whole process of coding results in generation of correct applications.
Through OOAD approach, we expand our understanding of computer programming problems by providing an insight to the real world modeling through object-oriented approach. By following object-oriented approach, the whole programming cycle simplifies as a better understanding of problems results.
In object-oriented programming we generate an abstraction and then specify it in the form of classes, therefore any chances of unintentional errors creeping becomes very less. Moreover, due to other useful features offered by object-oriented programming such as polymorphism, inheritance and abstraction, the reusability of existing class members, such as data members and function members, is possible. All of the above factors accrue in generation of correct applications in a far efficient manner than offered by previous programming languages.
Reusability
The foremost advantage of object-oriented programming is the reusability feature. Through reusability, classes can be used with or without changes, depending upon the requirement.
Now the question is what exactly is reusability. Once the class has been written, created, and debugged, it can be distributed to other programmers for use in their own programs. This is called reusability. It is similar to the way a library of functions in a procedural language can be incorporated into different programs. Through reusability feature of the object oriented analysis and design, a lot of effort and mundane work on the part of programmer is obviated.
In OOP, the concept of inheritance provides an important extension to the idea of reusability. A programmer can take an existing class and, without modifying it, add additional features and capabilities to it. This is done by deriving a new class from the existing one. The new class will inherit the capabilities of the old one, but is free to add new features of its own. Thus through inheritence the existing features of a class can be extended without affecting the base or parent class.
For example, you might have written a class that creates a menu system, such as that used in windows or other Graphic User Interface (GUIs). Now this class is working fine, and you don’t want to change it, but you want to enhance the capability to make some menu entries flash on and off. To do this, you simply create a new class that inherits all the capabilities of the existing one but adds flashing menu entries.
The ease with which existing software can be reused is an important benefit of OOP. Many companies find that being able to reuse classes on a second project provides an increased return on their original programming investment. Thus through reusability a lot of re-keying and developing existing classes from scratch is prevented.
Classes
The objects contain data and code to manipulate the data. The entire set of data and code of an object can be made user-defined data type with the help of a class. In fact, objects are instances of type class. Once a class has been defined, we can create any number of objects belonging to that class. Each object is associated with the data of type class with which they are created. A class is thus a collection of objects of similar type. For example, mango, apple, and orange are members of the class fruit. Classes are user defined data types and behave like the built in types of a programming language.
A class is an Object Oriented concept that encapsulates the data and procedural abstractions required to describe the content and behavior of some real world entity.
The data abstractions (attributes) that describe the class are enclosed by a wall of procedural abstractions (called operations, methods or services) that are capable of manipulating the data in some way. The only way to reach the attributes is to go through one of the methods that form the wall. Therefore, the class encapsulates data and the processing that manipulates the data. This achieves information hiding and reduces the impact of side effect associated with change. Since the methods tend to manipulate a limited number of attributes, they are cohesive; and because communication occurs only through the methods that make up the wall, the class tends to be decoupled from other elements of a system. All of these design characteristics lead to high quality software.
Stated another way, a class is a generalized description that describes a collection of similar objects. By definition, all objects that exist within the class inherit its attributes and the operations that are available to manipulate the attributes. A super-class is a collection of classes, and a subclass is a specialized instance of a class. A super-class can be taken as an abstraction of a number of similar classes, whereas a sub-class is a specialization of a particular class.
These definitions imply the existence of a class hierarchy in which the attributes and operations of the super-class are inherited by subclasses that may each add additional private attributes and methods.
Instance Values
The instance’s dictionary meaning is example, illustration. By instance we mean an object and by instantiating a class, we use the class. Every class is a set of similar objects. So, by instantiating we actually make the objects of the class and use it in the program. By making the object corresponding to the class, we are modeling the real world. As in real world, we can generalize objects, similarly in the programming world; we first generalize the objects by making classes and then use it.
Every instance or more specifically an object has got certain characteristics. For example, a class called computer will has price, speed, and several other characteristics. But these characteristics will vary from one computer to another. Therefore, these characteristics have to be variable. Therefore in the computer world, we take some variable that represent those characteristics. These variables closely resemble the properties of the class that we are modeling. Now as the name suggests, the variable can take different values that are constrained by the data type of the particular variable. These values that the variable can take keeping in mind the constraints imposed by the data type chosen are called instance values.
For example, let us take a small class called pen. This class will have some characteristics and some functions that can be operated on these values. Some of these properties will be the color, price and make of the pen. Some of the operations associated with these properties can be setting the color, price and make of the pen and displaying the set values on the screen of the shop.
Methods and Messages
An object encapsulates data (represented as a collection of attributes) and the algorithms that process the data. These algorithms are called methods and can be viewed as modules in a conventional sense.
Each of the operations that are encapsulated by an object provides a representation of one of the behaviors of the object. For example, the operation GetColor for the object automobile has been designed to receive a message that requests the color of the particular instance of the class. Whenever an object receives a stimulus it initiates some behavior. This can be as simple as retrieving the color of an automobile or as complex as the initiation of a chain of stimuli that are passed among a variety of different objects. In the latter case, consider an example in which the initial stimulus received by the object 1 result in the generation of two other stimuli that are sent to the object 2 and object 3. Operations encapsulated by the second and the third objects act on the stimuli, returning necessary information to the first object. Object 1 then uses the returned information to satisfy the behavior demanded by the initial stimulus.
Messages are the means by which the objects interact. Using the terminology introduced in the preceding section, a message stimulates some behavior to occur in the receiving object. The behavior is accomplished when an operation is executed.
Creating and Destroying Objects
The creation of objects involves the task of making copies of the member of class (attributes and methods) and putting them under a single unit object. Different OO programming languages use different syntax for creation of objects. The following quote is in fact true.
“Object oriented programming is not so much a coding technique as it is a code packaging technique, a way for code supplier to encapsulate functionality for delivery to customers”.
Constraints on Object and Instance Variables
A class defines the operations that can be performed on an instance. A class defines the variables of the instances. A variable associated with a specific instance is called an instance variable. These variables are used to store the instance state.
Constraints are functional relationships between entities of an object model, where entity includes objects, classes, attributes, links and association. Constraints restrict the values that entity resumes for example, the age of a person cannot be less than or equal to zero. Constraints are expressed in a declarative manner. In a programming language, constraints are converted to procedural form.
Pre and Post Conditions of Methods
Pre and post conditions of methods are used in contractual model of programming. A precondition is a condition that the caller of an operation agrees to satisfy. A post condition is a condition that the operation itself agrees to achieve. An invariant is a condition that a class must satisfy at all stable times. Condition and invariants form a part of class declaration and must be obeyed by all descendent classes. Violating a condition or invariant at run time raises an exception, which either causes the faulty operation to fail or executes an exception handler for the class, if it has been included in the class
LINKING BETWEEN C AND C++
Suppose we have a library of functions that have been compiled into object code and stored in the library code form. When we call a function from such a library in our program, the compiler with mark the name of the function as unresolved symbol in the object code of our program. To create an executable program, we have to use a linker and make sure that the linker searches the right library for the code of that function. If the linker finds the function’s object code in the library, it will combine that code with our program’s code to create an executable file.
Type-Safe linkage:
The key to linking C++ programs with functions lies in understanding how C++ resolves the names of functions. As we know, C++ lets us overload a function name, where by we can declare the same function with different sets of arguments. To help the linker, the C++ compiler uses an encoding scheme that creates a unique name for each overloaded function. The general idea of the encoding scheme is that is creates a unique name for each overloaded function. The general idea of the encoding algorithm is to combine the following component:
- The name of the function
- The name of the class in which the function is defined
- The list of argument types by the function.
To generate a unique signature for each function., we do not have to know the exact detail of the encoding algorithm, because it differs from one compiler to another. But knowing that a unique signature is generated for each function in a class should help us understand how the linker can figure out which of the many different version of a function to call.
Effect of function name encoding
How does function name encoding affect C++ program?
To see benefits of name encoding consider the following examples
#includevoid print (unsigned short x)
{
printf (“x=%u”, x);
}
void main (void)
{
short x= -1;
print (x);
}
The print function expects and unsigned short integer arguments, but main calls print with a signed short integer argument. You can see the result of this type mismatch from the output of the program:
X=65535
Even though print was called with a-1, the printed value is 65535. The explanation for this result is as follows: This program was run on a system that uses a 16-bit representation for short integers. The bit representation of the value –1 happens to be oxffff (all 16 bits are one) which when treated as an unsigned quantity, result in the value 65535
HOW TO MAKE A LIBRARY
While designing a class library, one has to decide the inheritance hierarchy of the classes; the way one class uses the facilities of others, a kind of operation that the classes will support.
ORGANIZING C++ CLASSES
If there were standard class library for C++, it would be easy to decide how to organize C++ classes. One can model the classes after the standard ones. Unfortunately, C++ does not have any classes yet. The (iostream) class library, which is part of AT&T’s C++ 2.0, may be the only standard class library that one can currently expect to find. But one needs much more than ’iostream’ class to develop complete application in C++. Additionally, even if there were many more standard classes for C++, one would still have to write classes that were customized for a particular application. From above discussion we can see that, sooner or later, you would face the task of the organization of a library of classes that will form the basis of application.
Inheritance hierarchy under single inheritance:
There two distinct ways two organize the inheritance tree of classes under single inheritance:
- A single class hierarchy in which all classes in the library are derived form a single root classes. The Smalltalk–80 programming language provides a class hierarchy of this type. Therefore, the organization of C++ classes is known as the ‘Smalltalk model’ or a ‘single tree model’. There the term root class refers to base class that is not drived from any other class.
- Multiple disjoint class hierarchies with more than one roote class. This has been referred to as the forest model, because there are multiple trees of class hierarchies in the library.
SINGLE CLASS HIERARCHY
As figure 1 shows, a library uses a single class hierarchy starting with a base class, usually named ‘object’, which declares a host of virtual function that apply to all other classes in the library. Each derived class defines precise versions of these functions, thus providing a standard interface to the library. Advantages of using single class hierarchy are given below:
- By defining a single root class, you can ensure that all classes provide the same standard set of public interface functions. This enhances the consistency & compatibility among classes.
- With this organization, it is easier to provide a capability such as ‘persistence’, which is the ability to store collections of objects in a disk file and retrieve them later. This is a consequence of having a single base class from which all classes are guaranteed to inherit. The single class hierarchy also makes it easy to provide a standard exception-handling mechanism.
- Because every class in the library is an ‘object’, it is easy to define polymorphic data structure. For example, the following array of pointers:
Object *Array of objptr
Looking at the other sides of the coin, many programmers find following disadvantages with a monolithic class hierarchy:
- The complier cannot provide strict type-checking, because many objects in the library are of type object, which can point to an instance of any class in the library. Such container class that is capable of storing instance of any class in the library.
- The root base class, object, typically includes a large number of virtual functions representing the union of all the virtual function implemented by the derived classes. This makes it burdensome to create derived class, because you have to provide the definition of all the virtual functions, even though many may not have any relevance to that derived class.
- Because of the large monolithic class hierarchy, compiling a single program may require processing a large number of header files. This can be a problem on MS-DOS systems that typically have a limited amount of memory.
Multiple class hierarchies:
In contrast to the monolithic, single class hierarchy of the Smalltalk80. Library, a C++ class library based on the forest model includes multiple class tree, with each tree providing a well – defined functionality. For instance figure 2 shows a number of class hierarchies in a C++ class library that provides the objects needed to build window-based user interface. Different types of windows are grouped together in the class hierarchy, with the “windows” class as the root. Each window has rectangle represented by the “rectangle” class, which, in turn, uses the “point” class to represent the corners of a window. The standalone ‘string’ class represents text strings that might appear in windows. One can model the physical display by a class hierarchy with a generic ‘display’ as the base class. From this generic display, we can further specialize to text or graphics display.
Here are the main advantages of the forest model of class library:
- Each class hierarchy is small enough so that you can understand and use the function easily.
- Virtual function declared in each root class is relevant to that class tree.
Thus, the primary disadvantages of the forest model are these:
- Because there is no common base class, it is difficult to design container class such as linked, stacks and queues that can store any type of object. One have to devise your own schemes for creating such data structure.
- Anything that requires a library-wide discipline becomes difficult to implement. For example, exception handing and persistence are harder to support under the forest than under a singletree model
Effects of multiple inheritances
The introduction of multiple inheritances in AT&T C++ release 2.0 changed the implication of organizing C++ class libraries according to one of the models just described. Because multiple inheritance allows a derived class to inherit from more than one base class, now one can combine and customize the capabilities of class in unique ways. The linked list is constructed by linking instance of a new class named ‘slink-string’, which is defined from the ‘string’ class and ‘single-link’ class as follows:
Class slink-string: public single-link, public string{
};
The ‘single link’ class is capable of holding a pointer to another instance of ‘single-link’. Here, multiple inheritances allow you to combine the capabilities of the two classes into a single class. Following are some ways of applying multiple inheritances to extend the capabilities of C++ libraries.
- You can derive a class from two or more classes in a library. You can do this even if the class library follows a singletree model of inheritance hierarchy. You can combine these two classes with multiple inheritance to define a ‘string ’ with a ‘link’.
- Even with a multiple-class hierarchy, you can add a standard capability such as persistence by defining a new base class and deriving other classes from it. You have to do extra work to create a whole new set of derived classes that incorporates the capability with multiple inheritances. With single inheritance, one lacks the opportunity to combine the behavior of two or more classes packages in a library.
CLIENT-SERVER RELATIONSHIP AMONG CLASSES
In additional to the inheritance relationship among classes, a class may also use the facilities of another class. This is referred to as the ‘client-server relationship’ among classes, because the client class calls the member functions of the server class to use its capabilities. In C++, the client class needs an instance of the server. The client class can get this instance in one of the following ways:
- One of the member functions of the client receives an instance of the server class as an argument.
- A member function of the client class calls a function that returns an instance of the server class.
- An instance of the server class is available as a global variable.
- The client class incorporates an instance of the server class as a member variable.
- A member variable of the client class is a pointer to an instance of the server class.
Of these, the last two cases are of interest because they constitute the most common ways of expressing a client-server relationship b/w incorporating an instance or a pointer to an instance. This is referred to as ‘composition’.
PUBLIC INTERFACE TO C++ CLASSES
The term public interface refers to the public member functions through which you access the capabilities of a class. The public interface to the classes in a library is as important as the relationship among the classes. Just as there is no standard C++ class library, there is also no standard interface to C++ classes. However, if one is designing a class library, it is good practice to provide a minimal set of member functions. Some member functions are needed to ensure proper operations, others to provide a standard interface to the library.
Default and copy constructors:
Each class in the library should have a default constructor that is a constructor that takes no argument. The default constructor should initialize any data members that the class contains. For example, here is the default constructor for the rectangle-shape class:
class rectangle_shape: public shape{
public:
rectangle_shape (): p1 (0), p2 (0){} /*This method of initialization is called initialization list.
//….
private:
point * p1, *_p2; };
This constructor sets the data members _p1 and _p2 to zero.
The default constructor is important because it is called when you are allocating arrays of class instances or when a class instance is defined without any initial value. Thus, the rectangle_shape: rectangle_shape () constructor is called to initialize the rectangle shape objects in the following:
rectangle_shape rect [16];rectangle_shape r;
Each class should also include a copy constructor of the form x (const x &) where x is the name of the class. The copy constructor is called in the following cases:
- Whenever an object is initialized with another of the same type, such as rectangle shape r2=r;
- When an object is passed by value to a function.
- When an object is returned by value from a function.
Destructors:
Defining a destructor is important for classes that include pointers as member variables. The destructor should reverse the effects of the constructor. Thus if the constructor allocates memory by calling ‘new’, the destructor should reallocate the memory by using the ‘delete ’ operator.
Assignment operator:
One should define the assignment operator for each class, because derived classes do not inherit the assignment operator from the base class. As, if we do not define the assignment operator, the C++ compiler provides a default one that simply copies each member of one class instance to the corresponding member of another.
Input and Output Functions
Each class should also define two functions for input-output. Output function prints a formatted text representation of each member of an object on an output stream.
These functions enable you to define the operators so that they can accept as arguments, instances of any class in the library. For examples, you might use the names printout and read-in for the input and output functions , respectively. Each of these functions should take a single argument i.e. the reference to the stream on which the input-output operation occurs. Then, for a class x, you would define the output operator <
# includeostream& operator<< (ostream& os, const x& X)
{
X.print_out (os);
return os;
}
FUNCTIONS
A function groups a number of program statements into a unit and gives it a name. This unit can then be invoked from other parts of program. Division of program into functions is known as MODULAR APPROACH. Another reason to use function is to reduce program files. Any sequence of code that comes very frequently in the program is a candidate of being made into a function. Use of function helps in saving memory space because function code is stored at only one place memory, even though the function is executed many times in the course of the program.
Function in C++ is similar to procedures and subroutines in other programming languages. Fig. 1 shows how same code of function is used for all calls to function
First example demonstrates a simple function, which is used to print a line of 45 asterisks.
This example program generates a table, and lines of asterisks are used to make the table more readable.
# includevoid startline ();
void main() /*function declaration */
{
starline (); /*call to function*/
cout << “data type range” <
starline ();
cout<< “char -128 to 127 0 ” <
<<”short -32,768 to 32,767” <
<<”int system dependent” <
<<”long -2,147,483,648, to 2,147,483,647” <
starline (); /*call to function*/
}
void startline () /*function declaration*/
{
for (int j=0.j<45;j++)
{
cout<<’+’,
}
cout<
}
In the above example we can see how to add a function other than “main ()” to the program. We have to use three components: the function “declaration”, the ‘call’ to the function, and the function “definition”
Function declaration
Just as you can’t use a variable without first telling the compiler what it is; you also can’t use a function without telling the compiler about it. There are two ways to do this. The approach we show here is to declare the function before the its called. (as shown in the previous example). In other approach we write the function body above the ‘main ()’ function .The declaration tells the compiler that at some later point we plan to present a function called ‘starline’.
void starline ();
The keyword ’void’ specifies that the function has no return value, and the empty parentheses indicate that it takes no arguments. Function declarations are also called prototypes, since they provide a modes or blueprint for the function.
Calling the function:
The function is “called” (or invoked) from the main () program by simply writing the name of function, followed by parentheses.
starline ();
The syntax for calling the function is very similar to that of the declaration, except that the return type is not used. A semicolon executing the call statement causes the function to execute terminate the call. Then, control is transferred to the function, the statements in the function definition are executed, and then control returns to the statement following the function call.
Function Definition: -
The “definition” contains the actual code for the function. Here’s the definition for starline( ):
void starline ( ){
for (int j=0; j<45; j++)
cout << ‘*’
cout << endl;
}
The definition consists of a line called declarator, followed by the function body. The function body is delimited by the braces.
The declarator must agree with the declaration: It must use the same function name, have the same argument types in the same order (if any arguments) and have the same return type. The only difference between declarator and declaration is that the declarator is not terminated by the semicolon.
PASSING ARGUMENTS TO FUNCTIONS
An ‘argument’ is a piece of data, passed from a program to the function to operate with different values, or even to do different things, depending on the requirements of the program calling.
Passing Constants: Let us suppose we decide that the starline( ) function in the last example is too rigid. Instead of a function that always print 45 asterisks, we want a function that will print any character any number of times.
# includevoid main( )
{
repchar (‘-‘, 43);
cout<< “Data type Range”<< endl;
repchar(‘=’, 23);
cout << “ char -128 to 127” << endl
<< “ Short -32768 to 32767” <
<< “ int system dependent” << endl
<< “ long -2,147, 483, 648 to 2,147, 483, 647”;
repchar (’-‘, 43);
}
void repchar (char ch, int n)
{
for ( int j=0; j
cout << ch;
cout << endl;
}
The new function is called repchar ( ). Its declaration looks like this:
void repchar (char, int); /* declaration specifies data types */
The items in the parentheses are the data types of the arguments that will be sent to repchar( ): char and int. In a function call, specific values – constants in this case, are inserted in the appropriate place in the parentheses.
repchar(‘-‘, 43); /* function call specifies actual values */
The declarator in the function definition specifies both the data types and the names of the parameters:
void repchar (char ch, int n) /* declarator specifies parameter names and data types */
The parameters names ‘ch’ and ‘n’, are used in the function as if they were normal variables. When the function is called, its parameters are automatically initialized to the values passed by the calling program.
Passing Variables: Variables, instead of constants, may be passed as arguments. This program, incorporates the same ‘repchar( )’ function, but lets the user specify the character and the number of times it should be repeated.
# includevoid repchar ( char, int); /* function declaration.*/
void main ( )
{
char chin;
int nin;
cout << “Enter a character:”;
cin >> chin;
cout << “Enter no. of times to repeat it:”;
cin >> nin;
repchar (chin, nin);
}
void repchar (char ch, int n)
{
for (int j=0; j
cout << ch;
}
Passing by value: -
In the above example, the particular values possessed by chin and nin when the function call is executed will be passed to the function. As it did when constants were passed to it, the function creates new variables to hold the values of these variable arguments. The function gives these new variables the names and data types of the parameters specified in the declarator: ch of type char and n of type int. Passing arguments in this way, where the function creates copies of the arguments passed to it, is called passing by value:
RETURNING VALUES FROM FUNCTIONS
When a function completes its execution, it can return a single value to the calling program. Usually, this return value consists of an answer to the problem the function has solved. The next example demonstrates a function that returns a weight in kilogram after being given a weight in pound.
# includefloat lbstokg (float); // declaration
void main ( )
{
float lbs, kgs;
cout << “In enter your weight in pounds:”;
cin >> lbs;
kgs = lbstokg (lbs);
cout <<”Your weight in kilogram is” <<
}
float lbstokg (float pounds)
{
float kilograms = 0.453592 * pounds;
return kilograms;
}
When a function returns a value, the data type of this value must be specified. The function declaration does this by placing the data type, float in this case, before the function name in the declarator and the definition
float lbstokg (float);
The first float specifies the return type. The float in parentheses specifies that an argument to be passed to lbstokg( ) is also of type float.
While many arguments may be sent to a function, only one value can be returned from it.
REFERENCE ARGUMENTS
When arguments are passed by value, the called function creates a new variable of the same type as the argument and copies the argument’s value into it. As we noted, the function cannot access the original variable in the calling program, only the copy it created. Passing arguments by value is useful when the function does not need to modify the original variable in the calling program.
Passing arguments by reference uses a different mechanism instead of a value being passed to the function, a ‘reference’ to the original variable, in the calling program, is passed. The function can access the actual variables in the calling program. Among other benefits, this provides a mechanism for passing more than one value from the function back to the calling program.
The next example shows a simple variable passed by reference.
# includevoid main ( )
{
void intfrac (float, float, float);
float number, intpart, fracpart; /*global variables */
do {
cout <<”In Enter a real number;”;
cin >> number;
intfrac (number, intpart, fracpart);
cout <<”Integer part is” << intpart
<<”,fraction part is” << fractpart<
} while (number ! = 0.0); /* exit loop on 0.0 */
}
void intfrac (float n, float intp, float fracp)
{
long temp = static_cast (n); /*convert to long*/
intp = static_cast (temp); /*back to float*/
fracp = n-intp;
}
The function declaration echoes the usage of the ampersand in the definition:
void intfrac (float, float &, float &); /* ampersands */
As in the definition, the ampersand follows those arguments that are passed by reference.
The ampersand is not used in the function call
inlfrac (number, intpart, fracpart); /* no ampersands */
While ‘intpart’ and ‘fracpart’ are passed by reference, the variable ‘number’ is passed by value. ‘intp’ and ‘intpart’ are different names for the same place in memory, as are ‘fracp’ and ‘fracpart’. On the other hand, since it is passed by value, the parameter ‘n’ in ‘intfrac( )’ is a separate variable into which the value of ‘number’ is copied. It can be passed by value because the ‘intfrac( )’ function does not need to modify ‘number’.
OVERLOADED FUNCTIONS
An overloaded function appears to perform different activities depending on the kind of data send to it. We can overload functions only when there is difference in their fields: number of arguments and type of arguments. It would be far more convenient to use the same name for all three functions, even though they each have different arguments. Here’s an example, that makes this possible:
# includevoid repchar ( );
void repchar (char);
void repchar (char, int);
void main( )
{
repchar ( );
repchar (‘=’);
repchar (‘+’, 30);
}
void repchar ( )
{
for (int j=0; j<45; j++) /* always loops 45 times */
cout << “*”; /* always prints asterisks */
cout << endl;
}
void repchar (char ch)
{
for (int j=0; j<45; j++) /* always loops 45 times */
cout << ch; /* prints specified character */
cout << endl;
}
void repchar (char ch, int n)
{
for (int j=0; j
cout << ch; /* prints specified character */
cout << endl1;
}
The program contains three functions with the same name. There are three declarations, three function calls, and three function definitions. What keeps the compiler from becoming hopelessly confused? It uses the number of arguments, and their data types, to distinguish one function from another. In other words, the declaration.
void repchar ( );
which takes no arguments, describes an entirely different function than the declaration :
void repchar (char);
Which takes one argument of type ‘char’, or the declaration
void repchar (char, int);
INLINE FUNCTIONS
One of the objectives of using functions in a program is to save some memory space, which becomes appreciable when a function is likely to be called many times. However, every time a function is called, it takes a lot of extra time in executing a series of instructions for tasks such as jumping to the function, saving registers, pushing arguments into the stack and returning to the calling function. When a function is small, a substantial percentage of execution time may be spent in such over-heads. One solution to this problem is to use macro definitions, popularly known as ‘macros’. Preprocessor macros are popular in C. The major drawback with macros is that they are not really functions and therefore, the usual error checking does not occur during compilation.
C++ has a different solution to this problem to eliminate the cost of calls to small functions. C++ proposes a new feature called ‘inline functions’. Inline function is a function that is expanded inline when it is invoked. That is, the compiles replaces the function call with the corresponding function code. Inline functions are defines as follows.
We should exercise care before making a function ‘inline’. The speed benefits of ‘inline’ functions diminish as the function grows in size. Usually, the functions are made inline when they are small enough to be defined in one or two lines.
Remember that the ‘inline’ keyword merely sends a ‘request’, not a command, to the compiler. The compiler may ignore this request if the function definition is too long or too complicated and compile the function as a normal function.
# includeinline float mul (float x, float y) /* inline function*/
{
return (x*y);
}
inline double div (double p, double q) /* inline function */
{
return (p/q);
}
void main( )
{
float a = 12.345;
float b = 9.82;
cout <<< “\n”;
cout <
<< “\n”;}
DEFAULT ARGUMENTS:
C++ allows us to call a function without specifying all its arguments. In such cases, the function assigns a default value to the parameter, which does not have a matching argument in the function call. Default values are specified when the function is declared. Here is an example of a prototype (i.e. function declaration with default values).
float amount (float principal, int period, float rate = 0.15)
The default value is specified in a manner syntactically similar to a variable initialization. The above prototype declares a default value of 0.15 to the argument ‘rate’. A subsequent function call like
value = amount (5000, 7) ; /* one argument missing */
passes the value of 5000 to ‘principal’ and 7 to ‘period’ and then lets the functions use default value of 0.15 for ‘rate’. The call
value = amount (5000, 5, 0.12) ; /* no missing argument *
passes an explicit value of 0.12 to ‘rate’.
A default argument is checked for type at the time of declaration and evaluated at the time of call. One important point to note is that only the trailing arguments can have default value. That is, we must add defaults from ‘right to left’. We cannot provide a default value to a particular argument in the middle of a argument list. Default arguments are useful in situations where some arguments always have the same value.
‘CONST’ FUNCTION ARGUMENTS: -
We have seen that passing an argument by reference can be used to allow a function to modify a variable in the calling program. However, there are other reasons to pass by reference One is efficiency. Some variables used for function arguments can be very large; a large structure would be an example. If an argument is large, then passing by reference is more efficient because, behind the scenes, only an address is really passed, not the entire variable.
Suppose, you want to pass an argument by reference for efficiency, but not only do you want the function not to modify it, you want a guarantee that the function cannot modify it.
To obtain such a guarantee, you can apply the constant modifier to the variable in the function declaration. The example given below shows how this looks like.
#includevoid afunc (int &a, const int &b); // declaration
void main ( )
{
int alpha = 7;
int beta = 11;
afunc (alpha, beta);
}
void afunc (int &a, const int &b) ;// definition
{
a = 107; /* OK */
b = 111; /* error : cannot modify constant argument */
}.
Defining Macros:
By defining a macro, one can define a symbol (a token) to be equal to some C++ code and use that symbol wherever the code is required in program. When the source file is preprocessed, every occurrence of a macro’s name is replaced with its definition. A common use of this feature is to define a symbolic name for a numerical constant and use the symbol instead of the numbers in your program. This improves the readability of the source code, because with a descriptive name, you are not left guessing why a particular number is being used in the program. You can define such macros in a straightforward manner using the ‘# define’ directives as follows.
# define PI 3.14159# define BUFSIZE 512
Once these symbols are defined, you can use PI and BUFSIZE instead of the numerical constants through out the source file.
The capability of macros, however go well beyond replacing a symbol for a constant. Macros can accept parameters and replace each occurrence of a parameter with the value provided for it when the macro is used in the program. Thus, the code resulting from the expansion of a macro can change depending on the parameter you provide. When using the macro.
For example, the following macro accepts a parameter and expands to an expression designed to calculate the square of the parameter:
# define square(x) ((x)*(x))
If you use square(z) in your program, it becomes ((z)*(z)) after the source of file is processed. This macro is essentially equivalent to a function that computes the square of its arguments. You do not, however, have the overhead of calling a function, because the expression generated by the macro is placed directly in the source file.
New feature provided by ANSI C preprocessor is the ‘stringizing’ operator, which makes a string out of any parameter with a # prefix by putting that parameter in quotes. Suppose we want to print out the value of certain variables in a program. Instead of calling the ‘cout’ directly, we can define a utility macro that will do the work. The required macro to do above job is given below:
# define Trace (x) cout<<#x<<”=”<<<”/n”;
Then, to print out the value of a variable named current_index.
For instance we can simply write this:
Trace (current_index);
When the preprocessor expands this, it generates the following statement:
cout <<<”=” <<<”/n”;
Conditional directives: -
One can use “conditional directives” such as #if, #ifdef, #ifndef, #else, #elif, and #endif to control which parts of a source file get compiled and under which conditions. With this feature, one maintains a single set of source files that can be selectively compiled with different compilers and in different environments.
Common way of using conditional directives are given below:
# ifdef __PROJECT_H# define __PROJECT_H
/* declarations to be included once */
/* _ _ _ */
# endif
The following example shows how you can include a different header file depending on the version number of the software directives. To include a header file only ones, we can use the following:
# if CPU_TYPE = = 8086# include
# elif CPU_TYPE = = 80386
# include
# else
# error Unknown CPU type.
# endif.
The # error directive is used to display error messages during preprocessing.
Other directives:
Several other preprocessor directives are meant for miscellaneous tasks. For example, we can use the # undefined directive to remove the current definition of a symbol. The #pragma is another special-purpose directive that we can use to convey information to the C++ compiler. Preprocessor directives that begin with # pragma are known as pragmas, and they are used to access special features of a compiler and as such they vary from one compiler to another.
ANSI C++ compilers maintain several predefined macros. Of these, the macros _ _file _ _ and _ _line _ _, respectively, refer to the current source file name and the current line number being processed, One can use the #line directive to change these. For example, to set _ _ file _ _ to “file – io.c” and _ _ LINE _ _to 100, one would say this:
# Line 100 “file_io.c”
Introduction
About my this article, in this you all can get knowledge about preprocessor directives and pragmas. The preprocessor directives are used to help the programmer to make his/her program readable as well as meaningful. It is not compulsory to know the preprocessor directives, because one may write his programs without using any directives. But with the help of preprocessor we can easily change our source program even if we are working in different environments. Thus if we use any preprocessor directives then it means that the preprocessor has to perform specific actions, such as replace a lengthy string into shorter one, or ignore some portion of the source program, or insert the contents of other files into the source file etc.
Preprocessor Directives
The C preprocessor is a program that manipulates the source program before this program is passed to the compiler. A preprocessor directive is used to instruct the C preprocessor to perform a specific action in the source program before its compilation. Therefore the preprocessor directives are also known as preprocessor commands. This software is always included in a C package along with the standard C library functions. The C compiler automatically invokes the preprocessor in its first pass compilation either you are using any preprocessor directives or not. But the user can also invoke the preprocessor to manipulate the source program without its compilation.
The C preprocessor has following directives:
- #define #ifdef
- #elif #ifndef
- #else #include
- #endif #line
- #if #undef
Each of these preprocessor directives begins with a special symbol, #. The number sign (#) must be first non-white space character on the line containing the directive; white-space characters can appear between the number sign and the first letter of the directive. As stated earlier, the C preprocessor modifies the C source program according to the directives used in the program. It does not mean that the C preprocessor modifies the original file, rather it creates a new file that contains the processed version of the program. This new file is then submitted to the compiler.
The C preprocessor directives are divided into four categories:
- Macro Substitution
- File inclusion
- Conditional compilation
- Line control
Macro Substitution
Before the discussion of the preprocessor directives that supports macros, we must know, what is a macro? In earlier chapters, we have used identifiers to represent constants, variables, arrays, functions, structures and so on. But if an identifier is used to represent the statement or expression we call it macro. Generally we have two types of macros:
- Object–like macros
- Function–like macros
Object–like macros do not take any argument whereas the function-like macros take arguments, just like functions. Now we discuss these macros in some details.
Object-Like Macros
C preprocessor provides #define preprocessor directive to support macro facility. The syntax of #define directive is as:
#define <identifiername>
Here the #define directive substitutes the substitutiontext wherever our source program encounters the identifiername. For example, if the directive
#define PI 3.142857
is included in the program, then in all lines following the definition, the symbol PI is replaced by the number 3.142857.
Program-abc.c illustrates this concept:
/* Program - abc.c */
#include
#define PI 3.142857
main()
{
float radius, area, circum;
printf("\nEnter the radius of a circle : ");
scanf("%f", &radius);
area = PI * radius * radius;
circum = 2 * PI * radius;
printf("\nThe area of a circle is : %f", area);
printf("\nThe circumference of the a circle is : %f", circum);
}
The output of this program is as….
Enter the radius of a circle : 40
The area of a circle is : 5028.571289
The circumference of the a circle is : 251.428558
In this program, PI is a macro name and 3.142857 is its substitution text argument. This program would not be compiled directly, rather than it firstly invokes the preprocessor and the preprocessor substitutes the every occurrence of PI in the source program with 3.142857. Thus the following statement
area = PI * radius * radius;is replaced as
area = 3.142857 * radius * radius;
Similarly the following statement
circum = 2 * PI * radius;
is replaced as:
circum = 2 * 3.142857 * radius;
After replacing all macros with its substitution text, the expanded source program is given to the C compiler. However, you can also use more than one macro in the program.
Now let us see another program that uses two macros LENGTH and BREADTH.
/* Program - abc1.c */#include
#define LENGTH 15
#define BREADTH 20
main{
int area, peri;
area = LENGTH * BREADTH;peri = 2 * (LENGTH + BREADTH);
printf ("\nThe area of a rectangle is : %d", area);printf ("\nThe perimeter of a rectangle is : %d", peri);
}The output of the program is as….
The area of a rectangle is : 300The perimeter of a rectangle is : 70
Macros are very useful in making C code more readable and compact. For example, consider the following definitions:
#define and &&#define or ||
These definitions enables the programmer to write more readable statements, such as:
In these programs we have used integer constants in place of substitution text. We can also use some other parameters such as string literals in place of substitution text. Program-abc2.c using string literals as a substitution text:
#include #define FALSE printf("\nYou are a big liar.") char name1[20]; printf ("\nEnter your name : "); scanf ("%s",name1); printf ("Enter your name again : "); scanf ("%s",name2); if (!(strcmp(name2,name1))) TRUE; else FALSE; } Here are the sample outputs of this program…. First Run: Enter your name : Amita Enter your name again : Amita You are a true person. Second Run: Enter your name : Amita Enter your name again : Anita You are a big liar. When this program is subjected to the C preprocessor, the C preprocessor replaces the text whenever it encounters a macro TRUE and when it traces FALSE. If our substitution text can be continued onto the next line then we will simply place a backslash (\) before the new line character. Program-abc4.c clears this concept more. #include #define STATEMENT1 "You know when I was young, I make fool elders. \ \nNow when I am elder, my younger make me fool. \ \nSo remember - history repeats itself." #define STATEMENT2 "I am sorry." main (){ char ch ; printf("\nDo you want to know my thoughts : "); scanf ("%c",&ch); if ((ch == 'y' ) || ( ch == 'Y' )) printf (STATEMENT1); else printf (STATEMENT2); } Here are the sample outputs of this program…. First Run: Do you want to know my thoughts : y You know when I was young, I make fool elders Now when I am elder, my younger make me fool. So remember - history repeats itself. Second Run: Do you want to know my thoughts : n I am sorry. While using macros, one should remember that there must be at least one blank space between identifier used as macro name and substitution text. And it is optional to have a blank space between # and define. If the substitution text is empty then the C preprocessor removes every occurrence of identifier from the source file, but note that the identifier is still considered defined and yields the value 1 when tested with #if directive. The #if directive will be discussed later on. The macros, which have arguments such that they may look and act like function calls are called as function-like macros. But macro call is not same as function call because unlike function call, no control is transferred to a macro. When a macro call encounters into the source program, the preprocessor replaces the macro templates with its substitution text. The syntax of function–like macros is as: Here the list of parameters consists of one or more formal parameters that are separated by commas. Remember that each parameter in list of parameters must be unique. Each parameter can appear more than once in substitution text and the names can appear in any order. Program-abc5.c uses a simple function–like macro. #include #define SUM(x,y) ( x+y ) main(){ int a, b, c; printf("\nEnter any two integer numbers : "); scanf("%d %d", &a, &b); c = SUM(a,b); printf("The addition of two numbers : %d", c); } The output of this program is as…. Enter any two integer numbers : 10 20 The addition of two numbers : 30 In this program, the actual arguments a and b are passed to the macro SUM(). In macro these actual arguments are collected into formal parameters x and y respectively. So whenever this program is compiled, the preprocessor in invoked firstly and then that preprocessor expanded the statement SUM(a, b) into the statement (x+y). Thus the statement c = (a+b); Notice that the macro definition itself (the line containing the preprocessor directive #define) does not include a semicolon. When the macro is invoked later, it is followed by an explicitly specified semicolon as Another main point to note is that the arguments names used in a macro are local to the macro in which it is defined. Thus there is no conflict between a macro’s formal parameter names and identifiers used in the program itself. In the definition of SUM, it does not matter if x and y are variables used in the program because the parameters x and y are replaced by the preprocessor.Since we know that there is no standard function provided in C to calculate the square of any number. So let us see anther simple program of function–like macro that squares any given number. #include #define SQUARE(x) (x*x) main (){ long int a, b, c, d; printf ("\nEnter any integer number : "); scanf ("%ld", &a); b= SQUARE(a) ; printf ("\nThe square of a number %ld is %ld", a, b); c = SQUARE(b); printf ("\nThe square of a number %ld is %ld", b, c); d = SQUARE(c); printf ("\nThe square of a number %ld is %ld", c, d); } Here are sample outputs of this program…. First Run: Enter any integer number : 2 The square of a number 2 is 4 The square of a number 4 is 16 The square of a number 16 is 256 Second Run: Enter any integer number : 5 The square of a number 5 is 2 The square of a number 25 is 625 The square of a number 625 is 390625 No doubt good usage of #define will often reduce the need for comments within the program. Consider the following statement: printf(“\n%d is a leap year.”, year); else printf(“\n%d is not a leap year.”, year); Here the expression tests whether the variable year is a leap year or not. The above code segment can be made more easier by using #define directive as: and then using this we can rewrite the earlier if-statement as: printf(“\n%d is a leap year.”, year); else printf(“\n%d is not a leap year.”, year); Program-abc7.c illustrates this fact. #include #define IS_LEAP_YEAR(year) (year%4==0)&&(year%100!=0)||(year%400==0) main(){ int year; printf("\nEnter a year : "); scanf("%d", &year); if (IS_LEAP_YEAR(year)) printf("%d is a leap year.", year); else printf("%d is not a leap year.", year); } Here are some sample outputs of this program…. First Run: Enter a year : 1900 1900 is not a leap year. Second Run: Enter a year : 1784 1784 is a leap year. C preprocessor also provides the facility of using nested macros. By nested macros, we mean a macro call within another macro call. Program-abc8.c illustrates this concept of nested macro more. #include #define MAX(x,y) ((x>y) ? (x):(y)) main(){ int a, b, c, large; printf ("\nEnter any three integer numbers : "); scanf ("%d %d %d", &a, &b, &c); large = MAX(a, MAX(b,c)); printf("The largest of these three numbers is %d", large); } Here are sample outputs of this program…. First Run: Enter any three integer numbers : 10 15 20 The largest of these three numbers is 20 Second Run: Enter any three integer numbers : 10 15 12 The largest of these three numbers is 15 When we use macro with arguments, we must remember that the actual argument list must have same number of arguments as formal parameter list. The formal parameter list must be enclosed in parentheses and there should be no blank space between the macro name and opening parentheses of parameters list. That is, following macro definition is completely wrong: And if out substitution text is an expression then it is compulsory to enclosed this expression in parentheses otherwise you must be ready for wrong result. Watch the output of the Program-abc9.c, if you do not use parenthesis in substitution text. #include #define SUM(x,y) x+y main(){ int a = 20; int b = 20; int number; number = a * b / SUM(a, b); printf("%d", number); } What should be the output of this program you expect? You would tell 10. But certainly it is wrong. Because C preprocessor encounter the macro call SUM(20,20), it immediately replaces it with 20+20 and the statement number = 20 * 20 / 20 +20 and it results in 40. And if we enclose the substitution text in parenthesis as (x+y) then the statement number = 20 * 20 / (20+20) and and it would result in 10. Thus the importance of parentheses in macro definition can not be overstressed. We can also make a mixed call, that is, calling one macro into another different macro. Program-abc10.c calls object–like macro into function like macro. #include #define LENGTH 10 #define AREA(x) (x*x) main (){ printf("\nArea of a square : %d", AREA(LENGTH)); } The output of this program is as…. Area of a square : 100 The stringizing operator (#) is used only with macros that takes arguments. The formal parameter that preceded the stringing operator in substitution text then when we pass the actual argument to the macro then it is enclosed in double quotation marks and treated as string literal. Program-abc11.c illustrates this concept: #include #define String(str) printf(#str) main (){ printf("\n"); String(I am a naughty person.); printf("\n"); String("I am a naughty person."); } The output of this program is as…. I am a naughty person. I am a naughty person. The token pasting operator (##) is used in both object–like macros and function–like macros. This operator is used to join separate tokens into a single token. Therefore it can not be first and last token in the macro definition.When we use formal argument with token pasting operator (##), the formal argument is immediately substituted by the unexpanded actual argument. Program-abc12.c uses token pasting operator (##). #include #define example(n) printf("NUMBER" #n " = %d",number##n) main (){ int number7 = 10; example (7); } The output of this program is as…. NUMBER7 = 10 The #undef directive is used to undefine a preprocessor symbol that has already been defined. The syntax of #undef directive is: The #undef directive removes the current definition of identifier. Once an identifier is undefined, the preprocessor ignores the subsequent occurrences of identifier. The purpose of #undef is just reverse of the #define directive that has empty substitution text argument. The #undef is typically paired with #define directive to create a region in a source program in which an identifier has a special meaning. For example, suppose a program wishes the constant NULL to have a value –1, the program could begin with the following directives: #undef NULL #define NULL –1 But remind that it would be dangerous to redefine the value of NULL; it is just to show you an example.It is not compulsory to perform the redefinition at the beginning of the program. You can also redefine in the middle of a program so that it has one value in the first part and another value at the end. Another main point to note is that a symbol need not be redefined after it is undefined. Sometimes we need to accumulate a collection of commonly used constants and macro definitions that are used in almost every program. Therefore it is desirable to store these constants and definitions in a file that can be inserted automatically into every program. Such files are called as header files and stored either in standard directory or in a source directory. By convention, header file names end with the character ‘.h’.The preprocessor directive, #include, is used to include the contents of other files into the current source file. Usually, however, the files are included at the beginning of a program. The syntax of using the #include directive is as: #include Here the filename is the name of a file whose contents are added into the current source file and the file having that specific name should exist. Let we want to include the contents of a header file math.h in current source file, then there are two options of inserting it into the current source file: #include In the first declaration the preprocessor searches the file math.h into parent’s file directory firstly. For example, if you include a file named file2 within a file named file1, file1 is the parent file. If the file is not found then the processor searches the directories specified on the compiler command line and if the file is not still found then finally the standard libraries are searched. And after searching the file math.h its entire contents are inserted into the current file. In the second declaration, where the file specification is enclosed in angle brackets, the preprocessor does not search the current working directory. It begins its searching for the files in the directories specified on the compiler command line and then in the standard libraries. The compiler specifies the location of standard libraries. And after searching the file math.h, the preprocessor stops searching and insert its entire contents into the current file. If the file specification is enclosed in angle brackets, the preprocessor does not search the current directory. If you specify the complete path for the include file, between two sets of double quotation marks (“ ”) then the preprocessor searches only that specified path and ignore the standard directories. The C preprocessor also uses the facility of nesting of include files that is an #include directive can appear in a file named by another #include directive. When the include files are nested , the preprocessor starts its searching from the directories of parent file , then continues through the directories of any grandparent files. For example, if you include a file named file2 within a file named file1, file1 is the parent file. If file2 could include file3 then file1 would still the parent of file file2 but would be the “grandparent” of file1. One can use nesting of include files up to 10 levels. Once the nested #include is processed, the preprocessor continues to insert the enclosing include file into the original one. While using nested files, a file should not include itself because this would lead to infinite recursion. It can not even include another file that contains another file that includes the first file, either as this would also be an infinite recursion The C preprocessors provides some preprocessor directives that allow us to suppress compilation of some part of source program by testing a constant expression or identifier to determine which set of instructions would be passed to the compiler and which one would be suppressed from the source file. The C preprocessor provides following directives to control the flow of program : The #if directive is used together with the #else, #elif and #endif directives in order to control the compilation of portions of a source program. The syntax of using these directives is as: [Statement-block] [#elif restricted-constant-expression Statement-block] [#elif restricted-constant-expression Statement-block] …. [#else Statement-block] #endif] Note that any number of #elif directives can appear between the #if and #endif directives, but atmost one #else directive is permitted. The #else directive, if present, must be the last directive before #endif. The #if and #elif test an expression that may include only integer constants values. No variables or function calls are permitted, nor are floating-point, characters or string context. The preprocessor directive selects a single Statement-block by evaluating the restricted-constant-expression following #if and each #elif directive until it finds a true (non-zero) restricted-constant-expression. If all occurrences of restricted-constant-expression are false (zero), the preprocessor selects the Statement-block after the #else directive. And if there is no #else directive and all instances of restricted-constant-expressions results in false then no Statement-block is executed. Remind that any statement block not selected by the preprocessor directive is removed from the file during preprocessing. Thus these statement blocks are not compiled. If the preprocessor selects a Statement-block then it processes that Statement-block first and then passes it to the compiler. If Statement-block itself contains preprocessor directives, the preprocessor carries out those directives first. Program-abc13.c uses #if directive together with #else and #endif directives #include #define RESULT 60 main(){ #if RESULT >= 40 printf("\nCongratulations you are pass." ); #else printf("\nSorry you are fail."); #endif } The output of this program is as…. Congratulations you are pass. However if you change the substitution text to 30 in place of 60 as: then you would get the following output: Sorry you are fail. In the above program if we use the preprocessor selects the statement Congratulations you are pass. and by pass the compilation of statement which is defined in #else directive. On the other hand, if we use the preprocessor selects the statement which is defined in #else directive Sorry you are fail. and by pass the compilation of statement which is defined in #if directive. The C preprocessor allows us to use nested conditional directives, that is in one conditional directive can be used in another conditional directive. Each nested #else or #endif directive belongs to the closest preceding #if directive. Program-abc14.c illustrates this concept. #include #define RESULT 29 main(){ #if (RESULT >= 60) printf("First Division"); #else #if (RESULT >=50) printf("Second Division"); #else #if (RESULT >=40) printf("Third Division"); #else printf("Fail"); #endif #endif #endif } The output of this program is as…. Fail Here each #if directive must be matched by a closing #endif directive. In this program instead of using so many #else-#if directives we can also use #elif directives. And another advantage of using #elif is that we will use just a single #endif conditional directive. The above program is redefined as: #include #define RESULT 29 main(){ #if (RESULT >= 60) printf("First Division"); #elif (RESULT >=50) printf("Second Division"); #elif (RESULT >=40) printf("Third Division"); #else printf("Fail"); #endif } The #ifdef and #ifndef directives works in same manner as the #if directives but it checks the constant expression on defined identifier only. When the C preprocessor encounters an #ifdef directive, it checks to see whether the identifier is defined (in macro template) or not defined. If the identifier is defined, the condition results in true (non-zero); otherwise it results in false (zero). And #ifndef directive works in reverse fashion. In this if the identifier has not been defined, or you can say that its definition has been removed with #undef, then the condition results in true (non-zero); otherwise it results in false (zero). These directives are provided only for compatibility with previous versions of the language. The syntax of the #ifdef is as: [Statement-block] #endif If the identifier is defined then the Statement-block is processed; otherwise the preprocessor suppresses Statement-block. Program-abc16.c illustrates this concept.
#define TEA main() { #ifdef TEA printf("\nTea is good for health."); printf("\nWhen you feel tired, take a cup of tea."); printf("\nTea has become the necessity of almost every human."); printf("\nGo and take a cup of tea."); #endif } The output of this program is as…. Tea is good for health. When you feel tired, take a cup of tea. Tea has become the necessity of almost every human. Go and take a cup of tea. In this program we have used four statements in the block of #ifdef directive. If the preprocessor finds TEA to be defined then it would process the block; otherwise it skips this block. After sometimes if you want to use the same code but with two option that if an identifier is defined then process Statement-block1; otherwise process Statement-block2. Program-abc17.c illustrates this. #include #define TEA main(){ #ifdef TEA printf("\nTea is good for health."); printf("\nWhen you feel tired, take a cup of tea."); printf("\nTea has become the necessity of almost every human."); printf("\nGo and take a cup of tea."); #else printf("\nTea is never good for health."); printf("\nYou can certainly live without tea. ") printf("\nGo and throw this habit."); #endif } When this program is processed, the preprocessor looks for identifier TEA. If it is defined then first Statement-block is compiled; otherwise Statement-block set is compiled. Similarly we can use #ifndef directive. The above program can be rewritten as: #include main(){ #ifndef TEA printf("\nTea is good for health."); printf("\nWhen you feel tired, take a cup of tea."); printf("\nTea has become the necessity of almost every human."); printf("\nGo and take a cup of tea."); #else printf("\nTea is never good for health."); printf("\nYou can certainly live without tea. "); printf("\nGo and throw this habit."); #endif } When this program is processed, the preprocessor looks for identifier TEA. If it is not defined then first Statement-block is compiled; otherwise Statement-block is compiled. The #line directive is used to control the processing of line. The syntax of #line directive is as: The constant value can be any integer constant and the filename can be any combination of characters and must be enclosed in double quotation marks. However if you omit the filename then the previous name remains unchanged. The #line directive instructs the preprocessor to change the compiler’s internally stored line number and file name to a given line number and file name. We use this facility in debugging of errors during the compilation of our source program. The line number normally refers to the current line number and file name refers to the current file name. When we change the line number, the compiler ignores the previous value. For example In this, the internally stored line number is changed and set to 20 and the file name is also changed to example.c. ANSI C has already defined some macros, such as __LINE__ for obtaining the line number of the current source line and __FILE__ for obtaining the current source file. Additionally it provides __DATE__ and __TIME__ for obtaining the date of compilation in the form Mmmddyyyy and for obtaining the time of compilation in the form “hh:mm:ss” respectively. A pragma directive is used to instruct the compiler to a particular action at compile time without affecting the program as a whole. The meaning of pragmas may vary from one compiler to another. The syntax of pragma is as: The char-seq is a series of characters that gives a specific compiler instruction and argument, if any. An recognized pragma is ignored. For example #pragma warning -xyz #pragma warning .xyz This pragma is used to change the state of warning message. -xyz is used to turn – off the warning xyz .xyz is used to toggle the On/off State If there is no character or letter after the number sign, then it is said to be Null directive. A directive of the following form is a null directive. In this case the preprocessor takes no action except to eliminate the line./* Program - abc2.c */
printf("\nYou are a true person.")
printf("\nYou are a big liar.")
/* Program - abc4.c */
Function Like Macros
#define
/* Program - abc5.c */
c = SUM (a,b); is equivalent to
SUM(a, b);
/* Program - abc6.c */
if ((year % 4 == 0 && (year %100 != 0) || (year %400 == 0))
#define IS_LEAP_YEAR(year) (year % 4 == 0 && (year %100 != 0) || (year %400 == 0)
if (IS_LEAP_YEAR(year))
/* Program - abc7.c */
Nested Macros
/* Program - abc08.c */
#define MAX (x,y) ((x>y) ? (x):(y))
/* Program - abc9.c */
number = a * b / SUM (a, b); would be expanded as
number = a * b / SUM (a,b); would be expanded as
/* Program - abc10.c */
Macro Using Sringizing Operator (#)
/* Program - abc11.c */
Macro Using Token Pasting Operator (##)
/* Program - abc12.c */
The #undef Directive
#undef identifier
#include
File Inclusion : #include Directive
#include “filename”
#include “math.h”
Conditional Compilation
The #if, #else, #elif and #endif Directives
#if restricted-constant-expression
/* Program - abc13.c */
#define RESULT 30
#define RESULT 60
#define RESULT 30
/* Program - abc14.c */
/* Program - abc15.c */
The #ifdef and #ifndef Directives
#ifdef identifier
#include
/* Program - abc17.c */
/* Program - abc18.c */
Line Control
#line constant
#line 20 “example.c”
Pragmas
#pragma char_seq
#pragma warning +xyz
+xyz is used to turn – on the warning xyz
Null Directive
Conclusion
C Preprocessor is a program that takes our source program before it is transferred to the compiler. C preprocessor directives are broadly divided into four categories – macro substitution, file inclusion, conditional compilation and line control. These preprocessor directives are also called as preprocessor commands. The C compiler automatically invokes the preprocessor in its first pass compilation and it does not matter whether you are using any preprocessor directive or not.
More Articles …
Page 13 of 21