optional · CSS study notes
3. Objects and Classes
- Class: a user-defined blueprint/template defining data members (attributes) and member functions (behavior)
- Object: a concrete instance of a class, occupying its own memory for data members
| Specifier | Accessible from |
|---|---|
private | Only within the same class (default for class) |
protected | Same class + any derived class, not from outside |
public | Anywhere the object is visible |
Note: struct defaults to public access; class defaults to private — a classic MCQ trap.
class Rectangle
{
private:
double length, width;
public:
Rectangle(double l, double w) : length(l), width(w) {}
double area() { return length * width; }
};
int main()
{
Rectangle r(5, 3); // r is an OBJECT -- an instance of the Rectangle CLASS
cout << r.area(); // 15
}
Class vs Object — the blueprint vs the house
A class is a template; it occupies no memory for data on its own (memory is only reserved when an object is created). An object is a real instance built from that template, occupying its own dedicated memory.
class Rectangle // BLUEPRINT -- defines what a rectangle looks like, no memory for actual data yet
{
private:
double length, width;
public:
Rectangle(double l, double w) : length(l), width(w) {}
double area() { return length * width; }
};
int main()
{
Rectangle r1(5, 3); // OBJECT #1 -- its own length/width in memory
Rectangle r2(10, 2); // OBJECT #2 -- separate length/width, doesn't affect r1
cout << r1.area(); // 15
cout << r2.area(); // 20 -- independent of r1
}
Multiple objects, one class
Each object gets its own copy of instance data members but shares the same member function code (there's only one area() function in memory, used by every object).
How memory works, briefly
When Rectangle r1(5, 3); runs:
- Memory is allocated for
r1'slengthandwidth - The constructor initializes them to 5 and 3
r1.area()readsr1's ownlength/width— not some shared or global value
Important MCQs
- A class, by itself (before any object is created), occupies memory for:
- a) All its data members b) No data — memory is allocated only when an object is instantiated c) Only static members d) Only private members
- In C++, the default access specifier for members of a
class(notstruct) is:- a) public b) protected c) private d) none/undefined
structin C++ defaults its members to:- a) private b) protected c) public d) static
- If
Rectangle r1(5,3)andRectangle r2(10,2)are both created, changingr1's data:- a) Also changes r2 b) Has no effect on r2 — each object has its own copy of instance data c) Causes a compile error d) Deletes r2
✅ Self-Test Questions (with Answers)
Q1. If a class is a blueprint, why doesn't declaring the class itself use memory for data members?
Ans: A class declaration is a type definition, not an object definition. It tells the compiler the layout — which data members exist, of what types, in what order, with what alignment and therefore what total size — but it does not ask for any storage to be set aside. This is exactly like an architect's blueprint for a house: the drawing specifies how many rooms there are and how big they are, yet no bricks are laid and no land is occupied until someone actually builds a house from it. Memory for data members is reserved only at instantiation, when an object is created — on the stack for an automatic object, or on the free store for new.
class Rectangle { // BLUEPRINT: 0 bytes of data reserved by this line
double length, width;
public:
Rectangle(double l, double w) : length(l), width(w) {}
double area() const { return length * width; }
};
int main() {
cout << sizeof(Rectangle); // 16 -- the SIZE of a future object, not memory in use
Rectangle r1(5, 3); // NOW 16 bytes are allocated, for r1
Rectangle* r2 = new Rectangle(10, 2); // 16 more bytes, on the heap
delete r2;
}
Points worth stating in an exam answer:
sizeof(Rectangle)reports the size one object would occupy; it does not mean the class is consuming that memory.- Member functions are never stored per object either — a single copy of the code is shared by all objects, and the correct object is identified by the hidden
thispointer. - The exceptions are
staticdata members, which belong to the class rather than to any object; they exist for the whole program even if no object is ever created, which is why they must be defined once outside the class. - A polymorphic class adds one hidden vptr per object (not per class), pointing at the class's single shared vtable.
Q2. Create two objects of the same class with different data — explain why changing one doesn't affect the other.
Ans: Each object created from a class gets its own separate block of memory for its instance data members. When Rectangle r1(5,3); and Rectangle r2(10,2); are declared, the compiler reserves two independent storage areas — r1 has its own length and width, r2 has a different pair at a different address. Writing to r1.length therefore touches only the bytes belonging to r1; r2 is untouched because it never shared that storage in the first place. Member functions, by contrast, are not duplicated — one copy of area()'s machine code exists for the whole class, and it knows which object's data to use because the compiler silently passes the this pointer (the address of the invoking object) as a hidden first argument.
#include <iostream>
using namespace std;
class Rectangle {
double length, width; // INSTANCE members: one pair PER OBJECT
public:
Rectangle(double l, double w) : length(l), width(w) {}
void setLength(double l) { length = l; } // writes this->length only
double area() const { return length * width; }
};
int main() {
Rectangle r1(5, 3); // r1 -> its own length=5, width=3
Rectangle r2(10, 2); // r2 -> a DIFFERENT length=10, width=2
cout << r1.area() << " " << r2.area() << '\n'; // 15 20
r1.setLength(100); // modifies r1 ONLY
cout << r1.area() << " " << r2.area() << '\n'; // 300 20 <- r2 unchanged
}
Contrast with static members: if length were declared static, there would be exactly one shared copy belonging to the class, and changing it through r1 would change what r2 sees. That is the distinction examiners test — instance members belong to the object, static members belong to the class. Remember also that the class itself reserves no data memory; allocation happens only at instantiation.
📌 Sample & Repeated FPSC Questions (2016–2026)
2022, Paper I, Q5(a) (10 marks) — Define a class called Circle using C++. An object of the class Circle can be created using any of the following ways (implement all): (1) default — radius 1, centered at (0,0); (2) given radius, centered at (0,0); (3) given center coordinates, radius assumed 1; (4) given radius and center; (5) from another Circle object.
class Circle {
double x, y, radius;
public:
Circle() : x(0), y(0), radius(1) {} // 1: default
Circle(double r) : x(0), y(0), radius(r) {} // 2: radius only
Circle(double cx, double cy) : x(cx), y(cy), radius(1) {} // 3: center only
Circle(double r, double cx, double cy) : x(cx), y(cy), radius(r) {} // 4: both
Circle(const Circle &c) : x(c.x), y(c.y), radius(c.radius) {} // 5: copy constructor
void display() const { cout << "(" << x << "," << y << ") r=" << radius; }
};
int main() {
Circle c1; // way 1
Circle c2(5.0); // way 2
Circle c3(2.0, 3.0); // way 3
Circle c4(4.0, 1.0, 1.0); // way 4
Circle c5(c4); // way 5
}
Key reasoning points:
- Each of the 5 "ways" is simply a differently-signatured constructor overload — the compiler picks the right one by the number/type of arguments at each call site.
- Way 5 is literally the copy constructor,
Circle(const Circle&). - Watch the argument count carefully:
Circle(double),Circle(double,double), andCircle(double,double,double)are distinguished purely by arity (1, 2, 3 args) — mixing up the order of parameters is the most common mistake here.
2016, Paper I, Q3(c) (8 marks) — Suppose you are trying to build a Library Information System using object oriented approach... describe which classes, data members, and member functions you'd define (Student, Teacher, Book, IssueBook etc.), tracking issued/returned books and calculating fines.
class Person { protected: int id; char name[50]; char contactNo[15]; };
class Student : public Person { char rollNo[20]; char department[30]; };
class Teacher : public Person { char designation[30]; };
class Book { char isbn[20]; char title[50]; int totalCopies, availableCopies; };
class IssueBook{ Book book; Person* borrower; char issueDate[12], dueDate[12]; bool isReturned; };
class Fine { IssueBook issueRecord; int daysLate; float amount, perDayRate; };
Key reasoning points:
Personis the shared base class forStudentandTeacher(both can borrow books) — avoids duplicating id/name/contact fields.IssueBookstores its borrower as aPerson*, so it can point to either aStudentor aTeacher— an early, informal hint at polymorphism, even before virtual functions enter the picture.Fineis deliberately kept as its own class rather than folded intoIssueBook, so the late-fee calculation (daysLate * perDayRate) stays encapsulated and reusable on its own.
Exam angle: "design classes, don't write full code" questions are graded mainly on which classes you pick and where fields go — a clean, minimal class list beats an over-engineered one.
📜 Past-Paper Questions & MCQs (CSS 2016–2026)
2022 · Paper-I · Q5(a) · 10 marks
Q. Define a class Circle that can be created in five ways: default (radius 1, centre (0,0)); with a radius; with centre coordinates; with radius and centre; and from another Circle object. Create five objects in main.
Ans.
#include <iostream>
using namespace std;
class Circle {
float radius, cx, cy;
public:
Circle() : radius(1), cx(0), cy(0) {} // (1) default
Circle(float r) : radius(r), cx(0), cy(0) {} // (2) radius only
Circle(float x, float y) : radius(1), cx(x), cy(y) {} // (3) centre only
Circle(float r, float x, float y) : radius(r), cx(x), cy(y) {} // (4) radius and centre
Circle(const Circle &c) : radius(c.radius), cx(c.cx), cy(c.cy) {} // (5) copy
void show() { cout << "r=" << radius << " centre=(" << cx << "," << cy << ")\n"; }
};
int main() {
Circle c1; // default
Circle c2(5); // radius
Circle c3(2, 3); // centre
Circle c4(4, 1, 1); // radius and centre
Circle c5(c4); // copy of c4
c1.show(); c2.show(); c3.show(); c4.show(); c5.show();
}
- These are overloaded constructors: same name, different parameter lists.
2025 · Paper-I · Q3(a) · 6 marks
Q. What is the difference between shallow copy and deep copy when creating objects in C++? Discuss where each should be used, with examples.
Ans.
- Shallow copy: copies member values as they are. If a member is a pointer, only the address is copied, so both objects share the same memory. Deleting one leaves the other dangling, and a double delete can crash the program.
- Deep copy: allocates new memory for the copy and copies the content, so the objects are independent.
class Buffer {
int *data; int n;
public:
Buffer(int n) : n(n), data(new int[n]) {}
Buffer(const Buffer &b) : n(b.n), data(new int[b.n]) { // deep copy constructor
for (int i = 0; i < n; i++) data[i] = b.data[i];
}
~Buffer() { delete[] data; }
};
- Use shallow copy when the class has no dynamic memory, or the data is meant to be shared and read-only.
- Use deep copy when the class owns dynamic memory or resources. Write a copy constructor, copy assignment operator and destructor (the Rule of Three).