Function Rewriting
String Manipulation Functions
String Copy
char* customCopy(char* destination, const char* source) {
char* result = destination;
while (*destination++ = *source++);
return result;
}
String Concatenation
char* customConcat(char* destination, const char* source) {
char* result = destination;
while (*destination) {
destination++;
}
while (*destination++ = *source++);
return result;
}
String Comparison
int customCompare(const char* str1, const char* str2) {
const char* s1 = str1;
const char* s2 = str2;
while (*s1 && *s2 && *s1++ == *s2++);
return *s1 - *s2;
}
String Length
size_t customLength(const char* str) {
const char* temp = str;
size_t length = 0;
while (*temp) {
temp++;
length++;
}
return length;
}
Inheritance Fundamentals
Overview
Inheritance allows a new class to acquire properties and methods from an existing class. The existing class is known as the base class or parent class, while the new class is called the derived class or child class.
Inheritance Syntax
class DerivedClass : AccessSpecifier BaseClass {
// Class members
};
Inheritance Types
| Inheritance Type | Base Class Member Access | Derived Class Member Access |
|---|---|---|
| public | public | public |
| protected | protected | |
| private | Not accessible | |
| protected | public | protected |
| protected | protected | |
| private | Not accessible | |
| private | public | private |
| protected | private | |
| private | Not accessible |
Constructor and Destructor Behavior
Derived classes don't inherit constructors from base classes. Instead, they call base class constructors to initialize inherited members. The constructor execution order follows: base class constructors first, then derived class constructors.
Similarly, derived classes don't inherit destructors. However, unlike constructors, the compiler knows how to call destructors in inheritance relationships. The destructor execution order is the reverse of constructor execution order.
Function Name Conflicts
When a derived class has functions with the same name as base class functions, the derived class functions override the base class functions within the derived class context. To access base class functions, you need to specify them using the scope resolution operator with the class name.
Multiple Inheritance
C++ supports multiple inheritance, where a derived class can inherit from multiple base classes. If multiple base classes have functions with the same name, you can specify which one to use by qualifying with the base class name.
Virtual Inheritance
Virtual inheritance is a concept specific to multiple inheritance, particularly useful in diamond inheritance scenarios. It prevents duplication of inherited members from a common base class. The 'virtual' keyword is used to specify virtual inheritance.
Polymorphism
Concept
Polymorphism allows objects of different classes to be treated as objects of a common superclass. When a base class pointer points to a base class object, it calls base class methods. When it points to a derived class object, it calls derived class methods.
Requirements for Polymorphism
- Base class functions must be declared as virtual
- Derived class functions must override base class methods with the same name, return type, and parameter list
- There must be an inheritance relationship between the classes
Function Overloading, Overriding, and Hiding
- Overloading: Multiple functions with the same name but different parameters in the same scope
- Overriding: Derived class provides a specific implementation of a function that is already defined in its base class
- Hiding: When a derived class defines a function with the same name as a base class function, but with different parameters
Pure Virtual Functions
A pure virtual function is declared in a base class but has no implementation. It's defined using the 'virtual' keyword and assigned to zero (e.g., virtual void function() = 0;). Classes containing pure virtual functions are abstract classes and cannot be instantiated. Abstract classes are used to define interfaces that derived classes must implement.
C++ String Class Operations
String Constructors
#include <iostream>
#include <string>
int main() {
// Default constructor - creates an empty string
str emptyStr;
// Constructor with C-string
str textStr("Hello World");
// Copy constructor
str copyStr(textStr);
// Substring constructor
substr subStr(copyStr, 5);
// Output strings
std::cout << "emptyStr: " << emptyStr << std::endl;
std::cout << "textStr: " << textStr << std::endl;
std::cout << "copyStr: " << copyStr << std::endl;
std::cout << "subStr: " << subStr << std::endl;
return 0;
}
String Length
#include <iostream>
#include <string>
int main() {
str emptyStr;
str textStr("Hello World");
str copyStr(textStr);
substr subStr(copyStr, 5);
// Output strings
std::cout << "emptyStr: " << emptyStr << std::endl;
std::cout << "textStr: " << textStr << std::endl;
std::cout << "copyStr: " << copyStr << std::endl;
std::cout << "subStr: " << subStr << std::endl;
// Get length
std::cout << "emptyStr size: " << emptyStr.size() << std::endl;
std::cout << "textStr size: " << textStr.size() << std::endl;
std::cout << "copyStr size: " << copyStr.size() << std::endl;
std::cout << "subStr size: " << subStr.size() << std::endl;
std::cout << "emptyStr length: " << emptyStr.length() << std::endl;
std::cout << "textStr length: " << textStr.length() << std::endl;
std::cout << "copyStr length: " << copyStr.length() << std::endl;
std::cout << "subStr length: " << subStr.length() << std::endl;
return 0;
}
Accessing Characters
#include <iostream>
#include <string>
int main() {
str textStr("Hello World");
// Access using subscript operator
std::cout << "textStr[2]: " << textStr[2] << std::endl;
// Access using at() method
std::cout << "textStr.at(2): " << textStr.at(2) << std::endl;
return 0;
}
Appending Strings
#include <iostream>
#include <string>
int main() {
str baseStr;
// Append using += operator
baseStr += "Hello ";
baseStr += "World!";
std::cout << "baseStr: " << baseStr << std::endl;
// Alternative using append method
str altStr;
altStr.append("This is ");
altStr.append("a test");
std::cout << "altStr: " << altStr << std::endl;
return 0;
}
Inserting Strings
#include <iostream>
#include <string>
int main() {
str textStr("Hello World");
// Insert substring at position 5
textStr.insert(5, "Beautiful ");
std::cout << "Modified textStr: " << textStr << std::endl;
return 0;
}
Deleting Substrings
#include <iostream>
#include <string>
int main() {
str textStr("Hello Beautiful World");
// Erase 10 characters starting from position 6
textStr.erase(6, 10);
std::cout << "Modified textStr: " << textStr << std::endl;
return 0;
}
Replacing Substrings
#include <iostream>
#include <string>
int main() {
str textStr("Hello Beautiful World");
// Replace 10 characters starting from position 6 with "Wonderful"
textStr.replace(6, 10, "Wonderful");
std::cout << "Modified textStr: " << textStr << std::endl;
return 0;
}
Operator Overloading in C++
Introduction
Unlike C, C++ supports operator overloading, which allows you to redefine the behavior of operators for user-defined types. This enables you to use operators with objects in a way that's intuitive and consistent with their intended functionality.
Overloading Methods
Operator functions can be implemented in two ways:
- As member functions: Defined inside the class. The number of parameters is one less than the operands because the implicit 'this' pointer serves as the first operand.
- As friend functions: Defined inside or outside the class. The number of parameters matches the number of operands.
Syntax
// Member function
ReturnType operator OperatorSymbol(Parameters);
// Friend function
friend ReturnType operator OperatorSymbol(Parameters);
Implementation Forms
- Can be implemented as class member functions
- Can be implemented as global functions, declared as friends of the class
Unoverloadable Operators
- sizeof (size operator)
- :: (scope resolution operator)
- ?: (conditional operator)
- .* and ->* (pointer-to-member operators)
- . (member access operator)
- # (preprocessor operator)
Overloadable Operators
| Binary Arithmetic Operators | + (addition), - (subtraction), * (multiplication), / (division), % (modulo) |
| Relational Operators | == (equality), != (inequality), < (less than), > (greater than), <= (less than or equal), >= (greater than or equal) |
| Logical Operators | || (logical OR), && (logical AND), ! (logical NOT) |
| Unary Operators | + (unary plus), - (unary minus), * (dereference), & (address of) |
| Increment/Decrement | ++ (increment), -- (decrement) |
| Bitwise Operators | | (bitwise OR), & (bitwise AND), ~ (bitwise NOT), ^ (bitwise XOR), << (left shift), >> (right shift) |
| Assignment Operators | =, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= |
| Memory Management | new, delete, new[], delete[] |
| Other Operators | () (function call), -> (member access), , (comma), [] (subscript) |
Must Be Members
The following operators must be overloaded as class members:
- Assignment operator (=)
- Subscript operator ([])
- Function call operator ()
- Pointer-to-member operator (->)
Best Practices
- Prefer overloading binary operators as global functions (friends)
- Prefer overloading unary operators as member functions
Relational Operator Overloading
When overloading relational operators, it's common to implement them in terms of each other to reduce redundancy. For example, you can implement != in terms of ==, and > in terms of <.
Input/Output Operator Overloading
The stream insertion (<<) and extraction (>>) operators are typically overloaded as global functions that take ostream& and istream& as parameters, respectively.
Subscript Operator Overloading
The subscript operator ([]) is commonly overloaded for container-like classes to provide array-like access to elements. It's often overloaded twice: one as a const version for read-only access and one as a non-const version for modification.
Increment/Decrement Operator Overloading
Increment (++) and decrement (--) operators can be overloaded in both prefix and postfix forms. The postfix version is distinguished by an unused int parameter.
new and delete Operator Overloading
You can overload new and delete operators to customize memory allocation and deallocation for your class. This is useful for implementing custom memory management strategies.
Custom String Class Implementation
Implementing a custom string class is a great exercise in operator overloading. It typically involves overloading assignment, concatenation, comparison, and subscript operators, among others.