Unit 5 · 10 teaching hours · 26 min read
Object-Oriented Programming (OOP)
What the syllabus asks for 4
- Programming paradigms: procedural, structural and object oriented
- Features of OOP: Class, Object, Polymorphism and Inheritance
- Advantages of OOP
- Application of OOP
Every program you wrote in Unit 4 was a list of instructions that pushed data
around: main() called a function, the function worked on variables, and the
variables sat outside in the open. Object-oriented programming turns that
picture inside out. Data and the code that is allowed to touch it are locked
together into one unit called an object, and a program becomes a set of
objects sending messages to each other. This unit is about that idea — the four
features the syllabus names (class, object, inheritance, polymorphism), why the
software industry moved to it, and where it is used.
This is a 10-hour unit but it is mostly theory. Group B almost always has one of: define class and object with an example, differentiate between procedural and object-oriented programming, explain any four features of OOP. Group C asks you to write a program with a class, a constructor, inheritance and function overloading/overriding, so learn one complete program template by heart. All example code here is C++, the language NEB uses for OOP.
5.1 Programming paradigms: procedural, structural and object oriented
A programming paradigm is a style of organising a program — a way of thinking about what a program is. Three paradigms matter for this course.
Procedural (or monolithic-then-procedural) programming. The program is a sequence of instructions grouped into procedures (functions). Data is usually global, so any function can read or change it. Design is top-down: split the job into functions, then split those into smaller functions. FORTRAN (1957), COBOL (1959) and C (1972) are procedural languages.
Structured programming. This is procedural programming with discipline
added. It insists that every algorithm be built from only three control
structures — sequence, selection (if, switch) and iteration (for, while)
— each with a single entry and a single exit, and that goto be avoided.
Dijkstra's 1968 letter "Go To Statement Considered Harmful" started it. A
structured program is divided into independent modules that communicate through
parameters and return values instead of global variables. C is the classic
structured language, and everything you did in Unit 4 was structured
programming.
Object-oriented programming. The program is organised around the data, not the actions. Data and the functions that operate on it are wrapped into a single unit (an object); outside code can only reach the data through the functions the object chooses to publish. Design is bottom-up: first identify the objects in the problem, then give them behaviour. Simula 67 (Dahl and Nygaard, 1967) was the first object-oriented language; Smalltalk (Xerox PARC, 1970s) made the ideas famous; C++ (Bjarne Stroustrup, Bell Labs — "C with Classes" in 1979, renamed C++ in 1983, released 1985), Java (1995), Python, C# and Kotlin are the languages in use today.
The same tiny job — the area of a rectangle — written both ways makes the difference concrete. First procedural C:
#include <stdio.h>
struct Rect { float len, bre; }; /* data only */
float area(struct Rect r) { /* function kept separate */
return r.len * r.bre;
}
int main() {
struct Rect r = {12.5f, 8.0f};
printf("Area = %.2f sq. m\n", area(r));
return 0;
}
Output:
Area = 100.00 sq. m
Nothing stops another part of the program from writing r.len = -5;. Now the
object-oriented version in C++:
#include <iostream>
using namespace std;
class Rect {
float len, bre; // data: private, hidden
public:
Rect(float l, float b) { // constructor
len = l; bre = b;
}
float area() { // behaviour: bundled with the data
return len * bre;
}
};
int main() {
Rect r(12.5, 8.0);
cout << "Area = " << r.area() << " sq. m" << endl;
return 0;
}
Output:
Area = 100 sq. m
r.len is now inaccessible from main(); the only way in is the constructor,
and the only way out is area().
| Feature | Procedural / structured | Object-oriented |
|---|---|---|
| Basic unit | function (procedure) | class / object |
| Design direction | top-down | bottom-up |
| Data handling | mostly global, shared | private inside objects |
| Data security | weak — any function can change data | strong — access specifiers protect data |
| Code reuse | by calling functions again | by inheritance, plus functions |
| Adding a new feature | often means editing existing functions | usually means adding a new class |
| Real-world modelling | poor | direct (one object per real thing) |
| Suitable for | small and medium programs | large, long-lived, multi-programmer software |
| Examples | C, FORTRAN, COBOL, Pascal | C++, Java, Python, C#, Kotlin |
The syllabus word structural means structured programming, which is a disciplined form of procedural programming — not a separate family. C is both procedural and structured. Do not write that C is "structural but not procedural".
5.2 Features of OOP: class, object, polymorphism and inheritance
5.2.1 Class and object
A class is a user-defined data type that binds together data members (attributes) and member functions (methods) under one name. It is only a blueprint — writing a class allocates no memory for data. An object is an instance of a class: a real variable of that class type, with its own copy of the data members, created in memory at run time.
Class is to object what struct Student is to a particular student variable —
except that a class also carries the functions. In Rect r(12.5, 8.0); the class
is Rect and the object is r.
The general form of a class in C++ is:
class ClassName {
private: // default; hidden from outside
// data members
protected: // visible to derived classes only
// data members
public: // the interface
ClassName(); // constructor
~ClassName(); // destructor
// member functions
}; // note the semicolon
A constructor is a member function with the same name as the class, with no
return type, called automatically when an object is created; it is used to
initialise the data members. A destructor (~ClassName()) is called
automatically when the object is destroyed. Here is a full class with both kinds
of constructor, a validating setter and a static member:
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
class Student {
// ---- private data members: hidden state of the object ----
string name;
int roll;
int marks[3];
public:
static int count; // one copy shared by all objects
Student() { // default constructor
name = "not admitted"; roll = 0;
marks[0] = marks[1] = marks[2] = 0;
count++;
}
Student(string n, int r, int m1, int m2, int m3) { // parameterised
name = n; roll = r;
marks[0] = m1; marks[1] = m2; marks[2] = m3;
count++;
}
int total() const { return marks[0] + marks[1] + marks[2]; }
double percentage() const { return total() / 3.0; }
char grade() const {
double p = percentage();
if (p >= 80) return 'A';
else if (p >= 60) return 'B';
else if (p >= 40) return 'C';
else return 'F';
}
void setMarks(int i, int m) { // controlled write access
if (i >= 0 && i < 3 && m >= 0 && m <= 100) marks[i] = m;
else cout << "Invalid mark rejected" << endl;
}
void display() const {
cout << setw(5) << roll << setw(16) << name << setw(7) << total()
<< setw(9) << fixed << setprecision(2) << percentage()
<< setw(7) << grade() << endl;
}
};
int Student::count = 0; // static member defined outside
int main() {
Student s1("Sita Rai", 1, 78, 85, 92);
Student s2("Bikash Thapa", 2, 55, 61, 48);
cout << setw(5) << "Roll" << setw(16) << "Name" << setw(7) << "Total"
<< setw(9) << "Percent" << setw(7) << "Grade" << endl;
s1.display();
s2.display();
s2.setMarks(1, 105); // rejected by the setter
cout << "Students created = " << Student::count << endl;
return 0;
}
Output (compiled with g++ student.cpp -o student):
Roll Name Total Percent Grade
1 Sita Rai 255 85.00 A
2 Bikash Thapa 164 54.67 C
Invalid mark rejected
Students created = 2
5.2.2 Data encapsulation and data hiding
Encapsulation is the wrapping of data and the functions that act on it into
one unit — the class. The consequence is data hiding: because name, roll
and marks are private, no statement outside the class can read or write them
directly. s1.marks[0] = 999; is a compile-time error. All traffic goes through
the public interface, which can check it — that is why setMarks(1, 105)
printed a rejection instead of storing an impossible mark.
| Access specifier | Same class | Derived class | Outside the class |
|---|---|---|---|
private |
yes | no | no |
protected |
yes | yes | no |
public |
yes | yes | yes |
5.2.3 Abstraction
Abstraction means showing only the essential features and hiding the
internal detail. You call s1.percentage() without knowing whether the marks
are stored in an array, three separate variables or a file. In C++, abstraction
is achieved with the class interface and, at a higher level, with abstract
classes — classes containing at least one pure virtual function
(virtual double earning() const = 0;), which cannot be instantiated and exist
only to fix the interface that derived classes must provide.
Encapsulation is how it is hidden (bundling + access specifiers); abstraction is what the user is shown (the interface). Give this one line and one example in the exam and the mark is yours.
5.2.4 Inheritance
Inheritance is the mechanism by which one class (the derived, child or sub-class) acquires the data members and member functions of another class (the base, parent or super-class), and may then add its own members or redefine inherited ones. It gives reusability and expresses an "is-a" relationship: a Teacher is a Person.
class Teacher : public Person { // public inheritance
// extra members
};
Five forms of inheritance are examinable:
| Type | Structure | Example |
|---|---|---|
| Single | one base, one derived | Person → Student |
| Multilevel | derived class becomes a base | Person → Student → HostelStudent |
| Multiple | one derived, two or more bases | Student + Athlete → SportsScholar |
| Hierarchical | one base, many derived | Person → Student, Teacher, Staff |
| Hybrid | a combination of the above | multilevel + multiple together |
When an object of a derived class is created, the base constructor runs first, then the derived constructor; destructors run in the exact reverse order. This order is a favourite one-mark question.
5.2.5 Polymorphism
Polymorphism (Greek: many forms) is the ability of one name, operator or message to behave differently depending on the type or number of the data it is used with. C++ provides compile-time polymorphism through function overloading and operator overloading, and run-time polymorphism through virtual functions and overriding.
Compile-time (static) polymorphism. Several functions share one name but differ in the number or type of parameters; the compiler picks the right one from the call:
#include <iostream>
using namespace std;
// compile-time polymorphism: one name, three different signatures
int volume(int s) { return s * s * s; } // cube
int volume(int l, int b, int h) { return l * b * h; } // cuboid
double volume(double r, double h) { return 3.1416 * r * r * h; } // cylinder
int main() {
cout << "Cube : " << volume(4) << endl;
cout << "Cuboid : " << volume(5, 4, 3) << endl;
cout << "Cylinder : " << volume(2.0, 7.0) << endl;
return 0;
}
Output:
Cube : 64
Cuboid : 60
Cylinder : 87.9648
Run-time (dynamic) polymorphism. A base-class pointer holds a derived-class
object, and the function that actually runs is decided while the program is
running. The base function must be declared virtual, otherwise the compiler
binds the call early and the base version runs:
#include <iostream>
using namespace std;
class Base {
public:
void show() { cout << "Base::show (not virtual)" << endl; }
virtual void draw() { cout << "Base::draw (virtual)" << endl; }
};
class Derived : public Base {
public:
void show() { cout << "Derived::show" << endl; }
void draw() override { cout << "Derived::draw" << endl; }
};
int main() {
Derived d;
Base *bp = &d; // base pointer holding a derived object
bp->show(); // early (compile-time) binding -> Base
bp->draw(); // late (run-time) binding -> Derived
return 0;
}
Output:
Base::show (not virtual)
Derived::draw
| Function overloading | Function overriding | |
|---|---|---|
| Where | same class (or scope) | base class and derived class |
| Signature | must differ | must be identical |
| Binding | early, at compile time | late, at run time |
| Keyword needed | none | virtual in the base class |
| Type of polymorphism | compile-time (static) | run-time (dynamic) |
5.2.6 Message passing and dynamic binding
Objects do not call each other's code directly; they send messages — a
message is the name of a member function plus its arguments, sent to a named
object (s1.display()). Dynamic binding (late binding) means the code
associated with a message is not known until run time, which is exactly what
makes p[i]->display() behave differently for each element of the array below.
Putting every feature into one program gives the classic Group C answer:
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
// ---------------- base class ----------------
class Person {
protected:
string name;
int age;
public:
Person(string n, int a) : name(n), age(a) {
cout << "Person constructor: " << name << endl;
}
virtual void display() const { // virtual -> late binding
cout << left << setw(14) << name << setw(5) << age;
}
virtual double earning() const = 0; // pure virtual: abstract class
virtual ~Person() { cout << "Person destructor: " << name << endl; }
};
// ---------------- derived class 1 ----------------
class Teacher : public Person {
string subject;
double salary;
public:
Teacher(string n, int a, string s, double sal)
: Person(n, a), subject(s), salary(sal) {
cout << "Teacher constructor: " << name << endl;
}
void display() const override {
Person::display(); // reuse the base version
cout << setw(10) << "Teacher" << setw(12) << subject
<< "Rs " << fixed << setprecision(2) << earning() << endl;
}
double earning() const override { return salary; }
};
// ---------------- derived class 2 ----------------
class Student : public Person {
string faculty;
double scholarship;
public:
Student(string n, int a, string f, double sc)
: Person(n, a), faculty(f), scholarship(sc) {
cout << "Student constructor: " << name << endl;
}
void display() const override {
Person::display();
cout << setw(10) << "Student" << setw(12) << faculty
<< "Rs " << fixed << setprecision(2) << earning() << endl;
}
double earning() const override { return scholarship; }
};
int main() {
Person *p[3]; // base-class pointers
p[0] = new Teacher("Ram Sharma", 41, "Computer", 48500);
p[1] = new Student("Sita Rai", 18, "Science", 1200);
p[2] = new Teacher("Gita Magar", 35, "Maths", 52000);
cout << "\n--- Staff and student list ---" << endl;
double sum = 0;
for (int i = 0; i < 3; i++) {
p[i]->display(); // same call, different code
sum += p[i]->earning();
}
cout << "Total monthly payout = Rs " << sum << "\n" << endl;
for (int i = 0; i < 3; i++) delete p[i]; // virtual destructor runs
return 0;
}
Output:
Person constructor: Ram Sharma
Teacher constructor: Ram Sharma
Person constructor: Sita Rai
Student constructor: Sita Rai
Person constructor: Gita Magar
Teacher constructor: Gita Magar
--- Staff and student list ---
Ram Sharma 41 Teacher Computer Rs 48500.00
Sita Rai 18 Student Science Rs 1200.00
Gita Magar 35 Teacher Maths Rs 52000.00
Total monthly payout = Rs 101700.00
Person destructor: Ram Sharma
Person destructor: Sita Rai
Person destructor: Gita Magar
A class diagram is how this design is drawn on paper. UML notation puts the
class name, then attributes, then methods in one box, marks private members with
- and public with +, uses a hollow triangle arrow for inheritance and a plain
line for association.
| OOP principle | What it means | Example from this chapter |
|---|---|---|
| Class | blueprint of data + methods | class Student { ... }; |
| Object | an instance with its own data | Student s1("Sita Rai", 1, 78, 85, 92); |
| Encapsulation | data + methods in one unit | marks[3] is private, reached via setMarks() |
| Data hiding | outsiders cannot touch the data | s1.marks[0] = 999; fails to compile |
| Abstraction | show the interface, hide the detail | percentage() hides how marks are stored |
| Inheritance | derived class reuses a base class | class Teacher : public Person |
| Polymorphism | one name, many behaviours | volume() overloaded; earning() overridden |
| Dynamic binding | function chosen at run time | p[i]->display() inside the loop |
| Message passing | calling a method on an object | s1.display() |
Problem. Write a C++ class Employee with a parameterised constructor that
stores a name and a basic salary. Add member functions to compute a 20 % allowance,
the gross pay, 1 % social-security tax on the gross, and the net pay. Show the
payslip for Hari Bahadur, whose basic salary is Rs 35,000.
Solution.
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
class Employee {
string name;
double basic;
public:
Employee(string n, double b) : name(n), basic(b) {}
double allowance() const { return 0.20 * basic; } // 20% of basic
double gross() const { return basic + allowance(); }
double tax() const { return 0.01 * gross(); } // 1% social security
double net() const { return gross() - tax(); }
void payslip() const {
cout << "Employee : " << name << endl;
cout << fixed << setprecision(2);
cout << " Basic Rs " << setw(10) << basic << endl;
cout << " Allowance Rs " << setw(10) << allowance() << endl;
cout << " Gross Rs " << setw(10) << gross() << endl;
cout << " Tax (1%) Rs " << setw(10) << tax() << endl;
cout << " Net pay Rs " << setw(10) << net() << endl;
}
};
int main() {
Employee e("Hari Bahadur", 35000);
e.payslip();
return 0;
}
Checking the arithmetic by hand: allowance = 0.20 \times 35000 = 7000, gross = 35000 + 7000 = 42000, tax = 0.01 \times 42000 = 420, net = 42000 - 420 = 41580. The program prints exactly that:
Employee : Hari Bahadur
Basic Rs 35000.00
Allowance Rs 7000.00
Gross Rs 42000.00
Tax (1%) Rs 420.00
Net pay Rs 41580.00
Problem. What does this program print, and why?
class A {
public:
A() { cout << "A "; }
void f() { cout << "A::f "; }
virtual void g() { cout << "A::g "; }
};
class B : public A {
public:
B() { cout << "B "; }
void f() { cout << "B::f "; }
void g() { cout << "B::g "; }
};
int main() { A *p = new B(); p->f(); p->g(); }
Solution. new B() first runs the base constructor A(), then B(), so the
first output is A B. f() is not virtual, so p->f() is bound at compile
time using the pointer's type A* and prints A::f. g() is virtual, so
the call is bound at run time using the object's real type B and prints B::g.
Full output: A B A::f B::g
- Forgetting the semicolon after the closing brace of a class.
- Writing a return type for a constructor (
void Student()is not a constructor). - Expecting a base pointer to call the derived function without
virtual— it will not. Overriding needsvirtualin the base class.
5.3 Advantages of OOP
- Reusability. Inheritance lets a tested class be extended instead of rewritten, and a class can be shipped as a library others reuse.
- Data security. Access specifiers make the data private, so an object cannot be corrupted by unrelated code.
- Easier maintenance. A bug in
percentage()can only be caused by code insideStudent, so the search space is one class, not the whole program. - Modelling power. Objects map one-to-one onto real things — student, invoice, bus route — so the design is easier to discuss with the customer.
- Extensibility. Adding a
Staffclass to the payroll program above needs no change at all tomain(), becausemain()only talks toPerson*. - Modular team work. Different programmers can own different classes and agree only on the public interfaces.
- Message passing makes communication between parts of a large system explicit and easy to trace.
The costs are real too, and examiners accept them as "disadvantages": programs are larger and need more memory, execution can be slightly slower because of dynamic binding, the design phase takes longer, and the paradigm is overkill for a 20-line calculation.
5.4 Application of OOP
| Application area | Why OOP fits | Typical tools |
|---|---|---|
| Graphical user interfaces | every widget is an object with properties and events | Qt, Android SDK, JavaFX |
| Simulation and modelling | the original purpose of Simula — each entity is an object | traffic, weather, queue simulations |
| Computer games and graphics | player, enemy, bullet all inherit from a common base | Unity (C#), Unreal (C++) |
| Mobile applications | activity/view classes are extended by the developer | Kotlin, Swift |
| Web frameworks | request, model, controller classes | Laravel (PHP), Django (Python) |
| Operating systems and device drivers | device classes with a common interface | C++ subsystems in Windows, macOS |
| CAD/CAM and design software | geometric shapes form a natural class hierarchy | AutoCAD, SolidWorks |
| Database and ORM layers | a table row becomes an object | Hibernate, Eloquent |
| Banking, ERP and payroll software | account, customer, transaction objects; auditability | Java/C# enterprise systems |
| AI and data-science libraries | model classes with a shared fit/predict interface |
scikit-learn, TensorFlow |
Problem. A Kathmandu school wants (a) a program to convert a temperature from Celsius to Fahrenheit for a physics class, and (b) a school management system holding students, teachers, fees, attendance and exam results, to be maintained for the next ten years by different programmers. Which paradigm suits each, and why?
Solution. (a) Procedural/structured C is the right choice. The task is a single formula, F = \frac{9}{5}C + 32; one function is enough, and a class would add code without adding safety.
(b) Object-oriented design is the right choice. There are many real-world
entities (Student, Teacher, Fee, Attendance, Exam) with shared attributes, so a
Person base class removes duplication; the data is sensitive, so private
members with validating setters protect it; the system will be extended for ten
years, so adding a class must not break existing code; and several programmers
can own separate classes at once.
Chapter summary
- A paradigm is a style of organising a program: procedural (functions on shared data), structured (procedural with only sequence/selection/iteration and modules), object-oriented (data and behaviour bundled into objects).
- A class is a blueprint binding data members and member functions; an object is an instance of a class with its own copy of the data.
- A constructor has the class's name, no return type, and runs automatically
at creation; the destructor
~Class()runs at destruction. In inheritance, constructors run base→derived and destructors derived→base. - Encapsulation bundles data with methods; data hiding follows from
private; abstraction exposes only the interface.private= class only,protected= class + derived,public= everywhere. - Inheritance (single, multilevel, multiple, hierarchical, hybrid) gives
reuse and expresses "is-a"; the derived class inherits with
class D : public B. - Polymorphism is compile-time (function/operator overloading, early
binding) or run-time (overriding a
virtualfunction through a base pointer, late binding). - Advantages: reusability, data security, easy maintenance, real-world modelling, extensibility, parallel team work. Costs: bigger code, slower dynamic dispatch, longer design phase.
- OOP is used for GUIs, simulation, games, mobile apps, web frameworks, operating systems, CAD, ORMs, banking systems and ML libraries.
Practice questions
Group A — Multiple choice (1 mark each)
- Which of the following is not a feature of object-oriented programming? [1]ainheritancebpolymorphismcencapsulationdglobal data sharing
- A class is best described as [1]aan instance of an objectba blueprint for objectsca block of memoryda library function
- Which member function is called automatically when an object is created? [1]adestructorbfriend functioncconstructord
main() - Members declared
protectedin a base class are accessible in [1]athe base class onlybthe base and derived classesceverywherednowhere - Function overloading is an example of [1]arun-time polymorphismbcompile-time polymorphismcinheritancedencapsulation
class C : public A, public Bis an example of [1]asingle inheritancebmultilevel inheritancecmultiple inheritancedhierarchical inheritance
Answers to Group A
1. (d) — OOP hides data rather than sharing it globally.
2. (b) — a class allocates no data memory; objects do.
3. (c) — the constructor; it has the class name and no return type.
4. (b) — protected is private to the outside world but visible to children.
5. (b) — the compiler picks the version from the signature.
6. (c) — one derived class with two base classes.
Group B — Short answer (5 marks each)
- Define class and object. Write a C++ class
Circlewith a constructor and a member function that returns the area, and create one object of it. [5] - Differentiate between procedural and object-oriented programming on any five points. [5]
- What is inheritance? Explain any three types with a diagram. [5]
- Distinguish between function overloading and function overriding with one example of each. [5]
- State any five advantages of OOP and any two of its disadvantages. [5]
Answers to Group B
1. Definitions as in §5.2.1. Program:
#include <iostream>
using namespace std;
class Circle {
double r;
public:
Circle(double radius) { r = radius; }
double area() { return 3.1416 * r * r; }
};
int main() {
Circle c(7.0);
cout << "Area = " << c.area() << endl; // prints Area = 153.938
return 0;
}
Check: 3.1416 \times 7^2 = 3.1416 \times 49 = 153.9384, so the output is
Area = 153.938 (cout shows six significant figures by default).
2. Use any five rows of the table in §5.1 — basic unit, design direction, data handling, security, reuse, suitability.
3. Inheritance = a derived class acquiring the members of a base class, giving
reuse and an "is-a" relationship. Single (A → B), multilevel (A → B → C),
multiple (A, B → C), hierarchical (one base, many derived) — draw as in the
figure in §5.2.4.
4. Table in §5.2.5: overloading is same class + different signature + early
binding; overriding is base/derived + identical signature + virtual + late
binding. Examples: volume(int) / volume(int,int,int), and
Person::earning() / Teacher::earning().
5. Advantages: reusability, data security, maintainability, real-world modelling, extensibility (§5.3). Disadvantages: larger programs and more memory; slower because of dynamic binding; longer design time.
Group C — Long answer (8 marks each)
- Explain the four features of OOP named in the syllabus — class, object, inheritance and polymorphism — giving a short C++ code fragment for each. [8]
- Write a complete C++ program that defines a base class
Personwithnameandage, derivesTeacher(with subject and salary) andStudent(with faculty and scholarship) from it, uses a virtual function to display the details of each, and prints the total monthly payment for an array of three base-class pointers. [8]
Answers to Group C
1. Definition + fragment for each of the four, as in §5.2: the class/object
fragment from Student, class Teacher : public Person for inheritance, the
overloaded volume() and the virtual earning() for the two kinds of
polymorphism. One mark for each definition, one for each fragment.
2. The complete program and its verified output are given at the end of
§5.2.6. Marking usually goes: class definitions with correct access specifiers
(2), constructors with an initialisation list or assignment (2), virtual
function correctly overridden (2), array of base pointers and the loop (1),
correct total and output format (1). The total for the data used is
Rs 48,500 + Rs 1,200 + Rs 52,000 = Rs 1,01,700.
Practise more: Computer Science question bank