Procedural/Structured Programming: Exercises and Takeaways

Write the rest of the arithmetic functions and learn about the drawbacks of procedural/structured programming using only primitive data types.

Your playground

We’ve already implemented the add() function. Now, as an exercise, try to write the code for the rest of the arithmetic operations in the following playground:

Press + to interact
#include <iostream>
using namespace std;
//Helper Print Function
void print(string msg, int w, int n, int d){
cout<< msg <<" = " << w << " " << n << "/" << d << "\n";
}
// The four prototypes for the addition, subtraction, multiplication and division
void add(int l_w, int l_n, int l_d, int r_w, int r_n, int r_d, int &f_w, int &f_n, int &f_d);
void sub(int l_w, int l_n, int l_d, int r_w, int r_n, int r_d, int &f_w, int &f_n, int &f_d);
void mul(int l_w, int l_n, int l_d, int r_w, int r_n, int r_d, int &f_w, int &f_n, int &f_d);
void div(int l_w, int l_n, int l_d, int r_w, int r_n, int r_d, int &f_w, int &f_n, int &f_d);
int main() {
int f_w, f_n, f_d; //Final fraction
int l_w = -2, l_n = 1, l_d = 2; //Left fraction
int r_w = 0, r_n = 1, r_d = 2; //Right fraction
print ("L",l_w, l_n, l_d);
print ("R",r_w, r_n, r_d);
add(l_w, l_n, l_d, r_w, r_n, r_d, f_w, f_n, f_d); // Appended Code
print ("L + R", f_w, f_n, f_d);
/*
sub(l_w, l_n, l_d, r_w, r_n, r_d, f_w, f_n, f_d); // f = l-r
print ("L - R ", f_w, f_n, f_d);
mul(l_w, l_n, l_d, r_w, r_n, r_d, f_w, f_n, f_d); // f = l*r
print ("L * R ", f_w, f_n, f_d);
div(l_w, l_n, l_d, r_w, r_n, r_d, f_w, f_n, f_d); // f = l/r
print ("L / R ", f_w, f_n, f_d);
*/
return 0;
}
//Add function implementation already appeneded
void sub(int l_w, int l_n, int l_d, int r_w, int r_n, int r_d, int &f_w, int &f_n, int &f_d)
{
// Your Implementation
}
void mul(int l_w, int l_n, int l_d, int r_w, int r_n, int r_d, int &f_w, int &f_n, int &f_d)
{
// Your Implementation
}
void div(int l_w, int l_n, int l_d, int r_w, int r_n, int r_d, int &f_w, int &f_n, int &f_d)
{
// Your Implementation
}

Write the code to implement subtraction (sub()), multiplication (mul()), and division (div()) operations. You have all the code related to addition (add()) operations and its helper functions available to you, which you can modify to implement these operations.

Note: Remember that this entire exercise is based on reusability.

Exercise 1: Implementing the sub() function

Write the sub() function and uncomment the lines in main() where sub() is called.

Hint: The addition function already has the subtraction case handled. Think about reusing the code of the add() function.

2314    23+(14)\frac{2}{3}-\frac{1}{4} \implies \frac{2}{3}+\left(-\frac{1}{4}\right)

Can you see reusability in action here?

Exercise 2: Implementing the mul() function

Write the mul() function and uncomment the lines in main() where mul() is called.

Hint: After the conversion into an improper fraction, all you need to do is to multiply the numerator of the first fraction with the numerator of the second fraction and the denominator of the first fraction with the denominator of the second fraction to get the fraction multiplication result.

Exercise 3: Implementing the div() function

Write the div() function and uncomment the lines in main() where div() is called.

Hint: The mulitplication function already has the division case handled. Think about reusing the code of the mul() function.

23÷34    23×(43)\frac{2}{3}\div \frac{3}{4} \implies \frac{2}{3} \times \left(\frac{4}{3}\right)

Can you see reusability in action here?

Takeaways and issues with the procedural approach

Notice how we used the following essential parts of the procedural approach:

  • Modularity: Procedural programming emphasizes breaking down code into smaller, self-contained procedures. Each procedure focuses on performing a specific task, making the code modular. This modular structure enhances code organization and comprehension, making it easier to understand and maintain. It allows developers to disassociate a chunk of code from the rest, enabling them to focus on solving specific problems in isolation.

  • Reusability and inspiration from specialization: Procedural programming draws inspiration from specialization observed in real-life businesses. Just as specialized businesses focus on specific areas to excel, procedural programming encourages breaking down complex problems into smaller, specialized procedures. This approach enables developers to tackle individual tasks efficiently, fostering code reusability and maintainability.

  • Divide and conquer: The procedural approach employs the divide-and-conquer strategy, which involves breaking down complex tasks into smaller, more manageable subtasks that can be solved independently. This can result in improved performance, especially when dealing with large and complex applications. This approach promotes code reusability and maintainability and facilitates efficient problem-solving.

  • Simplicity: Procedural programming follows a clear and straightforward step-by-step approach. It emphasizes concise and readable code that is easy to comprehend. By avoiding complex abstractions, procedural programming simplifies the development process and aids in debugging and testing.

Although this approach works, it has some significant drawbacks that make it the least practical approach in our case study.

Imagine you want to expand the scope of this calculator. Try thinking of making an addition operation calculator for five fractions. How many variables do you think you need to pass to your function? Interesting, isn’t it?

Note: The number of variables can be calculated using the formula y=3x+3y =3x + 3, where xx is the number of fractions and yy is the number of variables, and +3+3 is for the final fraction variables passed to the function by reference.

This procedural/structured approach (working only in primitive data types) to programming in C++ has several drawbacks, including:

  • Limited data abstraction: The procedural approach lacks the ability to create abstract data types that can be used to encapsulate data and behavior into a single entity. This can result in code that is difficult to understand and maintain. For example, in the case of our calculator, three parameters for each fraction are passed, yielding a lot of variables passed to the function call.

  • Inability to return multiple values: C++ functions in the procedural approach can only return a single value, which can result in complex parameters passed as references or pointers to simulate returning multiple values.

  • Passing large numbers of variables: Functions in the procedural approach often require many variables to be passed as parameters, leading to cluttered code that is difficult to read and understand.

  • Difficulty in managing complexity: The procedural approach can lead to code that is difficult to manage as the size and complexity of the program grow. The lack of abstraction and modularity can make modifying and extending the code base challenging.

Therefore, using a procedural/structured approach with only primitive data types in C++ can potentially lead to code that becomes difficult to scale and maintain. As the program grows in complexity and scope, the number of variables can increase significantly, making the code harder to manage and comprehend.

However, we can overcome these challenges using a construct of grouping the data, known as struct, which will always be one step forward.