see link: https://www.geeksforgeeks.org/commonly-asked-c-interview-questions-set-1/
1) C++ is a kind of superset of C, most of C programs except few exceptions work in C++ as well.
2) C is a procedural programming language
, but C++ supports both procedural
and Object Oriented Programming (OOP)
.
3) Since C++ supports object oriented programming, it supports features like:
function overloading,
templates,
inheritance,
virtual functions,
friend functions.
These features are absent in C.
4) C++ supports exception handling at language level, in C exception handling is done in traditional if-else
style.
5) C++ supports references, C doesn’t.
6) In C, scanf()
and printf()
are mainly used input/output. C++ mainly uses streams to perform input and output operations. cin
is standard input stream and cout
is standard output stream.
see link https://www.geeksforgeeks.org/commonly-asked-c-interview-questions-set-1/
Both references and pointers can be used to change local variables of one function inside another function.
Both of them can also be used to save copying of big objects when passed as arguments to functions or returned from functions, to get efficiency gain.
References are less powerful than pointers:
1) Once a reference is created, it cannot be later made to reference another object; it cannot be reseated. This is often done with pointers.
2) References cannot be NULL. Pointers are often made NULL to indicate that they are not pointing to any valid thing.
3) A reference must be initialized when declared. There is no such restriction with pointers
Due to the above limitations, references in C++ cannot be used for implementing data structures like Linked List, Tree, etc. In Java, references don’t have above restrictions, and can be used to implement all data structures. References being more powerful in Java, is the main reason Java doesn’t need pointers.
References are safer and easier to use:
1) Safer: Since references must be initialized
, wild references like wild pointers are unlikely to exist. It is still possible to have references that don’t refer to a valid location (See questions 5 and 6 in the below exercise )
2) Easier to use: References don’t need dereferencing operator to access the value. They can be used like normal variables. ‘&’ operator
is needed only at the time of declaration. Also, members of an object reference can be accessed with dot operator (‘.’)
, unlike pointers where arrow operator (->)
is needed to access members.
A virtual function is a member function which is declared in the base class using the keyword
virtual
and is re-defined (overridden
) by the derived class.
Virtual functions
are used with inheritance, they are called according to the type of object
pointed or referred, not according to the type of pointer or reference. In other words, virtual functions are resolved late, at runtime
. Virtual keyword is used to make a function virtual.
Following things are necessary to write a C++ program with runtime polymorphism
(use of virtual functions, i.e., 多态性
)
1) A base class and a derived class.
2) A function with same name in base class and derived class.
3) A pointer or reference of base class type pointing or referring to an object of derived class.
For example, in the following program bp
is a pointer of type Base, but a call to bp->show()
calls show() function of Derived class, because bp
points to an object of Derived class.
#include<iostream>
using namespace std;
class Base {
public:
virtual void show() { cout<<" In Base \n"; }
};
class Derived: public Base {
public:
void show() { cout<<"In Derived \n"; }
};
int main(void) {
Base *bp = new Derived;
bp->show(); // RUN-TIME POLYMORPHISM
return 0;
}
Output:
this
pointer?The ‘this’
pointer is passed as a hidden argument to all nonstatic
member function calls and is available as a local variable within the body of all nonstatic
functions.
‘this’
pointer is a constant pointer that holds the memory address of the current object
.
‘this’
pointer is NOT available in static member functions as static member functions can be called without any object (with class name).
Can we do delete "this"
? Ideally delete
operator should not be used for "this"
pointer. However, if used, then following points must be considered.
1) delete
operator works only for objects allocated using operator new
. If the object is created using new
, then we can do delete this
, otherwise behavior is undefined.
2) Once delete this
is done, any member of the deleted object should not be accessed after deletion.
3) The best thing is to NOT do delete this
at all.
class A {
public:
void fun(){
cout << "try to run 'delete this'\n";
delete this;
}
};
int main() {
/* Following is Valid */
A *ptr = new A;
ptr->fun();
ptr = NULL; // make ptr=NULL to make sure that things are not accessed using ptr;
/* And following is Invalid: Undefined Behavior */
A a;
a.fun();
getchar();
return 0;
}
To compile using g++: g++ 001_static_vs_const.cpp -o 001_static_vs_const
Output:
#if 0 … #endif
block do?#if 0
//Code goes here
#endif
Answer:
#if
is a preprocessor command, which gets evaluated before the actual compilation step. The code inside that block doesn’t appear in the compiled binary.It’s often used for temporarily removing segments of code with the intention of turning them back on later.
see: https://www.tutorialspoint.com/cplusplus/cpp_variable_types.htm
In this section, we will learn about variables, literals, and constants in C++ with the help of examples.
C++的数据包括常量(literal)与变量(variable), 常量与变量都具有类型。
There are following basic types of variable in C++: bool, char, int, float, double, void, wchar_t
(A wide character type). C++ also allows to define various other types of variables, like Enumeration(enum
), Pointer (*
), Array, Reference (&
), Structures (struct
), and Classes (class
).
A variable definition tells the compiler where and how much storage to create for the variable. A variable definition specifies a data type, and contains a list of one or more variables of that type as follows −
int i, j, k;
int d = 3, f = 5; // definition and initializing d and f.
float f, salary;
double d;
For definition without an initializer: variables with static storage duration are implicitly initialized with
NULL` (all bytes have the value 0); the initial value of all other variables is undefined.
A variable declaration provides assurance to the compiler
that there is one variable existing with the given type and name so that compiler proceed for further compilation without needing complete detail about the variable. A variable declaration has its meaning at the time of compilation only, compiler needs actual variable definition at the time of linking
of the program.
A variable declaration is useful when you are using multiple files
and you define your variable in one of the files which will be available at the time of linking
of the program. You will use extern
keyword to declare a variable at any place. Though you can declare a variable multiple times in your C++ program, but it can be defined only once in a file, a function or a block of code. For example -
#include <iostream>
using namespace std;
// Variable declaration:
extern int a, b;
extern int c;
extern float f;
int main () {
// Variable definition:
int a, b;
int c;
float f;
// actual initialization
a = 10;
b = 20;
c = a + b;
cout << c << endl ;
f = 70.0/3.0;
cout << f << endl ;
return 0;
}
Same concept applies on function declaration
where you provide a function name at the time of its declaration and its actual definition can be given anywhere else. For example −
// function declaration
int func(int);
int main(){
// function call
int i = func();
}
// function definition
int func(int a) {
return a;
}
A scope is a region of the program and broadly speaking there are three places, where variables can be declared −
Inside a function or a block which is called local variables
,
In the definition of function parameters which is called formal parameters
.
Outside of all functions which is called global variables
.
Constants
refer to fixed values that the program may not alter and they are called literals
.
Constants can be of any of the basic data types and can be divided into into Integer Numerals, Floating-Point Numerals, Characters, Strings and Boolean Values
. Again, constants are treated just like regular variables except that their values cannot be modified after their definition.
An integer literal can be a decimal, octal, or hexadecimal constant. A prefix specifies the base or radix: 0x or 0X
for hexadecimal, 0
for octal, and nothing
for decimal.
An integer literal can also have a suffix that is a combination of U
and L
, for unsigned and long, respectively. The suffix can be uppercase or lowercase
and can be in any order.
Here are some examples of integer literals −
212 // Legal
215u // Legal, unsigned int
0xFeeL // Legal, L for long int
078 // Illegal: 8 is not an octal digit
032UU // Illegal: cannot repeat a suffix
//Following are other examples of various types of Integer literals;
85 // decimal
0213 // octal
0x4b // hexadecimal
30 // int
30u // unsigned int
30l // long
30ul // unsigned long
A floating-point literal has an integer part
, a decimal point, a fractional part
, and an exponent part
. You can represent floating point literals either in decimal form or exponential form.
While representing using decimal form, you must include the decimal point, the exponent, or both and while representing using exponential form, you must include the integer part, the fractional part, or both. The signed exponent is introduced by e
or E
.
Here are some examples of floating-point literals −
3.14159 // Legal
314159E-5L // Legal
510E // Illegal: incomplete exponent
210f // Illegal: no decimal or exponent
.e55 // Illegal: missing integer or fraction
There are two Boolean literals and they are part of standard C++ keywords:
A value of true
representing true.
A value of false
representing false.
You should not consider the value of true equal to 1 and value of false equal to 0.
Character literals are enclosed in single quotes (单引号)
. If the literal begins with L (uppercase only)
, it is a wide character literal (e.g., L’x’) and should be stored in wchar_t
type of variable . Otherwise, it is a narrow character literal (e.g., ‘x’) and can be stored in a simple variable of char
type.
A character literal can be a plain character (e.g., ‘x’), an escape sequence (e.g., ‘\t’), or a universal character (e.g., ‘\u02C0’).
There are certain characters in C++ when they are preceded by a backslash
they will have special meaning and they are used to represent like newline (\n)
or tab (\t)
.
String literals are enclosed in double quotes(双引号)
. A string contains characters that are similar to character literals: plain characters, escape sequences, and universal characters.
You can break a long line into multiple lines using string literals and separate them using whitespaces
.
Here are some examples of string literals. All the three forms are identical strings.
//All the three forms are identical strings.
"hello, dear"
"hello, \
dear"
"hello, " "d" "ear"
There are two simple ways in C++ to define constants:
Using #define
preprocessor. E.g., #define LENGTH 10
, #define WIDTH 5
and #define NEWLINE '\n'
;
Using const
keyword. E.g., const int LENGTH = 10;
, const int WIDTH = 5;
and const char NEWLINE = '\n';
.
see: https://www.learncpp.com/cpp-tutorial/const-constexpr-and-symbolic-constants/
- To make a variable constant, simply put the `const` keyword either before (which is recommend) or after the variable type, like so:
const double gravity { 9.8 }; // preferred use of const before type
int const sidesInSquare { 4 }; // okay, but not preferred
Const variables must be initialized when you define them, and then that value can not be changed via assignment.
Defining a const variable without initializing it will also cause a compile error
Note that it is a good programming practice to define constants in CAPITALS
.
Live Demo
#include <iostream>
using namespace std;
//#define LENGTH 10
//#define WIDTH 5
//#define NEWLINE '\n'
int main() {
const int LENGTH = 10;
const int WIDTH = 5;
const char NEWLINE = '\n';
int area;
area = LENGTH * WIDTH;
cout << area;
cout << NEWLINE;
return 0;
}
The data type modifiers are listed here:
signed
unsigned
long
short
The modifiers signed, unsigned, long, and short can be applied to integer base types. In addition, signed and unsigned can be applied to char, and long can be applied to double.
The modifiers signed and unsigned can also be used as prefix to long or short modifiers. For example, unsigned long int.
C++ allows a shorthand notation for declaring unsigned, short, or long integers. You can simply use the word unsigned, short, or long, without int. It automatically implies int. For example, the following two statements both declare unsigned integer variables.
unsigned x;
unsigned int y;
static
Storage Class1) making local variables static:
The static
storage class instructs the compiler to keep a local variable
in existence during the life-time of the program instead of creating and destroying it each time it comes into and goes out of scope. Therefore, making local variables static allows them to maintain their values between function calls.
2) making global variables static:
The static
modifier may also be applied to global variables
. When this is done, it causes that variable’s scope to be restricted to the file in which it is declared.
3) making class data member static:
In C++, when static
is used on a class data member, it causes only one copy of that member
to be shared by all objects of its class.
#include <iostream>
// Function declaration
void func(void);
static int count = 10; /* Global variable */
main() {
while(count--) {
func();
}
return 0;
}
// Function definition
void func( void ) {
static int i = 5; // local static variable
i++;
std::cout << "i is " << i ;
std::cout << " and count is " << count << std::endl;
}
When the above code is compiled and executed, it produces the following result −
i is 6 and count is 9
i is 7 and count is 8
i is 8 and count is 7
i is 9 and count is 6
i is 10 and count is 5
i is 11 and count is 4
i is 12 and count is 3
i is 13 and count is 2
i is 14 and count is 1
i is 15 and count is 0
extern
Storage ClassThe extern
storage class is used to give a reference of a global variable that is visible to ALL
the program files. When you use 'extern'
the variable cannot be initialized as all it does is point the variable name at a storage location that has been previously defined.
When you have multiple files and you define a global variable or function, which will be used in other files also, then extern
will be used in another file to give reference of defined variable or function. Just for understanding extern
is used to declare a global variable or function in another file.
The extern
modifier is most commonly used when there are two or more files sharing the same global variables or functions as explained below.
E.g., First File: main.cpp
#include <iostream>
int count ;
extern void write_extern();
main() {
count = 5;
write_extern();
}
Second File: support.cpp
#include <iostream>
extern int count;
void write_extern(void) {
std::cout << "Count is " << count << std::endl;
}
Here, extern
keyword is being used to declare count
in another file. Now compile these two files as follows −
$g++ main.cpp support.cpp -o my_exam1
This will produce my_exam1
executable program, try to execute it and check the result as follows −
$./my_exam1
>>> 5
++i
will increment the value of i
, and then return the incremented value.
i = 1;
j = ++i;
(i is 2, j is 2)
i++
will increment the value of i, but return the original value that i held before being incremented.
i = 1;
j = i++;
(i is 2, j is 1)
So basically ++i
returns the value after
it is incremented, while i++
return the value before
it is incremented. At the end, in both cases the i
will have its value incremented.
For a for loop
, either works. ++i
seems more common, perhaps because that is what is used in K&R.
In any case, follow the guideline "prefer ++i over i++"
and you won’t go wrong.
C++ | Python | Note | Examples |
& | & | Binary AND Operator | x & y : Does a “bitwise and”. Each bit of the output is 1 if the corresponding bit of |
| | | | Binary OR Operator | x | y : Does a “bitwise or”. Each bit of the output is 0 if the corresponding bit of |
^ | ^ | Binary XOR Operator | x ^ y : Does a “bitwise exclusive or”. Each bit of the output is the same as the corresponding bit in x if that bit in y is 0 , and it’s the complement of the bit in x if that bit in y is 1 |
~ | ~ | Binary Ones Complement Operator | ~ x : Returns the complement of x. i.e., the number you get by switching each 1 for a 0 and each 0 for a 1. This is the same as -x - 1 |
<< | << | Binary Left Shift Operator. | x << y : Returns |
>> | >> | Binary Right Shift Operator. | x >> y : Returns |
C++ | Python | Note | Examples |
&& | and | Called Logical AND operator | x && y (C++) or x and y (Python): Returns True if both statements are true |
|| | or | Called Logical OR operator | x || y (C++) or x or y (Python): Returns True if one of the statements is true |
! | not | Called Logical NOT operator | !x (C++) or not x (Python): Reverse the result, returns False if the result is true |
The following table lists some other operators that C++ supports.
Sr.No | Operator & Description |
sizeof | sizeof operator returns the size of a variable. For example, sizeof(a), where ‘a’ is integer, and will return 4. |
Condition ? X : Y | Conditional operator (?). If Condition is true then it returns value of X otherwise returns value of Y. |
, Comma operator | Comma operator causes a sequence of operations to be performed. The value of the entire comma expression is the value of the last expression of the comma-separated list. |
. (dot) and -> (arrow) | Member operators are used to reference individual members of classes, structures, and unions. |
Cast, e.g., int(x) | Casting operators convert one data type to another. For example, int(2.2000) would return 2. This syntax works in both C++ and Python. |
& Pointer operator | Pointer operator & returns the address of a variable. For example &a; will give actual address of the variable. |
* | Pointer operator * is pointer to a variable. For example *var; will pointer to a variable var. |
With the break
statement we can stop the loop even if the while condition is true:
With the continue
statement we can stop the current iteration, and continue with the next:
C++ Syntax : Here, statement(s)
may be a single statement or a block of statements. The condition
may be any expression, and true is any non-zero value. The loop iterates while the condition is true. When the condition becomes false, program control passes to the line immediately following the loop.
Python Syntax: note with the else
statement we can run a block of code once when the condition no longer is true:
C++ Syntax:
// for loop execution
for( int i = 10; i < 20; ++i){
cout << "value of i: " << i << endl;
}
Python Syntax:
for x in range(6):
print(x)
C++ supports the following control statements.
break
statement (C++ and Python): Terminates the loop or switch statement and transfers execution to the statement immediately following the loop or switch.
continue
statement (C++ and Python): Causes the loop to skip the remainder of its body and immediately retest its condition prior to reiterating.
goto
statement (C++): Transfers control to the labeled statement. Though it is not advised to use goto
statement in your program.
As you know every variable is a memory location and every memory location has its address defined which can be accessed using ampersand (&
) operator which denotes an address in memory.
int var1;
char var2[10];
cout << "Address of var1 variable: " << &var1 << endl;
// Address of var1 variable: 0xbfebd5c0
cout << "Address of var2 variable: " << &var2 << endl;
// Address of var2 variable: 0xbfebd5b6
A pointer is a variable whose value is the address of another variable. Like any variable or constant, you must declare a pointer before you can work with it. The general form of a pointer variable declaration is − type *var-name;
, e.g.,
int *ip = NULL; // pointer to an integer
double *dp; // pointer to a double
float *fp; // pointer to a float
char *ch // pointer to character
The NULL
pointer is a constant with a value of zero defined in several standard libraries, including iostream.
To check for a null pointer you can use an if statement as follows −
if(ptr)
succeeds if p is not null
if(!ptr)
succeeds if p is null
In fact, pointers and arrays are interchangeable in many cases. For example, a pointer that points to the beginning of an array can access that array by using either pointer arithmetic or array-style indexing.
int var[MAX] = {10, 100, 200};
int *ptr;
// let us have array address in pointer.
ptr = var;
for (int i = 0; i < MAX; i++) {
cout << "Address of var[" << i << "] = " << ptr << endl;
cout << "Value of var[" << i << "] = " << *ptr << endl;
// point to the next location
ptr++;
}
When the above code is compiled and executed, it produces result something as follows −
Address of var[0] = 0xbfa088b0
Value of var[0] = 10
Address of var[1] = 0xbfa088b4
Value of var[1] = 100
Address of var[2] = 0xbfa088b8
Value of var[2] = 200
However, pointers and arrays are not completely interchangeable. For example, consider the following program −
#include <iostream>
using namespace std;
const int MAX = 3;
int main () {
int var[MAX] = {10, 100, 200};
for (int i = 0; i < MAX; i++) {
*var = i; // This is a correct syntax
var++; // This is incorrect.
}
return 0;
}
It is perfectly acceptable to apply the pointer operator *
to var
but it is ILLEGAL to modify var
value. The reason for this is that var
is a constant that points to the beginning of an array and can not be used as l-value
(i.e., left side value wrt assignment =
).
Because an array name generates a pointer constant, it can still be used in pointer-style expressions, as long as it is not modified. For example, the following is a valid statement that assigns var[2]
the value 500 −
*(var + 2) = 500;
Above statement is valid and will compile successfully because var
itself is not changed.
A reference variable is an alias, that is, another name for an already existing variable. Once a reference is initialized with a variable, either the variable name or the reference name may be used to refer to the variable.
References are often confused with pointers but three major differences between references and pointers are −
You CANNOT have NULL
references. You must always be able to assume that a reference is connected to a legitimate piece of storage.
Once a reference is initialized to an object, it cannot be changed to refer to another object. Pointers can be pointed to another object at any time.
A reference must be initialized when it is created. Pointers can be initialized at any time.
Think of a variable name as a label
attached to the variable’s location
in memory. You can then think of a reference as a second label
attached to that memory location. Therefore, you can access the contents of the variable through either the original variable name or the reference. For example, suppose we have the following example −
int i = 17;
We can declare reference variables for i
as follows.
int& r = i;
Read the &
in these declarations as reference
. Thus, read the first declaration as "r is an integer reference initialized to i"
and read the second declaration as "s is a double reference initialized to d."
. Following example makes use of references on int and double −
Live Demo
#include <iostream>
using namespace std;
int main () {
// declare simple variables
int i = 5;
double d = 11.7;
// declare reference variables
int& r = i;
double& s = d;
cout << "Value of i : " << i << endl;
cout << "Value of i reference : " << r << endl;
cout << "Value of d : " << d << endl;
cout << "Value of d reference : " << s << endl;
return 0;
}
When the above code is compiled together and executed, it produces the following result −
Value of i : 5
Value of i reference : 5
Value of d : 11.7
Value of d reference : 11.7
References are usually used for function argument lists and function return values. So following are two important subjects related to C++ references
which should be clear to a C++ programmer −
C++ supports passing references as function parameter more safely than parameters.
We have discussed how we implement call by reference concept using pointers. Here is another example of call by reference which makes use of C++ reference −
#include <iostream>
using namespace std;
// function declaration
void swap(int& x, int& y);
int main () {
// local variable declaration:
int a = 100;
int b = 200;
cout << "Before swap, value of a :" << a << endl;
cout << "Before swap, value of b :" << b << endl;
/* calling a function to swap the values.*/
swap(a, b);
cout << "After swap, value of a :" << a << endl;
cout << "After swap, value of b :" << b << endl;
return 0;
}
// function definition to swap the values.
void swap(int& x, int& y) {
int temp;
temp = x; /* save the value at address x */
x = y; /* put y into x */
y = temp; /* put x into y */
return;
}
You can return reference from a C++ function like any other data type.
A C++ program can be made easier to read and maintain by using references rather than pointers.
A C++ function can return a reference in a similar way as it returns a pointer.
When a function returns a reference, it returns an implicit pointer
to its return value. This way, a function can be used on the left side of an assignment statement. For example, consider this simple program −
#include <iostream>
#include <ctime>
using namespace std;
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
double& setValues( int i ) {
return vals[i]; // return a reference to the ith element
}
// main function to call above defined function.
int main () {
cout << "Value before change" << endl;
for ( int i = 0; i < 5; i++ ) {
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
setValues(1) = 20.23; // change 2nd element
setValues(3) = 70.8; // change 4th element
cout << "Value after change" << endl;
for ( int i = 0; i < 5; i++ ) {
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
return 0;
}
When the above code is compiled together and executed, it produces the following result:
Value before change
vals[0] = 10.1
vals[1] = 12.6
vals[2] = 33.1
vals[3] = 24.1
vals[4] = 50
Value after change
vals[0] = 10.1
vals[1] = 20.23
vals[2] = 33.1
vals[3] = 70.8
vals[4] = 50
When returning a reference, be careful that the object being referred to does not go out of scope. So it is not legal to return a reference to local var. But you can always return a reference on a static
variable.
int& func() {
int q;
//! return q; // Compile time error
static int x;
return x; // Safe, x lives outside this scope
}
A function is a group of statements that together perform a task. Every C++ program has at least one function, which is main(), and all the most trivial programs can define additional functions.
Parameters − A parameter is like a placeholder. When a function is invoked, you pass a value to the parameter. This value is referred to as actual parameter
(opposed to the formal parameter
below ) or argument
. The parameter list refers to the type, order, and the number of the parameters of a function. Parameters are optional; that is, a function may contain no parameters.
A function declaration
tells the compiler about a function name and how to call the function. The actual body of the function can be defined separately.
A function declaration has the following parts −
return_type function_name( parameter list );
e.g., int max(int num1, int num2);
Parameter names are not important
in function declaration only their type is required, so following is also valid declaration: int max(int, int);
Function declaration is required when you define a function in one source file and you call that function in another file. In such case, you should declare the function at the top of the file calling the function.
If a function is to use arguments, it must declare variables that accept the values of the arguments. These variables are called the formal parameters
of the function.
The formal parameters
behave like other local variables inside the function and are created upon entry into the function and destroyed upon exit.
When a program calls a function, program control is transferred to the called function. A called function performs defined task and when it’s return statement is executed or when its function-ending closing brace is reached, it returns program control back to the main program.
To call a function, you simply need to pass the required parameters along with function name, and if function returns a value, then you can store returned value.
While calling a function, there are several ways that arguments
can be passed to a function:
The
call by value
method of passing arguments to a function copies theactual
value of an argument into theformal parameter
of the function. In this case, changes made to the parameter inside the function have no effect on the argument.
By default, C++ usescall by value
to pass arguments. In general, this means that code within a function cannot alter the arguments used to call the function.
The
call by pointer
method of passing arguments to a function copies the address of an argument into the formal parameter. Inside the function, the address is used to access the actual argument used in the call. This means that changes made to the parameter affect the passed argument.To pass the value by pointer, argument pointers are passed to the functions just like any other value. So accordingly you need to declare the function parameters as pointer types as in the following function swap(), which exchanges the values of the two integer variables pointed to by its arguments.
// function definition to swap the values.
void swap(int *x, int *y) {
int temp;
temp = *x; /* save the value at address x */
*x = *y; /* put y into x */
*y = temp; /* put x into y */
return;
}
The
call by reference
method of passing arguments to a function copies thereference of an argument
into the formal parameter. Inside the function, the reference is used to access the actual argument used in the call. This means that changes made to the parameter affect the passed argument.To pass the value by reference, argument reference is passed to the functions just like any other value. So accordingly you need to declare the function parameters as reference types as in the following function swap(), which exchanges the values of the two integer variables pointed to by its arguments.
// function definition to swap the values.
void swap(int &x, int &y) {
int temp;
temp = x; /* save the value at address x */
x = y; /* put y into x */
y = temp; /* put x into y */
return;
}
struct
C/C++ arrays allow you to define variables that combine several data items of the same kind, but structure is another user defined data type which allows you to combine data items of different kinds.
To define a structure, you must use the struct
statement. The struct statement defines a new data type, with more than one member, for your program. The format of the struct statement is this −
struct [structure tag] {
member definition;
member definition;
...
member definition;
} [one or more structure variables];
The structure tag
is optional and each member definition is a normal variable definition, such as int i
; or float f
; or any other valid variable definition. At the end of the structure’s definition, before the final semicolon, you can specify one or more structure variables but it is optional. Here is the way you would declare the Book structure −
struct Books {
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
To access any member of a structure, we use the member access operator (.)
. The member access operator is coded as a period between the structure variable name and the structure member that we wish to access. You would use struct
keyword to define variables of structure type. Following is the example to explain usage of structure −
struct Books {
char title[50];
char author[50];
char subject[100];
int book_id;
};
struct Books Book1; // Declare Book1 of type Book
// book 1 specification
strcpy( Book1.title, "Learn C++ Programming");
strcpy( Book1.author, "Chand Miyan");
strcpy( Book1.subject, "C++ Programming");
Book1.book_id = 6495407;
You can pass a structure as a function argument in very similar way as you pass any other variable or pointer. You would access structure variables in the similar way as you have accessed in the above example −
void printBook( struct Books book ) {
cout << "Book title : " << book.title <<endl;
cout << "Book author : " << book.author <<endl;
cout << "Book subject : " << book.subject <<endl;
cout << "Book id : " << book.book_id <<endl;
}
printBook( Book1 );
You can define pointers to structures in very similar way as you define pointer to any other variable as follows −
struct Books *struct_pointer;
Now, you can store the address of a structure variable in the above defined pointer variable. To find the address of a structure variable, place the &
operator before the structure’s name as follows −
struct_pointer = &Book1;
To access the members of a structure using a pointer to that structure, you must use the ->
operator as follows −
struct_pointer->title;
typedef
KeywordThere is an easier way to define structs or you could “alias” types you create. For example −
typedef struct {
char title[50];
char author[50];
char subject[100];
int book_id;
} Books;
Now, you can use Books
directly to define variables of Books type
without using struct keyword. Following is the example −
Books Book1, Book2;
You can use typedef
keyword for non-structs as well as follows −
typedef long int *pint32;
pint32 x, y, z;
Then x
, y
and z
are all pointers to long ints.
The main purpose of C++ programming is to add object orientation to the C programming language and classes are the central feature of C++ that supports object-oriented programming and are often called user-defined types.
The data and functions within a class are called members of the class.
A class definition starts with the keyword class
followed by the class name; and the class body, enclosed by a pair of curly braces
. A class definition must be followed either by a semicolon or a list of declarations. For example, we defined the Box data type using the keyword class as follows −
class Box {
public:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
The keyword public
determines the access attributes
of the members of the class that follows it. A public member can be accessed from outside the class anywhere within the scope of the class object. You can also specify the members of a class as private
or protected
which we will discuss in a sub-section.
A class provides the blueprints
for objects, so basically an object is created from a class. We declare objects
of a class with exactly the same sort of declaration that we declare variables of basic types. Following statements declare two objects of class Box −
Box Box1; // Declare Box1 of type Box
Box Box2; // Declare Box2 of type Box
Both of the objects Box1 and Box2 will have their own copy of data members.
The public data members of objects of a class can be accessed using the direct member access operator (.)
.
Member functions can be defined within the class definition or separately using scope resolution operator (::
).
Defining a member function within the class definition declares the function inline
, even if you do not use the inline
specifier. So either you can define Volume() function as below −
class Box {
public:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
double getVolume(void) {
return length * breadth * height;
}
};
If you like, you can define the same function outside
the class using the scope resolution operator (::)
as follows −
double Box::getVolume(void) {
return length * breadth * height;
}
Here, only important point is that you would have to use class name just before ::
operator. A member function will be called using a dot operator (.)
on a object where it will manipulate data related to that object only as follows −
Box myBox; // Create an object
myBox.getVolume(); // Call member function for the object
Data hiding
is one of the important features of Object Oriented Programming (OOP)
which allows preventing the functions of a program to access directly the internal representation of a class type. The access restriction
to the class members is specified by the labeled public
, private
, and protected
sections within the class body.
The keywords public, private, and protected
are called access specifiers.
A class can have multiple public, protected, or private labeled sections. Each section remains in effect until either another section label or the closing right brace of the class body is seen.
The default access for members and classes is private
.
A public member is accessible from anywhere outside the class but within a program. You can set and get the value of public variables without any member function.
A private member variable or function cannot be accessed, or even viewed from outside the class.
Only the class and friend functions
can access private members.
By default all the members of a class would be private, for example in the following class width
is a private member, which means until you label a member, it will be assumed a private member −
class Box {
double width;
public:
double length;
void setWidth( double wid );
double getWidth( void );
};
Practically, we define data
in private section and related functions
in public section so that they can be called from outside of the class as shown in the following program.
#include <iostream>
using namespace std;
class Box {
public:
double length;
void setWidth( double wid );
double getWidth( void );
private:
double width;
};
// Member functions definitions
double Box::getWidth(void) {
return width ;
}
void Box::setWidth( double wid ) {
width = wid;
}
// Main function for the program
int main() {
Box box;
// set box length without member function
box.length = 10.0; // OK: because length is public
cout << "Length of box : " << box.length <<endl;
// set box width without member function
// box.width = 10.0; // Error: because width is private
box.setWidth(10.0); // Use member function to set it.
cout << "Width of box : " << box.getWidth() <<endl;
return 0;
}
When the above code is compiled and executed, it produces the following result −
Length of box : 10
Width of box : 10
A protected
member variable or function is very similar to a private member but it provided one additional benefit that they can be accessed in child classes which are called derived classes
.
You can check following example where I have derived one child class SmallBox from a parent class Box.
Following example is similar to above example and here width
member will be accessible by any member function of its derived class SmallBox
.
include <iostream>
using namespace std;
class Box {
protected:
double width;
};
class SmallBox:Box { // SmallBox is the derived class.
public:
void setSmallWidth( double wid );
double getSmallWidth( void );
};
// Member functions of child class
double SmallBox::getSmallWidth(void) {
return width ;
}
void SmallBox::setSmallWidth( double wid ) {
width = wid;
}
// Main function for the program
int main() {
SmallBox box;
// set box width using member function
box.setSmallWidth(5.0);
cout << "Width of box : "<< box.getSmallWidth() << endl;
return 0;
}
When the above code is compiled and executed, it produces the following result −
Width of box : 5
A class constructor is a special member function of a class that is executed whenever we create new objects of that class.
A constructor will have exact same name
as the class and it does not have any return type at all, not even void
. Constructors can be very useful for setting initial values for certain member variables.
A default constructor does not have any parameter.
Parameterized Constructor: But if you need, a constructor can have parameters. This helps you to assign initial value
to an object at the time of its creation.
#include <iostream>
using namespace std;
class Line {
public:
void setLength( double len );
double getLength( void );
Line ();// This is a default constructor;
Line(double len); // This is a parameterized constructor;
private:
double length;
};
// Member functions definitions including constructor
Line::Line(void) {
cout << "Object is being created" << endl;
}
// Member functions definitions including constructor
Line::Line( double len) {
cout << "Object is being created, length = " << len << endl;
length = len;
}
void Line::setLength( double len ) {
length = len;
}
double Line::getLength( void ) {
return length;
}
// Main function for the program
int main() {
Line line(10.0);
// get initially set length.
cout << "Length of line : " << line.getLength() <<endl;
// set line length again
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
Using Initialization Lists to Initialize Fields:
In case of parameterized constructor, you can use following syntax to initialize the fields:
Line::Line( double len): length(len) {
cout << "Object is being created, length = " << len << endl;
}
If for a class A, you have multiple fields X, Y, Z, etc., to be initialized, then use can use same syntax and separate the fields by comma as follows −
A::A( double a, double b, double c): X(a), Y(b), Z(c) {
....
}
A destructor
is a special member function of a class that is executed whenever an object of it’s class goes out of scope or whenever the delete expression is applied to a pointer to the object of that class.
A destructor
will have exact same name as the class prefixed with a tilde (~)
and it can neither return a value nor can it take any parameters. Destructor
can be very useful for releasing resources
before coming out of the program like closing files, releasing memories
etc.
// Member functions definitions including constructor
Line::Line(void) {
cout << "Object is being created" << endl;
}
Line::~Line(void) {
cout << "Object is being deleted" << endl;
}
The copy constructor is a constructor which creates an object by initializing it with an object of the same class, which has been created previously. The copy constructor is used to:
Initialize one object from another of the same type.
Copy an object to pass it as an argument to a function.
Copy an object to return it from a function.
If a copy constructor is not defined in a class, the compiler itself
defines one.
If the class has pointer variables and has some dynamic memory allocations
, then it is a must to have a copy constructor
.
The most common form of copy constructor is shown here −
classname (const classname &obj) {
// body of constructor
}
For example:
Live Demo
#include <iostream>
using namespace std;
class Line {
public:
int getLength( void );
Line( int len ); // simple constructor
Line( const Line &obj); // copy constructor
~Line(); // destructor
private:
int *ptr;
};
// Member functions definitions including constructor
Line::Line(int len) {
cout << "Normal constructor allocating ptr" << endl;
// allocate memory for the pointer;
ptr = new int;
*ptr = len;
}
Line::Line(const Line &obj) {
cout << "Copy constructor allocating ptr." << endl;
ptr = new int;
*ptr = *obj.ptr; // copy the value
}
Line::~Line(void) {
cout << "Freeing memory!" << endl;
delete ptr;
}
int Line::getLength( void ) {
return *ptr;
}
void display(Line obj) {
cout << "Length of line : " << obj.getLength() <<endl;
}
void display2(Line & obj) {
cout << "Length of line : " << obj.getLength() <<endl;
}
// Main function for the program
int main() {
cout << "creating object line1" << endl;
Line line1(10);
cout << "\ncreating object line2" << endl;
Line line2 = line1; // This also calls copy constructor
cout << "\ncalling display(line1)" << endl;
display(line1);
cout << "\ncalling display(line2)" << endl;
display(line2);
cout << "\ncalling display2(line2)" << endl;
display2(line2);
return 0;
}
When the above code is compiled and executed, it produces the following result −
Note:
The copy constructor is also used to copy an object to pass it as an argument to a function, as shown in calling display(line1);
.
So if we use pass-by-reference for display2
, then we can avoid the copy.
A friend function of a class is defined outside that class’ scope but it has the right to access all private and protected
members of the class. Even though the prototypes for friend functions appear in the class definition, friends are not member functions.
A friend can be a function, function template, or member function, or a class or class template, in which case the entire class and all of its members are friends.
To declare a function as a friend of a class, precede the function prototype in the class definition with keyword friend
as follows −
class Box {
double width;
public:
double length;
friend void printWidth( Box box );
void setWidth( double wid );
};
To declare all member functions of class ClassTwo
as friends of class ClassOne
, place a following declaration in the definition of class ClassOne
−
friend class ClassTwo;
Consider the following program −
#include <iostream>
using namespace std;
class Box {
double width;
public:
friend void printWidth( Box box );
void setWidth( double wid );
};
// Member function definition
void Box::setWidth( double wid ) {
width = wid;
}
// Note: printWidth() is not a member function of any class.
void printWidth( Box box ) {
/* Because printWidth() is a friend of Box, it can
directly access any member of this class */
cout << "Width of box : " << box.width <<endl;
}
// Main function for the program
int main() {
Box box;
// set box width without member function
box.setWidth(10.0);
// Use friend function to print the wdith.
printWidth( box );
return 0;
}
When the above code is compiled and executed, it produces the following result −
Width of box : 10
C++ inline
function is powerful concept that is commonly used with classes. If a function is inline, the compiler places a copy of the code of that function at each point where the function is called at compile time.
Any change to an inline
function could require all clients of the function to be recompiled
because compiler would need to replace all the code once again otherwise it will continue with old functionality.
To inline a function, place the keyword inline
before the function name and define the function before any calls
are made to the function. The compiler can ignore
the inline qualifier in case defined function is more than a line.
A function definition in a class definition is an inline function definition, even without the use of the inline specifier.
inline int Max(int x, int y) {
return (x > y)? x : y;
}
Every object
in C++ has access to its own address through an important pointer called this
pointer. The this
pointer is an implicit parameter
to all member functions. Therefore, inside a member function, this may be used to refer to the invoking object.
Friend functions do not have a this pointer, because friends are not members of a class. Only member functions have a this pointer.
Let us try the following example to understand the concept of this pointer −
#include <iostream>
using namespace std;
class Box {
public:
// Constructor definition
Box(double l = 2.0, double b = 2.0, double h = 2.0) {
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
}
double Volume() {
return length * breadth * height;
}
int compare(Box box) {
return this->Volume() > box.Volume();
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
int main(void) {
Box Box1(3.3, 1.2, 1.5); // Declare box1
Box Box2(8.5, 6.0, 2.0); // Declare box2
if(Box1.compare(Box2)) {
cout << "Box2 is smaller than Box1" <<endl;
} else {
cout << "Box2 is equal to or larger than Box1" <<endl;
}
return 0;
}
When the above code is compiled and executed, it produces the following result −
Constructor called.
Constructor called.
Box2 is equal to or larger than Box1
We can define class members static using static
keyword. When we declare a member of a class as static it means no matter how many objects of the class are created, there is only one copy
of the static member.
A static member is shared by all objects of the class. All static data is initialized to zero
when the first object is created, if no other initialization is present. We can’t put it in the class definition but it can be initialized outside
the class as done in the following example by redeclaring
the static variable, using the scope resolution operator ::
to identify which class it belongs to.
Let us try the following example to understand the concept of static data members −
#include <iostream>
using namespace std;
class Box {
public:
static int objectCount;
// Constructor definition
Box(double l = 2.0, double b = 2.0, double h = 2.0) {
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// Increase every time object is created
objectCount++;
}
double Volume() {
return length * breadth * height;
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
// Initialize static member of class Box
int Box::objectCount = 0;
int main(void) {
Box Box1(3.3, 1.2, 1.5); // Declare box1
Box Box2(8.5, 6.0, 2.0); // Declare box2
// Print total number of objects.
cout << "Total objects: " << Box::objectCount << endl;
// Or we can access the static data via an object;
cout << "Total objects: " << Box2.objectCount << endl;
return 0;
}
When the above code is compiled and executed, it produces the following result −
Constructor called.
Constructor called.
Total objects: 2
Please note static data members can be accessed using either the class name
and the scope resolution operator ::
, or the object, as shown above:
// Print total number of objects.
cout << "Total objects: " << Box::objectCount << endl;
// Or we can access the static data via an object;
cout << "Total objects: " << Box2.objectCount << endl;
By declaring a function member as static, you make it independent
of any particular object of the class.
A static member function can be called even if no objects of the class exist and the static functions are accessed using only the class name
and the scope resolution operator ::
. Please note static data members can be accessed using either the class name
and the scope resolution operator ::
, or the object.
A static member function can only access
i) static data member
,
ii) other static member functions
and
iii) any other functions from outside the class
.
Static member functions have a class scope and they do not have access to the this
pointer of the class. You could use a static member function to determine whether some objects of the class have been created or not.
Let us try the following example to understand the concept of static function members −
#include <iostream>
using namespace std;
class Box {
public:
static int objectCount;
// Constructor definition
Box(double l = 2.0, double b = 2.0, double h = 2.0) {
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// Increase every time object is created
objectCount++;
}
double Volume() {
return length * breadth * height;
}
// static member function
static int getCount() {
return objectCount;
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
// Initialize static member of class Box
int Box::objectCount = 0;
int main(void) {
// Print total number of objects before creating object.
cout << "Inital Stage Count: " << Box::getCount() << endl;
Box Box1(3.3, 1.2, 1.5); // Declare box1
Box Box2(8.5, 6.0, 2.0); // Declare box2
// Print total number of objects after creating object.
cout << "Final Stage Count: " << Box::getCount() << endl;
return 0;
}
When the above code is compiled and executed, it produces the following result −
Inital Stage Count: 0
Constructor called.
Constructor called.
Final Stage Count: 2
One of the most important concepts in object-oriented programming is that of inheritance
. Inheritance allows us to define a class in terms of another class, which makes it easier to create and maintain an application. This also provides an opportunity to reuse
the code functionality
and fast implementation time
.
When creating a class, instead of writing completely new data members
and member functions
, the programmer can designate that the new class should inherit
the members of an existing class. This existing class is called the base class
, and the new class is referred to as the derived class
.
The idea of inheritance implements is a relationship. For example, mammal IS-A animal, dog IS-A mammal hence dog IS-A animal as well and so on.
A class can be derived from more than one classes, which means it can inherit data and functions from multiple
base classes. To define a derived class, we use a class derivation
list to specify the base class(es). A class derivation list names one or more base classes and has the form −
class derived-class: access-specifier base-class
Where access-specifier
is one of public, protected, or private
,
and base-class
is the name of a previously defined class.
If the access-specifier is not used, then it is private
by default.
Consider a base class Shape and its derived class Rectangle as follows −
#include <iostream>
using namespace std;
// Base class
class Shape {
public:
void setWidth(int w) {
width = w;
}
void setHeight(int h) {
height = h;
}
protected:
int width;
int height;
};
// Derived class
class Rectangle: public Shape {
public:
int getArea() {
return (width * height);
}
};
int main(void) {
Rectangle Rect;
Rect.setWidth(5);
Rect.setHeight(7);
// Print the area of the object.
cout << "Total area: " << Rect.getArea() << endl;
return 0;
}
When the above code is compiled and executed, it produces the following result −
Total area: 35
A derived class can access all the non-private
members of its base class. Thus base-class members that should not be accessible to the member functions of derived classes should be declared private
in the base class.
We can summarize the different access types according to - who can access them in the following way −
Access | public | protected | private |
Same class | yes | yes | yes |
Derived classes | yes | yes | no |
Outside classes | yes | no | no |
A derived class inherits all base class methods with the following exceptions −
Constructors, destructors and copy constructors of the base class.
Overloaded operators of the base class.
The friend
functions of the base class.
When deriving a class from a base class, the base class may be inherited through public
, protected
or private
inheritance. The type of inheritance is specified by the access-specifier as explained above.
We hardly use protected or private inheritance, but public
inheritance is commonly used. While using different type of inheritance, following rules are applied −
Public Inheritance : When deriving a class from a public
base class:
public
members of the base class become public
members of the derived class and
protected
members of the base class become protected
members of the derived class.
A base class’s private
members are never accessible directly from a derived class, but can be accessed through calls to the public and protected members of the base class.
Protected Inheritance: When deriving from a protected
base class, public and protected members of the base class become protected
members of the derived class.
Private Inheritance − When deriving from a private
base class, public and protected members of the base class become private
members of the derived class.
A C++ class can inherit members from more than one class and here is the extended syntax −
class derived-class: access baseA, access baseB ....
Where access is one of public, protected, or private
and would be given for every base class and they will be separated by comma as shown above. Let us try the following example −
#include <iostream>
using namespace std;
// Base class Shape
class Shape {
public:
void setWidth(int w) {
width = w;
}
void setHeight(int h) {
height = h;
}
protected:
int width;
int height;
};
// Base class PaintCost
class PaintCost {
public:
int getCost(int area) {
return area * 70;
}
};
// Derived class
class Rectangle: public Shape, public PaintCost {
public:
int getArea() {
return (width * height);
}
};
int main(void) {
Rectangle Rect;
int area;
Rect.setWidth(5);
Rect.setHeight(7);
area = Rect.getArea();
// Print the area of the object.
cout << "Total area: " << Rect.getArea() << endl;
// Print the total cost of painting
cout << "Total paint cost: $" << Rect.getCost(area) << endl;
return 0;
}
When the above code is compiled and executed, it produces the following result −
Total area: 35
Total paint cost: $2450
C++ allows you to specify more than one definition for a function name or an operator in the same scope, which is called function overloading
and operator overloading
respectively.
An overloaded declaration is a declaration that is declared with the same name as a previously declared declaration in the same scope, except that both declarations have different arguments and obviously different definition (implementation).
When you call an overloaded function or operator, the compiler determines the most appropriate definition to use, by comparing the argument types you have used to call the function or operator with the parameter types specified in the definitions. The process of selecting the most appropriate overloaded function or operator is called overload resolution
.
You can have multiple definitions for the same function name in the same scope. The definition of the function must differ from each other by the types and/or the number of arguments in the argument list.
You cannot
overload function declarations that differ only by return type
.
Following is the example where same function print() is being used to print different data types −
void print(int i) {
cout << "Printing int: " << i << endl;
}
void print(double f) {
cout << "Printing float: " << f << endl;
}
void print(char* c) {
cout << "Printing character: " << c << endl;
}
// Call print to print integer
print(5);
// Call print to print float
print(500.263);
// Call print to print character
print("Hello C++");
You can redefine or overload most of the built-in operators available in C++. Thus, a programmer can use operators with user-defined types as well.
Overloaded operators are functions with special names: the keyword "operator"
followed by the symbol for the operator being defined. Like any other function, an overloaded operator has a return type and a parameter list.
Box operator+(const Box&);
declares the addition operator that can be used to add
two Box objects and returns final Box object. Most overloaded operators may be defined as ordinary non-member functions or as class member functions. In case we define above function as non-member function of a class then we would have to pass two arguments for each operand as follows −
Box operator+(const Box&, const Box&);
Following is the example to show the concept of operator overloading using a member function. Here an object is passed as an argument whose properties will be accessed using this object, the object which will call this operator can be accessed using this operator as explained below −
class Box {
public:
double getVolume(void) {
return length * breadth * height;
}
void setLength( double len ) {
length = len;
}
void setBreadth( double bre ) {
breadth = bre;
}
void setHeight( double hei ) {
height = hei;
}
// Overload + operator to add two Box objects.
Box operator+(const Box& b) {
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
Then we can call it:
Box Box1; // Declare Box1 of type Box
Box Box2; // Declare Box2 of type Box
Box Box3; // Declare Box3 of type Box
...
// Add two object as follows:
Box3 = Box1 + Box2;
virtual
functionThe word polymorphism
means having many forms. Typically, polymorphism occurs when there is a hierarchy of classes and they are related by inheritance
.
C++ polymorphism means that a call to a member function will cause a different function to be executed depending on the type of object
that invokes the function.
static linkage
or early binding
Consider the following example (Note: it is an example to show a WRONG case) where a base class has been derived by other two classes -
include <iostream>
using namespace std;
class Shape {
protected:
int width, height;
public:
Shape( int a = 0, int b = 0){
width = a;
height = b;
}
int area() {
cout << "Parent class area :" <<endl;
return 0;
}
};
class Rectangle: public Shape {
public:
Rectangle( int a = 0, int b = 0):Shape(a, b) { }
int area () {
cout << "Rectangle class area :" <<endl;
return (width * height);
}
};
class Triangle: public Shape {
public:
Triangle( int a = 0, int b = 0):Shape(a, b) { }
int area () {
cout << "Triangle class area :" <<endl;
return (width * height / 2);
}
};
// Main function for the program
int main() {
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// store the address of Rectangle
shape = &rec;
// call rectangle area.
shape->area();
// store the address of Triangle
shape = &tri;
// call triangle area.
shape->area();
return 0;
}
When the above code is compiled and executed, it produces the following result −
Parent class area :
Parent class area :
The reason for the incorrect output is that the call of the function area() is being set once by the compiler as the version defined in the base class. This is called static resolution
of the function call, or static linkage
- the function call is fixed before the program is executed. This is also sometimes called early binding
because the area() function is set during the compilation
of the program.
But now, let’s make a slight modification in our program and precede the declaration of area() in the Shape class with the keyword virtual
so that it looks like this −
class Shape {
protected:
int width, height;
public:
Shape( int a = 0, int b = 0) {
width = a;
height = b;
}
virtual int area() {
cout << "Parent class area :" <<endl;
return 0;
}
};
After this slight modification, when the previous example code is compiled and executed, it produces the following result −
Rectangle class area
Triangle class area
This time, the compiler looks at the contents of the pointer instead of it’s type. Hence, since addresses of objects of tri
and rec
classes are stored in *shape
the respective area() function is called.
As you can see, each of the child classes has a separate implementation
for the function area().
This is how polymorphism is generally used. You have different classes with a function of the same name, and even the same parameters, but with different implementations.
dynamic linkage
or late binding
A virtual
function is a function in a base class that is declared using the keyword virtual
. Defining in a base class a virtual function, with another version in a derived class, signals to the compiler that we don’t want static linkage for this function.
What we do want is the selection of the function to be called at any given point in the program to be based on the kind of object for which it is called. This sort of operation is referred to as dynamic linkage
, or late binding
.
It is possible that you want to include a virtual function in a base class so that it may be redefined in a derived class to suit the objects of that class, but that there is no meaningful definition you could give for the function in the base class.
We can change the virtual function area() in the base class to the following −
class Shape {
protected:
int width, height;
public:
Shape(int a = 0, int b = 0) {
width = a;
height = b;
}
// pure virtual function
virtual int area() = 0;
};
The = 0
tells the compiler that the function has no body
and above virtual function will be called pure virtual function
.
Data abstraction refers to providing only essential information to the outside world and hiding their background details, i.e., to represent the needed information in program without presenting the details.
Data abstraction
is a programming (and design) technique that relies on the separation of interface
and implementation
.
Let’s take one real life example of a TV, which you can turn on and off, change the channel, adjust the volume, and add external components such as speakers, VCRs, and DVD players, BUT you do not know its internal details, that is, you do not know how it receives signals over the air or through a cable, how it translates them, and finally displays them on the screen.
Thus, we can say a television clearly separates its
internal implementation
from itsexternal interface
and you can play with its interfaces like the power button, channel changer, and volume control without having any knowledge of its internals.
In C++, classes
provides great level of data abstraction
. They provide sufficient public methods to the outside world to play with the functionality of the object and to manipulate object data (i.e., state
), without actually knowing how class has been implemented internally.
For example, your program can make a call to the sort()
function without knowing what algorithm
the function actually uses to sort the given values. In fact, the underlying implementation of the sorting functionality could change between releases of the library, and as long as the interface stays the same, your function call will still work.
In C++, we use classes
to define our own abstract data types (ADT)
. You can use the cout
object of class ostream
to stream data to standard output, but you don’t need to understand how cout
displays the text on the user’s screen. You need to only know the public interface
and the underlying implementation of ‘cout’
is free to change.
Any C++ program where you implement a class with public and private members is an example of data abstraction.
In C++, we use access labels to define the abstract interface to the class. A class may contain zero or more access labels −
Members defined with a public label
are accessible to all parts of the program. The data-abstraction view of a type is defined by its public members.
Members defined with a private label
are not accessible to code that uses the class. The private sections hide the implementation from code that uses the type.
Data abstraction provides two important advantages:
i)
Class internals are protected from inadvertent user-level errors, which might corrupt the state of the object.
ii)
The class implementation may evolve over time in response to changing requirements or bug reports without requiring change in user-level code.
By defining data members only in the private
section of the class, the class author is free to make changes in the data. If the implementation changes, only the class code needs to be examined to see what affect the change may have.
If data is public
, then any function that directly access the data members of the old representation might be broken.
Abstraction separates code into interface and implementation. So while designing your component, you must keep interface independent of the implementation so that if you change underlying implementation then interface would remain intact.
In this case whatever programs are using these interfaces, they would not be impacted and would just need a recompilation with the latest implementation.
All C++ programs are composed of the following two fundamental elements −
Program statements (code) − This is the part of a program that performs actions and they are called functions.
Program data − The data is the information of the program which gets affected by the program functions.
Encapsulation is an Object Oriented Programming
concept that binds together the data and functions that manipulate the data, and that keeps both safe from outside interference and misuse. Data encapsulation led to the important OOP concept of data hiding
.
Data encapsulation
is a mechanism of bundling the data, and the functions that use them and data abstraction
is a mechanism of exposing only the interfaces and hiding the implementation details from the user.
C++ supports the properties of encapsulation and data hiding through the creation of user-defined types, called classes
. We already have studied that a class can contain private
, protected
and public
members. By default, all items defined in a class are private. For example −
class Box {
public:
double getVolume(void) {
return length * breadth * height;
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
The variables length, breadth, and height are private. This means that they can be accessed only by other members of the Box class, and not by any other part of your program. This is one way encapsulation
is achieved.
To make parts of a class public
(i.e., accessible to other parts of your program), you must declare them after the public keyword
. All variables or functions defined after the public specifier are accessible by all other functions in your program.
Making one class a friend
of another exposes the implementation details and reduces encapsulation. The ideal is to keep as many of the details of each class hidden from all other classes as possible.
Designing Strategy: Most of us have learnt to make class members private by default unless we really need to expose them. That’s just good encapsulation. This is applied most frequently to data members, but it applies equally to all members, including virtual functions.
An interface describes the behavior or capabilities of a C++ class without committing to a particular implementation of that class.
The C++ interfaces are implemented using abstract classes
and these abstract classes should not be confused with data abstraction
which is a concept of keeping implementation details separate from associated data.
A class is made abstract by declaring at least one of its functions as pure virtual function
. A pure virtual function is specified by placing "= 0"
in its declaration as follows −
class Box {
public:
// pure virtual function
virtual double getVolume() = 0;
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
The purpose of an abstract class
(often referred to as an ABC
) is to provide an appropriate base class from which other classes can inherit. Abstract classes cannot be used to instantiate objects and serves only as an interface
. Attempting to instantiate an object of an abstract class causes a compilation error
.
Thus, if a subclass of an ABC needs to be instantiated
, it has to implement each of the virtual functions, which means that it supports the interface declared by the ABC. Failure to override a pure virtual function in a derived class, then attempting to instantiate objects of that class, is a compilation error.
Classes that can be used to instantiate objects are called concrete classes
.
An exception is a problem that arises during the execution of a program. A C++ exception is a response to an exceptional circumstance that arises while a program is running, such as an attempt to divide by zero.
Exceptions provide a way to transfer control from one part of a program to another. C++ exception handling is built upon three keywords: try
, catch
, and throw
.
throw − A program throws an exception when a problem shows up. This is done using a throw
keyword.
catch − A program catches an exception with an exception handler at the place in a program where you want to handle the problem. The catch
keyword indicates the catching of an exception.
try − A try block identifies a block of code for which particular exceptions will be activated. It’s followed by one or more catch
blocks.
try/catch
blockAssuming a block will raise an exception, a method catches an exception using a combination of the try
and catch
keywords. A try/catch
block is placed around the code that might generate an exception. Code within a try/catch
block is referred to as protected code, and the syntax for using try/catch
as follows −
try {
// protected code
} catch( ExceptionName e1 ) {
// catch block
} catch( ExceptionName e2 ) {
// catch block
} catch( ExceptionName eN ) {
// catch block
}
You can list down multiple catch statements to catch different type of exceptions in case your try block raises more than one exception in different situations.
#include <iostream>
#include <vector>
int main() {
try {
std::cout << "Throwing an integer exception...\n";
throw 42;
} catch (int i) {
std::cout << " the integer exception was caught, with value: " << i << '\n';
}
try {
std::cout << "Creating a vector of size 5... \n";
std::vector<int> v(5);
std::cout << "Accessing the 11th element of the vector...\n";
std::cout << v.at(10); // vector::at() throws std::out_of_range
} catch (const std::exception& e) { // caught by reference to base
std::cout << " a standard exception was caught, with message '"
<< e.what() << "'\n";
}
}
Possible output:
You can define your own exceptions by inheriting
and overriding
exception class functionality. Following is the example, which shows how you can use std::exception
class to implement your own exception in standard way −
#include <iostream>
#include <exception>
using namespace std;
class MyException : public exception {
public:
const char * what () const throw () {
return "C++ Exception";
}
};
int main() {
try {
throw MyException();
} catch(MyException& e) {
std::cout << "MyException caught" << std::endl;
std::cout << e.what() << std::endl;
} catch(std::exception& e) {
//Other errors
}
}
This would produce the following result −
MyException caught
C++ Exception
Here, what() is a public method provided by exception class and it has been overridden by all the child exception classes. This returns the cause of an exception.
A good understanding of how dynamic memory really works in C++ is essential to becoming a good C++ programmer. Memory in your C++ program is divided into two parts −
The stack − All variables declared inside the function will take up memory from the stack.
The heap − This is unused memory of the program and can be used to allocate the memory dynamically when program runs.
Many times, you are not aware in advance how much memory you will need to store particular information in a defined variable and the size of required memory can be determined at run time.
You can allocate memory at run time within the heap
for the variable of a given type using a special operator in C++ which returns the address of the space allocated. This operator is called new
operator.
If you are not in need of dynamically allocated memory anymore, you can use delete
operator, which de-allocates memory that was previously allocated by new
operator.
new
and delete
OperatorsThere is following generic syntax to use new operator to allocate memory dynamically for any data-type.
new data-type;
Here, data-type could be any built-in data type including an array or any user defined data types include class or structure. Let us start with built-in data types. For example we can define a pointer to type double and then request that the memory be allocated at execution time. We can do this using the new operator with the following statements −
double* pvalue = NULL; // Pointer initialized with null
pvalue = new double; // Request memory for the variable
The memory may not have been allocated successfully, if the free store had been used up. So it is good practice to check if new operator is returning NULL pointer and take appropriate action as below −
double* pvalue = NULL;
if( !(pvalue = new double )) {
cout << "Error: out of memory." <<endl;
exit(1);
}
The malloc()
function from C
, still exists in C++
, but it is recommended to avoid using malloc()
function. The main advantage of new
over malloc()
is that new
doesn’t just allocate memory, it constructs objects which is prime purpose of C++.
At any point, when you feel a variable that has been dynamically allocated is not anymore required, you can free up
the memory that it occupies in the free store with the ‘delete’
operator as follows −
delete pvalue; // Release memory pointed to by pvalue
Consider you want to allocate memory for an array of characters, i.e., string of 20 characters. Using the same syntax what we have used above we can allocate memory dynamically as shown below.
char* pvalue = new char[20];
double * pVal = new double [30];
To remove the array that we have just created the statement would look like this −
delete [] pvalue; // Delete array pointed to by pvalue
delete [] pVal;
do 1D index and 2D index conversion via idx = i*rwo_num + j
#include <iostream>
// M x N matrix
#define M 4
#define N 5
// Dynamically Allocate Memory for 2D Array in C++
int main(){
// dynamically allocate memory of size M*N
int * A = new int[M * N];
// assign values to allocated memory
for (int i = 0; i < M; i++)
for (int j = 0; j < N; j++)
*(A + i*N + j) = rand() % 100;
// print the 2D array
for (int i = 0; i < M; i++){
for (int j = 0; j < N; j++)
std::cout << *(A + i*N + j) << " "; // or (A + i*N)[j])
std::cout << std::endl;
}
// deallocate memory
delete[] A;
return 0;
}
see: https://stackoverflow.com/questions/936687/how-do-i-declare-a-2d-array-in-c-using-new
Please note you CANNOT do this:
int ** A = new int [ROW_NUM][COL_NUM]; // Wrong!!!
You will get the following error:
Instead, you should do:
int ** ary = new int*[ROW_NUM];
for(int i = 0; i < ROW_NUM; ++i) {
ary[i] = new int[COL_NUM];
}
The above, for ROW_NUM= 4 and COL_NUM = 5
, would produce the following:
and then clean up would be:
for(int i = 0; i < ROW_NUM; ++i) {
delete [] ary[i];
}
delete [] ary;
An alternative approach would be to use one large block of memory:
int *ary = new int[ROW_NUM*COL_NUM];
// ary[i][j] is then rewritten as
ary[i*ROW_NUM + j]
Objects are no different from simple data types. For example, consider the following code where we are going to use an array of objects to clarify the concept −
#include <iostream>
using namespace std;
class Box {
public:
Box() {
cout << "Constructor called!" <<endl;
}
~Box() {
cout << "Destructor called!" <<endl;
}
};
int main() {
Box* myBoxArray = new Box[4];
delete [] myBoxArray; // Delete array
return 0;
}
If you were to allocate an array of four Box objects, the Simple constructor
would be called four times and similarly while deleting these objects, destructor
will also be called same number of times.
If we compile and run above code, this would produce the following result:
Templates
are the foundation of generic programming, which involves writing code in a way that is independent of any particular type.
A template
is a blueprint or formula for creating a generic class or a function. The library containers
like iterators and algorithms are examples of generic programming and have been developed using template concept.
There is a single definition of each container, such as vector
, but we can define many different kinds of vectors for example, vector <int>
or vector <string>
.
You can use templates to define functions as well as classes, let us see how they work −
The general form of a template function definition is shown here −
template <class type> ret-type func-name(parameter list) {
// body of function
}
Here, type
is a placeholder name for a data type used by the function. This name can be used within the function definition.
The following is the example of a function template that returns the maximum of two values −
template <typename T>
inline T const& Max (T const& a, T const& b) {
return a < b ? b:a;
}
int i = 39;
int j = 20;
cout << "Max(i, j): " << Max(i, j) << endl;
double f1 = 13.5;
double f2 = 20.7;
cout << "Max(f1, f2): " << Max(f1, f2) << endl;
Just as we can define function templates, we can also define class templates. The general form of a generic class declaration is shown here −
template <class type> class class-name {
.
.
.
}
Here, type
is the placeholder type name, which will be specified
when a class is instantiated. You can define more than one generic data type by using a comma-separated list.
Following is the example to define class Stack<>
and implement generic methods to push
and pop
the elements from the stack −
#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>
using namespace std;
template <class T>
class Stack {
private:
vector<T> elems; // elements
public:
void push(T const&); // push element
void pop(); // pop element
T top() const; // return top element
bool empty() const { // return true if empty.
return elems.empty();
}
};
template <class T>
void Stack<T>::push (T const& elem) {
// append copy of passed element
elems.push_back(elem);
}
template <class T>
void Stack<T>::pop () {
if (elems.empty()) {
throw out_of_range("Stack<>::pop(): empty stack");
}
// remove last element
elems.pop_back();
}
template <class T>
T Stack<T>::top () const {
if (elems.empty()) {
throw out_of_range("Stack<>::top(): empty stack");
}
// return copy of last element
return elems.back();
}
int main() {
try {
Stack<int> intStack; // stack of ints
Stack<string> stringStack; // stack of strings
// manipulate int stack
intStack.push(7);
cout << intStack.top() <<endl;
// manipulate string stack
stringStack.push("hello");
cout << stringStack.top() << std::endl;
stringStack.pop();
stringStack.pop();
} catch (exception const& ex) {
cerr << "Exception: " << ex.what() <<endl;
return -1;
}
}
If we compile and run above code, this would produce the following result −
7
hello
Exception: Stack<>::pop(): empty stack
The preprocessors
are the directives, which give instructions to the compiler to preprocess the information before actual compilation starts.
All preprocessor directives begin with #
, and only white-space
characters may appear before a preprocessor
directive on a line.
Preprocessor directives are not C++ statements, so they do not end in a semicolon (;)
.
There are number of preprocessor directives supported by C++ like #include
, #define
, #if
, #else
, #line
, etc. Let us see important directives −
#define
Preprocessor#define PI 3.14159
#include <iostream>
using namespace std;
#define MIN(a,b) (((a)<(b)) ? a : b)
int main () {
int i, j;
i = 100;
j = 30;
cout <<"The minimum is " << MIN(i, j) << endl;
return 0;
}
#ifndef
#ifndef NULL
#define NULL 0
#endif
You can compile a program for debugging purpose.
#ifdef DEBUG
cerr <<"Variable x = " << x << endl;
#endif
You can use #if 0
statement to comment out a portion of the program as follows −
#if 0
code prevented from compiling
#endif
Let us see an example for all the above macros −
If we compile and run above code, this would produce the following result −
Single, Multilevel, Multiple, Hierarchical and Hybrid.
In single inheritance, a class is allowed to inherit from only one class. i.e. one sub class is inherited by one base class only.
A ==> B (the one we are talking about)
// sub class derived from two base classes
class Car: public Vehicle{
...
};
Multiple Inheritance is a feature of C++ where a class can inherit from more than one classes. i.e one sub class is inherited from more than one base classes.
(B, C) ==> A (the one we are talking about)
// sub class derived from two base classes
class Car: public Vehicle, public FourWheeler {
...
};
In this type of inheritance, a derived class is created from another derived class.
C –> B –> A (the one we are talking about)
In this type of inheritance, more than one sub class is inherited from a single base class. i.e. more than one derived class is created from a single base class.
B –> (A, C) (A and C are the ones we are focusing on)
Hybrid Inheritance is implemented by combining more than one type of inheritance.
For example: Combining Hierarchical inheritance and Multiple Inheritance.
Below image shows the combination of hierarchical and multiple inheritance:
Exceptions are the problems which arise at the time of execution of program. It is an event which is thrown at runtime. It protects the code and run the program even after throwing an exception. Exception handling is used to handle the exceptions. We can use try catch block to protect the code.
Catch block is used to catch all types of exception. The keyword “catch” is used to catch exceptions.
Here is an example of catching all the exceptions in C++ language,
#include <iostream>
using namespace std;
void func(int a) {
try {
if(a==0) throw 23.33;
if(a==1) throw 's';
} catch(...) {
cout << "Caught Exception!\n";
}
}
int main() {
func(0);
func(1);
return 0;
}
Output
Caught Exception!
Caught Exception!
A simple C++ main function:
main() 前面的 int 的作用是向操作系统返回一个零值。如果程序不能正常执行, 则会自动向操作系统返回一个非零值,一般为-1.
注意C++所有语句最后都应当有一个分号";"
.
#include
是C++的一个预处理命令, 它以 #
开头以, 与C++语句相区别, 行的末尾没有分号;
#include <iostream> //包含头文件iostream
using namespace std; //使用命名空间std
int main( ){
cout << "This is a C++ program.";
return 0;
}
类 (class) 是C++新增加的重要的数据类型, 是C++对C的最重要的发展。有了类, 就可以实现面向对象程序设计方法中的封装、信息隐蔽、继承、派生、多态
等功能。在一个类中可以包括数据成员和成员函数, 他们可以被指定为private和public属性。private数据成员和成员函数只能被本类的成员函数所调用。
Object oriented programming is a way of solving complex problems by breaking them into smaller problems using objects.
Before Object Oriented Programming (commonly referred as OOP), programs were written in procedural language, they were nothing but a long list of instructions.
On the other hand, the OOP is all about creating objects that can interact with each other, this makes it easier to develop programs in OOP as we can understand the relationship between them.
In Object Oriented Programming
we write programs using classes and objects utilizing features of OOPs such as abstraction
, encapsulation
, inheritance
and polymorphism
.
1) Abstraction
: is a process of hiding irrelevant details from user.
2) Encapsulation
: is a process of combining data and function into a single unit like capsule. This is to avoid the access of private data members from outside the class. To achieve encapsulation, we make all data members of class private and create public functions, using them we can get the values from these data members or set the value to these data members.
3) Inheritance
: is a feature using which an object of child class acquires the properties of parent class.
...
in C++ functionhttps://en.cppreference.com/w/cpp/language/variadic_arguments
https://en.cppreference.com/w/cpp/utility/variadic
In C++, we can write functions with three dots (…)
as parameter. This will make that function to support variable number of arguments. The va_arg
, va_end
, and va_start
macros (defined in cstdarg.h file) provide access to function arguments when the number of arguments is variable.
Here the three dots is called a trailing ...
;
A typical memory representation of C program consists of following sections.
Text segment
Initialized data segment
Uninitialized data segment
Stack
Heap
A text segment , also known as a code segment or simply as text, is one of the sections of a program in an object file or in memory, which contains executable instructions
.
As a memory region, a text segment may be placed below the heap or stack in order to prevent heaps and stack overflows from overwriting it.
Usually, the text segment is shareable so that only a single copy needs to be in memory for frequently executed programs, such as text editors
, the C compiler
, the shells
, and so on.
Also, the text segment is often read-only, to prevent a program from accidentally modifying its instructions.
Initialized data segment, usually called simply the Data Segment. A data segment is a portion of virtual address space of a program, which contains the global variables
and static variables
that are initialized by the programmer.
Note that, data segment is not read-only, since the values of the variables can be altered at run time.
This segment can be further classified into i) initialized read-only area
and ii) initialized read-write area
.
For instance the global string defined by char s[] = “hello world”
in C and a C statement like int debug=1
outside the main (i.e. global) would be stored in initialized read-write area. And a global C statement like const char* str1 = “hello world”
makes the string literal
(i.e., 字符串常量
) “hello world” to be stored in initialized read-only
area and the character pointer variable str1 in initialized read-write area.
E.g., static int i = 10;
will be stored in data segment and global int i = 10;
will also be stored in data segment.
Uninitialized data segment, often called the “bss”
segment, named after an ancient assembler operator that stood for “block started by symbol”.
Data in this segment is initialized by the kernel
to arithmetic 0
before the program starts executing.
uninitialized data starts at the end of the data segment and contains all global variables and static variables that are initialized to zero or do not have explicit initialization in source code.
For instance a variable declared static int i
; would be contained in the BSS segment.
For instance a global variable declared int j
; would be contained in the BSS segment.
The stack area traditionally adjoined the heap area and grew the opposite direction; when the stack pointer met the heap pointer, free memory was exhausted. (With modern large address spaces and virtual memory techniques they may be placed almost anywhere, but they still typically grow opposite directions.)
The stack area contains the program stack, a LIFO
structure, typically located in the higher parts of memory. On the standard PC x86
computer architecture it grows toward address zero; on some other architectures it grows the opposite direction. A “stack pointer”
register tracks the top of the stack; it is adjusted each time a value is “pushed” into the stack. The set of values pushed for one function call is termed a “stack frame”
; A stack frame consists at minimum of a return address.
Stack, where automatic variables are stored, along with information that is saved each time a function is called. Each time a function is called, the address of where to return to and certain information about the caller’s environment, such as some of the machine registers, are saved on the stack. The newly called function then allocates room on the stack for its automatic and temporary variables. This is how recursive functions in C can work
. Each time a recursive function calls itself, a new stack frame
is used, so one set of variables doesn’t interfere with
the variables from another instance of the function.
Heap is the segment where dynamic memory allocation usually takes place, which is different from the ADT Heap ( i.e., data structure in Algorithm).
The heap area begins at the end of the BSS segment and grows to larger addresses from there.The Heap area is managed by malloc
, realloc
, and free
(in C) and new
and delete
(in C++), which may use the brk
and sbrk
system calls to adjust its size (note that the use of brk/sbrk
and a single “heap area”
is not required to fulfill the contract of malloc/realloc/free
; they may also be implemented using mmap
to reserve potentially non-contiguous regions of virtual memory into the process’ virtual address space). The Heap area is shared by all shared libraries and dynamically loaded modules in a process.
The size(1)
command reports the sizes (in bytes) of the text, data, and bss segments. ( for more details please refer man page of size(1) )
run $ g++ 002_memory-layout.cpp -o 002_memory-layout && size 002_memory-layout
:
Now check the size of bss: bss = 16
; ( The tutorial shows the expected result is bss = 12
, maybe my compilation system treat int
as a 64-bit
integer. )
Now check the size of bss: bss = 16
Now check the size of bss: bss = 12
and data: data = 644
Now check the size of bss: bss = 8
and data: data = 648
Static variables have a property of preserving their value even after they are out of their scope! Hence, static variables preserve their previous value in their previous scope and are not initialized again in the new scope.
Syntax:
static data_type var_name = var_value;
E.g.,static int count = 0;
Following are some interesting facts about static variables in C.
A static int variable remains in memory while the program is running. A normal or auto variable is destroyed when a function call (where the variable was declared) is over.
For example, we can use static int
to count the number of times a function is called, but an auto variable
can’t be used for this purpose.
For example below program prints “1 2”
#include<stdio.h>
int fun_static() {
static int count = 0;
count++;
return count;
}
int fun() {
int count = 0;
count++;
return count;
}
int main() {
// prints “1 2”
printf("%d ", fun_static());
printf("%d ", fun_static());
// prints “1 1”
printf("%d ", fun());
printf("%d ", fun());
return 0;
}
Static variables are allocated memory in data segment
, not stack segment
.
Recall: Memory layout of C programs: A typical memory representation of C program consists of following sections.
Text segment
Initialized data segment
Uninitialized data segment
Stack
Heap
Static variables (like global variables) are initialized as 0 if not initialized explicitly.
In C, static variables can only be initialized using constant literals (But in C++ it works!!!). For example, following program fails in compilation. See this for more details.
If we change the program to following, then it works in C without any error.
The reason for this is simple: All objects with static storage duration must be initialized (set to their initial values) before execution of main() starts. So a value which is not known at translation time cannot be used for initialization of static variables.
However, in C++ it works:
Static global variables and functions are also possible in C/C++. The purpose of these is to limit scope of a variable or function to a file
.
In C, functions are global by default. The “static”
keyword before a function name makes it static. For example, below function fun() is static.
static int fun(void)
{
printf("I am a static function ");
}
Unlike global functions in C, access to static functions is restricted to the file where they are declared.
Therefore, when we want to restrict access to functions, we make them static.
Another reason for making functions static can be reuse of the same function name in other files. For example, if we store following program in one file file1.c:
/* Inside file1.c */
static void fun1(void)
{
puts("fun1 called");
}
And store following program in another file file2.c:
/* Inside file2.c */
int main(void)
{
fun1();
getchar();
return 0;
}
Now, if we compile the above code with command “gcc file2.c file1.c”
, we get the error “undefined reference to fun1”
. This is because fun1() is declared static in file1.c and cannot be used in file2.c.
The reason is C compiler requires the entire structure elements to be placed together (i.e.) memory allocation for structure members should be contiguous. It is possible to declare structure inside the function (stack segment) or allocate memory dynamically (heap segment) or it can be even global (BSS or data segment). Whatever might be the case, all structure members should reside in the same memory segment because the value for the structure element is fetched by counting the offset
of the element from the beginning address of the structure. Separating out one member alone to data segment defeats the purpose of static variable and it is possible to have an entire structure as static.
Static keyword has different meanings when used with different types. We can use static keyword with:
Static Variables : Variables in a function, Variables in a class
Static Members of Class : Class objects and Functions in a class
Static variables in a Function:
When a variable is declared as static, space for it gets allocated for the lifetime of the program. Even if the function is called multiple times, space for the static variable is allocated only once and the value of variable in the previous call gets carried through the next function call.
Static variables in a class:
As the variables declared as static are initialized only once as they are allocated space in separate static storage so, the static variables in a class are shared by the objects. There can not be multiple copies of same static variables for different objects.
Also because of this reason static variables can not be initialized using constructors. A static variable inside a class should be initialized explicitly by the user using the class name and scope resolution operator (::
) outside the class.
Just like variables, objects also when declared as static have a scope till the lifetime of program.
// CPP program to illustrate
// class objects as static
#include<iostream>
using namespace std;
class GfG {
int i = 0;
public:
GfG(){
i = 0;
cout << "Inside Constructor\n";
}
~GfG(){
cout << "Inside Destructor\n";
}
};
int main(){
int x = 0;
if (x==0){
static GfG obj;
}
cout << "End of main\n";
}
Output:
Inside Constructor
End of main
Inside Destructor
You can see the destructor is invoked after the end of main. This happened because the scope of static object is through out the life time of the program.
Just like the static data members or static variables inside the class, static member functions also does not depend on object of class. We are allowed to invoke a static member function using the object and the dot ("."
) operator but it is recommended to invoke the static members using the class name and the scope resolution operator (::
).
Static member functions are allowed to access only the static data members or other static member functions, they can not access the non-static data members or member functions of the class.
// C++ program to demonstrate static
// member function in a class
#include<iostream>
using namespace std;
class GfG {
public:
// static member function
static void printMsg(){
cout<<"Welcome to GfG!";
}
};
// main function
int main(){
// invoking a static member function
GfG::printMsg();
}
Output:
Welcome to GfG!
Interesting facts about Static Members Functions in C++:
static member functions do not have this pointer.
A static member function CANNOT be virtual
Member function declarations with the same name and the name parameter-type-list cannot be overloaded if any of them is a static member function declaration.
static member function can not be declared const, volatile, or const volatile.
Examples 1: In C++, a static member function of a class cannot be virtual. For example, below program gives compilation error.
#include<iostream>
using namespace std;
class Test {
public:
// Error: Virtual member functions cannot be static
virtual static void fun() { }
};
Examples 2: Also, static member function cannot be const and volatile. Following code also fails in compilation.
#include<iostream>
using namespace std;
class Test {
public:
// Error: Static member function cannot be const
static void fun() const { }
};
To make a variable constant, simply put the const keyword either before or after the variable type, like so:
In the case of const fundamental data types, initialization can be done through copy, direct, or uniform initialization
:
const int value1 = 5; // copy initialization
const int value2(7); // direct initialization
const int value3 { 9 }; // uniform initialization (C++11)
Const is often used with function parameters:
void printInteger(const int myValue){
std::cout << myValue;
}
Making a function parameter const does two things.
First, it tells the person calling the function that the function will not change the value of myValue
.
Second, it ensures that the function doesn’t change the value of myValue
.
When arguments are passed by value, we generally don’t care if the function changes the value of the parameter (since it’s just a copy that will be destroyed at the end of the function anyway). For this reason, we usually don’t const
parameters passed by value
. But later on, we’ll talk about other kinds of function parameters (where changing the value of the parameter will change the value of the argument passed in). For these types of parameters, judicious use of const
is important.
C++ actually has two different kinds of constants.
const double gravity { 9.8 };
std::cout << "Enter your age: ";
int age{};
std::cin >> age;
const int usersAge { age }; // usersAge can not be changed
Runtime constants
- are those whose initialization values can only be resolved at runtime (when your program is running). Variables such as usersAge
in the snippets above are runtime constants, because the compiler can’t determine their initial values at compile time. usersAge
relies on user input (which can only be given at runtime). However, once initialized, the value of these constants can’t be changed.
Compile-time constants
- are those whose initialization values can be resolved at compile-time (when your program is compiling). Variable gravity
above is an example of a compile-time constant. Compile-time constants enable the compiler to perform optimizations that aren’t available with runtime constants. For example, whenever gravity is used, the compiler can simply substitute the identifier gravity with the literal double 9.8
.
When you declare a const variable, the compiler will implicitly keep track of whether it’s a runtime or compile-time constant.
To help provide more specificity, C++11 introduced the keyword constexpr
, which ensures that a constant must be a compile-time
constant:
constexpr double gravity { 9.8 }; // ok, the value of 9.8 can be resolved at compile-time
constexpr int sum { 4 + 5 }; // ok, the value of 4 + 5 can be resolved at compile-time
std::cout << "Enter your age: ";
int age{};
std::cin >> age;
constexpr int myAge { age }; // NOT okay, age can not be resolved at compile-time
constexpr
variables are const.
A symbolic constant
is a name given to a constant literal value
. There are two ways to declare symbolic constants in C++. One of them is good, and one of them is not. We’ll show you both.
Bad: Using object-like macros with a substitution parameter as symbolic constants, e..g, #define MAX_STUDENTS_PER_CLASS 30
A better solution: Use constexpr
variables: Because these are just normal variables, they are watchable
in the debugger, have normal scoping, and avoid other weird behaviors.
In many applications, a given symbolic constant needs to be used throughout your code (not just in one location). Instead of redefining these every time they are needed, it’s better to declare them once in a central location
and use them wherever needed
. That way, if you ever need to change them, you only need to change them in one place.
There are multiple ways to facilitate this within C++, but the following is probably easiest:
1) Create a header file to hold these constants;
2) Inside this header file, declare a namespace;
3) Add all your constants inside the namespace (make sure they’re constexpr
in C++11/14, or inline constexpr
in C++17 or newer);
4) #include
the header file wherever you need it;
For example:
constants.h (C++11/14):
#ifndef CONSTANTS_H
#define CONSTANTS_H
// define your own namespace to hold constants
namespace constants {
constexpr double pi { 3.14159 };
constexpr double avogadro { 6.0221413e23 };
constexpr double my_gravity { 9.2 }; // m/s^2 -- gravity is light on this planet
// ... other related constants
}
#endif
constants.h (C++17 or newer): In C++17, prefer “inline constexpr” instead:
#ifndef CONSTANTS_H
#define CONSTANTS_H
// define your own namespace to hold constants
namespace constants{
inline constexpr double pi { 3.14159 }; // inline constexpr is C++17 or newer only
inline constexpr double avogadro { 6.0221413e23 };
inline constexpr double my_gravity { 9.2 }; // m/s^2 -- gravity is light on this planet
// ... other related constants
}
#endif
Use the scope resolution operator (::)
to access your constants in .cpp
files: e.g., in main.cpp:
#include "constants.h"
#include <iostream>
int main(){
std::cout << "Enter a radius: ";
int radius{};
std::cin >> radius;
double circumference { 2.0 * radius * constants::pi };
std::cout << "The circumference is: " << circumference << '\n';
return 0;
}
Instantiated class objects can also be made const
by using the const
keyword. Initialization is done via class constructors:
const Date date1; // initialize using default constructor
const Date date2(2020, 10, 16); // initialize using parameterized constructor
const Date date3 { 2020, 10, 16 }; // initialize using parameterized constructor (C++11)
Once a const class object has been initialized via constructor, any attempt to modify the member variables of the object is disallowed, as it would violate the constness
of the object. This includes both changing member variables directly (if they are public), or calling member functions that set the value of member variables. Consider the following class:
class Something
{
public:
int m_value;
Something(): m_value{0} { }
void setValue(int value) { m_value = value; }
int getValue() { return m_value ; }
};
int main()
{
const Something something{}; // calls default constructor
something.m_value = 5; // compiler error: violates const
something.setValue(5); // compiler error: violates const
return 0;
}
Both of the above lines involving variable something are illegal because they violate the constness
of something by either attempting to change a member variable directly, or by calling a member function that attempts to change a member variable.
Just like with normal variables, you’ll generally want to make your class objects const when you need to ensure they aren’t modified after creation.
Now, consider the following line of code:
std::cout << something.getValue();
Perhaps surprisingly, this will also cause a compile error, even though getValue() doesn’t do anything to change a member variable! It turns out that const
class objects can only explicitly call const
member functions, and getValue() has not been marked as a const member function
.
A const member function
is a member function that guarantees it will not modify the object or call any non-const member functions (as they may somehow modify the object).
To make getValue() a const member function, we simply append the const keyword to the function prototype, after
the parameter list, but before the function body:
class Something {
public:
int m_value;
Something(): m_value{0} { }
void resetValue() { m_value = 0; }
void setValue(int value) { m_value = value; }
int getValue() const { return m_value; } // note addition of const keyword after parameter list, but before function body
};
Now getValue()
has been made a const member function, which means we can call it on any const objects
.
For member functions defined outside of the class definition, the const
keyword must be used on both the function prototype in the class definition and on the function definition:
class Something
{
public:
int m_value;
Something(): m_value{0} { }
void resetValue() { m_value = 0; }
void setValue(int value) { m_value = value; }
int getValue() const; // note addition of const keyword here
};
int Something::getValue() const // and here
{
return m_value;
}
Furthermore, any const member function that attempts to change a member variable or call a non-const member function will cause a compiler error to occur. For example:
class Something
{
public:
int m_value ;
void resetValue() const { m_value = 0; } // compile error, const functions can't change member variables.
};
In this example, resetValue()
has been marked as a const member function, but it attempts to change m_value
. This will cause a compiler error.
Note that constructors
cannot be marked as const. This is because constructors need to be able to initialize their member variables, and a const constructor would definitely not be able to do so. Consequently, the language disallows const constructors.
Although instantiating const class objects is one way to create const objects, a more common way is by passing an object
to a function by const reference
.
In the lesson on passing arguments by reference, we covered the merits of passing class arguments by const reference instead of by value.
To recap, passing a class argument by value causes a copy of the class to be made (which is slow, and will call the class constructor) – most of the time, we don’t need a copy, a reference to the original argument works just fine, and is more performant because it avoids the needless copy. We typically make the reference const
in order to ensure the function does not inadvertently change
the argument, and to allow the function to work with R-values (e.g. literals)
, which can be passed as const references, but not non-const references.
Can you figure out what’s wrong with the following code?
#include <iostream>
class Date
{
private:
int m_year;
int m_month;
int m_day;
public:
Date(int year, int month, int day)
{
setDate(year, month, day);
}
void setDate(int year, int month, int day)
{
m_year = year;
m_month = month;
m_day = day;
}
int getYear() { return m_year; }
int getMonth() { return m_month; }
int getDay() { return m_day; }
};
// note: We're passing date by const reference here to avoid making a copy of date
void printDate(const Date &date)
{
std::cout << date.getYear() << '/' << date.getMonth() << '/' << date.getDay() << '\n';
}
int main()
{
Date date{2016, 10, 16};
printDate(date);
return 0;
}
The answer is that inside of the printDate
function, date is treated as a const object. And with that const date, we’re calling functions getYear(), getMonth(), and getDay(), which are all non-const. Since we can’t call non-const member functions on const objects, this will cause a compile error.
The fix is simple: make getYear(), getMonth(), and getDay() const:
class Date
{
private:
int m_year;
int m_month;
int m_day;
public:
Date(int year, int month, int day)
{
setDate(year, month, day);
}
// setDate() cannot be const, modifies member variables
void setDate(int year, int month, int day)
{
m_year = year;
m_month = month;
m_day = day;
}
// The following getters can all be made const
int getYear() const { return m_year; }
int getMonth() const { return m_month; }
int getDay() const { return m_day; }
};
Now in function printDate(), const object date
will be able to successfully call getYear(), getMonth(), and getDay().
Finally, although it is not done very often, it is possible to overload a function in such a way to have a const and non-const version of the same function:
#include <string>
class Something
{
private:
std::string m_value;
public:
Something(const std::string &value=""): m_value{ value } {}
const std::string& getValue() const { return m_value; } // getValue() for const objects
std::string& getValue() { return m_value; } // getValue() for non-const objects
};
The const version of the function will be called on any const objects, and the non-const version will be called on any non-const objects:
int main()
{
Something something{};
something.getValue() = "Hi"; // calls non-const getValue();
const Something something2{};
something2.getValue(); // calls const getValue();
return 0;
}
Overloading a function with a const and non-const version is typically done when the return value needs to differ in constness. In the example above, the non-const version of getValue() will only work with non-const objects, but is more flexible in that we can use it to both read and write m_value
(which we do by assigning the string “Hi”).
The const version of getValue() will work with either const or non-const objects, but returns a const reference, to ensure we can’t modify the const object’s data.
This works because the const-ness of the function is considered part of the function’s signature, so a const and non-const function which differ only in const-ness are considered distinct
.
Summary:
Because passing objects by const reference is common, your classes should be const-friendly
. That means making any member function that does not modify the state of the class object const!
Syntax: e.g.,
Point(int i = 0, int j = 0):x(i), y(j) {}
andPoint(int i = 0, int j = 0):x{i}, y{j} {} // C++11
Initializer List is used in initializing the data members of a class. The list of members to be initialized is indicated with constructor as a comma-separated list followed by a colon. Following is an example that uses the initializer list to initialize x and y of Point class.
#include<iostream>
using namespace std;
class Point {
private:
int x;
int y;
public:
Point(int i = 0, int j = 0):x(i), y(j) {}
/* The above use of Initializer list is optional as the
constructor can also be written as:
Point(int i = 0, int j = 0) {
x = i;
y = j;
}
*/
int getX() const {return x;}
int getY() const {return y;}
};
int main() {
Point t1(10, 15);
cout<<"x = "<<t1.getX()<<", ";
cout<<"y = "<<t1.getY();
return 0;
}
/* OUTPUT:
x = 10, y = 15
*/
The above code is just an example for syntax of the Initializer list. In the above code, x and y can also be easily initialized inside the constructor.
However
, there are situations where initialization of data members inside constructor doesn’t work and Initializer List
MUST be used. Following are such cases:
1) For initialization of non-static const data members:
2) For initialization of reference members:
3) For initialization of member objects which do not have default constructor:
4) For initialization of base class members : Like point 3, the parameterized constructor of the base class can only be called using Initializer List.
5) When constructor’s parameter name is same as data member.
6) For Performance reasons.
Let’s talk about the details for each of them.
const data members must be initialized using Initializer List
. In the following example, “t” is a const data member of Test class and is initialized using Initializer List
.
Reason for initializing the const data member in initializer list is because no memory is allocated separately for const data member, it is folded in the symbol table due to which we need to initialize it in the initializer list.
Also, it is a copy constructor and we don’t need to call the assignment operator which means we are avoiding one extra operation.
#include<iostream>
using namespace std;
class Test {
const int t;
public:
Test(int tt):t(tt) {} //Initializer list must be used
int getT() { return t; }
};
int main() {
Test t1(10);
cout<<t1.getT();
return 0;
}
/* OUTPUT:
10
*/
Reference members must be initialized using Initializer List. In the following example, “t” is a reference member of Test class and is initialized using Initializer List.
// Initialization of reference data members
#include<iostream>
using namespace std;
class Test {
int &t;
public:
Test(int &tt):t(tt) {} //Initializer list must be used
int getT() { return t; }
};
int main() {
int x = 20;
Test t1(x);
cout<<t1.getT()<<endl;
x = 30;
cout<<t1.getT()<<endl;
return 0;
}
/* OUTPUT:
20
30
*/
In the following example, an object “a” of class “A” is data member of class “B”, and “A” doesn’t have default constructor. Initializer List must be used to initialize “a”.
#include <iostream>
using namespace std;
class A {
int i;
public:
A(int );
};
A::A(int arg) {
i = arg;
cout << "A's Constructor called: Value of i: " << i << endl;
}
// Class B contains object of A
class B {
A a;
public:
B(int );
};
B::B(int x):a(x) { //Initializer list must be used
cout << "B's Constructor called";
}
int main() {
B obj(10);
return 0;
}
/* OUTPUT:
A's Constructor called: Value of i: 10
B's Constructor called
*/
If class A had both default and parameterized constructors, then Initializer List is not must if we want to initialize “a” using default constructor, but it is must to initialize “a” using parameterized constructor.
Like point 3, the parameterized constructor of the base class can only be called using Initializer List.
Recall: the default inheritance model in C++ is private inheritance for classes and public for structs.
#include <iostream>
using namespace std;
class A {
int i;
public:
A(int );
};
A::A(int arg) {
i = arg;
cout << "A's Constructor called: Value of i: " << i << endl;
}
// Class B is derived from A
// The default inheritance model in C++ is private inheritance for classes and public for structs.
class B: A {
public:
B(int );
};
B::B(int x):A(x) { //Initializer list must be used
cout << "B's Constructor called";
}
int main() {
B obj(10);
return 0;
}
If constructor’s parameter name is same as data member name then the data member must be initialized either using this
pointer or Initializer List
. In the following example, both member name and parameter name for A() is “i”.
#include <iostream>
using namespace std;
class A {
int i;
public:
A(int );
int getI() const { return i; }
};
A::A(int i):i(i) { } // Either Initializer list or this pointer must be used
/* The above constructor can also be written as
A::A(int i) {
this->i = i;
}
*/
int main() {
A a(10);
cout<<a.getI();
return 0;
}
/* OUTPUT:
10
*/
It is better to initialize all class variables in Initializer List
instead of assigning values inside body. Consider the following example:
// Without Initializer List
class MyClass {
Type variable;
public:
MyClass(Type a) { // Assume that Type is an already
// declared class and it has appropriate
// constructors and operators
variable = a;
}
};
Here compiler follows following steps to create an object of type MyClass
1) Type’s constructor is called first for “a”.
2) The assignment operator of “Type” is called inside body of MyClass() constructor to assign variable = a;
.
3) And then finally destructor
of “Type” is called for “a” since it goes out of scope.
Now consider the same code with MyClass() constructor with Initializer List
// With Initializer List
class MyClass {
Type variable;
public:
MyClass(Type a):variable(a) { // Assume that Type is an already
// declared class and it has appropriate
// constructors and operators
}
};
With the Initializer List, the following steps are followed by compiler:
1) Copy constructor of “Type” class is called to initialize: variable(a). The arguments in the initializer list are used to copy construct “variable” directly.
2) The destructor
of “Type” is called for “a” since it goes out of scope.
As we can see from this example if we use assignment
inside constructor body there are three function calls: constructor + destructor + one addition assignment operator call
.
And if we use Initializer List there are only two function calls: copy constructor + destructor call
.
While pass by value is suitable in many cases, it has a couple of limitations.
First, when passing a large struct or class to a function, pass by value will make a copy of the argument into the function parameter. In many cases, this is a needless performance hit, as the original argument would have sufficed.
Second, when passing arguments by value, the only way to return a value back to the caller is via the function’s return value. While this is often suitable, there are cases where it would be more clear and efficient to have the function modify the argument passed in. Pass by reference solves both of these issues.
To pass a variable by reference, we simply declare the function parameters as references rather than as normal variables:
void addOne(int &ref) // ref is a reference variable
{
ref = ref + 1;
}
When the function is called, ref
will become a reference to the argument. Since a reference to a variable is treated exactly the same as the variable itself, any changes made to the reference are passed through to the argument!
The following example shows this in action:
void addOne(int &ref)
{
ref = ref + 1;
}
int main()
{
int value = 5;
cout << "value = " << value << '\n';
addOne(value);
cout << "value = " << value << '\n';
return 0;
}
This program is the same as the one we used for the pass by value example, except foo’s parameter is now a reference instead of a normal variable. When we call addOne(value), ref becomes a reference to main’s value variable. This snippet produces the output:
value = 5
value = 6
As you can see, the function changed the value of the argument from 5 to 6!
Sometimes we need a function to return multiple values. However, functions can only have one return value. One way to return multiple values is using reference parameters
:
#include <iostream>
#include <cmath> // for std::sin() and std::cos()
void getSinCos(double degrees, double &sinOut, double &cosOut)
{
// sin() and cos() take radians, not degrees, so we need to convert
static constexpr double pi { 3.14159265358979323846 }; // the value of pi
double radians = degrees * pi / 180.0;
sinOut = std::sin(radians);
cosOut = std::cos(radians);
}
int main()
{
double sin(0.0);// C++ 11 <==> double sin = 0.0
double cos(0.0);
// getSinCos will return the sin and cos in variables sin and cos
getSinCos(30.0, sin, cos);
std::cout << "The sin is " << sin << '\n';
std::cout << "The cos is " << cos << '\n';
return 0;
}
This function takes one parameter (by value) as input, and “returns” two parameters (by reference) as output. Parameters that are only used for returning values back to the caller are called out parameters. We’ve named these out
parameters with the suffix “out”
to denote that they’re out parameters. This helps remind the caller that the initial value passed to these parameters doesn’t matter, and that we should expect them to be rewritten. By convention, output parameters are typically the rightmost parameters.
This method, while functional, has a few minor downsides. First, the caller must pass in arguments to hold the updated outputs even if it doesn’t intend to use them. More importantly, the syntax is a bit unnatural, with both the input and output parameters being put together in the function call. It’s not obvious from the caller’s end that sin and cos are out parameters and will be changed. This is probably the most dangerous part of this method (as it can lead to mistakes being made). Some programmers and companies feel this is a big enough problem to advise avoiding output parameters altogether, or using pass by address for out parameters instead (which has a clearer syntax indicating whether a parameter is modifiable or not).
Personally, we recommend avoiding out parameters altogether if possible. If you do use them, naming out parameters (and output arguments) with an “out” suffix (or prefix) can help make it clear that the value might be modified.
Non-const references can only reference non-const l-values
(e.g. non-const
variables), so a reference parameter cannot accept an argument that is a const l-value or an r-value (e.g. literals and the results of expressions).
As mentioned in the introduction, one of the major disadvantages of pass by value
is that all arguments passed by value are copied into the function parameters. When the arguments are large structs or classes, this can take a lot of time. References provide a way to avoid this penalty
. When an argument is passed by reference, a reference is created to the actual argument (which takes minimal time) and no copying of values takes place. This allows us to pass large structs and classes with a minimum performance penalty.
However, this also opens us up to potential trouble. References
allow the function to change the value of the argument, which is undesirable when we want an argument be read-only
. If we know that a function should not change the value of an argument, but don’t want to pass by value, the best solution is to pass by const reference
.
You already know that a const
reference is a reference that does not allow the variable being referenced to be changed through the reference. Consequently, if we use a const reference
as a parameter, we guarantee to the caller that the function will not change the argument!
The following function will produce a compiler error:
void foo(const std::string &x){ // x is a const reference
x = "hello";//compile error: a const reference cannot have its value changed!
}
Using const is useful for several reasons:
It enlists the compilers help in ensuring values that shouldn’t be changed aren’t changed (the compiler will throw an error if you try, like in the above example).
It tells the programmer that the function won’t change the value of the argument. This can help with debugging.
You can’t pass a const argument to a non-const reference parameter. Using const parameters ensures you can pass both non-const and const arguments to the function.
Const references can accept any type of argument, including non-const l-values, const l-values, and r-values.
A reminder: Non-const references cannot bind to r-values. A function with a non-const reference parameter cannot be called with literals or temporaries.
#include <string>
void foo(std::string& text) {}
int main(){
std::string text{ "hello" };
foo(text); // ok
foo(text + " world"); // illegal, non-const references can't bind to r-values.
return 0;
}
It’s possible to pass a pointer by reference, and have the function change the address of the pointer entirely:
#include <iostream>
void foo(int *&ptr) // pass pointer by reference
{
ptr = nullptr; // this changes the actual ptr argument passed in, not a copy
}
int main()
{
int x = 5;
int *ptr = &x;
std::cout << "ptr is: " << (ptr ? "non-null" : "null") << '\n'; // prints non-null
foo(ptr);
std::cout << "ptr is: " << (ptr ? "non-null" : "null") << '\n'; // prints null
return 0;
}
As a reminder, you can pass a C-style array by reference. This is useful if you need the ability for the function to change the array (e.g. for a sort
function) or you need access to the array’s type information of a fixed array (to do sizeof()
or a for-each
loop). However, note that in order for this to work, you explicitly need to define the array size in the parameter:
#include <iostream>
// Note: You need to specify the array size in the function declaration
void printElements(int (&arr)[4])
{
int length{ sizeof(arr) / sizeof(arr[0]) }; // we can now do this since the array won't decay
for (int i{ 0 }; i < length; ++i)
{
std::cout << arr[i] << '\n';
}
}
int main()
{
int arr[]{ 99, 20, 14, 80 };
printElements(arr);
return 0;
}
This means this only works with fixed arrays of one particular length. If you want this to work with fixed arrays of any length, you can make the array length a template parameter.
Advantages of passing by reference:
References allow a function to change the value of the argument, which is sometimes useful. Otherwise, const references
can be used to guarantee the function won’t change the argument.
Because a copy of the argument is not made, pass by reference is fast
, even when used with large structs or classes.
References can be used to return multiple values from a function (via out parameters).
References must be initialized, so there’s no worry about null values.
Disadvantages of passing by reference:
Because a non-const reference cannot be initialized with a const l-value or an r-value (e.g. a literal or an expression), arguments to non-const reference parameters must be normal variables.
It can be hard to tell whether an argument passed by non-const reference is meant to be input, output, or both. Judicious use of const
and a naming suffix for out
variables can help.
It’s impossible to tell from the function call whether the argument may change. An argument passed by value and passed by reference looks the same. We can only tell whether an argument is passed by value or reference by looking at the function declaration. This can lead to situations where the programmer does not realize a function will change the value of the argument.
When to use pass by reference:
When passing structs or classes (use const
if read-only).
When you need the function to modify an argument.
When you need access to the type information of a fixed array.
When not to use pass by reference:
When passing fundamental types that don’t need to be modified (use pass by value).
void DoWork(int n);
void DoWork(const int &n);
The difference is more prominent when you are passing a big struct/class.
struct MyData {
int a,b,c,d,e,f,g,h;
long array[1234];
};
void DoWork(MyData md);
void DoWork(const MyData& md);
when you use use ‘normal’ parameter, you pass the parameter by value and hence creating a copy of the parameter you pass. if you are using const reference
, you pass it by reference and the original data is not copied.
In both cases, the original data cannot be modified from inside the function.
End of the file
Nov. 28, 2020, by CCJ at Secaucus, NJ, US.