Loading...

Go Back

Next page
Go Back Course Outline

C++ Full Course


Functins in C++

Functions

Functions are fundamental building blocks in most programming languages. They allow you to encapsulate a block of code that performs a specific task, which can then be executed (or "called") multiple times from different parts of your program. This promotes code reusability, modularity, and makes your programs easier to understand and maintain.

Function Declaration & Definition

In many statically-typed languages like C++, you'll often see a distinction between function declaration and function definition.

Function Declaration (or Prototype)

This tells the compiler about the function's existence, its name, the types of parameters it accepts, and the type of value it returns. It essentially provides the function's signature. Declarations are often placed in header files (.h or .hpp) to make the function's interface known to other parts of the program before its actual implementation.

Function Definition

This contains the actual code that the function executes. It includes the function's header (which is similar to the declaration) and the function's body (the block of statements within curly braces {}).

Return Types

A function can optionally return a value back to the part of the code that called it. The return type specifies the data type of this value. If a function doesn't return any value, its return type is typically void.

Parameters

Parameters are variables listed in the function's declaration and definition that receive values (arguments) when the function is called. They act as inputs to the function. A function can have zero or more parameters. Each parameter has a specific data type and a name.

C++ Example:

                        
                        
                        // Function Declaration (in a header file or before main())
                        int add(int a, int b); // Declares a function named 'add' that takes two integers and returns an integer
                        
                        // Function Definition (in a source file or after main())
                        int add(int a, int b) { // Defines the 'add' function
                          int sum = a + b;
                          return sum;         // Returns the calculated sum
                        }
                        
                        #include <iostream>
                        
                        int main() {
                          int num1 = 5;
                          int num2 = 3;
                          int result = add(num1, num2); // Calling the 'add' function with arguments
                          std::cout << "The sum is: " << result << std::endl;
                          return 0;
                        }
                        
                        
The sum is: 8

The int add(int a, int b); line declares the function add. The subsequent block defines the function, specifying that it takes two integer parameters (a and b), calculates their sum, and returns the integer result. In main(), the add function is called with the arguments num1 (value 5) and num2 (value 3). The returned value (8) is stored in the result variable and then printed.



Parameter Passing

When you call a function, the values of the arguments you provide are passed to the function's parameters. Different programming languages (and sometimes within the same language) offer various mechanisms for parameter passing. Here are the common ones, particularly relevant in languages like C++:

1. Pass by Value:

A copy of the argument's value is passed to the function's parameter. Any modifications made to the parameter inside the function do not affect the original argument in the calling code.

C++ Example:

                        
                        
                        #include <iostream>
                        
                        void modifyValue(int x) {
                          x = x * 2;
                          std::cout << "Inside function, x = " << x << std::endl;
                        }
                        
                        int main() {
                          int num = 10;
                          std::cout << "Before function call, num = " << num << std::endl;
                          modifyValue(num);
                          std::cout << "After function call, num = " << num << std::endl;
                          return 0;
                        }
                        
                        
Before function call, num = 10 Inside function, x = 20 After function call, num = 10

The modifyValue function receives a copy of the num variable's value (10). Inside the function, x is modified to 20, but this change only affects the local copy of x. The original num in main() remains unchanged after the function call.

2. Pass by Reference (&):

An alias (another name) for the original argument is passed to the function's parameter. The parameter directly refers to the original memory location of the argument. Any modifications made to the parameter inside the function directly affect the original argument in the calling code.

C++ Example:

                        
                        
                        #include <iostream>
                        
                        void modifyReference(int& y) {
                          y = y * 2;
                          std::cout << "Inside function, y = " << y << std::endl;
                        }
                        
                        int main() {
                          int num = 10;
                          std::cout << "Before function call, num = " << num << std::endl;
                          modifyReference(num);
                          std::cout << "After function call, num = " << num << std::endl;
                          return 0;
                        }
                        
                        
Before function call, num = 10 Inside function, y = 20 After function call, num = 20

The int& y in the function definition indicates that y is a reference to an integer. When modifyReference(num) is called, y becomes an alias for the num variable. Modifying y inside the function directly modifies the original num in main().

3. Pass by Pointer:

The memory address of the argument is passed to the function's parameter (which is a pointer). The function can then use the pointer to indirectly access and modify the original argument. This requires using the dereference operator (*) to access the value at the memory address.

C++ Example:

                        
                        
                        #include <iostream>
                        
                        void modifyPointer(int* z) {
                          *z = *z * 2; // Dereference the pointer to modify the value at the address
                          std::cout << "Inside function, *z = " << *z << std::endl;
                        }
                        
                        int main() {
                          int num = 10;
                          std::cout << "Before function call, num = " << num << std::endl;
                          modifyPointer(&num); // Pass the address of 'num' using the '&' (address-of) operator
                          std::cout << "After function call, num = " << num << std::endl;
                          return 0;
                        }
                        
                        
Before function call, num = 10 Inside function, *z = 20 After function call, num = 20

The int* z in the function definition indicates that z is a pointer to an integer. When modifyPointer(&num) is called, the memory address of num is passed to z. Inside the function, *z dereferences the pointer z, allowing you to access and modify the value stored at that memory address (which is the original num).

4. const Correctness:

The const keyword can be used with parameters (especially references and pointers) to indicate that the function will not modify the value of the argument passed. This is a crucial aspect of writing safe and maintainable code.

  • const with Pass by Value: While you can use const with pass-by-value parameters, it's generally less impactful because the function is already working with a copy. It primarily prevents the function from reassigning the parameter variable itself within the function's scope.
  • const with Pass by Reference (const &): This is very common and important. It allows the function to access the original argument without the overhead of copying (like pass by value) but guarantees that the function will not modify the original argument.
  • const with Pass by Pointer (const *): This indicates that the function will not modify the value that the pointer points to. You can still modify the pointer itself (e.g., make it point to a different memory location), but not the data it currently references.

C++ Examples with const:

                        
                        
                        #include <iostream>
                        
                        void printValue(const int val) { // Pass by value, marked as const (less common use)
                          // val = 20; // Error: Cannot assign to variable with const-qualified type 'const int'
                          std::cout << "Value: " << val << std::endl;
                        }
                        
                        void printReference(const int& ref) { // Pass by const reference (very common for read-only access)
                          // ref = 30; // Error: Cannot assign to variable with const-qualified type 'const int &'
                          std::cout << "Reference value: " << ref << std::endl;
                        }
                        
                        void printPointer(const int* ptr) { // Pass by const pointer (function won't modify the pointed-to value)
                          // *ptr = 40; // Error: Cannot assign to return expression with const-qualified type 'const int'
                          std::cout << "Pointer value: " << *ptr << std::endl;
                        }
                        
                        int main() {
                          int num = 15;
                          printValue(num);
                          printReference(num);
                          printPointer(&num);
                          return 0;
                        }
                        
                        
Value: 15 Reference value: 15 Pointer value: 15

The printValue function takes a const int by value, preventing reassignment within the function. The printReference function takes a const int&, allowing efficient access without copying and guaranteeing no modification of the original. The printPointer function takes a const int*, ensuring that the value pointed to by the pointer is not modified.

Benefits of const Correctness:

  • Improved Code Safety: Prevents accidental modifications to data that should remain unchanged.
  • Enhanced Readability: Makes it clear which functions are intended to modify their arguments and which are not.
  • Compiler Optimizations: Can enable the compiler to perform more aggressive optimizations.
  • Better Collaboration: Provides clearer contracts between different parts of the code.

Overloading

Function overloading is a feature that allows you to define multiple functions with the same name but different parameter lists (different number of parameters, different types of parameters, or both). The compiler determines which function to call based on the arguments provided in the function call.

C++ Example:

                        
                        
                        #include <iostream>
                        
                        int add(int a, int b) {
                          std::cout << "Adding two integers" << std::endl;
                          return a + b;
                        }
                        
                        double add(double a, double b) {
                          std::cout << "Adding two doubles" << std::endl;
                          return a + b;
                        }
                        
                        int add(int a, int b, int c) {
                          std::cout << "Adding three integers" << std::endl;
                          return a + b + c;
                        }
                        
                        int main() {
                          int sum1 = add(5, 3);       // Calls the first 'add' function
                          double sum2 = add(2.5, 1.7); // Calls the second 'add' function
                          int sum3 = add(1, 2, 3);    // Calls the third 'add' function
                        
                          std::cout << "Sum of integers: " << sum1 << std::endl;
                          std::cout << "Sum of doubles: " << sum2 << std::endl;
                          std::cout << "Sum of three integers: " << sum3 << std::endl;
                        
                          return 0;
                        }
                        
                        
Adding two integers Adding two doubles Adding three integers Sum of integers: 8 Sum of doubles: 4.2 Sum of three integers: 6

We have three functions named add. The compiler distinguishes between them based on the types and number of arguments passed during the function call. add(5, 3) matches the int add(int a, int b) signature. add(2.5, 1.7) matches the double add(double a, double b) signature. add(1, 2, 3) matches the int add(int a, int b, int c) signature.



Default Arguments:

You can provide default values for function parameters. If an argument is omitted during the function call, the default value is used. Default arguments must be specified from right to left in the parameter list.

C++ Example with Default Arguments:

                        
                        
                        #include <iostream>
                        #include <string>
                        
                        void greet(std::string name, std::string greeting = "Hello") {
                          std::cout << greeting << ", " << name << "!" << std::endl;
                        }
                        
                        int main() {
                          greet("Alice");       // Uses the default greeting "Hello"
                          greet("Bob", "Hi");   // Overrides the default greeting
                          return 0;
                        }
                        
                        
Hello, Alice! Hi, Bob!

The greet function has two parameters: name (without a default) and greeting (with a default value of "Hello"). When greet("Alice") is called, the greeting argument is omitted, so the default value "Hello" is used. When greet("Bob", "Hi") is called, the provided argument "Hi" overrides the default value for greeting.

Inline Functions

The inline keyword is a request to the compiler to replace the function call with the actual code of the function at the point of the call. This is done with the intention of reducing the overhead associated with function calls (such as pushing arguments onto the stack, jumping to the function's code, and returning).

When inline might be beneficial:

  • For small, frequently called functions.
  • Where the overhead of the function call is significant compared to the function's execution time.

Important Considerations:

  • inline is just a suggestion to the compiler. The compiler is free to ignore it if it deems inlining not beneficial (e.g., for very large or complex functions).
  • Excessive inlining can lead to code bloat (larger executable size), which might negatively impact cache performance and overall program speed.
  • Compilers are often quite sophisticated at making inlining decisions on their own, so explicitly using inline might not always be necessary or even helpful.

C++ Example:



#include <iostream>

inline int square(int n) {
  return n * n;
}

int main() {
  int num = 5;
  int squared_num = square(num); // The compiler might replace this with '5 * 5'
  std::cout << "The square of " << num << " is: " << squared_num << std::endl;
  return 0;
}

The square of 5 is: 25

The square function is declared with the inline keyword. When the compiler encounters the call square(num), it might choose to replace it directly with the code num * num (which becomes 5 * 5 in this case) instead of performing a traditional function call.

In summary, functions are essential for structuring your code effectively. Understanding function declarations, definitions, different parameter passing mechanisms, overloading, default arguments, and the concept of inline functions will significantly improve your ability to write robust, efficient, and maintainable programs. Remember that the specific syntax and nuances might vary slightly between different programming languages, but the underlying concepts are generally applicable.

Go Back

Next page