A quick walkthrough on what-you-need-to-remember on previous course (CSC10012 - Fundamentals of Programming).
In this course: g++
and any IDEs/editors that you feel comfortable with.
If you accidentally used Visual Studio in previous courses, you might notice some differences in how the code behaves in this course.
#include<iostream>
using namespace std;
int main() {
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int max_element = INT_MIN;
for (int i = 0; i < 10; ++i) {
if (a[i] > max_element) {
max_element = a[i];
}
}
cout << "The maximum element is " << max_element << "\n";
return 0;
}
Key points: If the operator stands before the variable, then we execute the operator first.
For example:
int a = 10, b = 10;
cout << a++ << " " << ++b;
// 10 11
// Note that both a and b now holds the value of 11.
TL;DR:
Example:
int a = 5;
int b = 2;
cout << a / b; // ?
// Fix:
cout << (float)a / b;
cout << (a * 1.0 / b); // Easier
TL;DR: variables only exist in their scope.
#include<iostream>
using namespace std;
int main() {
int x = 5;
{
int x = 10; // ??
x = x * 2;
cout << x << "\n";
}
cout << x << "\n";
return 0;
}
Some notable scopes:
main
function & any other functions: global variable.main
function / any other functions: local variable.#include<iostream>
using namespace std;
int global = 10;
int main() {
int local = 5;
{
int local = 10;
cout << local << " " << global << "\n";
global = global + local;
}
cout << local << " " << global << "\n";
return 0;
}
return_type function_name(datatype1 param1, [datatype2 param2, ..]) {
// do something..
return ..
}
Example:
// Adding two integers
int add(int a, int b) {
return a + b;
}
void
function Differences? the first one must-returning-something, the other don't.
If your function has a return-value, please remember to return something (!)
// Adding two integers
int add(int a, int b) {
int x = a + b;
}
// This function would trigger a warning with g++ compilers (Warning : No return statement in function returning non-void).
A void function is a function that does not return any value.
// Say hello to {name}
void hello(string name) {
cout << "Hello, " << name << "!\n";
}
TL;DR:
Example:
// Function prototype:
int add(int a, int b);
int add(int, int); // note that this also works!
// Function definition:
int add(int a, int b) {
return a + b;
}
int add(int a, int b) {
return a + b;
}
// In this function, a and b are parameters.
add(5 + 6, 1); // (5 + 6) and 1 are arguments.
int a = 9, b = 10;
add(a, b); // a and b are arguments.
TL;DR:
Example:
// Pass by value
void increase_val(int a) {
a = a + 1;
}
// Pass by reference
void increase_ref(int &a) {
a = a + 1;
}
int a1 = 0;
increase_val(a1);
int a2 = 0;
increase_ref(a2);
cout << a1 << " " << a2;
// Output?
Important: with a pass-by-reference parameter, you can only pass a variable to it.
void increase(int &a) {
a = a + 1;
}
// This works
int a = 10;
increase(a);
// This will NOT works
increase(10);
Parameters that have a default value assigned to them. If no value is provided when the function is called, the default value is used
// Adding three integers a, b and c
int add(int a, int b, int c = 0) {
return a + b + c;
}
cout << sum(10, 11) << "\n"; // 21
cout << sum(10, 20, 30) << "\n"; // 60
Important: Default parameters must be declared on the right-hand side of any parameters without default values.
// This is valid
int add(int a, int b = 0, int c = 0) {
return a + b + c;
}
// This is NOT valid
int add(int a = 0, int b, int c = 0) {
return a + b + c;
}
TL;DR: same function name, but with different implementations.
int add(int a, int b) {
return a + b;
}
// Valid overload-ers:
float add(float a, float b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
// INVALID, why?
float add(int a, int b) {
return a + b;
}
TL;DR: functions that goes against every coding convention short, inline functions for specific purpose, (most) cannot be reuse.
// Calculating sum of 2 integers a, b
int sumTwoNumber = [](int a, int b) {
return a + b;
}
// Even shorter form:
auto sumTwoNumber = [](int a, int b) -> int {return a + b;};
cout << sumTwoNumber(10, 11) << "\n";
TL;DR: grouping variables to form a new datatype, yet meaningful.
Example:
struct Student {
string ID;
string name;
double FP, calculus, DSA; // !!!
};
You can either
Example:
// (with the previously defined Student structure)
Student An;
An.name = "An";
An.ID = "24121234";
An.FP = 10;
An.DSA = 10;
An.calculus = 10;
// or,
Student Binh{"24125678", "Binh", 9, 9, 9};
// Is the order in brace initialization important?
TL;DR: Be careful when working with structures that contain pointers.
If you use assignment operator on structs, C++ will create a shallow copy by default - which can create annoying behaviors.
struct Student {
char* name; // To-be-continue ..
double height, weight;
};
// Create a student named An
Student An;
An.name = new char[10];
strcpy(An.name, "An"); // #include<cstring> to use this.
An.weight = 75.5;
An.height = 150;
// Binh has the same height/weight of An, but with different name
Student Binh = An;
strcpy(Binh.name, "Binh");
cout << An.name << " " << Binh.name;
// What happened?
In a shallow copy, Binh's name
pointer points to the same memory location as An's name
pointer. This means that if Binh changes the value pointed to by name
, An's name
will also change because they both refer to the same memory.
To fix the previous code:
Student Binh = An;
Binh.name = new char[10]; // Pointing the name pointer of Binh to a new address.
strcpy(Binh.name, "Binh");
// **Deep** copy - copy the value of the memory block, not only the address.
We will discuss this in more detail when we learn about pointers, or in later courses.
TL;DR: Structures with the same members but in different order might have different sizes in memory due to memory alignment requirements.
struct First {
int a;
double b;
char c;
};
struct Second {
int a;
char c;
double b;
};
cout << sizeof(First) << " " << sizeof(Second);
// 24 16
TL;DR: operators are basically functions, therefore we can overloading them to define the ways operators works on structs.
#include<iostream>
using namespace std;
struct Fraction {
int numerator;
int denominator;
};
// Comparions
bool operator<(Fraction a, Fraction b) {
return a.numerator * b.denominator < b.numerator * a.denominator;
}
bool operator>(Fraction a, Fraction b) {
return a.numerator * b.denominator > b.numerator * a.denominator;
}
bool operator==(Fraction a, Fraction b) {
return a.numerator * b.denominator == b.numerator * a.denominator;
}
// Arithmetic
Fraction operator+(Fraction a, Fraction b) {
Fraction result;
// Naive method to add two fractions.
result.numerator = a.numerator * b.denominator + b.numerator * a.denominator;
result.denominator = a.denominator * b.denominator;
// TODO: simplifying the result ..
return result;
}
// Output
ostream& operator<<(ostream& out, Fraction a) {
out << a.numerator << "/" << a.denominator;
return out;
}
// Usage
int main() {
Fraction a{1, 2};
Fraction b{3, 4};
// Compare
if (a > b) {
cout << "a > b";
}
else if (a < b) {
cout << "a < b";
}
else {
cout << "a = b";
}
cout << "\n";
// Adding them up.
cout << a + b << "\n";
return 0;
}
TL;DR: quick accessing attributes of a structure.
// With previously-defined Student structure
Student Binh{"24125678", "Binh", 9, 9, 9};
auto [studentId, studentName, fpScore, calculusScore, dsaScore] = Binh;
cout << studentId << "\n"; // 24125678
cout << studentName << "\n"; // Binh
// ..
TL;DR: group of same-datatype elements on continuous memory blocks.
// Create an array with 5 elements 1, 2, 3, 4, 5
int a[5] = {1, 2, 3, 4, 5};
// Create an array with 10 zeros.
int a[10] = {0};
int b[10] = {2}; // What could be b's values?
Note that if you don't set default values for elements in the array, the result may vary.
Indices of an array of elements are from 0 to .
int a[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 10; ++i) {
cout << a[i] << " ";
}
// What could be the output?
To randomly access any element: you can find that element directly using its position without needing to look at other elements first.
1-D arrays, by default, are passed by reference to the function.
void mysteryFunction(int A[], int n) {
for (int i = 0; i < n; ++i) {
A[i] *= 2;
}
}
int A[5] = {1, 2, 3, 4, 5};
mysteryFunction(A, 5);
for (int i = 0; i < 5; ++i) {
cout << A[i] << " ";
}
// What will be the output?
TL;DR: A matrix, or array of 1-D arrays.
You can omit the size of the first dimension of a 2D array in a function parameter.
// Printing a 2D array with less than / equals 20 rows/columns.
// Valid (prototypes)
void printMatrix(int A[][20], int n);
void printMatrix(int A[20][20], int n);
// Invalid!
void printMatrix(int A[][], int n);
void printMatrix(int A[20][], int n);
Just keep in mind, that the compiler needs to know the datatype of each element of any 1-D arrays.
Just-keep-going.
Key points:
Example:
// Task: Insert 7 to index i = 6;
int a[10] = {1, 2, 3, 4, 5, 6, 8, 9};
int n = 8; // Current size of the array.
int indexToInsert = 6;
// Step 1: increase the array's capacity (size).
n++;
// Step 2: shifting the array to the right to have a space for the new element
for (int i = n - 1; i > indexToInsert; --i) {
a[i] = a[i - 1];
}
// Step 3: put the new element to its correct position.
a[indexToInsert] = 7;
// 1 2 3 4 5 6 7 8 9
for (int i = 0; i < n; ++i) {
cout << a[i] << " ";
}
Same as Inserting, but shifting to the left and decrease the array's capacity.
Important: this section covers the definition of a string in C/C++. Do not confuse with std::string
- which is a datatype in C++.
TL;DR: a string is an array of characters that ends with the null character \0
.
// a is an array of characters.
char a[10] = {'h', 'e', 'l', 'l', 'o'};
// b is a string (C-string).
char b[10] = {'h', 'e', 'l', 'l', 'o', '\0'};
What is the following program's output?
#include<iostream>
#include<cstring> // to use strlen
using namespace std;
int main() {
// a is an array of characters.
char a[10] = {'h', 'e', 'l', 'l', 'o'};
// b is a string (C-string).
char b[10] = {'h', 'e', 'l', 'l', 'o', '\0'};
cout << strlen(a) << " " << strlen(b);
return 0;
}
Don't be surprised if you get the same result. The correct answer for questions above is undefined behavior - if you're luck, a
's remaining elements will be 0 (naturally NULL).
Pointers are variables that store the address of a memory location.
Important: Pointers themselves do not directly handle memory allocation (i.e., using new
, delete
, etc.).
int a = 10;
int *c = &a; // In human language: c is a pointer, holding the address of a.
*c = 11; // In human language: set a value for the memory block that c is pointing to.
cout << a; // What is the result?
int* a = new int{10};
cout << *a; // What is the output?
delete a; // Deallocate.
If you allocate memory for an array, the deallocation operator must use []
.
int* a = new int[10]{1, 2, 3, 4, 5};
for (int i = 0; i < 10; ++i) {
cout << a[i] << " ";
}
// What is the output?
delete[] a; // Deallocate.
A pointer is also a variable. Since it has a memory location, you can have a pointer that points to another pointer.
int a = 10;
int* c = &a;
int** d = &c;
**d = 11;
cout << a; // What is the result?
TL;DR: difference between pointer to constant and constant pointer:
const <datatype>*
: pointer to constant (i.e. start with const
)<datatype>* const
: constant pointer (i.e. end with const
).int a = 10, b = 11;
// Pointer to constant
const int* c = &a;
*c = 11; // You can't do this.
c = &b; // You can do this.
// Constant pointer
int* const c = &a;
*c = 11; // You can do this.
c = &b; // You can't do this.
Pointers to arrays hold the address of the first element in the array.
int* a = new int[10]{1, 2, 3, 4, 5};
cout << a << " " << &a[0]; // Should be the same.
Since arrays store elements in continuous memory locations, we can access any element directly using its position.
int* a = new int[10]{1, 2, 3, 4, 5};
// Element at index 1 i.e. 1 step from a[0]
cout << *(a + 1);
// Element at index 3 i.e. 3 steps from a[0]
cout << *(a + 3);
// Hence, a[i] has the same meaning as *(a + i)
// a[i][j]
*(*(a + i) + j);
With a pointer to a structure, you can use the ->
(arrow) operator to access the structure's members.
// Using the definition of Student in Structure section.
Student* An = new Student;
An->name = "An";
An->DSA = 10;
// ..
// Note that these expressions are equivalent:
An->FP = 9.8;
(*An).FP = 9.8;
Now you can returning an array via functions, using pointers.
// Initialize an array of n zeros;
int* initArray(int n) {
int* a = new int[n]{0};
return a;
}
A function pointer stores the memory address of a function. This is possible because functions also reside in memory and have their own addresses.
#include<iostream>
using namespace std;
bool ascending(int a, int b) {
return a < b;
}
bool descending(int a, int b) {
return a > b;
}
// Declaring the pointer to a function with two int parameters, returning a bool named `cmp`.
typedef bool(*cmp)(int, int);
// Interchange Sort, but the sorting condition is a parameter.
void sortArray(int* a, int n, cmp sortingCondition) {
for (int i = 0; i < n - 1; ++i) {
for (int j = i + 1; j < n; ++j) {
// sortingCondition is a **function**
if (!sortingCondition(a[i], a[j])) {
swap(a[i], a[j]); // std::swap
}
}
}
}
int main() {
int* a = new int[10]{3, 2, 9, 1, 7, 5, 4, 6, 8, 10};
int n = 10;
sortArray(a, n, ascending);
for (int i = 0; i < n; ++i) cout << a[i] << " ";
cout << "\n";
sortArray(a, n, descending);
for (int i = 0; i < n; ++i) cout << a[i] << " ";
cout << "\n";
return 0;
}
// What is the result?
TL;DR: Solving pain points of arrays in inserting/removing elements.
struct Node {
int value;
Node* pNext; // Pointer points to the next node.
};
In 4 steps:
In 3 steps: