C++ – Linux Hint https://linuxhint.com Exploring and Master Linux Ecosystem Sun, 28 Feb 2021 23:14:40 +0000 en-US hourly 1 https://wordpress.org/?v=5.6.2 C++ Friend Function https://linuxhint.com/c-friend-function/ Fri, 26 Feb 2021 18:49:47 +0000 https://linuxhint.com/?p=91929 A function is a block of code that performs a certain task and provides the output. It is mainly used to eliminate repetitive code. In this tutorial, we will look into the friend function in C++ and explain its concept with working examples.

What is Friend Function?

Friend function is a non-member function that can access the private and protected members of a class. “Friend” is a keyword used to indicate that a function is the friend of a class. This allows the compiler to know that the particular function is a friend of the given class. The friend function then should be able to access the private and protected member of a given class. Without the friend keyword, a non-member outside function can only access the public members of a class.

Key Features of Friend Function:

Here are the key features of the friend function:

  1. A friend function is not in the scope of the class.
  2. The friend function cannot be invoked using an instance of a class.
  3. It can access the members using the object and dot operator.

Syntax of Friend Function:

Here is the syntax of the friend function:

class Class_Name_Demo
{
   ………………………………………
   ………………………………………

    friend return_Type function_Name(arg_1, arg_2,);
};

Example of Friend Function:

Now, let us look into an example program to understand the concept of the friend function in C++. In the below example program, we have the “Friend_Demo” class. It has three different types of data members, i.e., private, protected, and public.

We have defined another function, i.e., “friendDemo_Func()” outside the scope of the “Friend_Demo” class and tried to access the members (private, protected, and public) of the “Friend_Demo” class.

But, as you can see in the output below when we compile the program, it throws compilation errors. The friend function is going to exactly solve this problem.

#include <iostream>
using namespace std;

class Friend_Demo
{
   private:
    int i_private;
   protected:
    int i_protected;
   public:
    int i_public;
};

void friendDemo_Func()
{
    Friend_Demo fd;
   
    fd.i_private    = 10;
    fd.i_protected  = 15;
    fd.i_public     = 20;
   
    cout << fd.i_private << endl;
    cout << fd.i_protected << endl;
    cout << fd.i_public << endl;
}

int main()
{
    friendDemo_Func();

    return 0;
}

In the previous program, we were getting compilation errors while trying to access the private, protected, and public members of a class from a non-member function. This is because a non-member function is not allowed to access the private and protected members of a class from outside the scope of the class.

Now, in this example, we have declared “friendDemo_Func()” function as a friend inside the scope of the class, i.e., “Friend_Demo”:

friend void friendDemo_Func();

We have created an object, i.e., “fd” of the “Friend_Demo” class inside the “friendDemo_Func()” function. Now, we can access the private, protected, and public members of the “Friend_Demo” class using the dot operator. We have assigned 10, 15, and 20 to i_private, i_protected, and i_public, respectively.

As you can see in the output below, this program is now compiled and executed without any errors and print the output as expected.

#include <iostream>
using namespace std;

class Friend_Demo
{
   private:
    int i_private;
   protected:
    int i_protected;
   public:
    int i_public;
    friend void friendDemo_Func();
};

void friendDemo_Func()
{
    Friend_Demo fd;
   
    fd.i_private    = 10;
    fd.i_protected  = 15;
    fd.i_public = 20;
   
    cout << fd.i_private << endl;
    cout << fd.i_protected << endl;
    cout << fd.i_public << endl;
}

int main()
{
    friendDemo_Func();

    return 0;
}

Conclusion:

In this article, I have explained the concept of the friend function in C++. I have also shown two working examples to explain how the friend function behaves in C++. Sometimes, the friend function can be very useful in a complex programming environment. However, a programmer should be cautious about overusing it and compromising its OOP features. ]]> Unique and Ordered Containers in C++ https://linuxhint.com/unique-and-ordered-containers-in-c/ Sun, 14 Feb 2021 18:10:03 +0000 https://linuxhint.com/?p=89978 {6, 10, 2, 8, 4} is a set; {2, 4, 6, 8, 10} is a set of the same integers, arranged in ascending order. In Mathematics, a set has unique elements (distinct elements), and that is, no element occurs more than once. Furthermore, a multiset is a set, where any element can occur more than once. {6, 6, 10, 2, 2, 8, 4, 4, 4} is a multiset. {2, 2, 4, 4, 4, 6, 6, 8, 10} is the same multiset, but with the elements arranged in ascending order. This article does not deal with multiset. It deals with the C++ data structure called, set.

A map in software is like an array, but it is an array with two columns instead of one. The first column has the keys and the second column has the values. Each row is one pair, making a key/value pair. A key is directly related to its value.

An example of a map is {{‘c’,30}, {‘b’,20}, {‘d’,30}, {‘e’,40}, {‘a’,10}}. The first key/value pair inserted here, is {‘c’,3}, where ‘c’ is the key and 30 is the value. This map is not ordered by keys. Ordering this map by keys produces {{‘a’,10}, {‘b’,20}, {‘c’,30}, {‘d’,30}, {‘e’,40}}. Notice that there can be duplicated values, but not duplicated keys. An ordered map is a map ordered by keys.

A multiset is to a set, as a multimap is to a map. This means that there are maps with duplicate keys. An example of a multimap is {{‘a’,10}, {‘b’,20}, {‘b’,20}, {‘c’,30}, {‘c’,30}, {‘d’,30}, {‘e’,40}}. And as stated above, this article does not deal with multimap, rather, it deals with the C++ data structure called, map.

In C++, a data structure is a structure with properties (data members) and methods (member functions). The data of the structure is a list; a set is a list; a map is a list of key/value pairs.

This article discusses the basics of sets and maps in C++, and to better understand this article, the reader should have had a basic knowledge of C++.

Article Content:

Class and its Objects:

In C++, the set, the map, and other similar structures are called containers. A class is a generalized unit with data members, which are variables, and member functions that are related. When data members are given values, an object is formed. However, an object is formed in a process called, instantiation. As a class can lead to different values for the same data member variables, different objects can then be instantiated from the same class.

In C++, an unusable set is a class, as well as an unusable map. When an object is instantiated from the unusable set or the unusable map, the object becomes the real data structure. With the set and map data structures, the principal data member is a list. Well, the set and the map form a group of containers called, ordered associative containers. Unordered set and the unordered map also exist, but those are unfortunately not addressed in this article.

Creating a set or a map:

Instantiating a set from its set class is creating a set; instantiating a map from its map class is creating a map. The object so created is given a name of the programmer’s choice.

In order to create a set, the program should begin with:

#include <iostream>
#include <set>
using namespace std;

Note the directive “#include <set>”, which includes the set library that has the set class from which set data structures will be instantiated.

In order to create a map, the program should begin with:

#include <iostream>
#include <map>
using namespace std;

Note the directive “#include <map>”, which includes the map library that has the map class from which map data structures will be instantiated.

The syntax to create an empty set is:

set<type> objectName

Example:

set<int> setObj;

An example to create a set with content is:

set<int> setObj({6, 10, 2, 8, 4});

The syntax to create an empty map is:

map<type1, type2> objectName

Example:

map<char, int> mapObj;

An example to create a map with content is:

map<char,int> mapObj({{'c',30},{'b',20},{'d',30},{'e',40},{'a',10}});

Iterator Basics:

An iterator is an elaborated pointer, which can be used to traverse the list of the data structure from the beginning to the end.

The begin() member Function

The begin() member function returns an iterator that points to the first element of the list. The following example illustrates this for the set:

set<int> setObj({6, 10, 2, 8, 4});
set<int>::iterator iter = setObj.begin();
cout << *iter << '\n';

Note the way begin() has been used with setObj and the dot operator. iter is the returned iterator object. Also, note the way it has been declared. * is the indirection operator. As used with iter, it returns the first element of the set; the first element is 2 instead of 6 – see explanation below.

The following example illustrates the use of the begin() function for the map:

map<char,int> mapObj({{'c',30},{'b',20},{'d',30},{'e',40},{'a',10}});
map<char,int>::iterator iter = mapObj.begin();
cout << "{" << (*iter).first <<',' << (*iter).second << "}\n";

Note the way begin() has been used with mapObj and the dot operator. iter is the returned iterator object. Also, note the way it has been declared. “first”, as used here, refers to the key. “second” refers to the value corresponding to the key. Observe how they have been used with iter to obtain the start element components of the list. The first element is {a,10} instead of {c,30} – see explanation below.

The “begin() const” member Function

The “begin() const” member function returns an iterator that points to the first element of the list when the declaration of the set begins with const (for constant). Under this condition, the value in the list, referred to by the iterator returned, cannot be changed by the iterator. The following example illustrates its use for the set:

const set<int> setObj({6, 10, 2, 8, 4});
set<int>::const_iterator iter = setObj.begin();
cout << *iter << '\n';

Note the way begin() has been used with setObj and the dot operator. No “const” has been typed just after begin(). However, “const” has preceded the declaration. iter here is the returned constant iterator object, which is different from the normal iterator. Also, note the way it has been declared. * is the indirection operator; as used with iter, it returns the first element of the set. The first element is 2 instead of 6 – see explanation below.

The following example illustrates the use of the “begin() const” function for the map:

const map<char,int> mapObj({{'c',30},{'b',20},{'d',30},{'e',40},{'a',10}});
map<char,int>::const_iterator iter = mapObj.begin();
cout << "{" << (*iter).first <<',' << (*iter).second << "}\n";

Note the way begin() has been used with mapObj and the dot operator. No “const” has been typed just after begin(). However, “const” has preceded the declaration. iter here is the returned constant iterator object, which is different from the normal iterator. Also, note the way it has been declared. “first”, as used here, refers to the key; “second”, as used here, refers to the value corresponding to the key. Observe how they have been used with iter to obtain the start element components of the list. The first element is {a,10} instead of {c,30} – see explanation below.

The end() member Function

The end() member function returns an iterator that points just after the end of the list. The following example illustrates this for the set:

set<int> setObj({6, 10, 2, 8, 4});
set<int>::iterator iter = setObj.end();
cout << *iter << '\n';

Note the way end() has been used with setObj and the dot operator. iter is the returned iterator object. Also, note the way it has been declared. * is the indirection operator; as used with iter, it returns the last+1 element of the set. In the author’s computer, this last+1 element is 5, which is not on the list. So, careful not to use this element.

The following example illustrates the use of the end() function for the map:

map<char,int> mapObj({{'c',30},{'b',20},{'d',30},{'e',40},{'a',10}});
map<char,int>::iterator iter = mapObj.end();
cout << "{" << (*iter).first <<',' << (*iter).second << "}\n";

Note the way end() has been used with mapObj and the dot operator. iter is the returned iterator object. Also, note the way it has been declared. * is the indirection operator; as used with iter, it returns the last+1 element of the map. In the author’s computer, this last+1 element is {,0}, which is not in the list. So, careful not to use this element.

The “end() const” member Function

The “end() const” member function returns an iterator that points just after the end of the list when the declaration of the set begins with const (for constant). Under this condition, the value in the list, referred to by the iterator returned, cannot be changed by the iterator. The following example illustrates its use for the set:

const set<int> setObj({6, 10, 2, 8, 4});
set<int>::const_iterator iter = setObj.end();
cout << *iter << '\n';

Note the way end() has been used with setObj and the dot operator. No “const” has been typed just after the end(). However, “const” has preceded the declaration. iter is the returned iterator object. Also, note the way it has been declared. * is the indirection operator; as used with iter, it returns the last+1 element of the set.

The following example illustrates the use of the “end() const” function for the map:

const map<char,int> mapObj({{'c',30},{'b',20},{'d',30},{'e',40},{'a',10}});
map<char,int>::const_iterator iter = mapObj.end();
cout << "{" << (*iter).first <<',' << (*iter).second << "}\n";

Note the way end() has been used with mapObj and the dot operator. No “const” has been typed just after the end(). However, “const” has preceded the declaration. iter is the returned constant iterator object, which is different from the normal iterator. Also, carefully observe the way it has been declared.

Element Access for set and map:

Set

With the set, the element is read using the indirection operator. The first two elements of a set are read in the following example:

set<int> setObj({6, 10, 2, 8, 4});
set<int>::iterator iter = setObj.begin();
cout << *iter << '\n';
++iter;
cout << *iter << '\n';

The output is 2, then followed by 4 – see explanation below. To point at the next element of the list, the iterator is incremented.

Note: An element cannot be changed using the indirection operator for the set. For example, “*iter = 9;” is not possible.

map

A map consists of key/value pairs. A value can be read using the corresponding key, and changed using the same key. The following code segment illustrates this:

map<char,int> mapObj({{'c',30},{'b',20},{'d',30},{'e',40},{'a',10}});
cout << mapObj['b'] << '\n';
mapObj['b'] = 55;
cout << mapObj['b'] << '\n';

The output is:

20
55

The dot operator has not been used here. Instead, it is the square brackets operator, which takes the key as content, that has been used.

Order of Elements in a set or map:

Elements can be inserted into a set, in any order. However, once inserted, the set rearranges its elements in ascending order. Ascending order is the default order. If descending order is needed, then the set has to be declared as in the following example:

set<int, greater<int> > setObj({6, 10, 2, 8, 4});

So, after the type, e.g., int, for the template, there is a comma, followed by “greater<type>” in the angle brackets.

Elements can be inserted into a map in any order. However, once inserted, the map rearranges its elements in ascending order by key (only) while maintaining the relationship between each key and its value. Ascending order is the default order; if descending order is needed, then the map has to be declared as in the following example:

map<char, int, greater<int> > mapObj({{'c',30},{'b',20},{'d',30},{'e',40},{'a',10}});

So, after the type pair, e.g., “char, int”, for the template, there is a comma, followed by “greater<type>” in the angle brackets.

Traversing a set

The while-loop or for-loop with the iterator can be used to traverse a set. The following example uses a for-loop to traverse a set that has been configured in descending order:

set<int, greater<int> > setObj({6, 10, 2, 8, 4});
for (set<int>::iterator iter = setObj.begin(); iter != setObj.end(); ++iter)
  {
    cout << *iter << ' ';
  }

The output is:

10 8 6 4 2

Incrementing an iterator points it to the next element.

Traversing a map

The while-loop or for-loop with the iterator can be used to traverse a map. The following example uses a for-loop to traverse a map that has been configured in descending order:

map<char, int, greater<int> > mapObj({{'c',30},{'b',20},{'d',30},{'e',40},{'a',10}});
for (map<char,int>::iterator iter = mapObj.begin(); iter != mapObj.end(); ++iter)
  {
    cout << "{" << (*iter).first << ", " << (*iter).second << "}" << ", ";
  }

The output is:

{e, 40}, {d, 30}, {c, 30}, {b, 20}, {a, 10},

Incrementing an iterator points it to the next element. “first”, in the code, refers to the key and “second” refers to the corresponding value. Note how these values have been obtained for the output.

Other Commonly Used Member Functions:

The size() Function

This function returns an integer, which is the number of elements in the list. Set example:

set<int, greater<int> > setObj({6, 10, 2, 8, 4});
cout << setObj.size() << '\n';

The output is 5.

Map example:

map<char, int, greater<int> > mapObj({{'c',30},{'b',20},{'d',30},{'e',40},{'a',10}});
cout << mapObj.size() << '\n';

The output is 5.

The insert() Function

set

set does not allow duplicate. So, any duplicate inserted is silently rejected. With the set, the argument to the insert() function is the value to be inserted. The value is fitted into a position, in which the order in the set remains ascending or descending. Example:

set<int> setObj({6, 10, 2, 8, 4});
setObj.insert(6);
setObj.insert(9);
setObj.insert(12);
for (set<int>::iterator iter = setObj.begin(); iter != setObj.end(); ++iter)
   {
     cout << *iter << ' ';
   }

The output is:

2 4 6 8 9 10 12

Note: The insert() member function can be used to populate an empty set.

map

map does not allow duplicate by key. So, any duplicate inserted is silently rejected. With the map, the argument to the insert() function is the key/value pair in braces. The element is fitted into a position by key, in which the order in the map remains ascending or descending. Example:

map<char, int> mapObj({{'c',30},{'b',20},{'d',30},{'e',40},{'a',10}});
mapObj.insert({'e',80});
mapObj.insert({'f',50});
mapObj.insert({'g',60});
for (map<char,int>::iterator iter = mapObj.begin(); iter != mapObj.end(); ++iter)
cout << "{" << (*iter).first << ", " << (*iter).second << "}" << ", ";

The output is:

{a, 10}, {b, 20}, {c, 30}, {d, 30}, {e, 40}, {f, 50}, {g, 60},

Note: The insert() member function can be used to populate an empty map.

The empty() Function

This function returns true if the list is empty, and false if otherwise. Set example:

set<int> setObj({6, 10, 2, 8, 4});
bool ret = setObj.empty();
cout << ret << '\n';

The output is 0 for false, meaning the set here is not empty.

Map example:

map<char, int> mapObj({{'c',30},{'b',20},{'d',30},{'e',40},{'a',10}});
bool ret = mapObj.empty();
cout << ret << '\n';

The output is 0 for false, meaning the map here is not empty.

The erase() Function

set

Consider the following code segment:

set<int> setObj({10, 20, 30, 40, 50});
set<int>::iterator iter = setObj.begin();
set<int>::iterator itr = setObj.erase(iter);
cout << "new size: " << setObj.size() << '\n';
cout << "next value: " << *itr << '\n';
itr = setObj.erase(itr);
cout << "new size: " << setObj.size() << '\n';
cout << "next value: " << *itr << '\n';

The output is:

new size: 4
next value: 20
new size: 3
next value: 30

The erase() function takes an iterator that points to an element as an argument. After erasing the element, the erase() function returns an iterator that points to the next element.

map

Consider the following code segment:

map<char,int> mapObj({{'a',10},{'b',20},{'c',30},{'d',40},{'e',50}});
map<char,int>::iterator iter = mapObj.begin();
map<char,int>::iterator itr = mapObj.erase(iter);
cout << "new size: " << mapObj.size() << '\n';
cout << "next value pair: {" << (*itr).first <<',' << (*itr).second << "}\n";
itr = mapObj.erase(itr);
cout << "new size: " << mapObj.size() << '\n';
cout << "next value pair: {" << (*itr).first <<',' << (*itr).second << "}\n";

The output is:

new size: 4
next value pair: {b,20}
new size: 3
next value pair: {c,30}

The erase() function takes an iterator that points to an element as an argument. After erasing the element, the erase() function returns an iterator that points to the next element.

The clear() Function

The clear() function removes all the elements in the list. Set example:

set<int> setObj({6, 10, 2, 8, 4});
setObj.clear();
cout << setObj.size() << '\n';

The output is 0.

map example:

map<char, int> mapObj({{'c',30},{'b',20},{'d',30},{'e',40},{'a',10}});
mapObj.clear();
cout << mapObj.size() << '\n';

The output is 0.

Conclusion:

A set data structure in C++ is a structure in which the list of elements is stored in ascending order by default, or in descending order by programmer’s choice. All the elements of the set are unique. A map data structure in C++ is a structure in which the list is a hash of key/value pairs, stored in ascending order of keys by default, or in descending order of keys by programmer’s choice. The keys are also unique, and there can be duplicated values. The main data member of either of the structures is the list. Either structure has member functions, some of which are commonly used.

]]>
Lambda Expressions in C++ https://linuxhint.com/lambda-expressions-in-c/ Wed, 10 Feb 2021 07:21:26 +0000 https://linuxhint.com/?p=89443

Why Lambda Expression?

Consider the following statement:

    int myInt = 52;

Here, myInt is an identifier, an lvalue. 52 is a literal, a prvalue. Today, it is possible to code a function specially and put it in the position of 52. Such a function is called a lambda expression. Consider also the following short program:

#include <iostream>

using namespace std;

int fn(int par)

    {

        int answer = par + 3;

        return answer;

    }


int main()

{

    fn(5);

   

    return 0;

}

Today, it is possible to code a function specially and put it in the position of the argument of 5, of the function call, fn(5). Such a function is called a lambda expression. The lambda expression (function) in that position is a prvalue.

Any literal except the string literal is a prvalue. The lambda expression is a special function design that would fit as a literal in code. It is an anonymous (unnamed) function. This article explains the new C++ primary expression, called the lambda expression. Basic knowledge in C++ is a requirement to understand this article.

Article Content

Illustration of Lambda Expression

In the following program, a function, which is a lambda expression, is assigned to a variable:

#include <iostream>

using namespace std;

auto fn = [](int param)

            {

                int answer = param + 3;

                return answer;

            };


int main()

{

    auto variab = fn(2);

    cout << variab << '\n';


    return 0;

}

The output is:

    5

Outside the main() function, there is the variable, fn. Its type is auto. Auto in this situation means that the actual type, such as int or float, is determined by the right operand of the assignment operator (=). On the right of the assignment operator is a lambda expression. A lambda expression is a function without the preceding return type. Note the use and position of the square brackets, []. The function returns 5, an int, which will determine the type for fn.

In the main() function, there is the statement:

    auto variab = fn(2);

This means, fn outside main(), ends up as the identifier for a function. Its implicit parameters are those of the lambda expression. The type for variab is auto.

Note that the lambda expression ends with a semicolon, just like the class or struct definition, ends with a semicolon.

In the following program, a function, which is a lambda expression returning the value of 5, is an argument to another function:

#include <iostream>

using namespace std;

void otherfn (int no1, int (*ptr)(int))

{

        int no2 = (*ptr)(2);

        cout << no1 << ' ' << no2 << '\n';

    }


int main()

{

    otherfn(4, [](int param)

            {

                int answer = param + 3;

                return answer;

            });


    return 0;
}

The output is :

    4  5

There are two functions here, the lambda expression and the otherfn() function. The lambda expression is the second argument of the otherfn(), called in main(). Note that the lambda function (expression) does not end with a semicolon in this call because, here, it is an argument (not a stand-alone function).

The lambda function parameter in the definition of the otherfn() function is a pointer to a function. The pointer has the name, ptr. The name, ptr, is used in the otherfn() definition to call the lambda function.

The statement,

    int no2 = (*ptr)(2);

In the otherfn() definition, it calls the lambda function with an argument of 2. The return value of the call, "(*ptr)(2)" from the lambda function, is assigned to no2.

The above program also shows how the lambda function can be used in the C++ callback function scheme.

Parts of Lambda Expression

The parts of a typical lambda function is as follows:

    [] () {}
  • [] is the capture clause. It can have items.
  • () is for the parameter list.
  • {} is for the function body. If the function is standing alone, then it should end with a semicolon.

Captures

The lambda function definition can be assigned to a variable or used as the argument to a different function call. The definition for such a function call should have as a parameter, a pointer to a function, corresponding to the lambda function definition.

The lambda function definition is different from the normal function definition. It can be assigned to a variable in the global scope; this function-assigned-to-variable can also be coded inside another function. When assigned to a global scope variable, its body can see other variables in the global scope. When assigned to a variable inside a normal function definition, its body can see other variables in the function scope only with the capture clause’s help, [].

The capture clause [], also known as the lambda-introducer, allows variables to be sent from the surrounding (function) scope into the lambda expression’s function body. The lambda expression’s function body is said to capture the variable when it receives the object. Without the capture clause [], a variable cannot be sent from the surrounding scope into the lambda expression’s function body. The following program illustrates this, with the main() function scope, as the surrounding scope:

#include <iostream>

using namespace std;

int main()

{

    int id = 5;


    auto fn = [id]()

            {

                cout << id << '\n';

            };

    fn();


    return 0;

}

The output is 5. Without the name, id, inside [], the lambda expression would not have seen the variable id of the main() function scope.

Capturing by Reference

The above example use of the capture clause is capturing by value (see details below). In capturing by reference, the location (storage) of the variable, e.g., id above, of the surrounding scope, is made available inside the lambda function body. So, changing the value of the variable inside the lambda function body will change the value of that same variable in the surrounding scope. Each variable repeated in the capture clause is preceded by the ampersand (&) to achieve this. The following program illustrates this:

#include <iostream>

using namespace std;

int main()

{

    int id = 5; float ft = 2.3; char ch = 'A';

    auto fn = [&id, &ft, &ch]()

            {

                id = 6; ft = 3.4; ch = 'B';

            };

    fn();

    cout << id << ", " <<  ft << ", " <<  ch << '\n';

    return 0;

}

The output is:

    6, 3.4, B

Confirming that the variable names inside the lambda expression’s function body are for the same variables outside the lambda expression.

Capturing by Value

In capturing by value, a copy of the variable’s location, of the surrounding scope, is made available inside the lambda function body. Though the variable inside the lambda function body is a copy, its value cannot be changed inside the body as of now. To achieve capturing by value, each variable repeated in the capture clause is not preceded by anything. The following program illustrates this:

#include <iostream>

using namespace std;

int main()

{

    int id = 5; float ft = 2.3; char ch = 'A';

    auto fn = [id, ft, ch]()

            {

               //id = 6; ft = 3.4; ch = 'B';

               cout << id << ", " <<  ft << ", " <<  ch << '\n';

            };

    fn();

    id = 6; ft = 3.4; ch = 'B';

    cout << id << ", " <<  ft << ", " <<  ch << '\n';

    return 0;

}

The output is:

    5, 2.3, A

    6, 3.4, B

If the comment indicator is removed, the program will not compile. The compiler will issue an error message that the variables inside the function body’s definition of the lambda expression cannot be changed. Though the variables cannot be changed inside the lambda function, they can be changed outside the lambda function, as the above program’s output shows.

Mixing Captures

Capturing by reference and capturing by value can be mixed, as the following program shows:

#include <iostream>

using namespace std;

int main()

{

    int id = 5; float ft = 2.3; char ch = 'A'; bool bl = true;


    auto fn = [id, ft, &ch, &bl]()

            {

                ch = 'B'; bl = false;

                cout << id << ", " <<  ft << ", " <<  ch << ", " <<  bl << '\n';

            };

    fn();


    return 0;

}

The output is:

    5, 2.3, B, 0

When all captured, are by reference:

If all variables to be captured are captured by reference, then just one & will suffice in the capture clause. The following program illustrates this:

#include <iostream>

using namespace std;

int main()

{

    int id = 5; float ft = 2.3; char ch = 'A'; bool bl = true;


    auto fn = [&]()

            {

                id = 6; ft = 3.4; ch = 'B'; bl = false;

            };

    fn();

    cout << id << ", " <<  ft << ", " <<  ch << ", " <<  bl << '\n';


    return 0;

}

The output is:

    6, 3.4, B, 0

If some variables are to be captured by reference and others by value, then one & will represent all the references, and the rest will each not be preceded by anything, as the following program shows:

using namespace std;

int main()

{

    int id = 5; float ft = 2.3; char ch = 'A'; bool bl = true;


    auto fn = [&, id, ft]()

            {

                ch = 'B'; bl = false;

                cout << id << ", " <<  ft << ", " <<  ch << ", " <<  bl << '\n';

            };

    fn();


    return 0;

}

The output is:

    5, 2.3, B, 0

Note that & alone (i.e., & not followed by an identifier) has to be the first character in the capture clause.

When all captured, are by value:

If all variables to be captured are to be captured by value, then just one = will suffice in the capture clause. The following program illustrates this:

#include <iostream>

using namespace std;

int main()
{

    int id = 5; float ft = 2.3; char ch = 'A'; bool bl = true;


    auto fn = [=]()

            {

                cout << id << ", " <<  ft << ", " <<  ch << ", " <<  bl << '\n';

            };

    fn();


    return 0;


}

The output is:

    5, 2.3, A, 1

Note: = is read-only, as of now.

If some variables are to be captured by value and others by reference, then one = will represent all the read-only copied variables, and the rest will each have &, as the following program shows:

#include <iostream>

using namespace std;

int main()

{

    int id = 5; float ft = 2.3; char ch = 'A'; bool bl = true;


    auto fn = [=, &ch, &bl]()

            {

                ch = 'B'; bl = false;

                cout << id << ", " <<  ft << ", " <<  ch << ", " <<  bl << '\n';

            };

    fn();


    return 0;

}

The output is:

    5, 2.3, B, 0

Note that = alone has to be the first character in the capture clause.

Classical Callback Function Scheme with Lambda Expression

The following program shows how a classical callback function scheme can be done with the lambda expression:

#include <iostream>

using namespace std;

char *output;


auto cba = [](char out[])

    {

        output = out;

    };

 

void principalFunc(char input[], void (*pt)(char[]))

    {

        (*pt)(input);

        cout<<"for principal function"<<'\n';

    }


void fn()

    {

        cout<<"Now"<<'\n';

    }


int main()

{

    char input[] = "for callback function";

    principalFunc(input, cba);

    fn();

    cout<<output<<'\n';

 

   return 0;

}

The output is:

    for principal function

    Now

    for callback function

Recall that when a lambda expression definition is assigned to a variable in the global scope, its function body can see global variables without employing the capture clause.

The trailing-return-type

The return type of a lambda expression is auto, meaning the compiler determines the return type from the return expression (if present). If the programmer really wants to indicate the return type, then he will do it as in the following program:

#include <iostream>

using namespace std;

auto fn = [](int param) -> int

            {

                int answer = param + 3;

                return answer;

            };


int main()

{

    auto variab = fn(2);

    cout << variab << '\n';


    return 0;

}

The output is 5. After the parameter list, the arrow operator is typed. This is followed by the return type (int in this case).

Closure

Consider the following code segment:

struct Cla

    {

        int id = 5;

        char ch = 'a';

    } obj1, obj2;

Here, Cla is the name of the struct class.  Obj1 and obj2 are two objects that will be instantiated from the struct class. Lambda expression is similar in implementation. The lambda function definition is a kind of class. When the lambda function is called (invoked), an object is instantiated from its definition. This object is called a closure. It is the closure that does the work the lambda is expected to do.

However, coding the lambda expression like the struct above will have obj1 and obj2 replaced by the corresponding parameters’ arguments. The following program illustrates this:

#include <iostream>

using namespace std;

auto fn = [](int param1, int param2)

            {

                int answer = param1 + param2;

                return answer;

            } (2, 3);


int main()

{

    auto var = fn;

    cout << var << '\n';


    return 0;

}

The output is 5. The arguments are 2 and 3 in parentheses. Note that the lambda expression function call, fn, does not take any argument since the arguments have already been coded at the end of the lambda function definition.

Conclusion

The lambda expression is an anonymous function. It is in two parts: class and object. Its definition is a kind of class. When the expression is called, an object is formed from the definition. This object is called a closure. It is the closure that does the work the lambda is expected to do.

For the lambda expression to receive a variable from an outer function scope, it needs a non-empty capture clause into its function body.

]]>
Overloading in C++ https://linuxhint.com/cpp_overloading/ Mon, 08 Feb 2021 01:28:32 +0000 https://linuxhint.com/?p=89209 C++ does not allow a function that adds two integers and return an integer, to add two floats and return a float. Imagine that there is a function to add two integers and return an integer. Would it not be nice to have another function with the same name, that adds but two or even more floats to return a float? Doing so is said to be, overloading the first function.

Arithmetic operators are typically used for arithmetic operations. Is it not nice to have the +, join two strings? Enabling that is said to be overloading the arithmetic addition operator, for strings.

The increment operator, ++ adds 1 to an int or a float. When dealing with pointers, it does not add 1 to the pointer. It makes the pointer point to the next consecutive object in memory. An iterator points to the next object in a linked-list, but the linked-list objects are in different places in memory (not in consecutive regions). Would it not be nice to overload the increment operator for an iterator, to increment but point to the following element, in the linked-list?

This article explains overloading in C++. It is divided into two parts: function overloading and operator overloading. Having already basic knowledge in C++ is necessary to understand the rest of the article.

Article Content

Function Overloading

The following function adds two ints and returns an int:

int add(int no1, int no2)
    {
        int sum = no1 + no2;
        return sum;
    }

The prototype of this function is:

    int add(int no1, int no2);

The prototype of a function in the header of the function, ending with a semicolon. The following function with the same name, but with a different prototype, would add three floats and return a float:

float add(float no1, float no2, float no3)
    {
        float sum = no1 + no2 + no3;
        return sum;
    }

How does the compiler differentiate which function to call, since two or more functions have the same name? The compiler uses the number of arguments and argument types to determine which function to call. The parameter list of overloaded functions should differ in their number and/or parameter types. So, the function call,

int sm = add(2, 3);

would call the integer function, while the function call,

float sme = add(2.3, 3.4, 2.0);

would call the float function. Note: there are situations where the compiler will reject an overloaded function when the number of arguments is the same but of different types! – Reason: – see later.

The following program puts the above code segments into action:

#include <iostream>
using namespace std;

int add(int no1, int no2)
    {
        int sum = no1 + no2;
        return sum;
    }

float add(float no1, float no2, float no3)
    {
        float sum = no1 + no2 + no3;
        return sum;
    }

int main()
{
    int sm = add(2, 3);
    cout<<sm<<'\n';
    float sme = add(2.3, 3.4, 2.0);
    cout<<sme<<'\n';
   
    return 0;
}

The output is:
5
7.7

Operator Overloading

Arithmetic operators are used to overload operations in class types. An iterator is a class type. The increment and decrement operators are used to overload operations for an iterator.

Example String Class Operator Overloading

This section provides an example, where + is overloaded for a simply designed string class, called a spring class. + concatenates the literals of two string objects, returning a new object with the concatenated literals. Concatenating two literals means joining the second literal to the end of the first literal.

Now, C++ has a special member function for all classes, called the operator. The programmer can use this special function to overload operators, such as +. The following program shows the overloading of the + operator for two strings.

#include <iostream>
using namespace std;

class spring
    {
        public:
        //data members
        char val[100];
        int n;
        char concat[100];
        //member functions
        spring (char arr[])
            {
                for (int i=0; i<100; ++i) {
                    val[i] = arr[i];
                    if (arr[i]  == '\0')
                        break;
                    }
                int i;
                for (i=0; i<100; ++i) if (arr[i]  == '\0') break;
                n = i;
            }
        spring operator+(spring& st) {
                int newLen = n + st.n;
                char newStr[newLen+1];
                for (int i=0; i<n; ++i) newStr[i] = val[i];                
                for (int i=n; i<newLen; ++i) newStr[i] = st.val[i-n];                
                newStr[newLen] = '\0';
                spring obj(newStr);
                return obj;  
            }
    };

int main()
{
    char ch1[] = "I hate you! "; spring str1(ch1);
    char ch2[] = "But she loves you!"; spring str2(ch2);
    char ch3[] = "one"; spring str3(ch3);
    str3 = str1 + str2;
    cout<<str3.val<<'\n';
   
    return 0;
}

The value of str1 is "I hate you! ". The value of str2 is "But she loves you!". The value of str3, which is, str1 + str2, is the output:

"I hate you! But she loves you!"

which is the concatenation of the two string literals. The strings themselves are instantiated objects.

The definition of the operator function is inside the description(definition) of the string class. It begins with the return type, "spring" for "string". The special name, "operator, follow this". After that, there is the symbol of the operator (to be overloaded). Then there is the parameter list, which is actually the operand list. + is a binary operator: meaning it takes a left and a right operand. However, by the C++ specification, the parameter list here has only the right parameter. Then there is the body of the operator function, which mimics the ordinary operator behaviour.

By the C++ specification, the + operator definition takes only the right operand parameter, because the rest of the class description is the left operand parameter.

In the above code, only the operator+() function definition, is concerned with the + overloading. The rest of the code for the class is normal coding. Inside this definition, the two string literals are concatenated into the array, newStr[]. After that, a new string object is actually created (instantiated), using an argument, newStr[]. At the end of the operator+() function definition, the newly created object, having the concatenated string, is returned.

In the main() function, the addition is done by the statement:

str3 = str1 + str2;

Where str1, str2 and str3 are string objects that have already been created in main(). The expression, “str1 + str2” with its +, calls the operator+() member function in the str1 object. The operator+() member function in the str1 object uses str2 as its argument and returns the new object with (developed) the concatenated string. The assignment operator (=) of the complete statement, replaces the content (values of variables) of the str3 object, with those of the returned object. In the main() function, after addition, the value of the data member str3.val is no longer "one"; it is the concatenated (addition) string, "I hate you! But she loves you!". The operator+() member function in the str1 object, uses its own object's string literal, and the string literal of its argument, str2 to come up with a joined string literal.

Iterator Operator Overloading

When dealing with the iterator, at least two objects are involved: a linked-list and the iterator itself. In fact, at least two classes are involved: a class from which a linked-list is instantiated, and a class from which an iterator is instantiated.

Linked-list

A diagram for a doubly linked-list object is:

This list has three elements, but there can be more. The three elements here are elements of integers. The first one has the value, 14; the next one has the value, 88; and the last one has the value, 47. Each element here consists of three consecutive locations.

This is unlike the array, where each element is one location, and all the array elements are in consecutive locations. Here, the different elements are in different places in the memory series, but each element consists of three consecutive locations.

For each element, the middle location holds the value. The right location has the pointer to the next element. The left location has the pointer to the previous element. For the last element, the right location points to a theoretical end of the list. For the first element, the left location points to a theoretical start of the list.

With the array, the increment operator (++), increments the pointer to point to the physically next location. With the list, the elements are not in consecutive regions in memory. So, the increment operator can be overloaded, move the iterator (pointer) from one element to the logically next element. The same projection applies to the decrement operator (–).

A forward iterator is an iterator that when engaged, points to the next element. A reverse iterator is an iterator, that when engaged, points to the previous element.

Overloading ++ ad —

Overloading of these operators is done in the class description (definition) of the iterator.

The syntax for the prototype of the increment operator overloading, prefix, is

ReturnType operator++();

The syntax for the prototype of the increment operator overloading, postfix, is

ReturnType operator++(int);

The syntax for the prototype of the decrement operator overloading, prefix, is

ReturnType operator--();

The syntax for the prototype of the increment operator overloading, postfix, is

ReturnType operator--(int);

Conclusion

Overloading means giving a different meaning to a function or an operator. Functions are overloaded in the same scope. What differentiates overloaded functions are the number and/or types of parameters in their parameter lists. In some cases, where the number of the parameters is the same, but with different types, the compiler rejects the overloading – see later. Many ordinary operators can be overloaded in classes from which objects are instantiated. This is done by giving a return type, parameter list, and body, to the special function named, operator, in the class description.

]]>
C++ Qualifiers and Storage Class Specifiers https://linuxhint.com/c-qualifiers-and-storage-class-specifiers/ Sun, 07 Feb 2021 19:40:02 +0000 https://linuxhint.com/?p=89302

CV stands for Constant-Volatile. The declaration of an object that is not preceded by const and/or volatile is a cv-unqualified type. On the other hand, the declaration of an object that is preceded by const and/or volatile is a cv-qualified type. If an object is declared const, the value in its location cannot be changed. A volatile variable is a variable whose value is under the influence of the programmer, and hence cannot be changed by the compiler.Storage Class Specifiers refer to the life, place, and way in which a type exists. Storage class specifiers are static, mutable, thread_local, and extern.

This article explains C++ Qualifiers and Storage Class Specifiers. Thus, some preliminary knowledge in C++ comes in handy to really appreciate the article.

Article Content:

Qualifiers:

const

An object declared constant is an object the storage (location) of whose value cannot be changed. For example, in the statement:

int const theInt = 5;

The value of 5 in the storage for theInt cannot be changed.

volatile

Consider the following statement:

int portVal = 26904873;

Compilers sometimes interfere with the value of a variable with the hope of optimizing the program. The compiler may maintain the value of a variable as constant when it is not supposed to be constant. Object values that have to do with memory-mapped IO ports, or Interrupt Service Routines of peripheral devices, can be interfered with by the compiler. To prevent such interference, make the variable volatile, like:

int volatile portVal;

portVal = 26904873;

or like:

int volatile portVal = 26904873;

Combining const and volatile:

const and volatile may occur in one statement as follows:

int const volatile portVal = 26904873;

cv-qualifiers

A variable preceded with const and/or volatile is a cv-qualified type. A variable not preceded with either const or volatile or both is a cv-unqualified type.

Ordering:

One type can be more cv-qualified than another:

 

  • No cv-qualifier is less than a const qualifier
  • No cv-qualifier is also less than a volatile qualifier
  • No cv-qualifier is less than a const-volatile qualifier
  • const qualifier is less than a const-volatile qualifier
  • volatile qualifier is less than a const-volatile qualifier

It has not yet been concluded if const and volatile are of the same rank.

Array and Instantiated Object:

When an array is declared constant, as in the following statement, it means that the value of each element of the array cannot be changed:

const char arr[] = {'a', 'b', 'c', 'd'};

Whether it’s an ‘a’, ‘b’, ‘c’, or ‘d’, it still can’t be changed to some other value (character).

A similar situation is applies to an instantiated object of a class. Consider the following program:

#include <iostream>
using namespace std;

    class Cla
        {
            public:
            char ch0 = 'a';
            char ch1 = 'b';
            char ch2 = 'c';
            char ch3 = 'd';
        };

int main()
{
    const Cla obj;

    return 0;
}

Due to the statement “const Cla obj;” with const in the main() function, neither ‘a’ nor ‘b’ nor ‘c’ nor ‘d’ can be changed to some other value.

Storage Class Specifiers:

Storage class specifiers are static, mutable, thread_local, and extern.

The static Storage Class Specifier

The static storage class specifier allows the variable to live after its scope has gone through, but it cannot be accessed directly.

The following program illustrates this, with a recursive function:

#include <iostream>
using namespace std;

int funct()
    {
        static int stac = 10;
        cout << stac < 50)
            {
                cout << '\n';
                return 0;
            }
        funct();
    }

int main()
{
    funct();

    return 0;
}

The output is:

10 20 30 40 50

If a static variable is not initialized at its first declaration, it assumes the default value for its type.

The static specifier can also be used with members of a class; the use here is different. Here, it allows the member to be accessed without instantiation for the object.

The following program illustrates this for a data member:

#include <iostream>
using namespace std;

    class Cla
        {
            public:
            static const int num = 8;
        };

int main()
{
    cout << Cla::num << '\n';

    return 0;
}

The output is:

8

The static data member has to be constant. Note that the use of the scope resolution operator to access the static variable outside its scope (in the main function).

The following program illustrates the use of “static” for a member function:

#include <iostream>
using namespace std;

    class Cla
        {
            public:
            static void method ()
                {
                    cout << "Of static member function!" << '\n';
                }
        };

int main()
{
    Cla::method();

    return 0;
}

The output is:

Of static member function!

Note that the use of the scope resolution operator to access the static member function outside its scope (in the main function).

The mutable Specifier

Remember, from above, that if an instantiated object begins with const, the value of any of its normal data members cannot be changed. And for any such data member to be changed, it has to be declared, mutable.

The following program illustrates this:

#include <iostream>
using namespace std;

    class Cla
        {
            public:
            char ch0 = 'a';
            char ch1 = 'b';
            mutable char ch2 = 'c';
            char ch3 = 'd';
        };

int main()
{
    const Cla obj;
    obj.ch2 = 'z';
    cout << obj.ch0 << ' ' << obj.ch1 << ' ' << obj.ch2 << ' ' << obj.ch3 << ' ' << '\n';

    return 0;
}

The output is:

‘a’ ‘b’ ‘z’ ‘d’

The thread_local Specifier

In the normal running of a program, one code segment is executed, then the next code segment, followed by another code segment after that, and so on. That is one thread; the main thread. If two code segments execute at the same time (same duration), then a second thread is needed. The result of the second thread may even be ready before the main thread.

The main() function is like the main thread. A program may have more than two threads for such an asynchronous behavior.

The second thread needs a scope (block scope) in order to operate. This is typically provided by the function scope, a function. A variable in an outer scope that can be seen in the scope of the second thread.

The following short program illustrates the use of the thread_local specifier:

#include <iostream>
#include <thread>
using namespace std;

thread_local int inter = 1;

void thread_function()
{
    inter = inter + 1;
    cout << inter << "nd thread\n";
}

int main()
{
    thread thr(&thread_function);   // thr starts running
    cout << inter << "st or main thread\n";
    thr.join();   // main thread waits for the thread, thr to finish
    return 0;
}

The output is:

1st or main thread

2nd thread

The variable, inter, preceded by thread_local, means that inter has a separate instance in each thread. And that it can be modified in different threads to have different values. In this program, it is assigned the value, 1 in the main thread, and modified to the value, 2 in the second thread.

A thread needs a special object in order to operate. For this program, the library included by “#include <thread>” has a class called a thread, from which the object thr has been instantiated. The constructor for this object takes a reference to the thread function as an argument. The name of the thread function in this program is thread_function().

The join() member function for the special object, at its position employed, makes the main thread wait for the second thread to finish executing before it continues to execute, otherwise, the main() function may exit without the (second) thread having yielded its result.

The extern Specifier

In simple terms, for a declaration, memory is not allocated for the variable or function, while for a definition, memory is allocated. The extern reserved word allows a global variable or function to be declared in one file but defined in another. Such files are called translation units for the complete C++ application.

Type the following program and save it with the file-name, mainFile:

#include <iostream>
using namespace std;

int myInt;

const char ch;

void myFn();

int main()
{
    myFn();
   
    return 0;
}

The variable, myInt, the constant variable, ch, and the function, myFn(), have been declared without being defined.

Type the following program with the definitions, and save it with the file-name, otherFile, in the same directory:

#include <iostream>
using namespace std;

int myInt = 10;

const char ch = 'c';

void myFn()
    {
        cout << "myFn() says " << myInt << " and " << ch <<'\n';
    }

Try to compile the application at the terminal (DOS command prompt) with the following command, and notice that it may not compile:

g++ mainfile.cpp otherFile.cpp -o complete.exe

Now, precede the three declarations in mainFile with the word “extern”, as follows:

extern int myInt;

extern const char ch;

extern void myFn();

Re-save mainFile. Compile the application with:

g++ mainfile.cpp otherFile.cpp -o complete.exe

(This is how separate files for the same application are compiled in C++)

And it should compile. Now, run the application, complete.exe, and the output should be:

myFn() says 10 and c

Note that with the use of “extern”, a constant variable can be declared in one file but defined in another. When dealing with function declaration and definition in different files, the use of extern is optional.

When to use extern? Use it when you do not have header files with global declarations.

“extern” is also used with template declarations – see later.

Conclusion:

A variable preceded with const and/or volatile is a cv-qualified type. A variable, not preceded with either const or volatile or both, is a cv-unqualified type.

Storage class specifiers are static, mutable, thread_local, and extern. These affect the life span (duration), place, and way of employment of variables in an application.

]]>
Exception Handling in C++ https://linuxhint.com/exception-handling-in-c/ Sun, 07 Feb 2021 19:36:52 +0000 https://linuxhint.com/?p=89325 There are three types of software errors that exist. These are Syntax Errors, Logic Errors, and Runtime Errors.

Syntax Errors

A wrongly typed expression, statement, or construction is a syntax error.

Consider the following two statements:

int arr[] = {1, 2, 3}; //correct
int arr = {1, 2, 3}; //syntax error, missing []

They are definitions of the same array. The first one is correct. The second one is missing [], and that is a syntax error. A program with a syntax error does not succeed to compile. The compilation fails with an error message indicating the syntax error. Good thing is that a syntax error can always be fixed if the programmer knows what he is doing.

Logic Error

A logic error is an error committed by the programmer when some wrong logical coding is made. It may be the result of ignorance from the programmer to the programming language features or a misunderstanding of what the program should do.

In this situation, the program is compiled successfully. The program works fine, but it produces wrong results. Such an error may be because of making a loop iterate 5 times when it is made to iterate 10 times. It may also be that a loop is unconsciously made to iterate infinitely. The only way to solve this kind of error is to do careful programming and test the program thoroughly before handing it over to the customer.

Runtime Errors

Wrong or exceptional inputs cause runtime errors. In this case, the program was compiled successfully and works well in many situations. In certain situations, the program crashes (and stops).

Imagine that in a program code segment, 8 has to be divided by a number of denominators. So if the numerator 8 is divided by the denominator 4, the answer (quotient) would be 2. However, if the user inputs 0 as the denominator, the program would crash. Division by 0 is not allowed in Mathematics, and it is also not allowed in computing. Division-by-zero should be prevented in programming. Exception handling handles runtime errors, like division-by-zero. The following program shows how to handle the division-by-zero problem without using the exception feature in C++:

#include <iostream>
using namespace std;

int main()
    {
        int numerator = 8;
        int denominator = 2;

        if (denominator != 0 )
            {
                int result = numerator/denominator;
                cout << result << '\n';
            }
        else
            {
                cout << "Division by zero is not permitted!" << '\n';
            }
   
        return 0;
    }

The output is 4. If the denominator was 0, the output would have been:

“Division by zero is not permitted!”

The main code here is an if-else construct. If the denominator is not 0, the division will take place; if it is 0, the division will not take place. An error message will be sent to the user, and the program continues to run without crashing. Runtime errors are usually handled by avoiding the execution of a code segment and sending an error message to the user.

The exception feature in C++ uses a try-block for the if-block and a catch-block for the else-block to handle the error, just as follows:

#include <iostream>

using namespace std;

int main()
    {
        int numerator = 8;
        int denominator = 2;

        try
            {
                if (denominator != 0 )
                    {
                        int result = numerator/denominator;
                        cout << result << '\n';
                    }
                else
                    {
                        throw 0;
                    }
            }
        catch (int err)
            {
                if (err == 0)
                    cout << "Division by zero is not permitted!" << '\n';
            }
   
        return 0;
    }

Note that the try header does not have an argument. Also note that the catch-block, which is like a function definition, has a parameter. The type of parameter must be the same as the operand (argument) of the throw-expression. The throw-expression is in the try-block. It throws an argument of the programmer’s choice that is related to the error, and the catch-block catches it. In that way, the code in the try-block is not executed. Then, the catch-block displays the error message.

This article explains exception handling in C++. Basic knowledge in C++ is a prerequisite for the reader to understand this article.

Article Content:

Function Throwing an Exception:

A function can also throw an exception just like what the try-block does. The throwing takes place within the definition of the function. The following program illustrates this:

#include <iostream>
using namespace std;

void fn(const char* str)
    {
        if (islower(str[0]))
            throw 'l';
    }

int main()
{
    try
        {
            fn("smith");
        }
    catch (char ch)
        {
            if (ch == 'l')
                cout << "Person's name cannot begin in lowercase!" << '\n';
        }
   
    return 0;
}

Notice that this time, the try block has just the function call. It is the function called that has the throw operation. The catch block catches the exception, and the output is:

“Person's name cannot begin in lowercase!”

This time, the type thrown and caught is a char.

More than One Catch-Blocks for One Try-block:

There can be more than one catch-block for one try-block. Imagine the situation where an input can be any of the characters of the keyboard, but not a digit and not an alphabet. In this case, there must be two catch-blocks: one for an integer to check the digit and one for a char to check the alphabet. The following code illustrates this:

#include <iostream>

using namespace std;

char input = '*';

int main()
{
    try
        {
            if (isdigit(input))
                throw 10;
            if (isalpha(input))
                throw 'z';
        }
    catch (int)
        {
                cout << "Digit input is forbidden!" << '\n';
        }
    catch (char)
        {
                cout << "Character input is forbidden!" << '\n';
        }
   
    return 0;
}

There is no output. If the value of input were a digit, e.g., ‘1’, the output would have been:

"Digit input is forbidden!"

If the value of input were an alphabet, e.g., ‘a’, the output would have been:

"Character input is forbidden!"

Note that in the parameter list of the two catch-blocks, there is no identifier name. Also note that in the definition of the two catch-blocks, the particular arguments thrown have not been verified whether their values are exact or not.

What matters for a catch is the type; a catch must match the type of operand thrown. The particular value of the argument (operand) thrown can be used for further verification if needed.

More than one Handler for the Same Type

It is possible to have two handlers of the same type. When an exception is thrown, control is transferred to the nearest handler with a matching type. The following program illustrates this:

#include <iostream>
using namespace std;

char input = '1';

int main()
{
    try
        {
            if (isdigit(input))
                throw 10;
        }
    catch (int)
        {
            cout << "Digit input is forbidden!" << '\n';
        }
    catch (int)
        {
            cout << "Not allowed at all: digit input!" << '\n';
        }
   
    return 0;
}

The output is:

"Digit input is forbidden!"

Nested try/catch Blocks:

try/catch blocks can be nested. The above program for the input of non-alphanumeric characters from the keyboard is repeated here, but with the alphabetic error code nested:

#include <iostream>

using namespace std;

char input = '*';

int main()
{
    try
        {
            if (isdigit(input))
                throw 10;
            try
                {
                    if (isalpha(input))
                        throw 'z';
                }
            catch (char)
                {
                    cout << "Character input is forbidden!" << '\n';
                }
        }
    catch (int)
        {
                cout << "Digit input is forbidden!" << '\n';
        }
   
    return 0;
}

The error alphabetic try/catch-block is nested in the try-block of the digit code. The operation of this program and the previous operation from which it is copied are the same.

noexcept-specifier

Consider the following function:

void fn(const char* str) noexcept
    {
        if (islower(str[0]))
            throw 'l';
    }

Notice the specifier ‘noexcept’ just after the right parenthesis of the function parameter list. This means that the function should not throw an exception. If the function throws an exception, as in this case, it will compile with a warning message but will not run. An attempt to run the program will call the special function std::terminate(), which should halt the program gracefully instead of just allowing it to literally crash.

The noexcept specifier is in different forms. These are as follows:

    type func() noexcept;              : does not allow a throw expression
    type func() noexcept(true);     : allows a throw expression
    type func() throw();                : does not allow a throw expression
    type func() noexcept(false);    : allows a throw expression, which is optional
    type func();                             : allows a throw expression, which is optional

true or false in the parentheses can be replaced by an expression that results in true or false.

The Special std::terminate() Function:

If an exception cannot be handled, it should be re-thrown. In this case, the thrown expression may or may not have an operand. The special function std::terminate() will be called at runtime, which should halt the program gracefully instead of just allowing it to literally crash.

Type, compile, and run the following program:

#include <iostream>

using namespace std;

char input = '1';

int main()
{
    try
        {
            if (isdigit(input))
                throw 10;
        }
    catch (int)
        {
            throw;
        }
   
    return 0;
}

After a successful compilation, the program terminated without running, and the error message from the author’s computer is:

“terminate called after throwing an instance of ‘int’

Aborted (core dumped)”

Conclusion:

The exception feature in C++ prevents a code segment from executing based on some kind of input. The program continues to execute as necessary. The exception (error prevention) construct consists of a try-block and a catch-block. The try-block has the code segment of interest, which may be bypassed, depending on some input condition. The try-block has the throw expression, which throws an operand. This operand is also called the exception. If the operand type and the type for the parameter of the catch block are the same, then the exception is caught (handled). If the exception is not caught, the program will be terminated, but still, be safe since the code segment that was to be executed to give the wrong result has not been executed. Typical exception handling means bypassing the code segment and sending an error message to the user. The code segment is executed for normal input but bypassed for wrong inputs.

]]>
C++ Access Specifiers https://linuxhint.com/cplusplus-access-specifiers/ Sat, 23 Jan 2021 13:07:22 +0000 https://linuxhint.com/?p=87192 In C++, a class is a set of variables and functions that have been configured to work together. When the variables of the class are given values, an object is obtained. An object has the same variables and functions as a class, but this time, the variables have values. Many objects can be created from one class. One object differs from another object according to the different set of values assigned to the variables of the other object. Creating an object from a class is said to be instantiating the object. Even if two different objects have the same values for their variables, these objects are different entities, identified by different names in the program. The variables for an object and its corresponding class are called data members. The functions of an object and its corresponding class are called member functions. Data members and member functions are called members.

The word access means to read or change the value of a variable, and it also means to use a function. C++ access specifiers are the words, “private,” “protected,” and “public.” They decide whether a member can access other members of its class, or if a function or operator outside the class and not belonging to the class can access any member of the class. They also decide whether a member of a derived (child) class can access a member of a parent class.

Basic knowledge of C++ is required to understand this article and to test the code provided.

Article Content

The Public and Private Specifiers

Class
Any member of a class can access any other member of that same class, independent of which is labeled “public” or “private.” Consider the following program:

#include <iostream>
using namespace std;

class TheCla
    {
        private:
        int num1;
        int num2;
        public:
        TheCla(int n1, int n2)
            {
                num1 = n1; num2 = n2;
            }
        int method()
            {
                return num1;
            }
    };

int main()
{
    TheCla obj(10, 20);
    int no2 = obj.method();
    cout<<no2<<'\n';

    //int no1 = obj.num1;

    return 0;
}

The output is 10. The private members are num1 and num2. The public members are TheCla() and  method(). Note that TheCla() is the constructor function that initializes variables of interest. The region of an access specifier begins from its label to the end of the class description (definition) or to the start of another access specifier.

In the main() function, the first statement is the instantiation involving the constructor function, which initializes num1 and num2. The next statement calls the public member, method(), of the class.

Now, in the class description (definition), the public member function, TheCla(), accesses the private members, num1 and num2. Also, the public member function, method(), accesses the private member,  num1. Any member within a class description can access any other member within the same class description; it does not matter which member is private or public.

However, a function or operator not declared in the class description and outside the class description can access only public members of the class. The main() function, for example, is a function declared outside the class description. It has been able to access only the method() and the TheCla() public members. Inside the main() function, the TheCla() function is obj(10, 20).

An outside function or outside operator, such as the main() function, cannot access any of the private members of the class, such as num1 or num2. Remove the comment indicator, //, from the last-but-one statement in main(). If you attempt to compile the program, note that the program will not compile, giving an error message.

Default Specifier
The default specifier for a class is “private.” So, the above class description is the same as the following description, private, but without the specifier:

class TheCla
    {
        int num1;
        int num2;
        public:
        TheCla(int n1, int n2)
            {
                num1 = n1; num2 = n2;
            }
        int method()
            {
                return num1;
            }
    };

Note: the access specifier label begins with the specifier, and is then followed by a colon.

The Protected Specifier

Within a class description, and from an outside function or outside operator, the protected specifier is the same as the private specifier. Now, replace the private specifier in the above program with the specifier, protect, and remove the comment indicator, //, from the last-but-one statement in the main() function. If you attempt to compile the program, note that the program will not compile, giving an error message.

The issue of the protected specifier comes up when members of the derived (inherited) class must access members of the base (parent) class.

Public Derived Class with Public Members
Consider the following program:

#include <iostream>
using namespace std;

class TheCla
    {
        public:
        int num1 = 10;
        protected:
        int num2 = 20;
        private:
        int num3 = 30;
    };

class ChildCla : public TheCla
    {
        public:
        int method1()
            {
                return num1;
            }
        int method2()
            {
                return num2;
            }
        /*int method3()
            {
                return num3;
        } */

    };

int main()
    {
        ChildCla childObj;
        int no1 = childObj.method1();
        cout<<no1<<'\n';

        int no2 = childObj.method2();
        cout<<no2<<'\n';

        return 0;
    }

The output is:
10
20

In the base class, num1 is public, num2 is protected, and num3 is private. In the derived class, all the member functions are public. The first function, method1(), accesses the public data member, num1. The second function, method2(), accesses the protected data member, num2. The third function, method3(), though currently commented out, should access the private data member, num3.

A derived class is not declared without an access specifier (public, protected, or private). Above, the derived class is declared with the public specifier, that is:

    class ChildCla : public TheCla {}

Now un-comment the third member function definition in the derived class. If you attempt to compile the program, note that it will not compile, giving an error message.

Note: When the whole derived class is declared public, its members cannot access the private members of the base class. Its members can, however, access the public and protected members of the base class. The above program illustrates this.

However, note that a public member of the public derived class can access a protected member of the base class.

Derived Class Specifiers and Member Specifiers

Protected Derived Class with Public Members
Replace the “public” specifier with “protected” in the declaration of the derived class above, as follows:

    class ChildCla : protected TheCla {}

Compile and run the program and note that the result is the same as before.

So, when the whole derived class is declared protected, its members cannot access the private members of the base class. Its members can, however, access the public and protected members of the base class. This is the same as when the derived class is declared public.

Note: a protected member of the public derived class can access a protected member of the base class.

Private Derived Class with Public Members
Replace the “protected” specifier with “private” in the declaration of the derived class above, as follows:

    class ChildCla : private TheCla {}

Compile and run the program and note that the result is the same as before.

So, when the whole derived class is declared private, its members cannot access the private members of the base class. Its members can, however, access the public and protected members of the base class. This is the same as when the derived class is declared protected or public.

Public Derived Class with Protected Members
Type, compile, and run the following program, in which the whole derived class is protected, and its members are also protected. Some code segments are as follows:

#include <iostream>
using namespace std;

class TheCla
    {
        public:
        int num1 = 10;
        protected:
        int num2 = 20;
        private:
        int num3 = 30;
    };

class ChildCla : public TheCla
    {
        protected:
        int method1()
            {
                return num1;
            }
        int method2()
            {
                return num2;
            }
        /*int method3()
            {
                return num3;
            } */

    };

int main()
{
    /*ChildCla childObj;
    int no1 = childObj.method1();
    cout<<no1<<'\n';*/

    /*int no2 = childObj.method2();
    cout<<no2<<'\n'; */

    return 0;
}

The program works as it is. There is no output, and there is not supposed to be any output, based on how the program has been typed.

Now, un-comment the function definition, method3(), in the derived class. If you attempt to compile the program, note that it will not compile, giving an error message. This means that a private member cannot be accessed from an outside function, outside operator, or derived class. This is the same conclusion as was concluded above, concerning access to a private member.

Note: a protected member of the protected derived class can access a protected member of the base class.

Now, put the comments back in the derived class and un-comment the first code segment in the main() function. If you attempt to compile the program, note that the program will not compile because of the first code segment in the main() function. This effect is not new. Apart from the derived class, outside functions, and outside operators, the protected and private members of a (base or derived) class are of the same specifier, private. The main() function sees the protected member of any class, whether base or derived, as of the same specifier, private, and is forbidden from accessing it.

If the second code segment of the main() function is un-commented, the same explanation will apply. That is, the main() function will not be able to access a protected or private member of the derived class or of the base class. This is independent of whether a protected member of the derived class could access a protected member of the base class.

Protected Derived Class with Protected Members
Replace the “public” specifier with “protected” in the declaration of the derived class above, as follows:

    class ChildCla : protected TheCla {}

Put the comment of the code segments back into the main() function, if this has not already been done. Compile and run the program and note that the result is as before.

Private Derived Class with Protected Members
Replace the “protected” specifier with “private” in the declaration of the derived class above, as follows:

    class ChildCla : private TheCla

Compile and run the program and note that the result will be as before.

Public Derived Class with Private Members
Replace the “private” specifier with “public” in the declaration of the derived class above, as follows:

    class ChildCla : public TheCla {}

Make the members of the derived class private. Compile and run the program. The result is not different from the “Public Derived Class with Protected Members” case.

Protected Derived Class with Private Members
Replace the “public” specifier with “protected” in the declaration of the derived class above, as follows:

    class ChildCla : protected TheCla {}

Compile and run the program. This result is no different from the “Protected Derived Class with Protected Members” case.

Private Derived Class with Private Members
Replace the “protected” specifier with “private” in the declaration of the derived class above, as follows:

    class ChildCla : private TheCla {}

Compile and run the program. This result is no different from the “Private Derived Class with Protected Members” case.

Conclusion

C++ access specifiers are the words “private,” “protected,” and “public.” They decide access for members of a class. The region of an access specifier begins from its label, to the end of the class description (definition), or to the start of another access specifier. Any member of a class can access any other member of that same class. A private member of a class cannot be accessed by any outside function, any outside operator, or a derived class.

The member of the base class must be made protected so that a private member of the base class can be accessed by a member of the derived class. This protected member of the base class is seen as a private member of the base class by an outside function or an outside operator.

A public member of a class can be accessed by any outside function, any outside operator, or a derived class.

In the absence of any access specifier in a class, the private specifier is assumed. That is, the default access specifier is private.

References Used in This Work

]]>
How to use C++ Priority_queue? https://linuxhint.com/use-cpp-priority_queue/ Wed, 20 Jan 2021 02:27:02 +0000 https://linuxhint.com/?p=86086 In C++, a queue is a list data structure where the first element to be put in the list is the first element to be removed, when removal is to take place. A priority queue in C++ is similar, but has some ordering; it is the element with the greatest value that is removed first. The priority queue can still be configured so that it is the element with the least value that is removed first. Any queue must have at least the push() function and the pop() function. The push() function adds a new element at the back. For the normal queue, the pop() function removes the first element ever pushed in. For the priority queue, the pop() function removes the element with the highest priority, which could be the biggest or smallest, depending on the ordering scheme.

In order to use the C++ priority_queue, the program should begin with code like:

#include <iostream>
#include <queue>
using namespace std;

It includes the queue library into the program.

In order to continue reading, the reader should have had a basic knowledge of C++.

Article Content

Basic Construction

The data structure has to be constructed first before it can be used. Construction here means instantiating an object from the queue class of the library. The queue object must then have a name given to it by the programmer. The simplest syntax to create a priority queue is:

priority_queue<type> queueName;

With this syntax, the greatest value is removed first. An example of the instantiation is:

priority_queue<int> pq;

or

priority_queue<char> pq;

The vector and the deque are two data structures in C++. A priority_queue can be created with either of them. The syntax to create a priority queue from the vector structure is:

priority_queue<type, vector<same type>, compare> pq;

An example of this instantiation is:

priority_queue<int, vector<int>, less<int> > pq;

Notice the gap between > and > at the end of the declaration. This is to prevent confusion with >>. The default compare code is “less<int>”, meaning the greatest, and not necessarily the first value, would be removed first. So, the creation statement can simply be written as:

priority_queue<int, vector<int> > pq;

If the least value is to be removed first, then the statement has to be:

priority_queue<int, vector<int>, greater<int> > pq;

Important Member Functions

The push() Function
This function pushes a value, which is its argument, into the priority_queue. It returns void. The following code illustrates this:

priority_queue<int> pq;

pq.push(10);
pq.push(30);
pq.push(20);
pq.push(50);
pq.push(40);

This priority_queue has received 5 integer values in the order of 10, 30, 20, 50, 40. If all these elements are to be popped out of the priority queue, then they will come out in the order of 50, 40, 30, 20, 10.

The pop() Function
This function removes from the priority_queue the value with the highest priority. If the compare code is “greater<int>”, then it will remove the element with the smallest value. If called again, it removes the next element with the smallest value of the rest; called again, it removes the next smallest value present, and so on. It returns void. The following code illustrates this:

priority_queue<char, vector<char>, greater<int> > pq;
pq.push('a'); pq.push('c'); pq.push('b'); pq.push('e'); pq.push('d');

Note that in order to call a member function, the name of the object has to be followed by a dot, and then the function.

The top() Function
The pop() function removes the next value of highest priority, but does not return it, as pop() is a void function. Use the top() function in order to know the value of highest priority that has to be removed next. The top() function returns a copy of the value of highest priority in the priority_queue. The following code, where the next value of highest priority is the least value, illustrates this

priority_queue<char, vector<char>, greater<int> > pq;
pq.push('a'); pq.push('c'); pq.push('b'); pq.push('e'); pq.push('d');
char ch1 = pq.top(); pq.pop();
char ch2 = pq.top(); pq.pop();
char ch3 = pq.top(); pq.pop();
char ch4 = pq.top(); pq.pop();
char ch5 = pq.top(); pq.pop();

cout<<ch1<<' '<<ch2<<' '<<ch3<<' '<<ch4<<' '<<ch5<<'\n';

The output is ‘a’ ‘b’ ‘c’ ‘d’ ‘e’.

The empty() Function
If a programmer uses the top() function on an empty priority_queue, after the successful compilation, he would receive an error message such as:

Segmentation fault (core dumped)

So, always check if the priority queue is not empty before using the top() function. The empty() member function returns a bool, true, if the queue is empty, and false if the queue is not empty. The following code illustrates this:

priority_queue<int> pq;
int i1 = 10; int i2 = 30; int i3 = 20; int i4 = 50; int i5 = 40;
pq.push(i1); pq.push(i2); pq.push(i3); pq.push(i4); pq.push(i5);

while(!pq.empty())  
    {  
         cout << pq.top() << ' ';  
         pq.pop();  
    }
cout << '\n';

Other Priority Queue Functions

The size() Function
This function returns the length of the priority queue, as the following code illustrates:

priority_queue<int> pq;
int i1 = 10; int i2 = 30; int i3 = 20; int i4 = 50; int i5 = 40;
pq.push(i1); pq.push(i2); pq.push(i3); pq.push(i4); pq.push(i5);

int len = pq.size();
cout << len << '\n';

The output is 5.

The swap() Function
If two priority_queues are of the same type and size, then they can be swapped by this function, as the following code shows:

priority_queue<int> pq1;
int i1 = 10; int i2 = 30; int i3 = 20; int i4 = 50; int i5 = 40;
pq1.push(i1); pq1.push(i2); pq1.push(i3); pq1.push(i4); pq1.push(i5);

priority_queue<int> pqA;
int it1 = 1; int it2 = 3; int it3 = 2; int it4 = 5; int it5 = 4;
pqA.push(it1); pqA.push(it2); pqA.push(it3); pqA.push(it4); pqA.push(it5);

pq1.swap(pqA);

while(!pq1.empty())  
    {  
         cout << pq1.top() << ' ';  
         pq1.pop();  
    } cout<<'\n';

while(!pqA.empty())  
    {  
         cout << pqA.top() << ' ';  
         pqA.pop();  
    } cout<<'\n';

The output is:

 5  4  3  2  1
 50  40  30  20  10

The emplace() Fuction
The emplace() function is similar to the push function. The following code illustrates this:

priority_queue<int> pq1;
int i1 = 10; int i2 = 30; int i3 = 20; int i4 = 50; int i5 = 40;
pq1.emplace(i1); pq1.emplace(i2); pq1.emplace(i3); pq1.emplace(i4); pq1.emplace(i5);

while(!pq1.empty())  
    {  
         cout << pq1.top() << ' ';  
         pq1.pop();  
    } cout<<'\n';

The output is:

50 40 30 20 10

String Data

When comparing strings, the string class should be used and not the direct use of the string literals because it would compare pointers and not the actual strings. The following code shows how the string class is used:

#include <string>
priority_queue<string> pq1;
string s1 = string("pen"), s2 = string("pencil"), s3 = string("exercise book"), s4 = string("text book"), s5 = string("ruler");

pq1.push(s1); pq1.push(s2); pq1.push(s3); pq1.push(s4); pq1.push(s5);
while(!pq1.empty())  
    {  
         cout << pq1.top() << "   ";  
         pq1.pop();  
    } cout<<'\n';

The output is:

 text book  ruler  pencil  pen  exercise book

Other Priority Queue Constructions

Explicit Creation from a Vector
A priority queue can be created explicitly from a vector as the following code shows:

#include<vector>
vector<int> vtr = {10, 30, 20, 50, 40};

priority_queue<int> pq(vtr.begin(), vtr.end());

while(!pq.empty())  
    {  
         cout << pq.top() << ' ';  
         pq.pop();  
    } cout<<'\n';

The output is: 50 40 30 20 10. This time, the vector header also has to be included. The arguments for the constructor function take the start and end pointers of the vector. The data type for the vector and the data type for the priority_queue must be the same.

In order to make the least value the priority, the declaration for the constructor would be:

priority_queue<int, vector<int>, greater>int> > pq(vtr.begin(), vtr.end());

Explicit Creation from an Array
A priority queue can be created explicitly from an array as the following code shows:

int arr[] = {10, 30, 20, 50, 40};

priority_queue<int> pq(arr, arr+5);

while(!pq.empty())  
    {  
         cout << pq.top() << ' ';  
         pq.pop();  
    } cout<<'\n';

The output is: 50 40 30 20 10. The arguments for the constructor function take the start and end pointers of the array. arr returns the start pointer, “arr+5” returns the pointer just past the array, and 5 is the size of the array. The data type for the array and the data type for the priority_queue must be the same.

In order to make the least value the priority, the declaration for the constructor would be:

priority_queue<int, vector<int>, greater<int> > pq(arr, arr+5);

Note: In C++, the priority_queue is actually called an adaptor, not just a container.

Custom Compare Code

Having all values in the priority queue ascending or all descending is not the only option for the priority queue. For example, a list of 11 integers for a maximum heap is:

88, 86, 87, 84, 82, 79,74, 80, 81, , , 64, 69

The highest value is 88. This is followed by two numbers: 86 and 87, which are less than 88. The rest of the numbers are less than these three numbers, but not really in order. There are two empty cells in the list. The numbers 84 and 82 are less than 86. The numbers 79 and 74 are less than 87. The numbers 80 and 81 are less than 84. The numbers 64 and 69 are less than 79.

The placement of the numbers follow the max-heap criteria – see later. In order to provide such a scheme for the priority_queue, the programmer has to provide his own compare code – see later.

Conclusion

A C++ priority_queue is a first-in-first-out queue. The member function, push(), adds a new value into the queue. The member function, top(), reads the top value in the queue. The member function, pop(), removes without returning the top value of the queue. The member function, empty(), checks if the queue is empty. However, the priority_queue differs from the queue, in that, it follows some priority algorithm. It can be greatest, from first to last, or least, from first to last. The criteria (algorithm) can also be programmer-defined. ]]> Callback Function in C++ https://linuxhint.com/callback-function-in-c/ Tue, 19 Jan 2021 19:06:41 +0000 https://linuxhint.com/?p=86494

A callback function is a function, which is an argument, not a parameter, in another function. The other function can be called the principal function. So two functions are involved: the principal function and the callback function itself. In the parameter list of the principal function, the declaration of the callback function without its definition is present, just as object declarations without assignment are present. The principal function is called with arguments (in main()). One of the arguments in the principal function call is the effective definition of the callback function. In C++, this argument is a reference to the definition of the callback function; it is not the actual definition. The callback function itself is actually called within the definition of the principal function.

The basic callback function in C++ does not guarantee asynchronous behavior in a program.  Asynchronous behavior is the real benefit of the callback function scheme. In the asynchronous callback function scheme, the result of the principal function should be obtained for the program before the result of the callback function is obtained. It is possible to do this in C++; however, C++ has a library called future to guarantee the behavior of the asynchronous callback function scheme.

This article explains the basic callback function scheme. A lot of it is with pure C++. As far as the callback is concerned, the basic behavior of the future library is also explained. Basic knowledge of C++ and its pointers is necessary for the understanding of this article.

Article Content

Basic Callback Function Scheme

A callback function scheme needs a principal function, and the callback function itself. The declaration of the callback function is part of the parameter list of the principal function. The definition of the callback function is indicated in the function call of the principal function. The callback function is actually called within the definition of the principal function. The following program illustrates this:

#include <iostream>

using namespace std;

 

int principalFn(char ch[], int (*ptr)(int))

    {

        int id1 = 1;

        int id2 = 2;

        int idr = (*ptr)(id2);

        cout<<"principal function: "<<id1<<' '<<ch<<' '<<idr<<'\n';

        return id1;

    }


int cb(int iden)

    {

        cout<<"callback function"<<'\n';

        return iden;

    }


int main()

{

    int (*ptr)(int) = &cb;

    char cha[] = "and";

    principalFn(cha, cb);

 

   return 0;

}

The output is:

    callback function

    principal function: 1 and 2

The principal function is identified by principalFn(). The callback function is identified by cb(). The callback function is defined outside the principal function but actually called within the principal function.

Note the declaration of the callback function as a parameter in the parameter list of the principal function declaration. The declaration of the callback function is “int (*ptr)(int)”. Note the callback function expression, like a function call, in the definition of the principal function; any argument for the callback function call is passed there. The statement for this function call is:

    int idr = (*ptr)(id2);

Where id2 is an argument. ptr is part of the parameter, a pointer, that will be linked to the reference of the callback function in the main() function.

Note the expression:

    int (*ptr)(int) = &cb;

In the main() function, which links the declaration (without definition) of the callback function to the name of the definition of the same callback function.

The principal function is called, in the main() function, as:

    principalFn(cha, cb);

Where cha is a string and cb is the name of the callback function without any of its argument.

Synchronous Behavior of Callback Function

Consider the following program:

#include <iostream>

using namespace std;

 

void principalFn(void (*ptr)())

    {

        cout<<"principal function"<<'\n';

        (*ptr)();

    }


void cb()

    {

        cout<<"callback function"<<'\n';

    }


void fn()

    {

        cout<<"seen"<<'\n';

    }


int main()

{

    void (*ptr)() = &cb;

    principalFn(cb);

    fn();

 

   return 0;

}

The output is:

    principal function

    callback function

    seen

There is a new function here. All the new function does, is to display the output, “seen”. In the main() function, the principal function is called, then the new function, fn() is called. The output shows that the code for the principal function was executed, then that for the callback function was executed, and finally that for the fn() function was executed. This is synchronous (single-threaded) behavior.

If it were asynchronous behavior, when three code segments are called in order, the first code segment may be executed, followed instead by the execution of the third code segment, before the second code segment is executed.

Well, the function, fn() can be called from within the definition of the principal function, instead of from within the main() function, as follows:

#include <iostream>

using namespace std;

 

void fn()

    {

        cout<<"seen"<<'\n';

    }


void principalFn(void (*ptr)())

    {

        cout<<"principal function"<<'\n';

        fn();

        (*ptr)();

    }


void cb()

    {

        cout<<"callback function"<<'\n';

    }


int main()

{

    void (*ptr)() = &cb;

    principalFn(cb);

 

   return 0;

}

The output is:

    principal function

    seen

    callback function

This is an imitation of asynchronous behavior. It is not asynchronous behavior. It is still synchronous behavior.

Also, the order of execution of the code segment of the principal function and the code segment of the callback function can be swapped in the definition of the principal function. The following program illustrates this:

#include <iostream>

using namespace std;

 

void principalFn(void (*ptr)())

    {

        (*ptr)();

        cout<<"principal function"<<'\n';

    }


void cb()

    {

        cout<<"callback function"<<'\n';

    }


void fn()

    {

        cout<<"seen"<<'\n';

    }


int main()

{

    void (*ptr)() = &cb;

    principalFn(cb);

    fn();

 

   return 0;

}

The output is now,

    callback function

    principal function

    seen

This is also an imitation of asynchronous behavior. It is not asynchronous behavior. It is still synchronous behavior. True asynchronous behavior can be obtained as explained in the next section or with the library, future.

Asynchronous Behavior with Callback Function

The pseudo-code for the basic asynchronous callback function scheme is:

type output;

type cb(type output)

    {

        //statements

    }


type principalFn(type input, type cb(type output))

    {

        //statements

    }

Note the positions of the input and output data in the different places of the pseudo-code. The input of the callback function is its output. The parameters of the principal function are the input parameter for the general code and the parameter for the callback function. With this scheme, a third function can be executed (called) in the main() function before the output of the callback function is read (still in the main() function). The following code illustrates this:

#include <iostream>

using namespace std;

char *output;


void cb(char out[])

    {

        output = out;

    }

 

void principalFn(char input[], void (*ptr)(char[50]))

    {

        (*ptr)(input);

        cout<<"principal function"<<'\n';

    }


void fn()

    {

        cout<<"seen"<<'\n';

    }


int main()

{

    char input[] = "callback function";

    void (*ptr)(char[]) = &cb;

    principalFn(input, cb);

    fn();

    cout<<output<<'\n';

 

   return 0;

}

The program output is:

    principal function

    seen

    callback function

In this particular code, the output and input datum happens to be the same datum. The result of the third function call in the main() function has been displayed before the result of the callback function. The callback function executed, finished, and assigned its result (value) to the variable, output, allowing the program to continue without its interference. In the main() function, the output of the callback function was used (read and displayed) when it was needed, leading to asynchronous behavior for the whole scheme.

This is the single-threaded way to obtain callback function asynchronous behavior with pure C++.

Basic use of the future Library

The idea of the asynchronous callback function scheme is that the principal function returns before the callback function returns. This was done indirectly, effectively, in the above code.

Note from the above code that the callback function receives the main input for the code and produces the main output for the code. The C++ library, future, has a function called sync(). The first argument to this function is the callback function reference; the second argument is the input to the callback function. The sync() function returns without waiting for the execution of the callback function to complete but allows the callback function to complete. This provides asynchronous behavior. While the callback function continues to execute, since the sync() function has already returned, the statements below it continue to execute. This is like ideal asynchronous behavior.

The above program has been rewritten below, taking into consideration, the future library and its sync() function:

#include <iostream>

#include <future>

#include <string>

using namespace std;

future<string> output;

string cb(string stri)

    {

        return stri;

    }

 

void principalFn(string input)

    {

        output = async(cb, input);

        cout<<"principal function"<<'\n';

    }


void fn()

    {

        cout<<"seen"<<'\n';

    }


int main()

{

    string input = string("callback function");

    principalFn(input);

    fn();

    string ret = output.get();   //waits for callback to return if necessary

    cout<<ret<<'\n';

 

   return 0;

}

The sync() function finally stores the output of the callback function into the future object. The expected output can be obtained in the main() function, using the get() member function of the future object.

Conclusion

A callback function is a function, which is an argument, not a parameter, in another function. A callback function scheme needs a principal function, and the callback function itself. The declaration of the callback function is part of the parameter list of the principal function. The definition of the callback function is indicated in the function call of the principal function (in main()). The callback function is actually called within the definition of the principal function.

A callback function scheme is not necessarily asynchronous. To be sure that the callback function scheme is asynchronous, make the main input to the code, the input to the callback function; make the main output of the code, the output of the callback function; store the output of the callback function in a variable or data structure. In the main() function, after calling the principal function, execute other statements of the application. When the output of the callback function is needed, in the main() function, use (read and display) it there and then.

]]>
C++ Operator Overloading https://linuxhint.com/cpp-operator-overloading/ Tue, 19 Jan 2021 09:52:17 +0000 https://linuxhint.com/?p=86589 This article provides a guide to operator overloading in C++. Operator overloading is a useful and powerful feature of the C++ programming language. C++ allows overloading of most built-in operators. In this tutorial, we will use several examples to demonstrate the operator overloading mechanism.

What is Operator?

An operator is a symbol that indicates to the compiler to perform a particular operation. For example, there are various types of operators in C++, such as Arithmetic Operators, Logical Operators, Relational Operators, Assignment Operators, Bitwise Operators, and more.

What is Operator Overloading?

The C++ language allows programmers to give special meanings to operators. This means that you can redefine the operator for user-defined data types in C++. For example, “+” is used to add built-in data types, such as int, float, etc. To add two types of user-defined data, it is necessary to overload the “+” operator.

Syntax for Operator Overloading

C++ provides a special function called “operator” for operator overloading. The following is the syntax for operator overloading:

class sampleClass
{
    ..............
    Public:
       returnType operator symbol (arguments) {
           ..............
       }
     ..............
};

Here, “operator” is a keyword, and “symbol” is the operator that we want to overload.

Examples

Now that you understand the overall concept of operator overloading, let us go through a couple of working example programs for you to understand this idea more concretely. We will cover the following examples:

  1. Example 1: Unary Operator Overloading (1)
  2. Example 2: Unary Operator Overloading (2)
  3. Example 3: Binary Operator Overloading
  4. Example 4: Relational Operator Overloading

Example 1: Unary Operator Overloading (1)

In this example, we will demonstrate how a unary operator can be overloaded in C++. We have defined the class, “Square_Box,” and the public functions, “operator ++ ()” and “operator ++ (int),” to overload both the prefix and the postfix increment operators. In the “main()” function, we have created the object, “mySquare_Box1.” We have then applied the prefix and postfix increment operators to the “mySquare_Box1” object to demonstrate the unary operator overloading.

#include <iostream>
using namespace std;

class Square_Box
{
   private:
    float length;
    float width;
    float height;

   public:
    Square_Box() {}
    Square_Box(float l, float w, float h)
    {
        length = l;
        width = w;
        height = h;
    }

    // Operator Overloading  - "++" prefix operator
    void operator ++ ()
    {
        length++;
        width++;
        height++;
    }

    // Operator Overloading  - "++" postfix operator
    void operator ++ (int)
    {
        length++;
        width++;
        height++;
    }

    void output()
    {
        cout << "\tLength = " << length << endl;
        cout << "\tWidth = " << width << endl;
        cout << "\tHeight = " << height << endl;
        cout << endl;
    }
};

int main()
{
    Square_Box mySquare_Box1(3.0, 5.0, 6.0);

    cout << "Dimensions of mySquare_Box1 = " << endl;
    mySquare_Box1.output();
   
    mySquare_Box1++;
   
    cout << "Dimensions of mySquare_Box1 = " << endl;
    mySquare_Box1.output();
   
    ++mySquare_Box1;
   
    cout << "Dimensions of mySquare_Box1 = " << endl;
    mySquare_Box1.output();    

    return 0;
}

Example 2: Unary Operator Overloading (2)

This is another example in which we will demonstrate how a unary operator can be overloaded in C++. We have defined the class, “Square_Box,” and the public functions, “operator — ()” and “operator — (int),” to overload both the prefix and postfix decrement operators. In the “main()” function, we have created the “mySquare_Box1” object. We have then applied the prefix and postfix decrement operators to the “mySquare_Box1” object.

#include <iostream>
using namespace std;

class Square_Box
{
   private:
    float length;
    float width;
    float height;

   public:
    Square_Box() {}
    Square_Box(float l, float w, float h)
    {
        length = l;
        width = w;
        height = h;
    }

    // Operator Overloading  - "--" prefix operator
    void operator -- ()
    {
        length--;
        width--;
        height--;
    }

    // Operator Overloading  - "--" postfix operator
    void operator -- (int)
    {
        length--;
        width--;
        height--;
    }

    void output()
    {
        cout << "\tLength = " << length << endl;
        cout << "\tWidth = " << width << endl;
        cout << "\tHeight = " << height << endl;
        cout << endl;
    }
};

int main()
{
    Square_Box mySquare_Box1(3.0, 5.0, 6.0);

    cout << "Dimensions of mySquare_Box1 = " << endl;
    mySquare_Box1.output();
   
    mySquare_Box1--;
   
    cout << "Dimensions of mySquare_Box1 = " << endl;
    mySquare_Box1.output();
   
    --mySquare_Box1;
   
    cout << "Dimensions of mySquare_Box1 = " << endl;
    mySquare_Box1.output();    

    return 0;
}

Example 3: Binary Operator Overloading

Now, we will look at an example of binary operator overloading. The syntax for binary operator overloading will be somewhat different from unary operator overloading. In this example, we will overload the “+” operator to add two “Square_Box” objects.

#include <iostream>
using namespace std;

class Square_Box
{
   private:
    float length;
    float width;
    float height;

   public:
    Square_Box() {}
    Square_Box(float l, float w, float h)
    {
        length = l;
        width = w;
        height = h;
    }

    // Operator Overloading  - "+" operator
    Square_Box operator + (const Square_Box& obj)
    {
        Square_Box temp;
        temp.length = length + obj.length;
        temp.width = width + obj.width;
        temp.height = height + obj.height;
        return temp;
    }

    void output()
    {
        cout << "\tLength = " << length << endl;
        cout << "\tWidth = " << width << endl;
        cout << "\tHeight = " << height << endl;
        cout << endl;
    }
};

int main()
{
    Square_Box mySquare_Box1(3.0, 5.0, 6.0), mySquare_Box2(2.0, 3.0, 5.0), result;

    cout << "Dimensions of mySquare_Box1 = " << endl;
    mySquare_Box1.output();

    cout << "Dimensions of mySquare_Box2 = " << endl;
    mySquare_Box2.output();
   
    result = mySquare_Box1 + mySquare_Box2;
   
    cout << "Dimensions of resultant square box = " << endl;
    result.output();

    return 0;
}

Example 4: Relational Operator Overloading

Now, we will look at an example of relational operator overloading. The syntax for relational operator overloading is just like that of the binary operator overloading. In this example, we will overload the “<” and “>” operators to apply to the “Square_Box” objects.

#include <iostream>
using namespace std;

class Square_Box
{
   private:
    float length;
    float width;
    float height;

   public:
    Square_Box() {}
    Square_Box(float l, float w, float h)
    {
        length = l;
        width = w;
        height = h;
    }

    // Operator Overloading  - "<" operator
    bool operator < (const Square_Box& obj)
    {
        if(length < obj.length)
            return true;
        else
            return false;
    }

   
    // Operator Overloading  - ">" operator
    bool operator > (const Square_Box& obj)
    {
        if(length > obj.length)
            return true;
        else
            return false;
    }
    void output()
    {
        cout << "\tLength = " << length << endl;
        cout << "\tWidth = " << width << endl;
        cout << "\tHeight = " << height << endl;
        cout << endl;
    }
};

int main()
{
    Square_Box mySquare_Box1(2.0, 3.0, 5.0), mySquare_Box2(4.0, 6.0, 8.0);
    bool result;

    cout << "Dimensions of mySquare_Box1 = " << endl;
    mySquare_Box1.output();

    cout << "Dimensions of mySquare_Box2 = " << endl;
    mySquare_Box2.output();
   
    result = mySquare_Box1 < mySquare_Box2;
    cout << "mySquare_Box1 < mySquare_Box2 = " << result < mySquare_Box2;
    cout < mySquare_Box2 = " << result << endl;    

    return 0;
}

Conclusion

C++ is a general-purpose and flexible programming language that is widely used in a variety of domains. This programming language supports both compile-time and run-time polymorphism. This article showed you how to perform operator overloading in C++. This is a very useful feature of C++ that adds some extra effort for the developer to define the operator for overloading, but it definitely makes life easier for the user of the class. ]]> C++ Standard Conversions https://linuxhint.com/c-standard-conversions/ Thu, 14 Jan 2021 19:44:30 +0000 https://linuxhint.com/?p=85704 There are two entity types in C++, the fundamental types and the compound types. The fundamental types are the scalar types. The compound types are the rest of the entity types. Conversion can take place from one entity type to another appropriate type. Consider the following program:

#include
#include  
using namespace std;
int main()
{
    int rt1 = sqrt(5);
    int rt2 = sqrt(8);
    cout<<rt1<<", "<<rt2<<'\n';

    return 0;
}

The output is 2, 2, meaning that the program has returned the square root of 5 as 2 and the square root of 8 also as 2. So, the first two statements in the main() function have floored the answers of the square root of 5 and the square root of 8. This article does not discuss flooring or ceiling in C++. Rather, this article discusses the conversion of one C++ type to another appropriate C++ type; indicating any approximation in value made, loss of precision, or constraint added or removed. Basic knowledge of C++ is a prerequisite to understand this article.

Article Content

Integral Conversions

Integral conversions are integer conversions. Unsigned integers include “unsigned char,” “unsigned short int,” “unsigned int,” “unsigned long int,” and “unsigned long long int.” The corresponding signed integers include “signed char,” “short int,” “int,” “long int,” and “long long int.” Each int type should be held in as many bytes as its predecessor. For most systems, one entity type can be converted to a corresponding type without any issue. The problem occurs when converting from a larger range type to a smaller range type, or when converting a signed number to a corresponding unsigned number.

Each compiler has a maximum value that it can take for the short int. If a number higher than that maximum, meant for an int, is assigned to the short int, the compiler will follow some algorithm and return a number within the range of the short int. If the programmer is lucky, the compiler will warn of trouble with using inappropriate conversion. The same explanation applies to conversions of other int types.

The user should consult the documentation of the compiler to determine the limiting values for each entity type.

If a negative signed short int number is to be converted into an unsigned short int number, the compiler will follow some algorithm and return a positive number within the range of the unsigned short int. This kind of conversion should be avoided. The same explanation applies to conversions of other int types.

Any integer number, except 0, can be converted to Boolean true. 0 is converted to Boolean false. The following code illustrates this:

    int a = -27647;
    float b = 2.5;
    int c = 0;

    bool a1 = a;
    bool b1 = b;
    bool c1 = c;

    cout<<a1<<'\n';
    cout<<b1<<'\n';
    cout<<c1<<'\n';

The output is:

1 for true
1 for true
0 for false

Floating-Point Conversions

Floating-point types include “float,” “double,” and “long double.” Floating-point types are not grouped into signed and unsigned, like integers. Each type can have a signed or unsigned number. A floating-point type should have at least the same precision as its predecessor. That is, “long double” should have equal or greater precision to “double,” and “double” should have equal or greater precision to “float.”

Remember that the range of a floating-point type is not continuous; rather, it is in small steps. The greater the precision of the type, the smaller the steps, and the greater the number of bytes to store the number. So, when a floating-point number is converted from a lower precision type to a higher precision type, the programmer must accept a false increase in precision and a possible increase in the number of bytes for number-storage. When a floating-point number is converted from a higher precision type to a lower precision type, the programmer must accept a loss in precision. If the number of bytes for number-storage must be reduced, then the compiler will follow some algorithm and return a number as a substitute (which is probably not what the programmer wants). Also, bear in mind out-of-range problems.

Floating-Integral Conversions

A floating-point number is converted to an integer by truncating off the fractional part. The following code illustrates this:

    float f = 56.953;
    int i = f;
    cout<<i<<'\n';

The output is 56. The ranges for the float and integer must be compatible.

When an integer is converted into a float, the value displayed as a float is the same as was typed in as an integer. However, the float equivalent might be the exact value or have a slight fractional difference that is not displayed. The reason for the fractional difference is that floating-point numbers are represented in the computer in small fractional steps, and so representing the integer exactly would be a coincidence. So, though the integer displayed as a float is the same as was typed, the display may be an approximation of what is stored.

Integer Conversion Ranking

Any integer type has a rank that has been given to it. This ranking aids in conversion. The ranking is relative; the ranks are not at fixed levels. Except for char and signed char, no two signed integers have the same rank (assuming that char is signed). Unsigned integer types have the same ranking as their corresponding signed integer types. The ranking is as follows:

  • Assuming that char is signed, then char and signed char have the same rank.
  • The rank of a signed integer type is greater than the rank of a signed integer type of a smaller number of storage bytes. So, the rank of signed long long int is greater than the rank of signed long int, which is greater than the rank of signed int, which is greater than the rank of signed short int, which is greater than the rank of signed char.
  • The rank of any unsigned integer type equals the rank of the corresponding signed integer type.
  • The rank of unsigned char equals the rank of signed char.
  • bool has the least rank; its rank is less than that of signed char.
  • char16_t has the same rank as the short int. char32_t has the same rank as the int. For the g++ compiler, wchar_t has the same rank as the int.

Integral Promotions

Integral Promotions is Integer Promotions. There is no reason why an integer of fewer bytes cannot be represented by an integer of greater bytes. Integer Promotions deals with all that follows:

  • A signed short int (two bytes) can be converted to a signed int (four bytes). An unsigned short int (two bytes) can be converted to an unsigned int (four bytes). Note: converting a short int to a long int or a long long int leads to a waste of storage (object location) bytes and a waste of memory. Bool, char16_t, char32_t, and wchar_t are exempted from this promotion (with the g++ compiler, char32_t and wchar_t have the same number of bytes).
  • With the g++ compiler, a char16_t type can be converted to a signed int type or an unsigned int type; a char32_t type can be converted to a signed int type or an unsigned int type; and a wchar_t type can be converted to a signed or unsigned int type.
  • A bool type can be converted to an int type. In this case, true becomes 1 (four bytes) and false becomes 0 (four bytes). Int may be signed or signed.
  • Integer promotion also exists for unscoped enumeration type – see later.

Usual Arithmetic Conversions

Consider the following code:

float f = 2.5;
int i = f;
cout<<i<<'\n';

The code compiles without indicating any warning or error, giving the output of 2, which is probably not what was expected. = is a binary operator because it takes a left and right operand. Consider the following code:

int i1 = 7;
int i2 = 2;
float flt = i1 / i2;
cout<<flt<<'\n';

The output is 3, but this is wrong; it was supposed to be 3.5. The division operator, /, is also a binary operator.

C++ has usual arithmetic conversions that the programmer must know to avoid errors in coding. The usual arithmetic conversions on binary operators are as follows:

  • If either operand is of the type “long double,” then the other will be converted to long double.
  • Else, if either operand is double, the other will be converted to double.
  • Else, if either operand is float, the other will be converted to float. In the above code, the result of i1/i2 is officially 2; that is why flt is 2. The result of the binary, /, is applied as the right operand to the binary operator, =. So, the final value of 2 is a float (not an int).

ELSE, INTEGER PROMOTION WOULD TAKE PLACE AS FOLLOWS:

  • If both operands are of the same type, then no further conversion takes place.
  • Else, if both operands are signed integer types or both are unsigned integer types, then the operand of the type with the lower integer rank will be converted to the type of the operand with the higher rank.
  • Else, if one operand is signed and the other is unsigned, and if the unsigned operand type is greater than or equal to the rank of the signed operand type, and if the value of the signed operand is greater than or equal to zero, then the signed operand will be converted to the unsigned operand type (with range taken into consideration). If the signed operand is negative, then the compiler will follow an algorithm and return a number that may not be acceptable to the programmer.
  • Else, if one operand is a signed integer type and the other is an unsigned integer type, and if all possible values of the type of the operand with the unsigned integer type can be represented by the signed integer type, then the unsigned integer type will be converted to the type of the operand of the signed integer type.
  • Else, the two operands (a char and a bool, for example) would be converted to the unsigned integer type.

Floating-Point Promotion

Floating-point types include “float,” “double,” and “long double.” A floating-point type should have at least the same precision as its predecessor. Floating-point promotion allows for conversion from float to double or from double to long double.

Pointer Conversions

A pointer of one object type cannot be assigned to a pointer of a different object type. The following code will not compile:

int id = 6;
int* intPtr = &id;
float idf = 2.5;
float* floatPtr = &idf;
intPtr = floatPtr; // error here

A null pointer is a pointer whose address value is zero. A null pointer of one object type cannot be assigned to a null pointer of a different object type. The following code will not compile:

int id = 6;
int* intPtr = &id;
intPtr = 0;
float idf = 2.5;
float* floatPtr = &idf;
floatPtr = 0;
intPtr = floatPtr; // error here

A null pointer const of one object type cannot be assigned to a null pointer const of a different object type. The following code will not compile:

int id = 6;
int* intPtr = &id;
int* const intPC = 0;
float idf = 2.5;
float* floatPtr = &idf;
float* const floatPC = 0;
intPC = floatPC; // error here

A null pointer can be given a different address value for its type. The following code illustrates this:

float idf = 2.5;
float* floatPtr = 0;
floatPtr = &idf;
cout<<*floatPtr<<'\n';

The output is 2.5.

As expected, a null pointer constant cannot be assigned any address value of its type. The following code will not compile:

float idf = 2.5;
float* const floatPC = 0;
floatPC = &idf; //error here

However, a null pointer constant can be assigned to an ordinary pointer, but of the same type (this is to be expected). The following code illustrates this:

float idf = 2.5;
float* const floatPC = 0;
float* floatPter = &idf;
floatPter = floatPC; //OK
cout << floatPter << '\n';

The output is 0.

Two null pointer values of the same type compare (==) equal.

A pointer to an object type can be assigned to a pointer to void. The following code illustrates this:

float idf = 2.5;
float* floatPtr = &idf;
void* vd;
vd = floatPtr;

The code compiles without a warning or error message.

Function to Pointer Conversions

A pointer to a function that would not throw an exception can be assigned to a pointer to function. The following code illustrates this:

#include
using namespace std;

    void fn1() noexcept
        {
            cout << "with noexcept" << '\n';
        }

    void fn2()
        {
            //statements
        }

    void (*func1)() noexcept;
    void (*func2)();

int main()
{
    func1 = &fn1;
    func2 = &fn2;
    func2 = &fn1;

    func2();

    return 0;
}

The output is with noexcept.

Boolean Conversions

In C++, entities that can result in false include “zero,” “null pointer,” and “null member pointer.” All other entities result in true. The following code illustrates this:

    bool a = 0.0;    cout << a <<'\n';
    float* floatPtr = 0;
    bool b = floatPtr;    cout << b <<'\n';

    bool c = -2.5;     cout << c <<'\n';
    bool d = +2.5;     cout << d <<'\n';

The output is:

0 //for false
0 //for false
1 //for true
1 //for true

Lvalue, prvalue and xvalue

Consider the following code:

int id = 35;
int& id1 = id;
cout << id1 << '\n';

The output is 35. In the code, id and id1 are lvalues because they identify a location (object) in memory. The output 35 is a prvalue. Any literal, except a string literal, is a prvalue. Other prvalues are not so obvious, as in the examples that follow. Consider the following code:

int id = 62;
int* ptr = &id;
int* pter;

Ptr is an lvalue because it identifies a location (object) in memory. On the other hand, pter is not an lvalue. Pter is a pointer, but it does not identify any location in memory (it is not pointing to any object). So, pter is a prvalue.

Consider the following code:

    void fn()
        {
            //statements
        }

    void (*func)() = &fn;

    float (*functn)();

Fn() and (*func)() are lvalue expressions because they identify an entity (function) in memory. On the other hand, (*functn)() is not an lvalue expression. (*functn)() is a pointer to a function, but it does not identify any entity in memory (it is not pointing to any function in memory). So, (*functn)() is a prvalue expression.

Now, consider the following code:

    struct S
        {
            int n;
        };

    S obj;

S is a class and obj is an object instantiated from the class. Obj identifies an object in memory. A class is a generalized unit. So, S does not really identify any object in memory. S is said to be an unnamed object. S is also a prvalue expression.

The focus of this article is on prvalues. Prvalue means pure rvalue.

Xvalue

Xvalue stands for Expiring Value. Temporary values are expiring values. An lvalue can become an xvalue. A prvalue can also become an xvalue. The focus of this article is on prvalues. An xvalue is an lvalue or an unnamed rvalue reference whose storage can be reused (usually because it is near the end of its lifetime). Consider the following code that works:

    struct S
       {
            int n;
       };

    int q = S().n;

The expression “int q = S().n;” copies whatever value n holds to q. S() is just a means; it is not a regularly used expression. S() is a prvalue whose use has converted it to an xvalue.

Lvalue-to-rvalue Conversions

Consider the following statement:

int ii = 70;

70 is a prvalue (rvalue) and ii is an lvalue. Now, consider the following code:

int ii = 70;

int tt = ii;

In the second statement, ii is in the situation of a prvalue, so ii becomes a prvalue there. In other words, the compiler converts ii to a prvalue implicitly. That is, when an lvalue is used in a situation in which the implementation expects a prvalue, the implementation converts the lvalue to a prvalue.

Array-to-Pointer Conversions

Consider the following code that works:

    char* p;
    char q[] = {'a', 'b', 'c'};
    p = &q[0];
    ++p;
    cout<<*p<<'\n';

The output is b. The first statement is an expression and is a pointer to a character. But to which character is the statement pointing? – No character. So, it is a prvalue and not an lvalue. The second statement is an array in which q[] is an lvalue expression. The third statement turns the prvalue, p, into an lvalue expression, which points to the first element of the array.

Function-to-Pointer Conversions

Consider the following program:

#include
using namespace std;

    void (*func)();

    void fn()
        {
            //statements
        }

int main()
{
    func = &fn;

    return 0;
}

The expression “void (*func)();” is a pointer to a function. But to which function is the expression pointing? – No function. So, it is a prvalue and not an lvalue. Fn() is a function definition, where fn is an lvalue expression. In main(), “func = &fn;” turns the prvalue, func, into an lvalue expression that points to the function, fn().

Temporary Materialization Conversions

In C++, a prvalue can be converted to an xvalue of the same type. The following code illustrates this:

    struct S
       {
            int n;
       };

    int q = S().n;

Here, the prvalue, S(), has been converted to an xvalue. As an xvalue, it would not last long – see more explanation above.

Qualification Conversions

A cv-qualified type is a type qualified by the reserved word, “const,” and/or the reserved word, “volatile.”

Cv-qualification is also ranked. No cv-qualification is less than “const” qualification, which is less than “const volatile” qualification. No cv-qualification is less than “volatile” qualification, which is less than “const volatile” qualification. So, there are two streams of qualification ranking. One type can be more cv-qualified than another.

A lower prvalue cv-qualified type can be converted to a more cv-qualified prvalue type. Both types should be pointer-to-cv.

Conclusion

C++ entities can be converted from one type to a related type implicitly or explicitly. However, the programmer must understand what can be converted and what cannot be converted, and into what form. Conversion can take place in the following domains: Integral Conversions, Floating-Point Conversions, Floating-Integral Conversions, Usual Arithmetic Conversions, Pointer Conversions, Function to Pointer Conversions, Boolean Conversions, Lvalue-to-rvalue Conversions, Array-to-Pointer Conversions, Function-to-Pointer Conversions, Temporary Materialization Conversions, and Qualification Conversions. ]]> C++ Types https://linuxhint.com/c-types/ Tue, 12 Jan 2021 18:29:22 +0000 https://linuxhint.com/?p=85374 A C++ entity is a value, object, reference, function, enumerator, type, class member, bit-field, structured binding, namespace, template, template specialization, or parameter pack. An entity can be of one or more types. There are two categories of C++ types: fundamental and compound types. A scalar is arithmetic or a pointer object type. Fundamental types are scalars, while the rest of the entity types are compound types.

The memory of a computer is a series of cells. Each cell has the size of one byte, it is normally the space occupied by a Western European character. The size of an object is given in bytes. This article gives a summary of C++ types. You should already have basic knowledge of C++, in order to understand this article.

Article Content

– Fundamental Types
– Ways of Constructing Compound Types
– Arrays
– Enumeration
– Class
– Union
– References
– Functions
– Other Compound Types
– Conclusion

Fundamental Types

Fundamental types are scalar types.

bool

A Boolean type or bool type has a value of true or false for 1 or 0. True or false occupies one byte.

char, unsigned char, and signed char

A char is typically for one Western European character. It typically occupies one byte. There is also an unsigned and signed char, which is each an eight-bit integer. Unsigned chars do not involve negative values, while signed chars involve negative values. The kind of value a char holds depends on the compiler and may just be an unsigned char. These three types of chars are called, narrow character types, and each occupies one byte.

Integer

There are five unsigned standard integer types and five signed standard integer types. The five unsigned integer types are: “unsigned char”, “unsigned short int”, “unsigned int”, “unsigned long int”, and “unsigned long long int”. The five corresponding signed integer types are: “signed char”, “short int”, “int”, “long int”, and “long long int”.

“unsigned char” is the same type as the narrow character types (see above). “signed char” is the other type of the narrow character types (see above).

With the g++ compiler, “unsigned char” or “signed char” occupies one byte; “unsigned short int” or “short int” occupies two bytes; “unsigned int” or “int” occupies four bytes; “unsigned long int” or “long int” occupies 8 bytes; “unsigned long long int” or “long long int” still occupies 8 bytes (as of now).

char16_t, char32_t, wchar_t

When dealing with Western European characters, the char type is enough in many situations. However, when dealing with Chinese and other Eastern languages, char16_t, or char32_t, or wchar_t is needed. With the g++ compiler, char16_t occupies two bytes; char32_t occupies four bytes and wchar_t also occupies four bytes.

The bool, the char, the char16_t, the char32_t, the wchar_t, the signed, and the unsigned integer types, form another set, called integral (integer) types.

At this point in the article, two collective types have been mentioned: narrow character types and integral types.

Floating Point Types

Assume that the numbers 457,000 and 457,230 are the same reading, measured by two different measuring instruments. 457,230 is more precise than 457,000 because the value is more detailed (involves smaller places: + 200 plus 30). A floating-point number is a number with a fractional (decimal) part. Though numbers in the computer are a sequence of bits, some floating-point numbers are more precise than the others.

Some measuring instruments take measurements in minimum steps, say 10 units. Such an instrument would have the following readings: 10, 20, 30, 40, . . .100, 110, 130, 140, . . . 200, 210, 220, 230, 240, and so on. Though numbers in the computer are a sequence of bits, floating-point numbers range in some minimum steps (much smaller than 10 units).

C++ has three floating-point types, which are: float, double, and long double. For any compiler, double must have the precision that is higher than that of float or at least that of float; long double must have the precision that is higher than that of double or at least that of double.

There is a third collective name: arithmetic type. This is the name for integral and floating-point types. Note that this is also the name for all the scalar types, as explained so far.

With the g++ compiler, the number of bytes for a float is four; the number of bytes for a double is eight; the number of bytes for a long double is sixteen.

void Type

With the g++ compiler, the size of the void type is one byte. The byte officially has no bits, meaning its location has empty content.

Ways of Constructing Compound Types

Compound types are non-fundamental types. This means that compound types are non-scalar types. This section explains the basics of compound types.

Arrays

The following code segment shows an array of ints and an array of chars:

     int arrInt[] = {1, 2, 3, 4, 5};
     char arrCha[] = {'a', 'b', 'c', 'd', 'e'};

    cout << arrInt[2] <<' ' <<arrCha[2] <<'\n'

The output is: 3 c.

Enumeration

An enumeration is a type, with named constants. Consider the following code segment:

enum {a=3, b, c};
cout << b <<'\n';

The output is: 4. The first line of the code segment is an enumeration, and a, b, or c is an enumerator.

Class

A class is a generalized unit from which many objects of the same generalized unit can be created (instantiated). The following program shows a class and two objects, instantiated from it. Such an object is different from a scalar object.

#include
using namespace std;

class TheCla
    {
        public:
        int num = 5;
        int fn()
            {
                return num;
            }
    };

int main()
{
    TheCla obj1;
    TheCla obj2;

    cout << obj1.num << ' ' << obj2.num <<'\n';

    return 0;
}

The output is: 5 5. The name of the class is TheCla, and the names of the two objects are obj1 and obj2. Note the semicolon just after the description (definition) of the class. Note how the two objects were instantiated in the main() function.

Note: num is a data member and fn is a member function.

Union

struct

A struct is like an array but instead of having index/value pairs, it has name/value pairs. The names may be written in any order. The following program shows a struct and its use:

#include
using namespace std;

struct TheCla
    {
        int num = 5;
        float flt = 2.3;
        char ch = 'a';
    } obj1, obj2;


int main()
{
    cout << obj2.num <<", "<< obj2.flt <<", "<< obj2.ch <<'\n';

    return 0;
}

The output is:

5, 2.3, a

The name of the struct is TheCla. obj1 and obj2 are two different objects of the struct.

Union

The following program shows a union and its use:

#include
using namespace std;

union TheCla
    {
        int num;
        float flt = 2.3;
        char ch;
    } obj1, obj2;


int main()
{
    cout << obj2.flt <<'\n';

    return 0;
}

The output is: 2.3. The union is similar to a struct. The main difference between a struct and a union is that, for a struct, only one member can have a value (initialized) at any one time. In the above program, the member, flt has a value of 2.3. Each of the other members, num or ch, can only have a value next if the value for flt is abandoned.

References

A reference is a synonym for an identifier. The following code segment shows how to obtain a reference to an identifier:

int id = 5;
    int& ref1 = id;
    int& ref2 = id;
    cout << id << ' ' << ref1 << ' ' << ref2 <<'\n';

The output is: 5 5 5. ref1 and ref2 are synonyms to id.

lvalue Reference and rvalue Reference

The above references are lvalue references. The following code shows rvalue reference:

int&& ref = 5;
cout << ref <<'\n';

The output is: 5. This reference is created without identifying any location in memory. In order to achieve this, double & is needed, i.e., &&.

Pointer

A pointer is not really a C++ entity. However, it provides a better scheme for dealing with references. The following code shows how a pointer can be created:

int ptdId = 5;
int ptdId = 5;
    int *ptrId;
    ptrId = &ptdId;

    cout << *ptrId <<'\n';

The output is: 5. Note the difference in name between ptdId and ptdId. ptdId is the pointed object and ptrId is the pointer object. &ptdId returns the address of the pointed object that is assigned to ptrId. To return the value of the pointed object, use *ptrId.

Functions

Basic Function and its Call

The following code shows a basic function definition and its call:

#include
using namespace std;

int fn(int num)
    {
        cout<<"seen"<<'\n';
        return num;
    }

int main()
{
    int ret = fn(5);

    cout << ret <<'\n';

    return 0;
}

The output is

function definition

5

The function call is fn(5). The name of the function is fn.

Reference and Pointer to a Function

&fn return the address in memory of the function whose name is fn. The following statement declares a pointer to a function:

int (*func)();

Here, func is the name of the pointer to the function. The first pair of parentheses differentiate this function pointer from a scalar object pointer. func can be made to hold the address of a function identified by fn, as follows:

func = &fn;

The following program puts the function reference and pointer into action:

#include
using namespace std;

int fn(int num)
    {
        /* some statements */
        return num;
    }

int main()
{
    int (*func)(int);
    func = &fn;
    int ret = func(5);

    cout << ret <<'\n';

    return 0;
}

The output is: 5. Note that both fn and func each have the int parameter in the declaration.

Other Compound Types

The above basic compound types are compound in themselves. They are also used to construct elaborated compound types.

typedef

The typedef reserved word is used to replace a sequence of types with one name (for the sequence). The following code segment illustrates this:

typedef unsigned long int IduIL;

IduIL myInt = 555555555555555555;
cout << myInt <<'\n';

The output is 555555555555555555. In the code, IduIL has become a type that stands for “unsigned long int”.

Structured Binding

Structured binding is a feature that makes it possible for names to be given to subobjects. The following code illustrates this for the array:

int arr[3] = {1, 2, 3};
auto [x, y, z](arr);
cout << x <<' '<< y <<' '<< z <<'\n';

The output is 1 2 3. So, the values: 1, 2, 3 have been given the names, x, y, z. Note the use and position of the reserved word, auto. Also, note the use of the square brackets.

Bit-Field

The memory is a sequence of cells. Each cell takes a byte. Also, each byte consists of eight bits. A group of bits, not necessarily eight bits, can be set and changed. Such a group is called a bit-field. These groups would lie next to one another. If the groups will not make up a type, say 16 bits for a short int, padding bits are added. The following code illustrates this with the struct:

struct Date
        {
            unsigned short wkDay : 3;   //3 bits
            unsigned short monDay : 6;   //6 bits
            unsigned short mon : 5;    //5 bits
            unsigned short yr : 8;    //8 bits for 2 digit year
        } dte;

    dte.wkDay = 1; dte.monDay = 2; dte.mon = 2; dte.yr = 21;

    cout << dte.mon <<'/'<< dte.monDay <<'/'<< dte.yr <<'\n';

The output is: 2/2/21. The total number of bits for wkDay, MonDay, and mon is 3 + 6 + 5 = 14. So, two padding bits would be added to make up 16 bits for the short integer of 2 bytes (16 bits). The next 8 bits begin the next short int, which is then filled up with 8 padding bits.

Note: Avoid using bit-fields; use it only for research.

Namespace

A namespace is a set of names, which should not conflict with the same names of other sets of names. The following program illustrates the use of the same names from two different namespaces, applied in the main() function’s namespace:

#include
using namespace std;

namespace NS1
    {
        int myInt = 8;
        float flt;
    }

namespace NS2
    {
        int myInt = 9;
        float flt;
    }

int main()
{
    cout << NS1::myInt << '\n';
    cout << NS2::myInt << '\n';
    NS1::flt = 2.5;
    NS2::flt = 4.8;
    cout << NS1::flt << '\n';
    cout << NS2::flt << '\n';

    return 0;
}

The output is:

9

8

2.5

4.8

There are two conflicting same int names and two conflicting same float names in the code.

Template and Template Specialization

The template scheme allows the use of a placeholder for different possible scalar types. Specialization is choosing a particular scalar type. The following code illustrates this for a function:

#include
using namespace std;

    template void func (T cha, U no)
        {
            cout << "I need bread for " << cha << no << '.' << '\n';
        }

int main()
{
    func('$', 3);

    return 0;
}

The output is:

“I need bread for $3.”

Template Parameter Pack

Compilers are still to fully implement this feature – see later.

Conclusion

C++ types exist in two categories: fundamental types and compound types. Fundamental types are scalar types. Basic compound types are arrays, enumerations, classes, unions, references, pointers, and functions. These basic compound types are used to construct elaborated compound types, which are typedef, structured bindings, bit-fields, namespace, and template features.

Chrys

]]>
Object Lifetime and Storage Duration in C++ https://linuxhint.com/object-lifetime-and-storage-duration-in-c/ Mon, 04 Jan 2021 15:23:54 +0000 https://linuxhint.com/?p=83572 While creating an object, its location in memory has to be established, before it is initialized. Initialization means putting value into the location. The lifetime of an object starts just after initialization. When an object dies, its location (storage), which the object occupied is released and then the computer is shut down or the storage is taken up (used) by another object. Releasing a storage means, making the identifier or pointer that occupied the storage, invalid. The lifetime of an object ends, when its storage is released.

Some time is needed to create an object. Some time is needed to kill an object. When talking about an object, two things are involved: the location which is the storage, and the value. The meaning of lifetime and storage duration are similar; but the duration is seen more from the point of view of the location than from the point of view of the value. The storage duration is the time from when a location is associated to an object to the time when the location is dissociated from the object.

The rest of this article illustrates the object lifetime, and briefly explains the different storage durations. You should have basic knowledge in C++ in order to understand this article. You should also have knowledge in C++ scope.

Article Content

Illustration of Object Lifetime

Consider the following program:

#include <iostream>
using namespace std;

int main()
{
    if (1 == 1)
        {
            int x;
            x = 1;
            char y;
            y = 'A';
           
            cout << x << y << '\n';
        }

    return 0;
}

The output is, 1A .

The life of an object comes to an end, when it goes out of scope. The lifetime of object x, begins at “x = 1;” and ends at the end of the if-local-scope. The lifetime of object y, begins at “y = 'A';” and ends at the end of the if-local-scope. Before both object die, they are employed in the cout statement .

Storage Duration

Storage duration is determined by one of the following schemes: automatic storage duration; dynamic storage duration; static storage duration; thread storage duration. Storage duration categories, also apply to references.

Automatic Storage Duration

If a variable, is not declared explicitly as static, thread_local, or extern, then that variable has automatic storage duration. Examples are x and y above. The duration of such variables end when they go out of scope. The following program illustrates automatic storage duration for a reference and a pointer, in the global scope.

#include <iostream>
using namespace std;

int x = 1;
int& m = x;

char y = 'A';
char* n = &y;

int main()
{
    cout << m << *n << '\n';

    return 0;
}

The output is, 1A .

The duration of m starts from “int& m = x;” and ends at the end of the program. The duration of n starts from “char* n = &y;” and ends at the end of the program.

Dynamic Storage Duration

Free Store

In a modern computer, more than one program can be running at the same time. Each program has its own portion of memory. The rest of the memory that is not being used by any program, is known as free store. The following expression is used to return a location for an integer from free store

new int

This location (storage) for the integer, returned, still has to be identified by assignment to a pointer. The following code illustrates how to use the pointer with free store:

int *ptrInt = new int;
*ptrInt = 12;

cout<< *ptrInt  <<'\n';

The output is 12 .

To put an end to the life of the object, use the delete expression as follows:

delete ptrInt;

The argument for the delete expression, is a pointer. The following code illustrates its use:

int *ptrInt = new int;
*ptrInt = 12;

delete ptrInt;

A pointer created with the new expression and deleted with the delete expression, is of dynamic storage duration. This pointer dies as it goes out of scope, or is deleted. The duration of the object in the previous code, starts at “*ptrInt = 12;” and ends at the end of the declarative region (scope). There is more to the new and delete expressions than has been discussed here – see later.

Static Storage Duration

Static Object

An object declared static, behaves like the ordinary object, except that its storage duration, begins from when it is initialized to the end of the program. It cannot be seen outside its scope, but it can indirectly be employed from outside its scope.

Consider the following program, which is supposed to count from 1 to 5 (do not test the program) :

#include <iostream>
using namespace std;

int fn()
    {
        int stc = 1;
        cout << ' ' << stc;
        stc = stc + 1;
        if (stc > 5)
            return 0;
        fn();
    }

int main()
{
    fn();

    return 0;
}

The output is 1 1 1 1 1 1 1 1 . . . and never really ending. The function definition is a recurring function; meaning it keeps calling itself until a condition is met.

The solution is to make the stc object static. Once a static object has been initialized, its value cannot be changed, until the program ends. The following program (which you can test), which is the same as the above, but now with stc made static, counts from 1 to 5 :

#include <iostream>
using namespace std;

int fn()
    {
        static int stc = 1;
        cout << ' ' << stc;
        stc = stc + 1;
        if (stc > 5)
            return 0;
        fn();
    }

int main()
{
    fn();

    return 0;
}

The output is: 1 2 3 4 5 .

Note: The duration of a static object begins when the object has been initialized, and ends at the end of the program. In the meantime, the object can be used indirectly, from a different scope. Once a static object has been initialized, its initial value cannot be changed, even if its definition is re-evaluated. In the above code, the stc is not reset, the next time it is called. The next time it is called, it is incremented by “stc = stc + 1;”.

Static Data Member

A set of related variables and function can be put in a generalized unit called a class. If the variables are given particular values, the class becomes an object. However, an object is not created by just assigning values to the variable. The class is instantiated to obtain an object; and each object created has its own name different from other objects of the same class. The following program shows a class, called TheCla and an object, called obj; it also shows how the object is instantiated and used in the main() function:

#include <iostream>
using namespace std;

    class TheCla
        {
            public:
            int num;
            void func (char cha, const char *str)
                {
                    cout << "There are " << num << " books worth " << cha << str << " in the store." << '\n';
                }
        };

int main()
{
    TheCla obj;
    obj.num = 12;
    obj.func('$', "500");

    return 0;
}

The output is:

There are 12 books worth $500 in the store.

Notice, that in order to assign the value of 12 to the variable num, the object has to be instantiated, before the assignment could take place. It is possible for the programmer to assign the value without instantiating (creating) an object. In order to achieve this, the variable, num will have to be declared as static. Then it will be accessed as “TheCla::num” without the object name, but with the class name. The following program illustrates this:

#include <iostream>
using namespace std;

    class TheCla
        {
            public:
            static const int num = 12;
            void func (char cha, const char *str)
                {
                    cout << "There are " << num << " books worth " << cha << str << " in the store." << '\n';
                }
        };

int main()
{
    cout << TheCla::num << '\n';
    TheCla obj;
    obj.func('$', "500");

    return 0;
}

The output is:

12
There are 12 books worth $500 in the store.

Note that in order to access the data member, num in main(), the scope resolution operator, :: had to be used. Also not that the variable, num had to be made constant and initialized in the class description (definition).

Static Member Function

Notice, that in the previous program listing above, in order to use the func function in main(), an object had to be instantiated. It is possible for the programmer to call the function without instantiating (creating) an object. In order to achieve this, the function definition has to be preceded with the word “static”. Then it will be accessed as “TheCla::func()” without the object name, but with the class name. The following program illustrates this for static data member and static member function:

#include <iostream>
using namespace std;

    class TheCla
        {
            public:
            static const int num = 12;
            static void func (char cha, const char *str)
                {
                    cout << "There are " << num << " books worth " << cha << str << " in the store." << '\n';
                }
        };

int main()
{
    TheCla::func('$', "500");

    return 0;
}

The output is:

There are 12 books worth $500 in the store.

Thread Storage Duration

Thread as a feature in C++, has not yet been implemented by the g++ compiler. So, instead of explaining this, the quotation from the C++ specification is given as follows:

  1. All variables declared with the thread_local keyword have thread storage duration. The storage for these entities shall last for the duration of the thread in which they are created. There is a distinct object or reference per thread, and use of the declared name refers to the entity associated with the current thread.
  2. A variable with thread storage duration shall be initialized before its first odr-use and, if constructed, shall be destroyed on thread exit.”

Conclusion

The lifetime of an object begins when its initialization is complete, and ends when its storage is released. Dynamic storage duration starts when the storage created by (new Type) is initialized, and ends when the object goes out of scope or is deleted by “delete pointer”. The duration of a static object begins when the object has been initialized, and ends at the end of the program. Once a static object has been initialized, its initial value cannot be changed, even if its definition is re-evaluated. Static data members and static function members are accessed outside the class description with “ClassName::name”.

Chrys

]]>
C++ Call By Address and Call By Reference https://linuxhint.com/call-by-address-and-call-by-reference-cpp/ Mon, 28 Dec 2020 04:30:00 +0000 https://linuxhint.com/?p=83225

C++ is a flexible general-purpose programming language. It was originally created by Bjarne Stroustrup, a Danish computer scientist, back in 1985. C++ supports three-parameter passing methods, i.e., call by value, call by address, and call by reference. In this article, we are going to discuss about call by address and call by reference mechanism.

What is a function?

Before we jump into the actual topic, we need to understand what the function is in C++. Many of you may already be familiar with functions.

A function is basically a piece of code that can be used to perform a certain task. A function is mainly used to reduce the repetitive code in a C++ program. It takes input as parameters and returns the output as a return value. If we define the function once, we can call/use it multiple times in the later part of our program. That way, we save a lot of repetitive code in the program.

Every C++ program shall have the “main()” function. The “main()” function is the entry point for a C++ program. Apart from the “main()” function, the programmer can define as many functions as they want.

Here is the syntax of defining a function:

Return_type Function_Name (Input parameter List)

Function in C++ can accept 0 or more number of input parameters, whereas it can return only one return-value.

What is Address?

There are two types of variables in C++ (similar to C language) – Data Variable and Address Variable. The address variable is used to store the address of another data variable. For example, let’s consider the following code snippet:

int i = 100;
int *ptr = &i;

Here, the first statement tells us that the variable “i” is a data variable, and it is storing the value 100. In the second statement, we are declaring a pointer variable, i.e. “ptr,” and initializing it with the address of the variable “i”.

What is Reference?

The reference is another powerful feature of C++ language. Let’s consider the following code snippet:

int a = 200;
int &r = a;

In this example, we have declared an integer, i.e. “a” and then declared a reference variable “r”, which is initialized with the value of “a”. So, the reference variable is nothing but an alias of another variable.

Parameter passing methods:

There are three types of parameter passing methods in C++ language:

  1. Call by value / Pass by value
  2. Call by address / Pass by address
  3. Call by reference / Pass by reference

In this article, we are discussing about the – Call by address and Call by reference.

What is Call By Address / Pass by address?

In the case of the Call by address / Pass by address method, the function arguments are passed as address. The caller function passes the address of the parameters. Pointer variables are used in the function definition. With the help of the Call by address method, the function can access the actual parameters and modify them. We will see an example of the Call by address method later section of this article.

What is Call By Reference / Pass by reference?

In the Call by reference / Pass by reference method, the function parameters are passed as a reference. Inside the function definition, the actual parameters are accessed using the reference variable.

Examples:

Now, since we understand the concept of parameter passing methods, we will see several example programs to understand the parameter passing mechanism in C++:

  1. Example-1 – Call by Address (1)
  2. Example-2 – Call by Address (2)
  3. Example-3 – Call by Reference (1)
  4. Example-4 – Call by Reference (2)

The first two examples are given to explain how the Call by address method works in C++. The last two examples are to explain the Call by reference concept.

Example-1 – Call by Address (1)

In this example, we are going to demonstrate the call by address mechanism. From the “main()” function, we are calling the “hello()” function and passing the address of “var”. In the function definition, we are receiving the address of “var” in a pointer variable, i.e., “p”. Inside the function hello, the value of “var” is being changed to 200 with the help of the pointer. Therefore, the value of “var” is getting changed to 200 inside the “main()” function after the “hello()” function call.

#include <iostream>
using namespace std;

void hello(int *p)
{
    cout << endl << "Inside hello() function : " << endl;
    cout << "Value of *p = " << *p << endl;
    *p = 200;
    cout << "Value of *p = " << *p << endl;
    cout << "Exiting hello() function." << endl;
}

int main()
{
    int var = 100;
    cout << "Value of var inside main() function = " << var << endl;
   
    hello(&var);
   
    cout << endl << "Value of var inside main() function = " << var << endl;
   
    return 0;
}

Example-2 – Call by Address (2)

This is another example of the call by address method. In this example, we are going to explain how the call by address method can be used to solve a real-life problem. For example, we want to write a function to swap two variables. If we use the call by value mechanism to swap two variables, the actual variables do not get swapped in the caller function. The call by address method can be used in such a scenario. In this example, we are passing the address of both var_1 (&var_1) and var_2 (&var_2) to “mySwap()” function. Inside the “mySwap()” function, we are swapping the values of these two variables with the help of the pointers. As you can see in the below output, the actual value of these variables is getting swapped in the “main()” function after the “mySwap()” function is executed.

#include <iostream>
using namespace std;

void mySwap(int *vptr_1, int *vptr_2)
{
   int temp_var;
   temp_var = *vptr_1;
   *vptr_1 = *vptr_2;
   *vptr_2 = temp_var;
}

int main()
{
    int var_1 = 100;
    int var_2 = 300;
   
    cout << "Before calling mySwap() function, value of var_1 : " << var_1 << endl;
    cout << "Before calling mySwap() function, value of var_2 : " << var_2 << endl << endl;
       
    cout << "Calling mySwap() function - Call by address." << endl << endl;
    mySwap(&var_1, &var_2);
   
    cout << "After calling mySwap() function, value of var_1 : " << var_1 << endl;
    cout << "After calling mySwap() function, value of var_2 : " << var_2 << endl;
   
    return 0;
}

Example-3 – Call by Reference (1)

In this example, we are going to demonstrate how call by reference works in C++. In the “hello()” function definition, the value is being received as a reference variable (&p). With the help of the reference variable (i.e., p), we are able to change the value of the actual parameter (var) inside the “main()” function.

#include <iostream>
using namespace std;

void hello(int &p)
{
    cout << endl << "Inside hello() function : " << endl;
    cout << "Value of p = " << p << endl;
    p = 200;
    cout << "Value of p = " << p << endl;
    cout << "Exiting hello() function." << endl;
}

int main()
{
    int var = 100;
    cout << "Value of var inside main() function = " << var << endl;
   
    hello(var);
   
    cout << endl << "Value of var inside main() function = " << var << endl;
   
    return 0;
}

Example-4 – Call by Reference(2)

This is another example of a call by reference. In this example, we are going to demonstrate how call by reference works in C++ with the help of a real-world example. The “mySwap()” function is called from the “main()” function with the following parameters – var_1 and var_2. Inside the “mySwap()” function, we are receiving the parameters as reference variables.

#include <iostream>
using namespace std;

void mySwap(int &vref_1, int &vref_2)
{
   int temp_var;
   temp_var = vref_1;
   vref_1 = vref_2;
   vref_2 = temp_var;
}

int main()
{
    int var_1 = 100;
    int var_2 = 300;
   
    cout << "Before calling mySwap() function, value of var_1 : " << var_1 << endl;
    cout << "Before calling mySwap() function, value of var_2 : " << var_2 << endl << endl;
       
    cout << "Calling mySwap() function - Call by reference." << endl << endl;
    mySwap(var_1, var_2);
   
    cout << "After calling mySwap() function, value of var_1 : " << var_1 << endl;
    cout << "After calling mySwap() function, value of var_2 : " << var_2 << endl;
   
    return 0;
}

Conclusion

Understanding the parameter passing methods in C++ is very crucial. The C programming language supports the Call by value and Call by address only. But, C++ supports Call by reference along with the previous two mechanisms. In this article, we have seen several working examples to understand the concept of Call by address and Call by reference. Call by address is a very powerful and popular method in embedded domain applications. ]]> C++ Namespace https://linuxhint.com/c-namespace/ Thu, 24 Dec 2020 19:26:17 +0000 https://linuxhint.com/?p=82893 A namespace in C++ is a generalized scope. Its declaration begins with the reserved word, namespace, followed by a name of the programmer’s choice, and then the block in braces. The block contains basic declarations and/or definitions of C++ objects, functions, and other entities.

Consider the following two scalar statements in a global scope, in the following program:

#include
using namespace std;

int varId = 5;
float varId = 2.3;

int main()
{

    return 0;
}

An attempt to compile this program leads to a compilation error. There are two variables with the same name, varId. Though they are two different variables of two different types, int and float, the compiler rejects the two declarations because they are of the same name. The following program solves this problem by declaring the variables with the same name in two different generalized scopes:

#include
using namespace std;

namespace NA
    {
        int varId = 5;
    }

namespace NB
    {
        float varId = 2.3;
    }

int main()
{
    cout << NA::varId << '\n';
    cout << NB::varId << '\n';

    return 0;
}

The output is as follows:

5
2.3

There are two namespaces in the above program: NA, which has the definition of an integer, and NB, which has the definition of a float but with the same name as the integer for NA. Finally, when the program was run, the same name for two different variables was used. Note that to access the same name of two different variables, the particular name for the namespace must be used, followed by the common identifier. The namespace name and the common identifier are separated by the scope resolution operator, “::.” The name of the namespaces will differentiate the objects.

This article covers the basic concept of a namespace and its usage in the C++ programming language. To follow this article, you should have a basic knowledge of the C++ language. You should also have knowledge of the C++ scope, though it is briefly explained in this article. To learn more about C++ scope, search for the phrase, “Scope in C++” (without quotes) in the search box of any linuxhint.com web page and press Enter. This will lead you to the article this author wrote.

Article Content

What is a Namespace?

A declarative region is the largest part of a program in which the name of an entity (variable) is valid. This region is called a scope. A namespace in C++ is a generalized scope whose main purpose is to solve name conflicts. A namespace has basic declarations and/or definitions of entities.

Global Namespace and Its Problem

The global namespace is the global scope. Consider the following short program:

#include
using namespace std;

int ident = 55;
float ident = 12.17;

int main()
{

    return 0;
}

In the above program, there are two variables, both called ident. These variables are in the global scope; that is, they are in the global namespace. An attempt to compile this program will fail with an error message. The global scope does not accept more than one variable with the same name, so there is a need for a custom namespace.

Custom Namespace

A namespace does not have only one name. Instead, a namespace has a set of names to avoid conflict with other sets of names. To avoid conflict lower in the code, precede each name with the name of the namespace and :: . The following program illustrates this using two custom namespaces:

#include
using namespace std;

namespace NA
    {
        int varInt = 6;
        float flt;
    }

namespace NB
    {
        int varInt = 7;
        float flt;
    }

int main()
{
    cout << NA::varInt << '\n';
    cout << NB::varInt << '\n';
    NA::flt = 2.5;
    NB::flt = 4.8;
    cout << NA::flt << '\n';
    cout << NB::flt << '\n';

    return 0;
}

The output is:

6
7
2.5
4.8

Note that the names NA::flt and NB::flt have ultimately been defined in the main() function. C++ does not allow such a definition in the global scope.

Note that the custom namespace is a nested namespace for the global namespace.

The using Directive

To avoid typing “namepace::name” all the time instead of just “name” after declaring the namespace, you may use the using directive. The syntax to use the using directive is as follows:

using namespace Namespace_name;

The using directive is not a preprocessor directive, so it ends with a semicolon (;).

The following program illustrates the use of the using directive and more:

#include
using namespace std;

namespace NB
    {
        int varInt = 7;
        int func ()
            {
                return varInt;
            }
    }

int fn()
    {
        using namespace NB;
        int myVar2 =  func();
        //other objects and functions from NB follow.
        return myVar2;
    }

int myVar3 = NB::func();

int main()
{
    cout << fn() << ' ' << myVar3 << '\n';

    return 0;
}

The output of this program is 7 7. The term “using namespace NB;” has been placed at the beginning of the fn() definition. The func() from the NB namespace is called just below that, without preceding with “NB::.”

A variable declared in the global scope (global namespace) is seen from the point of declaration to the end of the file. It is also seen in the nested namespaces (nested scopes), such as the nested fn() function scope above. The using directive joins its namespace from the position at which it is placed to the end of the scope in which it is placed.

The name func() from the NB namespace cannot be seen below the fn() definition because “using namespace NB;” was placed within the function scope (block). Under this condition, to use “func()” outside the NB namespace block (scope), it must be preceded by “NB::,” as in the following statement:

int myVar3 = NB::func();

The using directive joins its namespace with the outer nesting namespace from the position at which it is placed to the end of the outer nesting namespace. In the following program, the NA namespace is joined with the global namespace. Both namespaces then extend into the fn() function definition namespace, in which they are joined with the NB namespace. The NB namespace ends at the end of the fn() function definition, and the two previous namespaces carry on until the end of the file (read through the code).

#include
using namespace std;

namespace NA
    {
        int varInt = 6;
        int func ()
            {
                return varInt;
            }
       
    }

namespace NB
    {
        int varInt = 7;
        int func ()
            {
                return varInt;
            }
    }

using namespace NA;
int myVar0 = varInt;
//other objects and functions from :: and NB follow.

int fn()
    {
        int myVar1 = varInt;
        using namespace NB;
        int myVar2 =  NB::func();
        //other objects and functions from NB follow, till end of this scope.
        return myVar1 + myVar2;
    }

//Only objects and functions from :: and NB follow.

int myVar3 = NB::func();

int main()
{
    cout << myVar0 << ' ' << fn() << ' ' << myVar3 << '\n';

    return 0;
}

The output is 6, 13, 7.

Note: The global namespace is indicated with :: , meaning that there is nothing before the scope resolution operator that follows.

Below the statement, the “using namespace NA;” variables from the global and NA namespaces can be used without an indication of their source namespace. The next statement uses the varInt of the NA namespace. The global and NA combined namespace region extends into the fn() function namespace. So, the varInt of the first statement in the fn() function scope, is of the NA namespace.

Since the region for the global and NA namespaces extend throughout the fn() scope, after the “int myVar2 = NB::func();,” any name from the NB namespace can only be used in the fn() scope without preceding it with “NB::,” only if it did not occur in the NA and global namespaces (blocks). Otherwise, it should be preceded by “NB::.” The region of the combined namespaces for NA and global continue below the fn() definition and into the main() function until the end of the file.

The extension of the NB namespace begins from “int myVar2 = NB::func();” in the fn() block and ends at the end of the fn() definition block.

Note: Namespaces whose regions are joined should not have the same variable name in their different namespace blocks, as this would still cause conflict.

Namespace Regions

A namespace is a scope. Apart from the global namespace (global scope), any namespace should be declared in a block. That block is the first part of the possibly distributed regions of the namespace. With the using directive, the namespace can be extended as regions in other scopes.

Entities declared in a namespace body are said to be members of the namespace, and names introduced by these declarations into the declarative region of the namespace are said to be member names of the namespace.

Nested Namespaces

The following program shows nested namespaces:

#include
using namespace std;

namespace A
    {
        int i = 1;
        namespace B
            {
                int i = 2;
                namespace C
                    {
                        int i = 3;
                    }
            }
    }

int main()
{
    cout << A::i << ' ' << A::B::i << ' ' <<  A::B::C::i << '\n';

    return 0;
}

The output is:

1 2 3

Notice that the three values have been accessed using the scope resolution operator.

Standard Namespace

C++ has a library called the standard library. The names of objects, functions, and other entities in this library are from a namespace called the standard namespace, written as std. The standard library contains sub-libraries, and one of these sub-libraries is iostream. The iostream library contains the object cout, which is used to send results to the console (terminal).

The name cout must be in the std namespace. To use iostream with its std namespace, the program should be as follows:

#include <iostream>
using namespace std;

Note the use of the using directive and std. The term “#include <iostream>” is a preprocessor directive and does not end with a semicolon. It includes the iostream “file” at the position of its directive.

Conclusion

A namespace is a scope. The namespace description (definition) contains basic declarations and/or definitions of C++ objects, functions, and other entities. Outside the namespace definition, the name can be accessed with the syntax, “namespaceName::name.” Apart from the global namespace (global scope), any namespace should be declared in a block. That block is the first part of the possibly distributed regions of the namespace. With the using directive, the namespace can be extended as regions in other scopes. Namespaces whose regions are joined should not have the same variable name in their different namespace blocks, as this would still cause name conflict.

Chrys

]]>
Expression Category Taxonomy in C++ https://linuxhint.com/expression_category_taxonomy_c/ Fri, 18 Dec 2020 17:19:12 +0000 https://linuxhint.com/?p=82122

A computation is any type of calculation that follows a well-defined algorithm. An expression is a sequence of operators and operands that specifies a computation. In other words, an expression is an identifier or a literal, or a sequence of both, joined by operators.In programming, an expression can result in a value and/or cause some happening. When it results in a value, the expression is a glvalue, rvalue, lvalue, xvalue, or prvalue. Each of these categories is a set of expressions. Each set has a definition and particular situations where its meaning prevails, differentiating it from another set. Each set is called a value category.

Note: A value or literal is still an expression, so these terms classify expressions and not really values.

glvalue and rvalue are the two subsets from the big set expression. glvalue exists in two further subsets: lvalue and xvalue. rvalue, the other subset for expression, also exists in two further subsets: xvalue and prvalue. So, xvalue is a subset of both glvalue and rvalue: that is, xvalue is the intersection of both glvalue and rvalue. The following taxonomy diagram, taken from the C++ specification, illustrates the relationship of all the sets:

prvalue, xvalue, and lvalue are the primary category values. glvalue is the union of lvalues and xvalues, while rvalues are the union of xvalues and prvalues.

You need basic knowledge in C++ in order to understand this article; you also need knowledge of Scope in C++.

Article Content

Basics

To really understand the expression category taxonomy, you need to recall or know the following basic features first: location and object, storage and resource, initialization, identifier and reference, lvalue and rvalue references, pointer, free store, and re-using of a resource.

Location and Object

Consider the following declaration:

int ident;

This is a declaration that identifies a location in memory. A location is a particular set of consecutive bytes in memory. A location can consist of one byte, two bytes, four bytes, sixty-four bytes, etc. The location for an integer for a 32bit machine is four bytes. Also, the location can be identified by an identifier.

In the above declaration, the location does not have any content. It means that it does not have any value, as the content is the value. So, an identifier identifies a location (small continuous space). When the location is given a particular content, the identifier then identifies both the location and the content; that is, the identifier then identifies both the location and the value.

Consider the following statements:

int ident1 = 5;

int ident2 = 100;

Each of these statements is a declaration and a definition. The first identifier has the value (content) 5, and the second identifier has the value 100. In a 32bit machine, each of these locations is four bytes long. The first identifier identifies both a location and a value. The second identifier also identifies both.

An object is a named region of storage in memory. So, an object is either a location without a value or a location with a value.

Object Storage and Resource

The location for an object is also called the storage or resource of the object.

Initialization

Consider the following code segment:

int ident;

ident = 8;

The first line declares an identifier. This declaration provides a location (storage or resource) for an integer object, identifying it with the name, ident. The next line puts the value 8 (in bits) into the location identified by ident. The putting of this value is initialization.

The following statement defines a vector with content, {1, 2, 3, 4, 5}, identified by vtr:

std::vector vtr{1, 2, 3, 4, 5};

Here, the initialization with {1, 2, 3, 4, 5}is done in the same statement of the definition (declaration). The assignment operator is not used. The following statement defines an array with content {1, 2, 3, 4, 5}:

int arr[] = {1, 2, 3, 4, 5};

This time, an assignment operator has been used for the initialization.

Identifier and Reference

Consider the following code segment:

int ident = 4;

int& ref1 = ident;

int& ref2 = ident;

cout<< ident <<' '<< ref1 <<' '<< ref2 << '\n';

The output is:

4 4 4

ident is an identifier, while ref1 and ref2 are references; they reference the same location. A reference is a synonym to an identifier. Conventionally, ref1 and ref2 are different names of one object, while ident is the identifier of the same object. However, ident can still be called the name of the object, which means, ident, ref1, and ref2 name the same location.

The main difference between an identifier and a reference is that, when passed as an argument to a function, if passed by identifier, a copy is made for the identifier in the function, while if passed by reference, the same location is used within the function. So, passing by identifier ends up with two locations, while passing by reference ends up with the same one location.

lvalue Reference and rvalue Reference

The normal way to create a reference is as follows:

int ident;

ident = 4;

int& ref = ident;

The storage (resource) is located and identified first (with a name such as ident), and then a reference (with a name such as a ref) is made. When passing as an argument to a function, a copy of the identifier will be made in the function, while for the case of a reference, the original location will be used (referred to) in the function.

Today, it is possible to just have a reference without identifying it. This means that it is possible to create a reference first without having an identifier for the location. This uses &&, as shown in the following statement:

int&& ref = 4;

Here, there is no preceding identification. To access the value of the object, simply use ref as you would use the ident above.

With the && declaration, there is no possibility of passing an argument to a function by identifier. The only choice is to pass by reference. In this case, there is only one location used within the function and not the second copied location as with an identifier.

A reference declaration with & is called lvalue reference. A reference declaration with && is called rvalue reference, which is also a prvalue reference (see below).

Pointer

Consider the following code:

int ptdInt = 5;

int *ptrInt;

ptrInt = &ptdInt;

cout<< *ptrInt <<'\n';

The output is 5.

Here, ptdInt is an identifier like the ident above. There are two objects (locations) here instead of one: the pointed object, ptdInt identified by ptdInt, and the pointer object, ptrInt identified by ptrInt. &ptdInt returns the address of the pointed object and puts it as the value in the pointer ptrInt object. To return (obtain) the value of the pointed object, use the identifier for the pointer object, as in “*ptrInt”.

Note: ptdInt is an identifier and not a reference, while the name, ref, mentioned previously, is a reference.

The second and third lines in the above code can be reduced to one line, leading to the following code:

int ptdInt = 5;

int *ptrInt = &ptdInt;

cout<< *ptrInt <<'\n';

Note: When a pointer is incremented, it points to the next location, which is not an addition of the value 1. When a pointer is decremented, it points to the previous location, which is not a subtraction of the value 1.

Free Store

An operating system allocates memory for each program that is running. A memory that is not allocated to any program is known as the free store. The expression that returns a location for an integer from the free store is:

new int

This returns a location for an integer that is not identified. The following code illustrates how to use the pointer with the free store:

int *ptrInt = new int;

*ptrInt = 12;

cout<< *ptrInt  <<'\n';

The output is 12.

To destroy the object, use the delete expression as follows:

delete ptrInt;

The argument to the delete expression is a pointer. The following code illustrates its use:

int *ptrInt = new int;

*ptrInt = 12;

delete ptrInt;

cout<< *ptrInt <<'\n';

The output is 0, and not anything like null or undefined. delete replaces the value for the location with the default value of the particular type of the location, then allows the location for re-use. The default value for an int location is 0.

Re-using a Resource

In expression category taxonomy, reusing a resource is the same as reusing a location or storage for an object. The following code illustrates how a location from free store can be reused:

int *ptrInt = new int;

*ptrInt = 12;

cout<< *ptrInt <<'\n';

delete ptrInt;

cout<< *ptrInt <<'\n';

*ptrInt = 24;

cout<< *ptrInt <<'\n';

The output is:

12

0

24

A value of 12 is first assigned to the unidentified location. Then the content of the location is deleted (in theory the object is deleted). The value of 24 is re-assigned to the same location.

The following program shows how an integer reference returned by a function is reused:

#include <iostream>

using namespace std;

int& fn()

{

int i = 5;

int& j = i;

return j;

}

int main()

{

int& myInt = fn();

cout<< myInt <<'\n';

myInt = 17;

cout<< myInt <<'\n';

return 0;

}

The output is:

5

17

An object such as i, declared in a local scope (function scope), ceases to exist at the end of the local scope. However, the function fn() above, returns the reference of i. Through this returned reference, the name, myInt in the main() function, reuses the location identified by i for the value 17.

lvalue

An lvalue is an expression whose evaluation determines the identity of an object, bit-field, or function. The identity is an official identity like ident above, or an lvalue reference name, a pointer, or the name of a function. Consider the following code which works:

int myInt = 512;

int& myRef = myInt;

int* ptr = &myInt;

int fn()

{

++ptr; --ptr;

return myInt;

}

Here, myInt is an lvalue; myRef is an lvalue reference expression; *ptr is an lvalue expression because its result is identifiable with ptr; ++ptr or –ptr is an lvalue expression because its result is identifiable with the new state (address) of ptr, and fn is an lvalue (expression).

Consider the following code segment:

int a = 2, b = 8;

int c = a + 16 + b + 64;

In the second statement, the location for ‘a’ has 2 and is identifiable by ‘a’, and so is an lvalue. The location for b has 8 and is identifiable by b, and so is an lvalue. The location for c will have the sum, and is identifiable by c, and so is an lvalue. In the second statement, the expressions or values of 16 and 64 are rvalues (see below).

Consider the following code segment:

char seq[5];

seq[0]='l', seq[1]='o', seq[2]='v', seq[3]='e', seq[4]='\0';

cout<< seq[2] <<'\n';

The output is ‘v’;

seq is an array. The location for ‘v’ or any similar value in the array is identified by seq[i], where i is an index. So, the expression, seq[i], is an lvalue expression. seq, which is the identifier for the whole array, is also an lvalue.

prvalue

A prvalue is an expression whose evaluation initializes an object or a bit-field or computes the value of the operand of an operator, as specified by the context in which it appears.

In the statement,

int myInt = 256;

256 is a prvalue (prvalue expression) that initializes the object identified by myInt. This object is not referenced.

In the statement,

int&& ref = 4;

4 is a prvalue (prvalue expression) that initializes the object referenced by ref. This object is not identified officially. ref is an example of an rvalue reference expression or prvalue reference expression; it is a name, but not an official identifier.

Consider the following code segment:

int ident;

ident = 6;

int& ref = ident;

6 is a prvalue that initializes the object identified by ident; the object is also referenced by ref. Here, the ref is an lvalue reference and not a prvalue reference.

Consider the following code segment:

int a = 2, b = 8;

int c = a + 15 + b + 63;

15 and 63 are each a constant that computes to itself, producing an operand (in bits) for the addition operator. So, 15 or 63 is a prvalue expression.

Any literal, except the string literal, is a prvalue (i.e., a prvalue expression). So, a literal such as 58 or 58.53, or true or false, is a prvalue. A literal can be used to initialize an object or would compute to itself (into some other form in bits) as the value of an operand for an operator. In the above code, the literal 2 initializes the object, a. It also computes itself as an operand for the assignment operator.

Why is a string literal not a prvalue? Consider the following code:

char str[] = "love not hate";

cout << str <<'\n';

cout << str[5] <<'\n';

The output is:

love not hate

n

str identifies the whole string. So, the expression, str, and not what it identifies, is an lvalue. Each character in the string can be identified by str[i], where i is an index. The expression, str[5], and not the character it identifies, is an lvalue. The string literal is an lvalue and not a prvalue.

In the following statement, an array literal initializes the object, arr:

ptrInt++ or  ptrInt-- 

Here, ptrInt is a pointer to an integer location. The whole expression, and not the final value of the location it points to, is a prvalue (expression). This is because the expression, ptrInt++ or ptrInt–, identifies the original first value of its location and not the second final value of the same location. On the other-hand, –ptrInt or  –ptrInt is an lvalue because it identifies the only value of the interest in the location. Another way of looking at it is that the original value computes the second final value.

In the second statement of the following code, a or b can still be considered as a prvalue:

int a = 2, b = 8;

int c = a + 15 + b + 63;

So, a or b in the second statement is an lvalue because it identifies an object. It is also a prvalue since it computes to the integer of an operand for the addition operator.

(new int), and not the location it establishes is a prvalue. In the following statement, the return address of the location is assigned to a pointer object:

int *ptrInt = new int

Here, *ptrInt is an lvalue, while (new int) is a prvalue. Remember, an lvalue or a prvalue is an expression. (new int) does not identify any object. Returning the address does not mean identifying the object with a name (such as ident, above). In *ptrInt, the name, ptrInt, is what really identifies the object, so *ptrInt is an lvalue. On the other hand, (new int) is a prvalue, as it computes a new location to an address of operand value for the assignment operator =.

xvalue

Today, lvalue stands for Location Value; prvalue stands for “pure” rvalue (see what rvalue stands for below). Today, xvalue stands for “eXpiring” lvalue.

The definition of xvalue, quoted from the C++ specification, is as follows:

“An xvalue is a glvalue that denotes an object or bit-field whose resources can be reused (usually because it is near the end of its lifetime). [Example: Certain kinds of expressions involving rvalue references yield xvalues, such as a call to a function whose return type is an rvalue reference or a cast to an rvalue reference type— end example]”

What this means is that both lvalue and prvalue can expire. The following code (copied from above) shows how the storage (resource) of the lvalue, *ptrInt is re-used after it has been deleted.

int *ptrInt = new int;

*ptrInt = 12;

cout<< *ptrInt <<'\n';

delete ptrInt;

cout<< *ptrInt <<'\n';

*ptrInt = 24;

cout<< *ptrInt <<'\n';

The output is:

12

0

24

The following program (copied from above) shows how the storage of an integer reference, which is an lvalue reference returned by a function, is reused in the main() function:

#include <iostream>

using namespace std;

int& fn()

{

int i = 5;

int& j = i;

return j;

}

int main()

{

int& myInt = fn();

cout<< myInt <<'\n';

myInt = 17;

cout<< myInt <<'\n';

return 0;

}

The output is:

5

17

When an object such as i in the fn() function goes out of scope, it naturally is destroyed. In this case, the storage of i has still been reused in the main() function.

The above two code samples illustrate the re-use of the storage of lvalues. It is possible to have a storage re-use of prvalues (rvalues) (see later).

The following quote concerning xvalue is from the C++ specification:

“In general, the effect of this rule is that named rvalue references are treated as lvalues and unnamed rvalue references to objects are treated as xvalues. rvalue references to functions are treated as lvalues whether named or not.” (see later).

So, an xvalue is an lvalue or a prvalue whose resources (storage) can be reused. xvalues is the intersection set of lvalues and prvalues.

There is more to xvalue than what has been addressed in this article. However, xvalue deserves a whole article on its own, and so the extra specifications for xvalue are not addressed in this article.

Expression Category Taxonomy Set

Another quotation from the C++ specification:

Note: Historically, lvalues and rvalues were so-called because they could appear on the left- and right-hand side of an assignment (although this is no longer generally true); glvalues are “generalized” lvalues, prvalues are “pure” rvalues, and xvalues are “eXpiring” lvalues. Despite their names, these terms classify expressions, not values. — end note”

So, glvalues is the union set of lvalues and xvalues and rvalues are the union set of xvalues and prvalues. xvalues is the intersection set of lvalues and prvalues.

As of now, the expression category taxonomy is better illustrated with a Venn diagram as follows:

Conclusion

An lvalue is an expression whose evaluation determines the identity of an object, bit-field, or function.

A prvalue is an expression whose evaluation initializes an object or a bit-field or computes the value of the operand of an operator, as specified by the context in which it appears.

An xvalue is an lvalue or a prvalue, with the additional property that its resources (storage) can be reused.

The C++ specification illustrates expression category taxonomy with a tree diagram, indicating that there is some hierarchy in the taxonomy. As of now, there is no hierarchy in the taxonomy, so a Venn diagram is used by some authors, as it illustrates the taxonomy better than the tree diagram.

]]>
Scope in C++ https://linuxhint.com/scope-in-cpp/ Mon, 14 Dec 2020 04:52:02 +0000 https://linuxhint.com/?p=81447 An entity in C++ has a name, which can be declared and/or defined. A declaration is a definition, but a definition is not necessarily a declaration. A definition allocates memory for the named entity, but a declaration may or may not allocate memory for the named entity. A declarative region is the largest part of a program in which the name of an entity (variable) is valid. That region is called a scope or a potential scope. This article explains scoping in C++. Furthermore, basic knowledge in C++ is needed to understand this article.

Article Content

Declarative Region and Scope

A declarative region is the largest part of a program text in which the name of an entity is valid. It is the region in which the unqualified name can be used (seen) to refer to the same entity. Consider the following short program:

#include <iostream>
using namespace std;

void fn()
    {
        int var = 3;
        if (1==1)
        {
            cout<<var<<'\n';
        }
    }

int main()
{
    fn();
    return 0;
}

The function fn() has two blocks: an inner block for the if-condition and an outer block for the function body. The identifier, var, is introduced and seen in the outer block. It is also seen in the inner block, with the cout statement. The outer and inner blocks are both the scope for the name, var.

However, the name, var, can still be used to declare a different entity such as a float in the inner block. The following code illustrates this:

#include <iostream>
using namespace std;

void fn()
    {
        int var = 3;
        if (1==1)
        {
            float var = 7.5;
            cout<<var<<'\n';
        }
    }

int main()
{
    fn();
    return 0;
}

The output is 7.5. In this case, the name, var, can no longer be used in the inner block to refer to the integer of value 3, which was introduced (declared) in the outer block. Such inner blocks are referred to as potential scope for entities declared in the outer block.

Note: An entity of the same type, like that of the outer block, can still be declared in the inner block. However, in this case, what is valid in the inner block is the new declaration and its meaning, while the old declaration and its meaning outside of the inner block remain valid in the outer block.

A declaration of the same name in an inner block normally overrides the declaration of the same name outside that inner block. Inner blocks can nest other inner blocks.

Global Scope

When a programmer just starts typing a file, that is the global scope. The following short program illustrates this:

#include <iostream>
using namespace std;

float var = 9.4;

int main()
{
    cout <<var<<'\n';
    cout <<::var<<'\n';

    return 0;
}

The output is:
9.4
9.4

In this case, the declarative region or scope for var begins from the point of declaration for var, continues downward until the end of the file (translation unit).

The block of the main() function is a different scope; it is a nested scope for the global scope. To access an entity of the global scope, from a different scope, the identifier is used directly or preceded by the scope resolution operator, :: .

Note: The entity, main(), is also declared in the global scope.

Block Scope

The if, while, do, for, or switch statement can each define a block. Such a statement is a compound statement. The name of a variable declared in a block has a block’s scope. Its scope begins at its point of declaration and ends at the end of its block. The following short program illustrates this for the variable, ident:

#include <iostream>
using namespace std;

int main()
{
    if (1==1)
        {
            /*some statements*/
            int ident = 5;
            cout<<ident<<'\n';
            /*some statements*/
        }
    return 0;
}

A variable, such as ident, declared at block scope is a local variable.

A variable declared outside the block scope and above it can be seen in the header of the block (e.g., condition for if-block) and also within the block. The following short program illustrates this for the variable, identif:

#include <iostream>  
using namespace std;

int main()
{
    int identif = 8;
 
    if (identif == 8)
        {
            cout<<identif<<'\n';
        }
    return 0;
}

The output is 8. There are two block scopes here: the block for the main() function and the nested if-compound statement. The nested block is the potential scope of the main() function block.

A declaration introduced in a block scope cannot be seen outside the block. The following short program, which does not compile, illustrates this with the variable, variab:

#include <iostream>  
using namespace std;

int main()
{
    if (1 == 1)
        {
            int variab = 15;
        }
    cout<<variab<<'\n';    //error: accessed outside its scope.

    return 0;
}

The compiler produces an error message for variab.

An entity introduced, declared in the header of a compound function, cannot be seen outside (below) the compound statement. The following for-loop code will not compile, resulting in an error message:

#include <iostream>
using namespace std;

int main()
{
    for (int i=0; i<4; ++i)
        {
            cout<<i<<' ';
        }
    cout<<i<<' ';

    return 0;
}

The iteration variable, i, is seen inside the for-loop block but not outside the for-loop block.

Function Scope

A function parameter is seen in the function block. An entity declared in a function block is seen from the point of declaration to the end of the function block. The following short program illustrates this:

#include <iostream>
#include <string>
using namespace std;

string fn(string str)
    {
        char stri[] = "bananas";
        /*other statements*/
        string totalStr = str + stri;
        return totalStr;
    }

int main()
{
    string totStr = fn("eating ");
    cout<<totStr<<'\n';

    return 0;
}

The output is:
eating bananas

Note: An entity declared outside the function (above it) can be seen in the function parameter list and also in the function block.

Label

The scope of a label is the function in which it appears. The following code illustrates this:

#include <iostream>
using namespace std;

void fn()
    {
        goto labl;
        /*other statements*/
        labl: int inte = 2;
        cout<<inte<<'\n';
    }

int main()
{
    fn();

    return 0;
}

The output is 2.

Enumeration Scope

Unscoped Enumeration
Consider the following if-block:

if (1==1)
        {
            enum {a, b, c=b+2};
            cout<<a<<' '<<b<<' '<<c<<'\n';
        }

The output is 0 1 3.

The first line in the block is an enumeration, a, b, and c are its enumerators. The scope of an enumerator begins from the point of declaration to the end of the enclosing block of the enumeration.

The following statement will not compile because the point of declaration of c is after that of a:

enum {a=c+2, b, c};

The following code segment will not compile because the enumerators are accessed after the enclosing block of the enumeration:

if (1==1)
    {
        enum {a, b, c=b+2};
    }
cout<<a<<' '<<b<<' '<<c<<'\n';  //error: out of scope

The above enumeration is described as an unscoped enumeration, and its enumerators are described as unscoped enumerators. This is because it begins only with the reserved-word, enum. Enumerations that begin with enum class or enum struct are described as scoped enumerations. Their enumerators are described as scoped enumerators.

Scoped Enumeration
The following statement is OK:

enum class nam {a, b, c=b+2};

This is an example of a scoped enumeration. The name of the class is nam. Here, the scope of the enumerator begins from the point of declaration to the end of the enumeration definition, and not the end of the enclosing block for the enumeration. The following code will not compile:

if (1==1)
    {
        enum class nam {a, b, c=b+2};
        cout<<a<<' '<<b<<' '<<c<<'\n';   //error: out of scope for enum class or enum struct  
    }

Class Scope

With normal scoping, the declarative region begins from a point, then continues and stops at a different point. The scope exists in one continuous region. With the class, the scope of an entity can be in different regions that are not joined together. The rules for nested blocks still apply. The following program illustrates this:

#include <iostream>
using namespace std;

//Base class
class Cla
    {
        private:
            int memP = 5;
        protected:
            int memPro = 9;
        public:
        void fn()
            {
                cout<<memP<<'\n';
            }
    };

//Derived Class
class DerCla: public Cla
    {
        public:
        int derMem = memPro;
    };
int main()
{
    Cla obj;
    obj.fn();
    DerCla derObj;
    cout<<derObj.derMem<<'\n';

    return 0;
}

The output is:
5
9

In the class Cla, the variable memP, is seen at the point of declaration. After that, the short portion of “protected” is skipped, then seen again in the class member function block. The derived class is skipped, then seen again at the main() function scope (block).

In the class Cla, the variable memPro, is seen at the point of declaration. The portion of the public function fn() is skipped, then seen in the derived class description block. It is seen again down in the main() function.

Scope Resolution Operator
The scope resolution operator in C++ is :: . It is used to access a static member of the class. The following program illustrates this:

#include <iostream>
using namespace std;

class Cla
    {
        public:
            static int const mem = 5;
        public:
            static void fn()
                {
                    cout<<mem<<'\n';
                }
    };
int main()
{
    cout<<Cla::mem<<'\n';
    Cla::fn();

    return 0;
}

The output is:
5
5

The static members are seen in the main() function block, accessed using the scope resolution operator.

Template Parameter Scope

The normal scope of a template parameter name begins from the point of declaration to the end of its block, as in the following code:

template<typename T, typename U>  struct Ages
    {
        T John = 11;
        U Peter  = 12.3;
        T Mary  = 13;
        U Joy   = 14.6;
    };

U and T are seen within the block.

For a template function prototype, the scope begins from the point of declaration to the end of the function parameter list, as in the following statement:

template<typename T, typename U> void func (T no, U cha, const char *str );

However, when it comes to the class description (definition), the scope can also be of different portions as in the following code:

#include <iostream>
using namespace std;

    template<class T, class U> class TheCla
        {
            public:
            T num;
            static U ch;

            void func (U cha, const char *str)
                {
                    cout << "There are " << num << " books worth " << cha << str << " in the store." << '\n';
                }
            static void fun (U ch)
                {
                    if (ch == 'a')
                        cout << "Official static member function" << '\n';
                }
        };

int main()
{
    TheCla<int, char> obj;
    obj.num = 12;
    obj.func('$', "500");

    return 0;
}

Name Hiding

An example of name hiding occurs when the name of the same object type is re-declared in a nested block. The following program illustrates this:

#include <iostream>
using namespace std;

void fn()
    {
        int var = 3;
        if (1==1)
            {
                int var = 4;
                cout<<var<<'\n';
            }
        cout<<var<<'\n';
    }

int main()
{
    fn();
    return 0;
}

The output is:
4
3

It’s because var in the nested block hid var in the outer block.

Possibility for Repeating Declaration in the Same Scope

The point of the declaration is where the name is introduced (for the first time) in its scope.

Function Prototype
Different entities, even of different types, cannot normally be declared in the same scope. However, a function prototype can be declared more than once in the same scope. The following program with two function prototypes and corresponding function definition illustrates this:

#include <iostream>
using namespace std;

void fn(int num);
void fn(int num);

void fn(int num)
    {
        cout<<num<<'\n';
    }

int main()
{
    fn(5);

    return 0;
}

The program works.

Overloaded functions
Overloaded functions are functions with the same name but different function signatures. As another exception, overloaded functions with the same name can be defined in the same scope. The following program illustrates this:

#include <iostream>
using namespace std;

void fn(int num)
    {
        cout<<num<<'\n';
    }

void fn(float no)
    {
        cout<<no<<'\n';
    }

int main()
{
    fn(5);
    float flt = 8.7;
    fn(flt);
   
    return 0;
}

The output is:
5
8.7

The overloaded functions have been defined in the global scope.

Namespace Scope

Namespace Scope deserves its own article. The said article has been written for this website, linuxhint.com. Just type the search words “Namespace Scope” in the search box of this site (page) and click OK, and you will get the article.

Scope in Different Portions

The class is not the only scheme where the scope can be in different portions. Friend specifier, certain uses of the elaborated-type-specifier, and using-directives are other schemes where the scope is in different places – for details, see later.

Conclusion

A scope is a declarative region. A declarative region is the largest part of a program text in which the name of an entity is valid. It can be divided into more than one portion in accordance with certain programming schemes, such as nested blocks. The portions that do not have the declaration point forms the potential scope. The potential scope may or may not have the declaration.

]]>
C++ Function Overloading https://linuxhint.com/c_function_overloading/ Tue, 08 Dec 2020 01:24:50 +0000 https://linuxhint.com/?p=79925

C++ is a flexible general-purpose programming language. This programming language was originally created by Bjarne Stroustrup, a Danish computer scientist, back in 1985. C++ supports polymorphism, inheritance, and more. This article covers function overloading to achieve compile-time polymorphism in the C++ programming language.

What is a Function?

A function is nothing more than a specific piece of code that performs a specific task based on inputs provided, and it returns the requested results to the user in the form of an output. Functions are used to eliminate repetitive code in large codebases.

After defining a function, you can reuse it at a later point in time, either in the same program or in a different program.

Function Syntax

A function in C++ has the following syntax:

returnType functionName(parameter_list)

{

        …………………

        …………………

        return return_value;

}

The returnType, parameter_list, and return statement are optional. A function in C++ can return a maximum of one value. If a function does not return any value, the returnType should be defined as void.

What is Function Overloading?

In C++, multiple function definitions can have the same function name, but with different parameters. This is called function overloading. With the help of the function overloading feature, compile-time polymorphism can be achieved in C++.

Functions can be overloaded in the following ways:

  1. The number of parameters can be different
  2. The data type of the parameters can be different
  3. The sequence of the parameters can be different

However, the return value is not considered for function overloading. 

The following functions are overloaded:

  1. int addition (int a, int b)
  2. float addition (float f, gloat g)
  3. float addition (float f, int i)
  4. float addition (int i, float f)
  5. int addition (int a, int b, int c)
  6. float addition (float f, float g, float h)

As you can see, with the help of the function overloading feature in C++, there can be multiple definitions/functionalities with the same function name and in the same scope.

Without the function overloading feature, you would need to write a separate function [for example, addition_1(), addition_2() etc] for each variation. For example, you may have to write addition_1() to add two integers, addition_2() to add two floats, and so on. However, as you can see above, the function overloading feature can be used to define multiple variations of the “addition()” function while still keeping the same function name.

The following functions are not considered to be overloaded because the only difference between these two is the return type (return type is not considered for function overloading in C++):

  1. int addition (int a, int b)
  2. float addition (int a, int b)

Examples

Now that you understand the concept of function overloading, we will go through a couple of working example programs to understand this concept more clearly. We will cover the following examples:

  1. Example 1: Simple Function
  2. Example 2: Simple Addition Function
  3. Example 3: Function Overload (1)
  4. Example 4: Function Overload (2)
  5. Example 5: Function Overload (3)

The first two examples explain how normal functions work in C++, while the last three examples demonstrate the function overloading feature in C++.

Example 1: Simple Function

In this example, we will demonstrate how a simple function can be defined and called in C++. We will define a class called “Display” and a public function called “display().” From the “main()” function, we will call the “display()” function with the help of the “Display” class object (d).

#include <iostream>


using namespace std;


class Display

{

public:

        void display()

        {

                cout << "Hello World!" << endl;

        }

};


int main()

{

        Display d;

        d.display();

        return 0;

}

Example 2: Simple Addition Function

In this example, we will demonstrate how to define a simple “addition()” function in C++. We will define a class called “DemoAdd” and a public function called “addition().” From the “main()” function, we will call the “addition()” function with the help of the “DemoAdd” class object (d).

In this example, the current implementation of the “addition()” function accepts only two integer parameters. That means that the current “addition()” function is capable of adding only two integers.

To add three integers instead of two, a function with a different name, such as “addition_1(),” can be defined. In C++, a function can be overloaded, meaning that another definition of the “addition()” function can be defined to add three integers and keep the same name, i.e., “addition().” In the next example, we will look at how to overload the “addition()” function.

#include <iostream>


using namespace std;


class DemoAdd

{

public:

        int addition(int a, int b)

        {

                int result;

                result = a + b;

       

                return result;

        }

};



int main()

{        

        DemoAdd d;

       

        int i1 = 10, i2 = 20, res;

        res = d.addition(i1, i2);

       

        cout << "Result = " << res << endl;

       

        return 0;

}

Example 3: Function Overload (1)

In the previous example, we defined the “addition()” function to add two integers and return the computed result. Now, in this example, we will overload the “addition()” function to add three integers. So, we will be able to call the “addition()” function with two integer arguments, as well as three integer arguments.

Without the function overloading feature, we would have to write another function with a different name.

#include <iostream>


using namespace std;


class DemoAdd

{

public:

        // First function definition of addition()

        int addition(int a, int b)

        {

                int result;

                result = a + b;

       

                return result;

        }


        // Overloaded version of addition() function

        int addition(int a, int b, int c)

        {

                int result;

                result = a + b + c;

       

                return result;

        }

};


int main()

{        

        DemoAdd d;

         int i1 = 10, i2 = 20, i3 = 30, res1, res2;

       

        res1 = d.addition(i1, i2);     // addition() with 2 parameters

        res2 = d.addition(i1, i2, i3); // addition() with 3 parameters

       

        cout << "Result = " << res1 << endl;

        cout << "Result = " << res2 << endl;

       

        return 0;

}

Example 4: Function Overload (2)

In earlier sections of this article, you learned that function overloading can be performed based on differences in parameter type. Here, we have overloaded the “addition()” function based on the parameter’s data type. In the first version of the addition function, we will add two integer type variables; and in the second version, we will add two float type variables.

#include <iostream>


using namespace std;


class DemoAdd

{

public:

        // First definition of addition()

        int addition(int a, int b)

        {

                int result;

                result = a + b;

       

                return result;

        }


        // Overloaded function definition

        float addition(float f, float g)

        {

                float result;

                result = f + g;

       

                return result;

        }

};


int main()

{        

        DemoAdd d;

         int i1 = 10, i2 = 20, res1;

        float f1 = 10.5, f2 = 20.7, res2;

       

        res1 = d.addition(i1, i2);  // addition(int a, int b) will be called

        res2 = d.addition(f1, f2);  // addition(float f, flat g) will be called

       

        cout << "Result = " << res1 << endl;

        cout << "Result = " << res2 << endl;

       

        return 0;

}

Example 5: Function Overload (3)

In this example, the “addition()” function is overloaded based on differences in the sequence of the parameter list. This is another way to overload a function in C++.

#include <iostream>


using namespace std;


class DemoAdd

{

public:

        // First function definition of addition() function

        float addition(int a, float b)

        {

                float result;

                result = (float)a + b;

       

                return result;

        }


        // Overloaded function definition of addition() function

        float addition(float a, int b)

        {

                float result;

                result = a + (float)b;

       

                return result;

        }

};


int main()

{        

        DemoAdd d;

         int i1 = 10;

        float f1 = 10.5, res1, res2;

       

        res1 = d.addition(i1, f1); // addition(int a, float b) will be called

        res2 = d.addition(f1, i1); // addition(float a, int b) will be called

       

        cout << "Result = " << res1 << endl;

        cout << "Result = " << res2 << endl;

       

        return 0;

}

Conclusion

C++ is a general-purpose and flexible programming language that is widely used in various domains. This programming language supports both compile-time and run-time polymorphism. In this article, you learned how to achieve compile-time polymorphism in C++ using the function overloading feature. This is a very helpful feature in C++ that helps programmers to write readable code. It can also be helpful for writing reusable code.

]]>
How to parse JSON in C++ https://linuxhint.com/parse-json-data-c/ Thu, 03 Dec 2020 19:05:41 +0000 https://linuxhint.com/?p=79284 The intention of this tutorial is to understand the JSON data and how to parse JSON data in C++. We will discuss JSON data, Object, Array, JSON syntax, and then go through several working examples to understand the parsing mechanism of JSON data in C++.

What is JSON?

JSON is a light-weight text-based representation for storing and transferring structured data in an organized way. The JSON data is represented in the form of ordered lists and key-value pairs. JSON stands for JavaScript Object Notation. As the full name indicates, it is derived from JavaScript. However, JSON data is supported in most of the popular programming languages.

It is often used to transfer the data from the server to a web page. It is much easier and cleaner to represent the structured data in JSON than XML.

JSON Syntax Rule

Here are the JSON syntax rules:

  1. JSON Data should always be in the form of key-value pairs.
  2. JSON Data is separated by commas.
  3. A Curly brace is used to represent JSON Object.
  4. A square bracket is used to represent a JSON Array.

What is JSON Data?

The JSON data is represented in the form of key-value pairs. This is similar to a dictionary or hash in other programming languages.

“Name” : ”Drake”

This is an example of simple JSON data. The key here is “Name” and “Drake” is the corresponding value. The key, i.e., “Name” and the value, i.e., “Drake” are separated by a colon.

JSON File Extension

The JSON data is normally stored in the file with the extension of “.json”. For example, to store the employee’s data, you can simply name the file as ‘employee.json’. This would be a simple text file. You can then open this JSON file in any of your favorite text editors.

JSON Object

The JSON object is nothing but the JSON data enclosed within the curly braces. Here is a sample JSON object:

{
   “Name”: ”Drake”,
   “Employee ID”: “23547a”,
    “Phone”:23547,
    “Department”: “Finance”
 }

A JSON object can contain multiple JSON data. Each JSON data is separated by a comma. JSON data is represented as key-value pairs. The key, i.e., “Name” and the value, i.e., “Drake” are separated by a colon. In the above example, there are four key-value pairs. The first key is “Name”; “Drake” is the corresponding value for it. Similarly, “EmployeeID”, “Phone”, and “Department” are the other three keys.

JSON Array

A JSON array can contain several comma-separated JSON objects. The JSON array is enclosed within a square bracket. Let’s look at an example of a JSON array:

"Students":[
    {"firstName":"Sean", "lastName":"Brown"},
    {"firstName":"Drake", "lastName":"Williams"},
    {"firstName":"Tom", "lastName":"Miller"},
    {“firstName”:”Peter”, “lastName”: “Johnson”}
]

This is an example of a JSON array. Here, “Students” is enclosed with a square bracket, i.e., array, and it contains four JSON objects. Each of these objects is represented in the form of key-value pairs and is separated by a comma.

A Sample JSON File

Now, since we understood JSON data, JSON objects, JSON array, let’s look at an example of a JSON file:

{
  “firstName”: “Sean”,
  “lastName”: “Brown”,
  “Student ID”: 21453,
  “Department”: “Computer Sc.”,
  “Subjects”:[“Math”, “Phy”, “Chem”]
    }

Parsing Libraries in C++:

There is no native solution for parsing JSON data in C++. However, there are several libraries to parse JSON data in C++. In this article, we are going to look into the two most popular libraries to parse JSON data in C++. Here are the GitHub links for parsing JSON data:

  1. https://github.com/nlohmann/json
  2. https://github.com/Tencent/rapidjson/

You may want to download these libraries to be able to execute the examples shown below.

Examples

Now, we have a basic understanding of JSON data, objects, arrays, and available parsing libraries. Let’s now look at a couple of examples to parse JSON data in C++:

  • Example-1: Parse JSON in C++
  • Example-2: Parse and Serialize JSON in C++
  • Example-3: Parse JSON in C++

For Example-1 and Example-2, we are going to make use of the “nlohmann” library. In the case of Example-3, we will use the “RapidJSON” library.

Example-1: Parse JSON in C++

In this example program, we will demonstrate how to access values of JSON data in C++.

#include <iostream>
#include "json.hpp"

using json = nlohmann::json;

int main()
{

    // jdEmployees
    json jdEmployees =
    {
        {"firstName","Sean"},
        {"lastName","Brown"},
        {"StudentID",21453},
        {"Department","Computer Sc."}
    };

    // Access the values
    std::string fName = jdEmployees.value("firstName", "oops");
    std::string lName = jdEmployees.value("lastName", "oops");
    int sID = jdEmployees.value("StudentID", 0);
    std::string dept = jdEmployees.value("Department", "oops");
   
    // Print the values
    std::cout << "First Name: " << fName << std::endl;
    std::cout << "Last Name: " << lName << std::endl;
    std::cout << "Student ID: " << sID << std::endl;
    std::cout << "Department: " << dept << std::endl;
             
    return 0;
}

Example-2: Parse and Serialize JSON in C++

In this example program, we are going to see how to parse and serialize JSON in C++. We are using “json::parse()” to parse the JSON data.

#include <iostream>
#include "json.hpp"
#include <iomanip>

using json = nlohmann::json;

int main()
{
    // Here is a JSON text
    char text[] = R"(
    {
        "
Book": {
            "
Width":  450,
            "
Height": 30,
            "
Title":  "Hello World",
            "
isBiography": false,
            "
NumOfCopies": 4,
            "
LibraryIDs": [2319, 1406, 3854, 987]
        }
    }
    )"
;

    // Let's parse and serialize JSON
    json j_complete = json::parse(text);
    std::cout << std::setw(4) << j_complete << std::endl;
}

Example-3: Parse JSON in C++

Now, we will demonstrate how to parse JSON string using the RapidJSON library. RapidJSON was originally inspired by the RapidXML. In this example program, we are parsing a JSON string into DOM. We have declared “mydoc” of type “Document” and then using the “mydoc.parse()” method to parse the JSON string.

#include <iostream>
#include "rapidjson/writer.h"
#include "rapidjson/document.h"
#include "rapidjson/stringbuffer.h"

using namespace rapidjson;

int main()
{

  const char* json = "{"firstName":"Sean","lastName":"Brown","empId":21453,
  "
department":"Computer Sc."}";

  // Parse the JSON string into DOM
  Document mydoc;
  mydoc.Parse(json);

  // DOM to string
  StringBuffer buffer;
  Writer<StringBuffer> writer(buffer);

  mydoc.Accept(writer);

  // Print the output
  std::cout << buffer.GetString() << std::endl;

  return 0;
}

Conclusion

In this article, we have briefly discussed JSON data, object, array, and syntax. As we know, there is no native solution for JSON data parsing in C++; we have used two different libraries to parse JSON data in C++. We looked into three different examples to demonstrate the JSON data parsing mechanism in C++. As compared to the “nlohmann” library, the RapidJSON is small, fast, and memory-friendly. ]]> How to Parse XML in C++ https://linuxhint.com/parse_xml_in_c__/ Fri, 20 Nov 2020 20:25:09 +0000 https://linuxhint.com/?p=77607

In this article, we are going to discuss how to parse XML in C++ programming language. We will see several working examples to understand the XML parsing mechanism in C++.

What is XML?

XML is a markup language and is mainly used for storing and transferring data in an organized way. XML stands for eXtensible Markup Language. It is very similar to HTML. The XML is completely focused on storing and transferring the data, whereas the HTML is used for displaying the data on the browser.

A Sample XML File/XML Syntax

Here is a sample XML file:

<?xml version="1.0" encoding="utf-8"?>

<EmployeeData>

    <Employee student_type="Part-time">

        <Name>Tom</Name>

    </Employee>

    <Employee student_type="Full-time">

        <Name>Drake</Name>

    </Employee>

</EmployeeData>

Unlike HTML, It is a tag-oriented markup language, and we can define our own tag in an XML file. In the above example, we have several user-defined tags such as “<Employee>”. Every tag will have the corresponding ending tag. “</Employee>” is the ending tag for “<Employee>”. We can define as many user-defined tags as we want to organize the data.

Parsing Libraries in C++:

There are various libraries to parse XML data in most of the high-level programming languages. C++ is not an exception. Here are the most popular C++ libraries to parse XML data:

  1. RapidXML
  2. PugiXML
  3. TinyXML

As the name suggests, the RapidXML is mainly focused on speed, and it is a DOM style parsing library. PugiXML supports Unicode conversion. You may want to use PugiXML if you want to convert UTF-16 doc to UTF-8. TinyXML is a bare-minimum version to parse XML data and not that fast as compared to the previous two. If you want to just get the job done and don’t care about the speed, you can choose TinyXML.

Examples
Now, we have a basic understanding of XML and XML parsing libraries in C++. Let’s now look at a couple of examples to parse xml file in C++:

  • Example-1: Parse XML in C++ using RapidXML
  • Example-2: Parse XML in C++ using PugiXML
  • Example-3: Parse XML in C++ using TinyXML

In each of these examples, we will use the respective libraries to parse a sample XML file.

Example-1: Parse XML in C++ using RapidXML

In this example program, we will demonstrate how to parse xml using RapidXML library in C++. Here is the input XML file (sample.xml):

<?xml version="1.0" encoding="utf-8"?>

<MyStudentsData>

    <Student student_type="Part-time">

        <Name>John</Name>

    </Student>

    <Student student_type="Full-time">

        <Name>Sean</Name>

    </Student>

    <Student student_type="Part-time">

        <Name>Sarah</Name>

    </Student>

</MyStudentsData>

Our goal here is to parse the above XML file using C++. Here is the C++ program to parse XML data using RapidXML. You can download the RapidXML library from Here.

#include <iostream>
#include <fstream>
#include <vector>
#include "rapidxml.hpp"

using namespace std;
using namespace rapidxml;


xml_document<> doc
xml_node<> * root_node = NULL;
   
int main(void)
{
    cout << "\nParsing my students data (sample.xml)....." << endl;
   
    // Read the sample.xml file
    ifstream theFile ("sample.xml");
    vector<char> buffer((istreambuf_iterator<char>(theFile)), istreambuf_iterator<char>());
    buffer.push_back('\0');
   
    // Parse the buffer
    doc.parse<0>(&buffer[0]);
   
    // Find out the root node
    root_node = doc.first_node("MyStudentsData");
   
    // Iterate over the student nodes
    for (xml_node<> * student_node = root_node->first_node("Student"); student_node; student_node = student_node->next_sibling())
    {
        cout << "\nStudent Type =   " << student_node->first_attribute("student_type")->value();
        cout << endl;
           
            // Interate over the Student Names
        for(xml_node<> * student_name_node = student_node->first_node("Name"); student_name_node; student_name_node = student_name_node->next_sibling())
        {
            cout << "Student Name =   " << student_name_node->value();
            cout << endl;
        }
        cout << endl;
    }
   
    return 0;
}

Example-2: Parse XML in C++ using PugiXML

In this example program, we will demonstrate how to parse xml using PugiXML library in C++. Here is the input XML file (sample.xml):

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>

<EmployeesData FormatVersion="1">

    <Employees>

        <Employee Name="John" Type="Part-Time">

        </Employee>
       
        <Employee Name="Sean" Type="Full-Time">

        </Employee>
       
        <Employee Name="Sarah" Type="Part-Time">

        </Employee>

    </Employees>

</EmployeesData>

In this example program, we will demonstrate how to parse xml using pugixml library in C++. You can download the PugiXML library from Here.

#include <iostream>
#include "pugixml.hpp"

using namespace std;
using namespace pugi;

int main()
{
    cout << "\nParsing employees data (sample.xml).....\n\n";
   
   
    xml_document doc;
   
    // load the XML file
    if (!doc.load_file("sample.xml")) return -1;

    xml_node tools = doc.child("EmployeesData").child("Employees");

   
    for (xml_node_iterator it = tools.begin(); it != tools.end(); ++it)
    {
        cout << "Employees:";

        for (xml_attribute_iterator ait = it->attributes_begin(); ait != it->attributes_end(); ++ait)
        {
            cout << " " << ait->name() << "=" << ait->value();
        }

        cout << endl;
    }

    cout << endl;
   
    return 0;
   
}

Example-3: Parse XML in C++ using TinyXML

In this example program, we will demonstrate how to parse xml using TinyXML library in C++. Here is the input XML file (sample.xml):

<?xml version="1.0" encoding="utf-8"?>

<MyStudentsData>

    <Student> John </Student>

    <Student> Sean </Student>

    <Student> Sarah </Student>

</MyStudentsData>

In this example program, we will demonstrate how to parse xml using TinyXML library in C++. You can download the TinyXML library from Here.

#include <iostream>
#include <fstream>
#include <vector>
#include "tinyxml2.cpp"

using namespace std;
using namespace tinyxml2;
   

int main(void)
{
    cout << "\nParsing my students data (sample.xml)....." << endl;
   
    // Read the sample.xml file
    XMLDocument doc;
    doc.LoadFile( "sample.xml" );
   
    const char* title = doc.FirstChildElement( "MyStudentsData" )->FirstChildElement( "Student" )->GetText();
    printf( "Student Name: %s\n", title );

   
    XMLText* textNode = doc.LastChildElement( "MyStudentsData" )->LastChildElement( "Student" )->FirstChild()->ToText();
    title = textNode->Value();
    printf( "Student Name: %s\n", title );
   
   
    return 0;
}

Conclusion

In this article, we have briefly discussed XML and looked into three different examples of how to parse XML in C++. TinyXML is a minimalistic library for parsing XML data.  Most of the programmers mainly use the RapidXML or PugiXML to parse XML data.

]]>
C++ Bitwise Operators https://linuxhint.com/cplusplus-bitwise-operators/ Mon, 16 Nov 2020 12:17:39 +0000 https://linuxhint.com/?p=76917 In this article, we are going to discuss bitwise operators in the C++ programming language. We will see several working examples to understand bitwise operations in detail. In C++, the bitwise operators work on the individual bit level.

Brief Overview of Bitwise Operators

An operator is a symbol that instructs the compiler to perform certain mathematical or logical operations. There are several types of operators in C++, such as:

  1. Arithmetic Operators
  2. Logical Operators
  3. Relational Operators
  4. Assignment Operators
  5. Bitwise Operators
  6. Misc Operators

All the Bitwise operators work at the individual bit level. The bitwise operator can only be applied to the integer and character data types. For example, if you have an integer type variable with the size of 32 bits and you apply bitwise NOT operation, the bitwise NOT operator will be applied for all 32 bits. So, eventually, all the 32 bits in the variable will be inversed.

There are six different bitwise operators are available in C++:

  1. Bitwise OR [represented as “|”]
  2. Bitwise AND [represented as “&”]
  3. Bitwise NOT [represented as “~”]
  4. Bitwise XOR [represented as “^”]
  5. Bitwise Left Shift [represented as “<<”]
  6. Bitwise Right Shift [represented as “>>”]

Bitwise OR Truth Table

The Bitwise OR operator produces 1 when at least one operand is set to 1. Here is the truth table for the Bitwise OR operator:

Bit-1 Bit-2 Bit-1 | Bit-2
0 0 0
0 1 1
1 0 1
1 1 1

Bitwise AND Truth Table

Bitwise AND operator produces 1 when both the operands are set to 1. Here is the truth table for the Bitwise AND operator:

Bit-1 Bit-2 Bit-1 & Bit-2
0 0 0
0 1 0
1 0 0
1 1 1

Bitwise NOT Truth Table

Bitwise NOT operator inverts the operand. Here is the truth table for Bitwise NOT operator:

Bit-1 ~Bit-1
0 1
1 0

Bitwise XOR Truth Table

Bitwise XOR operator produces 1 if, and only if, one of the operands is set to 1. Here is the truth table for Bitwise AND operator:

Bit-1 Bit-2 Bit-1 ^ Bit-2
0 0 0
0 1 1
1 0 1
1 1 0

Bitwise Left Shift Operator

Bitwise Left Shift operator shifts all the bits left by the specified number of specified bits. If you left shift all the bits of the data by 1, the original data will be multiplied by 2. Similarly, if you left shift all the bits of the data by 2, the original data will be multiplied by 4.

Bitwise Right Shift Operator

Bitwise Right Shift operator shifts all the bits right by the specified number of specified bits. If you right shift all the bits of the data by 1, the original data will be divided (integer division) by 2. Similarly, if you right shift all the bits of the data by 2, the original data will be divided (integer division) by 4.

Examples

Now, since we have understood the basic concept of bitwise operations, let us look at a couple of examples, which will help you to understand the bitwise operations in C++:

  • Example-1: Bitwise OR Operator
  • Example-2: Bitwise AND Operator
  • Example-3: Bitwise NOT Operator
  • Example-4: Bitwise XOR Operator
  • Example-5: Bitwise Left Shift Operator
  • Example-6: Bitwise Right Shift Operator
  • Example-7: Set Bit
  • Example-8: Clear Bit

The example-7 and 8 are for demonstrating the real-world usage of bitwise operators in the C++ programming language.

Example-1: Bitwise OR Operator

In this example program, we will demonstrate the Bitwise OR operator.

#include <iostream>
#include <string>
#include <bitset>
using namespace std;

// display() function
void display(string print_msg, int number)
{
    bitset<16> myBitSet(number);

    cout << print_msg;
    cout << myBitSet.to_string() << " (" << myBitSet.to_ulong() << ") " << endl;
}

int main()
{
    int first_num = 7, second_num = 9, result = 0;

    // Bitwise OR operation
    result = first_num | second_num;

    // print the input numbers
    cout << endl;
    display("First Number is        =  ", first_num);
    display("Second Number is       =  ", second_num);

    // print the output value
    display("first_num | second_num =  ", result);
    cout << endl;

    return 0;
}

Example-2: Bitwise AND Operator

In this example program, we will illustrate Bitwise AND operator.

#include <iostream>
#include <string>
#include <bitset>
using namespace std;

// display() function
void display(string print_msg, int number)
{
    bitset<16> myBitSet(number);

    cout << print_msg;
    cout << myBitSet.to_string() << " (" << myBitSet.to_ulong() << ") " << endl;
}

int main()
{
    int first_num = 7, second_num = 9, result = 0;
    // Bitwise AND operation
    result = first_num & second_num;

    // print the input numbers
    cout << endl;
    display("First Number is        =  ", first_num);
    splay("Second Number is       =  ", second_num);

    // print the output value
    display("first_num & second_num =  ", result);
    cout << endl;

    return 0;
}

Example-3: Bitwise NOT Operator

In this example program, we will understand how Bitwise NOT operator works in C++.

#include <iostream>
#include <string>
#include <bitset>
using namespace std;

// display() function
void display(string print_msg, int number)
{
    bitset<16> myBitSet(number);
    cout << print_msg;
    cout << myBitSet.to_string() << " (" << myBitSet.to_ulong() << ") " << endl;
}

int main()
{
    int first_num = 7, second_num = 9, result_1 = 0, result_2 = 0;

    // Bitwise NOT operation
    result_1 = ~first_num;
    result_2 = ~second_num;

    // print the input numbers and output value
    cout << endl;
    display("First Number is    =  ", first_num);
    display("~first_num         =  ", result_1);
    cout << endl;

    // print the input numbers and output value
    display("Second Number is   =  ", second_num);
    display("~second_num        =  ", result_2);

    cout << endl;

    return 0;
}

Example-4: Bitwise XOR Operator

This program intends to explain how the Bitwise XOR operator works in C++.

#include <iostream>
#include <string>
#include <bitset>
using namespace std;

// display() function
void display(string print_msg, int number)
{
    bitset<16> myBitSet(number);
    cout << print_msg;
    cout << myBitSet.to_string() << " (" << myBitSet.to_ulong() << ") " << endl;
}

int main()
{
    int first_num = 7, second_num = 9, result = 0;

    // Bitwise XOR operation
    result = first_num ^ second_num;

    // print the input numbers
    cout << endl;
    display("First Number is        =  ", first_num);
    display("Second Number is       =  ", second_num);

    // print the output value
    display("first_num ^ second_num =  ", result);
    cout << endl;

    return 0;
}

Example-5: Bitwise Left Shift Operator

Now, we will see the example of the Bitwise Left Shift operator. In this program, we have declared two numbers, first_num and second_num of integer type. Here, the “first_num” is left-shifted by one bit, and the “second_num” is left-shifted by two bits.

#include <iostream>
#include <string>
#include <bitset>
using namespace std;

// display() function
void display(string print_msg, int number)
{
    bitset<16> myBitSet(number);
    cout << print_msg;
    cout << myBitSet.to_string() << " (" << myBitSet.to_ulong() << ") " << endl;
}

int main()
{
    int first_num = 7, second_num = 9, result_1 = 0, result_2 = 0;

    // Bitwise Left Shift operation
    result_1 = first_num << 1;
    result_2 = second_num << 2;

    // print the input numbers and output value
    cout << endl;
    display("First Number is    =  ", first_num);
    display("first_num << 1     =  ", result_1);
    cout << endl;

    // print the input numbers and output value
    display("Second Number is   =  ", second_num);
    display("second_num << 2    =  ", result_2);

    cout << endl;

    return 0;
}

Example-6: Bitwise Right Shift Operator

Now, we will see another example to understand the Bitwise Right Shift operator. We have declared two numbers, first_num and second_num of integer type. Here, the “first_num” is right-shifted by one bit, and the “second_num” is right-shifted by two bits.

#include <iostream>
#include <string>
#include <bitset>
using namespace std;

// display() function
void display(string print_msg, int number)
{
    bitset<16> myBitSet(number);
    cout << print_msg;
    cout << myBitSet.to_string() << " (" << myBitSet.to_ulong() << ") " << endl;
}

int main()
{
    int first_num = 7, second_num = 9, result_1 = 0, result_2 = 0;

    // Bitwise Right Shift operation
    result_1 = first_num >> 1;
    result_2 = second_num >> 2;

    // print the input numbers and output value
    cout << endl;
    display("First Number is    =  ", first_num);
    display("first_num >> 1     =  ", result_1);
    cout << endl;

    // print the input numbers and output value
    display("Second Number is   =  ", second_num);
    display("second_num >> 2    =  ", result_2);

    cout << endl;

    return 0;
}

Example-7: Set Bit

This example intends  to show how to set a particular bit using bitwise operators.

#include <iostream>
#include <string>
#include <bitset>
using namespace std;

// display() function
void display(string print_msg, int number)
{
    bitset<16> myBitSet(number);
    cout << print_msg;
    cout << myBitSet.to_string() << " (" << myBitSet.to_ulong() << ") " << endl;
}

int main()
{
    int first_num = 7, second_num = 9;

    // print the input number - first_num
    cout << endl;
    display("First Number is           =  ", first_num);

    // Set 5th bit
    first_num |= (1UL << 5);

    // Print output
    display("Set 5th bit of first_num  =  ", first_num);
    cout << endl;

    // print the input number - second_num
    cout << endl;
    display("Second Number is           =  ", second_num);// Set 6th bit
    second_num |= (1UL << 6);

    // Print output
    display("Set 6th bit of second_num  =  ", second_num);
    cout << endl;

    return 0;
}

Example-8: Clear Bit

This example intends to show how to clear a particular bit using bitwise operators.

#include <iostream>
#include <string>
#include <bitset>
using namespace std;

// display() function
void display(string print_msg, int number)
{
    bitset<16> myBitSet(number);
    cout << print_msg;
    cout << myBitSet.to_string() << " (" << myBitSet.to_ulong() << ") " << endl;
}

int main()
{
    int first_num = 7, second_num = 9;
   
    // print the input number - first_num
    cout << endl;
    display("First Number is           =  ", first_num);
   
    // Clear 2nd bit
    first_num &= ~(1UL << 2);

    // Print output
    display("Set 2nd bit of first_num  =  ", first_num);
    cout << endl;

    // print the input number - second_num
    cout << endl;
    display("Second Number is           =  ", second_num);

    // Clear 3rd bit
    second_num &= ~(1UL << 3);

    // Print output
    display("Set 3rd bit of second_num  =  ", second_num);
    cout << endl;

    return 0;
}

Conclusion

The bitwise operator is primarily used to manipulate the individual bits for integer and character data type. The bitwise operator is heavily used in embedded software development. So, if you are developing a device driver or a system very close to the hardware level, you may want to use these bitwise operators.

]]>
Best C++ Editors https://linuxhint.com/best_c_editors/ Sun, 08 Nov 2020 05:07:55 +0000 https://linuxhint.com/?p=76018

Computer Science is one of the hottest prospects these days. With the world around us relying heavily on technology, this comes off as no surprise as everything is gradually becoming digitized and the demand for people skilled in this field keeps on increasing. The Internet has also exploded in the last couple of years and this has in turn led to an increase in the market for computers and devices related to it.

However, the beauty of Computer Science isn’t only in its high success in the industry but also in how it is structured. It offers the best blend of mathematics and engineering, along with providing a platform where programmers can create and develop things simple with just a computer, similar to how an artist does with a paintbrush. Since Computer Science itself is composed of multiple subfields, there have been various programming languages developed each of which has been specifically designed for certain tasks. One such programming language that is immensely popular and lies at the crux of game development, animations, and operating systems is C++ which shall also be the topic of our discussion in this article where we would be looking at the best editors that are available for C++ programming.

1) VS Code

The first name to appear on this list has to be VS Code, the powerful, open-source code editor designed by Microsoft that is available on all major platforms including Windows, Linux, and Mac OS. Although VS Code does not fall under the category of IDEs, it offers much more than what a traditional code editor does and is jam-packed with features that make it an excellent choice for writing and editing C++ programs. VS Code is well-known for its fluidity and flexibility, offering an interface that is extremely fast and easily customizable. Features like auto-completion, code refactoring color highlighting, and having support for multiple extensions make it an excellent choice for C++ programming.

Editor Features:

Extensions:

VS Code also comes with a built-in command-line interface as well as an integrated source control from where users can perform version control tasks such as pulling and pushing data, making commits, creating branches, and so on.

Preview of Source Control:

2) Sublime Text

Another great option available for C++ programming is Sublime Text, the simple, cross-platform text editor. Although Sublime Text is closed source and not free, it still has one of the largest communities to back it and is well regarded mainly due to its speed and efficiency. Sublime Text has one of the slickest and sleek user interface that is bundled with a large set of features such as having multiple cursors, an innovative command palette, and an extremely customizable interface, and this can further be topped with by using its wide variety of plugins.

Editing Tools:

Snippets from Command Palette:

Another awesome feature of Sublime is its unique search function which allows you to search and replace regular expressions, numbers, text, or case sensitive words. It also has the GoTo Anywhere Function, with which you can jump to any words, lines, or symbols that are specified instantly.

3) Atom

Atom is an Electron-based free and open-source, cross-platform code editor that has risen in popularity among developers. What makes Atom so good is the fact that it has support from thousands of packages each of which offers different functionalities. It even allows users to create their own packages which they can then provide it to the Atom community. Atom is extremely customizable and is built with numerous excellent features such as auto-completion, providing multiple panes to split your screen into, and a very powerful search feature.

Editing features:

Multiple Panes:

Split Left Pane:

Another excellent feature that comes along with Atom is its integration with GitHub and thus, you can perform all operations of it such as creating new branches, pushing, and pulling, and making commits. 

4) Brackets

Brackets is a cross-platform and open-source code editor developed by Adobe that falls under the MIT License and is, therefore, free to use. Brackets is well-known for being lightweight and providing an immaculate performance while not comprising any of its features which clearly indicates its powerful nature. Brackets are extremely customizable, and you can quickly change the UI of its interface according to your interests. For example, if you just want the editor to appear in your workspace, you can easily hide the sidebar. Similarly, if you are working with numerous files and want to check the differences between them, you can split your window into vertical or horizontal splits.

Horizontal split:

Vertical Split:

It also allows users to add extensions inside of it which provides more power to this simple-looking editor and allows users to manage their projects much more efficiently.

5) Geany

Geany is another powerful text editor whose name deserves a mention in this list. It is an extremely lightweight and cross-platform text editor that makes use of GTK and Scintilla and provides a variety of features to its users without putting a strain on their systems. Features like auto-completion, syntax highlighting, and code navigation are some of its key highlights. In addition to this, it also has a built-in terminal along with a build system that allows it to compile and execute your programs which often leads to people calling it a small IDE.

Geany also provides snippets to C++ Headers which can help users in writing their code in a much more efficient manner.

Which are the Best Editors for C++ Programming?

C++ is one of the most popular programming languages and is widely used in all sorts of areas of Computer Science. With so much importance being given to it, it is imperative to choose an editor that provides the best features and eases the work of the developer. All five editors mentioned above are excellent choices for writing and editing C++ code and are worth considering.

]]>
How to Read and Write to a File in C++ https://linuxhint.com/cplusplus_read_write/ Sat, 07 Nov 2020 13:08:13 +0000 https://linuxhint.com/?p=75889

In this article, we are going to show you how to read and write to a file in the C++ programming language by using several examples. To understand C++ file operations like read and write, we must first understand the concept of a stream in C++.

What is a Stream?

A stream is simply a flow of data or characters. There are two types of streams: input streams and output streams. An input stream is used to read the data from an external input device such as a keyboard, while an output stream is used to write data to the external output device such as a monitor. A file can be considered as both an input and output source.

In C++, we use a stream to send or to receive data to or from an external source.

We can use built-in classes to access an input/output stream, i.e., “ios”.

Here is the stream class hierarchy of the C++ programming language:

The “cin” and “cout” objects are used to read the data from the keyboard and to display the output on the monitor, respectively. In addition, “ifstream,” which stands for “input file stream,” is used to read a stream of data from a file, and “ofstream,” which stands for “output file stream,” is used to write a stream of data to a file.

The “iostram.h” file contains all the required standard input/output stream classes in the C++ programming language.

Examples

Now that you understand the basics of streams, we will discuss the following examples to help you to better understand file operations in C++:

  • Example 1: Open and Close a File
  • Example 2: Write to a File
  • Example 3: Read from a File
  • Example 4: Read and Write to a File
  • Example 5: Read and Write to a Binary File

Example 1: Open and Close a File

In this example program, we will demonstrate how to open/create a file and how to close the file in C++. As you can see in the below program, we have included the library required for file operations.

To open and close a file, we need an object of ofstream. Then, to read or write to a file, we have to open the file. We have included the fstream header file at line number-1 so that we can access ofstream class.

We have declared a myFile_Handler as an object of ofstream inside the main function. We can then use the open() function to create an empty file and the close() function to close the file.

#include <fstream>

using namespace std;

int main()
{
    ofstream myFile_Handler;

    // File Open
    myFile_Handler.open("File_1.txt");

    // File Close
    myFile_Handler.close();
    return 0;
}

Now, we will compile the program and examine the output. As you can see in the output window below, the “File_1.txt” file was created after executing the program. The size of the file is zero since we have not written any content in the file.

Example 2: Write to a File

In the previous example program, we showed you how to open a file and how to close the file. Now, we will show you how to write something in a file.

We can write to a file using the stream insertion operator, i.e., “<<”. In this program, we have used the file handler and insertion operator to write two lines in the file. The insertion operator (“<<”) indicates that we are inserting the string into the output file stream object.

#include <fstream>

using namespace std;

int main()
{
    ofstream myFile_Handler;
    // File Open
    myFile_Handler.open("File_1.txt");

    // Write to the file
    myFile_Handler << "This is a sample test File. " << endl;
    myFile_Handler << "This is the second line of the file. " << endl;

    // File Close
    myFile_Handler.close();
    return 0;
}

Now, we will compile the above program and execute it. As you can see below, we have successfully written to the file File_1.txt.

Example 3: Read from a File

In the previous examples, we showed you how to write content to a file. Now, let’s read the content from the file that we created in Example-2 and display the content on the standard output device, i.e., the monitor.

We use the getline() function to read the complete line from the file and then “cout” to print the line on the monitor.

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main()
{
    ifstream myFile_Handler;
    string myLine;

    // File Open in the Read Mode
    myFile_Handler.open("File_1.txt");

    if(myFile_Handler.is_open())
    {
        // Keep reading the file
        while(getline(myFile_Handler, myLine))
        {
            // print the line on the standard output
            cout << myLine << endl;
        }
    // File Close
    myFile_Handler.close();
    }
    else
    {
        cout << "Unable to open the file!";
    }
    return 0;
}

Now, we will print the content of File_1.txt using the following command:  cat File_1.txt. Once we compile and execute the program, it is clear that the output matches the content of the file. Therefore, we have successfully read the file and printed the content of the file to the monitor.

Example 4: Read and Write to a File

So far, we have showed you how to open, read, write, and close a file. In C++, we can also read and write to a file at the same time. To both read and write to a file, we have to get an fstream object and open the file in “ios::in” and “ios::out” mode.

In this example, we first write some content to the file. Then, we read the data from the file and print it to the monitor.

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main()
{
    fstream myFile_Handler;
    string myLine;

    // File Open
    myFile_Handler.open("File_1.txt", ios::in | ios::out);

    // Check if the file has opened
    if(!myFile_Handler)
    {
        cout << "File did not open!";
        exit(1);
    }

    // Write to the file
    myFile_Handler << "1. This is another sample test File. " << endl;
    myFile_Handler << "2. This is the second line of the file. " << endl;
   
    myFile_Handler.seekg(ios::beg);
   
    // Read the File
    if(myFile_Handler.is_open())
    {
        // Keep reading the file
        while( getline(myFile_Handler, myLine))
        {
            // print the line on the standard output
            cout << myLine << endl;
        }
       
        // File Close
        myFile_Handler.close();
    }
    else
    {
        cout << "Unable to open the file!";
    }
    myFile_Handler.close();
    return 0;
}

Now, we will compile and execute the program.

Example 5: Read and Write to a Binary File

In this example, we are going to declare a class and then write the object to a binary file. To simplify this example, we have declared the Employee class with a public variable emp_id. Then, we will read the binary file and print the output to the monitor.

#include <iostream>
#include <fstream>

using namespace std;

class Employee
{
public:
    int emp_id;
};

int main()
{
    ofstream binOutFile_Handler;
    ifstream binInFile_Handler;

    Employee empObj_W, empObj_R;

    // File Open
    binOutFile_Handler.open("Employee.dat", ios::out | ios::binary);

    // Check if the file has opened
    if(!binOutFile_Handler)
    {
        cout << "File did not open!";
        exit(1);
    }

    // Initialize empObj_W
    empObj_W.emp_id = 1512;

    // Write to the file
    binOutFile_Handler.write((char *) &empObj_W, sizeof(Employee));
    binOutFile_Handler.close();

    if(!binOutFile_Handler.good())
    {
        cout << "Error occured during writing the binary file!" << endl;
        exit(2);
    }

    // Now, let's read the employee.dat file
    binInFile_Handler.open("Employee.dat", ios::in | ios::binary);
    // Check if the file has opened
    if(!binInFile_Handler)
    {
        cout << "File did not open!";
        exit(3);
    }

    // Read the content of the binary file
    binInFile_Handler.read((char *) &empObj_R, sizeof(Employee));
    binInFile_Handler.close();

    if(!binInFile_Handler.good())
    {
        cout << "Error occured during reading the binary file!" << endl;
        exit(4);
    }

    // Print the output of empObj_R
    cout << "Details of the Employee : " << endl;
    cout << "Employee ID : " << empObj_R.emp_id << endl;

    return 0;
}

Conclusion

Files are mainly used to store the data, and they play an important role in real-world programming. In this article, we showed you how to use various file operations with the C++ programming language by working through several examples. Furthermore, we showed you how to read and write data into both text files and binary files.

]]>
Simple C++ Hello World Tutorial https://linuxhint.com/hello_world_cplusplus/ Sat, 07 Nov 2020 10:54:38 +0000 https://linuxhint.com/?p=75876 C++ is a flexible, general-purpose programming language that was originally created in 1985 by Bjarne Stroustrup, a Danish computer scientist. Today, C++ is considered to be one of the most powerful languages used for software development.

C++ is used in various domains, such as embedded software, real-time operating systems, game development, and finance, and because it supports both procedural and object-oriented programming styles, it is both strong and versatile.

In this article, we are going to discuss the basic structure of a C++ program and show you how to write a simple “Hello World” program.

C++ Program Structure

Before we write the “Hello World” program in C++, let us first discuss the primary elements of a C++ program. Here is an example of a C++ program skeleton:

Because every C++ program adheres to this basic structure, we will now explain the primary elements of this structure in depth.

The first line is “#include <iostream>”. Here, “iostream” stands for input/output stream, where a stream is a series of characters or bytes. This line instructs the preprocessor to include the content of the library in the program.

There are several libraries available in the C++ programming language. Libraries contain built-in objects and functions that programmers can use to write programs, and they are provided by the C++ compiler. When we install the C++ compiler, we get all the associated libraries.

The “iostream” includes the following objects:

  1. cin: the standard input stream
  2. cout: the standard output stream
  3. cerr: the standard output stream for errors
  4. clog: the output stream for logging

Every C++ program has a “main()” function. In this example, the value returned by the main function is an integer. Therefore, after the “main()” function is run here, a value of 0 will be returned.

The opening curly brace indicates the beginning of the body of the main function. The closing curly brace indicates the end of the body of the “main()” function. The rest of your code will be placed inside the curly braces

Hello World (HelloWorld.cpp)

Now, let us write a simple “Hello World” program and execute it. We will use the C++ standard library stream resources to write the string “Hello World” to the standard output.

#include <iostream>
int main()
{
    std::count << ”Hello World” << std::endl;
    return 0;
}

To compile the C++ program, you need to use the command g++ <filename> -o <output>.

We discussed the “iostream” header file in the previous section;  “cin” and “cout” are commonly used objects: “cin” is mainly used to get input from the keyboard and store the data as a variable, while “cout” is used to print the data on the screen.

We can use “cout” to display “Hello World” to the screen. However, we cannot use the “cout” object directly since it belongs to “std” namespace. Therefore, we use the scope resolution operator (i.e., ::). In addition, to print a new line, we used “std::endl”.

If you don’t use the scope resolution operator, you will get the following error:

#include <iostream>
int main()
{
    count << ”Hello World” << endl;
    return 0;
}

To fix the above error, you can either add the scope resolution operator correctly or you can mention the namespace at the beginning of the program. If you want to use “cout” without using the scope resolution operator, then you could write the following code:

#include <iostream>
using namespace std;
int main()
{
   
    count << ”Hello World” << endl;
    return 0;
}

In the above program, we mentioned the “std” namespace in the second line (i.e., “using namespace std;”). Therefore, we do not need to use the scope resolution operator every time we use an object from the “std” namespace, and we can simply use “cout” to print something to the standard output instead of writing “std::cout”. Similarly, we do not need to use the scope resolution operator for “endl”.

Now, we will compile this program and see the output:

As you can see, we get the same output.

Conclusion

C++ is a flexible, general-purpose programming language that is widely used in various domains. It is an extension of the C programming language and it inherits the syntax of C programming. In this article, we showed you how to write a simple “Hello World” program in the C++ programming language and explained various elements of the program.

]]>