0% found this document useful (0 votes)
12 views

CS1181 Intro-To-Computer-Science ISU 2024 MIDTERM StudyGuideExplainedWeek3&4

Uploaded by

cyberguyhurst
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
12 views

CS1181 Intro-To-Computer-Science ISU 2024 MIDTERM StudyGuideExplainedWeek3&4

Uploaded by

cyberguyhurst
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 18

Week 3

What is function prototype and why do you need that? Explain


with example.

What is a Function Prototype?


A function prototype is a declaration of a function that specifies its name, return type, and
parameters (with their types), but does not include the function body. It serves as a contract, telling
the compiler what the function's signature looks like and ensuring that the function is used correctly
in the rest of the program before it is fully defined.
In programming languages like C and C++, function prototypes are declared before the function's
actual definition or call. This helps the compiler understand how to call the function, what
arguments to pass, and what type of value it will return.

Syntax of a Function Prototype:


return_type function_name(parameter_type1, parameter_type2, ...);
Where:
• return_type: Specifies the data type of the value the function will return.
• function_name: The name of the function.
• parameter_type: The data types of the arguments the function expects. You can also
provide the names of parameters, but they are optional in the prototype.

Why Do We Need Function Prototypes?


Function prototypes are necessary because:
1. Early Declaration: They allow the compiler to know about the existence of a function
before it is used in the program, even if the function definition comes later.
2. Type Checking: The prototype enables the compiler to check that the arguments passed to a
function match the expected types and that the correct return type is handled. This prevents
runtime errors caused by type mismatches.
3. Modular Programming: In large programs, where functions may be defined in different
files, prototypes allow the function to be used across files (via header files) without needing
the actual function definition in each file.
Without a prototype, the compiler may not know how to handle the function call or may assume
that the function returns an integer by default (in older C standards).
What Happens Without a Function Prototype?
If you don’t declare a function prototype and use the function before its definition, the compiler will
make assumptions about the function’s return type and parameter types (in older versions of C).
This can lead to:
1. Incorrect Return Type Handling: If the function returns something other than int, the
compiler might not know how to handle the return value correctly.
2. Parameter Type Mismatch: The compiler might not check for mismatches between the
argument types passed to the function and the types expected by the function.
For example:
#include <stdio.h>
int main() {
int result;
result = add(5, 3); // Error: add is used before definition without prototype
printf("The sum is: %d\n", result);
return 0;
}
int add(int a, int b) {
return a + b;
}

This code will result in a compilation error because the function add() is called before its
definition, and there’s no prototype to tell the compiler about its signature.

1. Simple Function Prototype Example in C++


In this example, a function prototype is declared before the main() function. The actual function
definition appears later in the program, after main().
#include <iostream>
using namespace std;
// Function prototype declaration
int add(int a, int b);
int main() {
int result = add(5, 3); // Calling the function before definition
cout << "The sum is: " << result << endl;
return 0;
}
// Function definition
int add(int a, int b) {
return a + b;
}
.Summary:
In C++, a function prototype:
• Declares a function before it is defined.
• Specifies the function’s return type, name, and parameter types.
• Enables the compiler to check for correct function usage.
• Is essential for functions with default arguments, overloading, and in large programs where
function definitions are placed after their calls or in separate files.
Prototypes help ensure that your code is correctly type-checked and can be used before the full
function definition appears in your program.

What is function overloading, since a function can have the


following parts: a function name, a return type, argument(s),
function body. What can we change or cannot change in the
overloaded function(s)?

What is Function Overloading?


Function overloading is a feature in C++ (and some other programming languages) that allows
multiple functions to have the same name but differ in the type or number of their arguments. When
a function is called, the compiler differentiates between the functions based on their signature,
which includes the function name and the parameters (types and number of arguments).

What Can Be Changed in Overloaded Functions?


In overloaded functions, you can change:
1. Number of Parameters:
• Functions can be overloaded by using different numbers of parameters. For example,
one version of the function may take 2 arguments, while another may take 3.
2. Type of Parameters:
• Functions can be overloaded by having different types of parameters. For instance,
one version of the function might take an integer, while another takes a double.
3. Order of Parameters (Types):
• The order in which parameters of different types are passed can also allow function
overloading. For example, one function may take (int, double), and another takes
(double, int).

What Cannot Be Changed in Overloaded Functions?


In overloaded functions, you cannot change:
1. Function Name:
• The name of the function must remain the same for overloading to occur. Changing
the name would create an entirely different function.
2. Return Type:
• Changing the return type alone is not enough for function overloading. The function's
parameters must differ in type or number. The return type does not contribute to the
function signature, so a function cannot be overloaded based solely on return type
differences.
3. Function Body:
• The actual code (or body) of the function can be different in each overloaded version,
but the compiler does not use the body to distinguish between functions. Only the
signature matters.

Key Aspects of Function Overloading:


• The signature used for overloading consists of the function name and the parameter types
and number of parameters.
• The return type is not part of the signature and cannot be used to overload functions.
• Overloading occurs at compile time, where the compiler decides which function to call
based on the arguments passed.

Examples Function Prototype with Overloading in C++


In C++, you can have multiple functions with the same name but different parameters. This is
called function overloading. Each overloaded function must have a unique prototype.
int add(int a, int b);
double add(double a, double b);
here are two prototypes for the add function. One handles integers, and the other handles doubles.

• Function Calls:
• add(5, 3) calls the integer version of the function.
• add(2.5, 3.8) calls the double version of the function.

• Function Definitions: Each overloaded function definition provides the logic for adding the
two arguments based on their types.

Discuss with example defaults arguments in function

Function Prototype with Default Arguments in C++


C++ allows you to specify default values for function parameters in the function prototype. This
means if no value is passed for the parameter during a function call, the default value will be used.
Example:
#include <iostream>
using namespace std;

// Function definition
int multiply(int a, int b) {
return a * b;
b;
}

// Function prototype with default argument


int multiply(int a, int b = 2);

int main() {
int result1 = multiply(5); // Uses default value for b
int result2 = multiply(5, 3); // Uses provided value for b

// Function definition
int multiply(int a, int b)
{ return a * b; }
}

Discuss function arguments passing by value and passing by


reference

In C++ (and many other programming languages), when functions are called, arguments can be
passed in two primary ways: by value and by reference. Each method has distinct characteristics
and implications for how data is managed within the program.

1. Passing by Value
Passing by value means that a copy of the actual value is passed to the function. Any changes made
to the parameter inside the function do not affect the original argument used in the function call.

Characteristics:
• Copy Creation: A new copy of the variable is created in the function’s memory space. The
function works with this copy.
• Isolation: Changes made to the parameter within the function do not affect the original
variable. The original variable remains unchanged after the function call.
• Memory Overhead: For large data structures (like arrays or objects), passing by value can
incur significant memory overhead because it requires copying the entire structure.

2. Passing by Reference
Passing by reference means that a reference (or alias) to the original variable is passed to the
function. This allows the function to operate directly on the original data.
Characteristics:
• No Copy: Instead of creating a copy of the variable, the function works with a reference to
the original variable, which is more efficient.
• Direct Modification: Changes made to the parameter inside the function affect the original
variable. The function can modify the original data directly.
• No Memory Overhead: Because no copy of the variable is made, passing by reference is
generally more efficient for large data structures.

Example of Passing by Reference:


void modifyValue(int &num) {
num = 10; // Changing the original variable
}

int main() {
int value = 5;
modifyValue(value); // value is now 10 after the function call
// value has changed to 10
}

Summary of Differences
Feature Passing by Value Passing by Reference
Reference to the original
Parameter Type Copy of the value
variable
Impact on Original Changes affect the original
No impact; original remains unchanged
Value value
More memory usage for large structures Less memory usage; no copies
Memory Usage
due to copying are made
Performance Slower for large structures due to copying Faster for large structures

When to Use Each Method


• Use Passing by Value when:
• You want to ensure that the original data remains unchanged.
• You are working with small, simple data types (like primitive types).
• Use Passing by Reference when:
• You need to modify the original data.
• You are working with large data structures (like objects or arrays) and want to avoid
the overhead of copying.
Understanding these two methods of passing arguments is crucial for effective function design in
C++, enabling better performance and ensuring the desired behavior of functions.
What is inline function and what is the benefit/use of inline
function
What is an Inline Function?
An inline function is a special type of function in C++ that is expanded in line when it is called,
rather than being invoked through the usual function call mechanism. When the compiler
encounters an inline function, it replaces the function call with the actual code of the function. This
can lead to more efficient code by eliminating the overhead associated with function calls.

Syntax of Inline Function:


To define an inline function, the keyword inline is used before the function definition:

Example of an Inline Function


Here’s a simple example of an inline function:
#include <iostream>
using namespace std;

// Inline function definition


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

Int main() {
int num = 5;
cout << "The square of " << num << " is: " << square(num) << endl; // Inline replacement
return 0;
}
In this example, the function square() is defined as an inline function. When called in main(),
the compiler replaces square(num) with the actual code num * num, avoiding the overhead of
a function call.

Benefits of Inline Functions


1. Performance Improvement:
• Since inline functions avoid the overhead of a function call (such as pushing
arguments onto the stack, jumping to the function code, and returning), they can
significantly improve performance, especially for small, frequently called functions.
2. Reduced Function Call Overhead:
• Inline functions can reduce the overhead associated with function calls, which can be
particularly beneficial in performance-critical applications where a function is called
repeatedly in loops.
3. Code Optimization:
• The compiler can optimize inline functions better because it has access to the
complete function code at the point of call, enabling potential in-lining of other
optimizations.
4. Increased Readability:
• Inline functions can improve code readability by allowing the programmer to define
small functions that can be easily understood at a glance without having to look for
the function definition elsewhere.
5. Encapsulation:
• Inline functions allow the use of small, specific functionality without the need for
creating a full-fledged class or structure, making it easier to encapsulate
functionality.

Limitations of Inline Functions


1. Code Bloat:
• If an inline function is large or called many times, it can lead to code bloat, where
the binary size increases significantly because the function code is duplicated at each
call site.
2. Debugging Difficulty:
• Inline functions can make debugging more challenging, as the debugger might not
show the function call as a separate entity. Instead, it will show the inlined code,
which can obscure the call stack.
3. Limitations on Complexity:
• Inline functions should generally be small and simple. Complex functions are not
ideal for inlining, as the performance gains might not outweigh the increased binary
size.
4. Compiler Decision:
• Even if a function is declared as inline, the compiler may choose not to inline it if it
deems it inappropriate, based on its own heuristics.

When to Use Inline Functions


• Use Inline Functions When:
• You have small, frequently called functions (like accessors or simple calculations).
• You want to improve performance without sacrificing readability for small logical
blocks of code.
• You aim for better optimization in performance-critical sections of your code.

Conclusion
Inline functions provide a mechanism for optimizing performance in C++ by reducing the overhead
associated with function calls. However, they should be used judiciously, considering potential
drawbacks like code bloat and debugging challenges.

Review the slides explaining scope of variables (slides 40-43)


Global variables can be accessed by everything, Function variables can only be accessed by the
function, loop counters are limited to the loop. Can get around scope by global variables or by
passing by reference.
Week 4
Discuss LValue and RValue with example
LValue and RValue in C++
In C++, every expression is either an LValue (locator value) or an RValue (right-hand value). These
terms describe the nature of the expression and how it can be used in assignments and operations.

1. LValue (Locator Value):


• An LValue refers to an object that has a location in memory, meaning it can appear on the
left-hand side of an assignment.
• LValues represent persistent objects that occupy a region of memory, such as variables or
objects.
• LValues can be assigned a new value because they reference a memory location.

Example of LValue:
int x = 10; // 'x' is an LValue, it refers to a memory location that stores the value 10
x = 20; // You can assign a new value to 'x', so it is an LValue
In this example, x is an LValue because it represents a memory location that can hold different
values over time.

2. RValue (Right-hand Value):


• An RValue is a temporary value that does not persist beyond the expression that uses it.
RValues typically appear on the right-hand side of an assignment.
• RValues are temporary objects, such as constants, literals, or the result of an expression,
and they do not have a distinct memory address that persists after the expression is
evaluated.
• RValues cannot be assigned a new value because they are not stored in a modifiable
location in memory.

Example of RValue:
int x = 10; // '10' is an RValue, a literal constant
int y = x + 5; // 'x + 5' is an RValue, the result of the addition operation
In this example, 10 and x + 5 are RValues because they are temporary values used to initialize x
and y, respectively. They don't have memory locations after the statement is executed.

Differences Between LValue and RValue


Feature LValue RValue
Memory
Has a persistent memory address No persistent memory address
Location
Modifiability Can be assigned a new value Cannot be assigned a new value
Expression Appears on the left-hand side (or right- Appears only on the right-hand side
Position hand side) of an assignment of an assignment
Feature LValue RValue
Represents objects or variables that exist in Represents temporary data or literal
Type of Object
memory constants

LValue References vs RValue References


In modern C++ (C++11 and later), we can also distinguish between LValue references and RValue
references, allowing for more efficient memory usage, especially in situations involving temporary
objects (e.g., in move semantics).

LValue References (&):


• A reference to an LValue is declared using the & operator.
• It allows a function or an operation to refer to a variable directly, modifying it if needed.

Example of LValue Reference:


int x = 10;
int &ref = x; // 'ref' is an LValue reference to 'x'
ref = 20; // Modifies 'x' through the reference

RValue References (&&):


• A reference to an RValue is declared using the && operator.
• It allows binding to temporary values (RValues), enabling move semantics, which helps in
optimizing memory and resource management by "moving" temporary objects instead of
copying them.

Example of RValue Reference:


int x = 10;
int &&rref = x + 5; // 'rref' is an RValue reference to the result of 'x + 5'
std::cout << rref; // Outputs 15, 'rref' is bound to a temporary value

LValue and RValue in Functions


• LValue References in Functions: Functions can accept LValue references to modify the
original variable.
Example:
void modify(int &a) {
a = 100; // Modifies the original variable
}

int main() {
int x = 10;
modify(x); // 'x' is passed as an LValue reference, x is modified to 100
}

RValue References in Functions: Functions can accept RValue references to operate on temporary
objects, especially for performance optimization via move semantics.
Example:
void process(int &&a) {
std::cout << a << std::endl; // Operates on a temporary RValue
}
int main() {
process(10); // 10 is passed as an RValue
}

Conclusion
• LValues are variables or objects with a specific memory location, and they can be modified.
• RValues are temporary data that exist only within the expression they are used in and cannot
be modified.
• In modern C++, RValue references (&&) are introduced to handle temporary objects
efficiently, improving performance in certain scenarios (like with move semantics).
Understanding LValues and RValues is crucial for effective memory management, optimization, and
clean code in C++.

Discuss correctness and incorrectness of increment &


decrement operations. The exam question may come in the
form that you will be given similar examples to slide 15 and ask
whether the compiler will raise an error

Why would this be illegal? (i+j)++;?

Understanding ++ (Increment Operator)


The ++ operator is designed to increment a modifiable LValue (something that refers to a specific
memory location that can be changed, such as a variable).
• LValue: A variable or object that refers to a memory location, like i or j.
• RValue: A value that doesn't refer to a memory location and cannot be assigned to, such as
the result of an expression like i + j.

Why (i + j)++; is Illegal:


• i + j is an RValue: The expression i + j is an RValue because it produces a temporary
value (the sum of i and j), which is not stored in any specific memory location. You can't
modify an RValue because it is just a temporary result, not something that exists in memory
after the expression is evaluated.
• The ++ operator requires an LValue: The ++ operator only works with LValues because it
needs to increment a value stored in memory. Since i + j is not an LValue, applying ++
to it is not allowed.
Therefore, attempting to do (i + j)++; is trying to increment a temporary value (an RValue),
which is illegal because there's no specific memory location for the result of i + j to increment.

Examples of Legal and Illegal Use of ++:


Legal:
i++; // `i` is an LValue (a variable that refers to a memory location), so this is fine.
++j; // `j` is also an LValue, so this is legal.
Illegal:
(i + j)++; // `i + j` is an RValue, so this is illegal because you can't increment a temporary value.

Summary:
• (i + j) is an RValue (a temporary value that is not stored in memory).
• The ++ operator requires an LValue (a modifiable memory location) to operate on.
• Since i + j is not an LValue, (i + j)++; is illegal in C++ and results in a compilation
error

Correctness of Increment and Decrement Operations


The increment (++) and decrement (--) operators in C++ are commonly used to increase or
decrease the value of a variable by one. However, their correctness or incorrectness can arise based
on how and where they are used. Let's break this down into various aspects:

1. Types of Increment and Decrement Operators


• Prefix Increment/Decrement: ++i or --i
• Postfix Increment/Decrement: i++ or i--

Correctness of Increment and Decrement Operations


When They Are Correct:
• Using on LValues: These operators should be used on LValues (variables that occupy a
memory location). For example:
int x = 5;
++x; // Correct: x is an LValue, now x is 6.
--x; // Correct: x is an LValue, now x is 5.
Within Valid Expressions: The increment or decrement operation should be used within valid
expressions or statements, such as:
int y = 10;
y++; // Correct: y is now 11.
--y; // Correct: y is now 10.
Incorrectness of Increment and Decrement Operations
When They Are Incorrect:
• Using on RValues: Applying increment or decrement operators on RValues (temporary
values or expressions) is illegal:
(x + y)++; // Incorrect: x + y is an RValue, and you can't increment it.
Using on Constants: Attempting to increment or decrement a constant leads to errors:
const int z = 10;
z++; // Incorrect: z is a constant and cannot be modified.
Out of Scope: Incrementing or decrementing a variable that is out of scope can lead to undefined
behavior:

void foo() {
int a = 5;
}
a++; // Incorrect: a is out of scope.
• Potential for Undefined Behavior: If you increment or decrement a variable in a way that
causes undefined behavior, like modifying a variable multiple times in a single expression:
int a = 5;
int b = a++ + ++a; // Undefined behavior: a is modified multiple times in the same expression.

2. Impact of Prefix vs. Postfix Operators


• Prefix (++i, --i): The operation is performed first, and then the value is used. It modifies
the variable immediately.

int i = 5;
int result = ++i; // i is incremented first (i becomes 6), then result is assigned (result is 6).
Postfix (i++, i--): The current value is used in the expression before the modification occurs.
int j = 5;
int result2 = j++; // result2 gets the value 5, then j is incremented (j becomes 6).

3. Common Pitfalls
• Using Increment/Decrement in Loop Conditions: While commonly done, it's important to
ensure that these operators do not lead to logic errors or infinite loops.
for (int k = 0; k < 10; ++k) {
// Correct: Incrementing k in a well-defined loop.
}
Chaining Operations: Using increment or decrement in complex expressions can lead to confusion
and potential errors:
int x = 5;
int y = x++ + ++x; // This can lead to confusion regarding the order of operations and is not
// recommended.
Conclusion
• Correct Usage: Increment and decrement operations are correct when used on LValues and
within valid expressions.
• Incorrect Usage: They are incorrect when applied to RValues, constants, or when modifying
variables in ways that lead to undefined behavior.
• Understanding the Difference: It is crucial to understand the difference between prefix and
postfix forms and their effects on values to avoid pitfalls in your code.
Using increment and decrement operators correctly can lead to cleaner and more efficient code,
while misuse can introduce bugs and undefined behavior in your programs.
• Efficiency: Modern CPU's have dedicated instructions and hardware optimizations for
increment (++) and decrement (--) operations. These instructions are executed directly by
the ALU and are typically faster than general addition or subtraction operations
• Prefix vs. Postfix: Prefix increment or decrement may be slightly more efficient than
postfix in some situations, especially for user- defined types, because postfix requires a copy
of the object to be made before the increment or decrement.
• Side Effects: Be cautious when using increment and decrement operators inside more
complex expressions, modifying the value can sometimes lead to confusion or bugs.

Discuss operator precedence, associativity, and evaluation


order
1. Operator Precedence
Operator precedence defines the order in which operators are evaluated in an expression. Operators
with higher precedence are evaluated before those with lower precedence
Here’s a simplified list of some common operators in C++ with their precedence (from highest to
lowest):
• Member access (.)
• Pointer-to-member access (->)
• Function call (())
• Postfix increment/decrement (++, --)
• Unary plus/minus (+, -), logical NOT (!)
• Multiplication/Division/Modulus (*, /, %)
• Addition/Subtraction (+, -)
• Relational (<, <=, >, >=)
• Equality (==, !=)
• Bitwise AND (&)
• Bitwise XOR (^)
• Bitwise OR (|)
• Logical AND (&&)
• Logical OR (||)
• Ternary conditional (?:)
• Assignment operators (=, +=, etc.)

2. Associativity
Associativity determines the direction in which operators of the same precedence are evaluated. It
can be left-to-right or right-to-left.
• Left-to-Right Associativity: Most operators, including +, -, *, /, and &&, are left-to-right
associative. This means that when operators of the same precedence appear in an expression,
they are evaluated from left to right. For example:
int result = 10 - 5 + 3; // Evaluated as ((10 - 5) + 3)
Right-to-Left Associativity: Some operators, such as the assignment operator (=) and the ternary
operator (?:), are right-to-left associative. This means that when these operators appear, the
rightmost operator is evaluated first:
int a, b, c;
a = b = c = 5; // Evaluated as (a = (b = (c = 5)))

3. Evaluation Order
Evaluation order refers to the order in which the operands of an operator are evaluated. In C++, the
evaluation order is generally from left to right, but it can be affected by the specific operator
involved and its associativity. However, it is important to note that the evaluation order is not
strictly defined in all cases, particularly with expressions that involve multiple operators of the same
precedence.
For example, in the expression:
int x = 5;
int y = 10;
I nt result = x + y * 2; // y * 2 is evaluated before x + y

Summary
• Operator Precedence defines which operator gets evaluated first.
• Associativity determines the direction of evaluation for operators of the same precedence.
• Evaluation Order is the sequence in which the operands of an operator are evaluated.
Understanding these concepts allows programmers to predict how expressions will be evaluated,
leading to more reliable and maintainable code.
If you pass an array as function argument, is it by passed by
value or by reference? Discuss the reasoning behind such
behavior.
Its passed by reference automatically by the compiler to reduce overhead memory cost, which can
be significant if you are working on huge data set arrays
Elucidation:
In C++, when you pass an array as a function argument, it is effectively passed by reference.
However, it's important to clarify how this works, as it can be a bit misleading at first glance. Here’s
a detailed explanation:

1. Passing Arrays in C++


When you pass an array to a function, what actually gets passed is a pointer to the first element of
the array. This means that the function does not receive a copy of the entire array but rather a
reference to the original array's data.

Example:
Consider the following example:
#include <iostream>
void modifyArray(int arr[], int size) {
for (int i = 0; i < size; ++i) {
arr[i] += 1; // Modifies the original array
}
}
int main() {
int myArray[] = {1, 2, 3, 4, 5};
int size = sizeof(myArray) / sizeof(myArray[0]);
modifyArray(myArray, size);
// Print modified array
for (int i = 0; i < size; ++i) {
std::cout << myArray[i] << " "; // Output: 2 3 4 5 6
}
return 0; }

2. Reasoning Behind This Behavior


• Performance Efficiency: Passing large arrays by value (i.e., copying the entire array)
would be inefficient in terms of both time and space. Instead, passing a pointer allows the
function to access and modify the original array without incurring the overhead of copying.
• Memory Management: When arrays are passed by reference (through pointers), any
modifications made to the array inside the function affect the original array. This is essential
for scenarios where you need the function to modify the data.
3. Comparison with Passing by Value
If you were to pass an array by value, you'd need to explicitly copy its contents. This could be done
by creating a wrapper class or using std::array or std::vector. Here’s how passing an
array by value could look:
#include <iostream>
#include <vector>

void modifyVector(std::vector<int> vec) {


for (int i = 0; i < vec.size(); ++i) {
vec[i] += 1; // Modifies the local copy only
}
}

int main() {
std::vector<int> myVector = {1, 2, 3, 4, 5};

modifyVector(myVector);

// Print original vector


for (int i : myVector) {
std::cout << i << " "; // Output: 1 2 3 4 5 (unchanged)
}

return 0;
}
In this case, modifyVector receives a copy of myVector, and any modifications do not affect
the original vector.

4. Conclusion
• When passing arrays to functions in C++, they are passed by reference (effectively as
pointers), allowing functions to modify the original data.
• This behavior is rooted in performance efficiency and memory management considerations.
• If a copy of the array is desired, you should use container classes like std::vector or
std::array that provide a clearer way to manage copies and maintain safety.

You might also like