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?
Note: Do NOT assume you are declaring pointers sequentially.
int* a, b;
// What is a and b?
You should declare your pointers as detailed as possible:
int* a;
int* b;
or, if you're in a rush,
int *a, *b;
float* x = new float{5};
int* y;
y = x; // Error - obviously.
y = (int*) x; // runable, but ..
cout << *y; // what is the output?
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: