Programming Language & Its Applications β C++ Constructs with Objects and Classes, NEC licence examination syllabus (Nepal Engineering Council).
A reference is an alias β another name for an existing object, with no address arithmetic in sight.
* and &. Every time you write cout << x << y << z, the chaining works because operator<< returns a reference to the stream β return by value would copy the whole stream and break the chain. And every function you will ever write taking a const std::string& instead of a std::string is avoiding a heap allocation per call. Java has references for all objects and no way to opt out; Rust made them explicit again with borrow checking. Search "C++ pass by const reference vs by value" to see the modern rules, which changed with move semantics in C++11.#include <iostream>
int main() {
int x = 10;
int &r = x; /* alias */
int *p = &x; /* pointer */
r = 20; /* no dereference needed */
*p = 30; /* pointer needs * */
std::cout << "x=" << x << " r=" << r
<< " *p=" << *p << "\n";
std::cout << "&x=" << &x << "\n&r=" << &r
<< " (identical)\n";
/* r cannot be reseated */
int y = 99;
r = y; /* copies 99 INTO x */
std::cout << "after r=y: x=" << x
<< " y=" << y << "\n";
p = &y; /* p now points at y */
std::cout << "after p=&y: *p=" << *p << " x=" << x << "\n";
return 0;
}
Output:
x=30 r=30 *p=30
&x=0x7ffd4a2b1c34
&r=0x7ffd4a2b1c34 (identical)
after r=y: x=99 y=99
after p=&y: *p=99 x=99
#include <iostream>
#include <string>
/* C style - works, but noisy at both ends */
void swap_ptr(int *a, int *b) { int t=*a; *a=*b; *b=t; }
/* C++ style - identical machine code, cleaner source */
void swap_ref(int &a, int &b) { int t=a; a=b; b=t; }
/* by value - the caller's object is untouched */
void by_value(int a) { a = 999; }
/* const reference - no copy, and cannot modify.
THE default for any non-trivial parameter type. */
void report(const std::string &s) {
std::cout << "len " << s.size() << ": " << s << "\n";
/* s[0] = 'X'; <-- compile error, good */
}
int main() {
int x = 1, y = 2;
swap_ptr(&x, &y); std::cout << x << " " << y << "\n";
swap_ref(x, y); std::cout << x << " " << y << "\n";
int z = 5;
by_value(z); std::cout << "z still " << z << "\n";
std::string big(2000, 'a');
report(big); /* zero bytes copied */
return 0;
}
Output:
2 1
1 2
z still 5
len 2000: aaaaaaaa... (2000 a's)
const& is not simply "always better" is that a reference is an indirection: reading s means following a pointer, which for an int costs more than just copying the 4 bytes. That is why the cutoff is around 16 bytes β roughly two machine words, the size that fits in registers.
#include <iostream>
#include <vector>
class Counter {
int count = 0;
public:
/* return *this by reference -> enables CHAINING */
Counter& increment() { ++count; return *this; }
Counter& add(int n) { count += n; return *this; }
int value() const { return count; }
};
/* returning a reference to an ELEMENT lets you assign
through the function call - this is how v[i] works */
int& at(std::vector<int> &v, size_t i) { return v[i]; }
/* DANGER: reference to a LOCAL. The object dies at return,
so the caller holds a dangling reference. */
/* int& bad() { int local = 42; return local; } */
int main() {
Counter c;
/* chaining, only possible because each call returns
a reference to the same object */
c.increment().increment().add(10).increment();
std::cout << "count = " << c.value() << "\n";
std::vector<int> v{1, 2, 3, 4, 5};
at(v, 2) = 99; /* ASSIGN through a call */
for (int n : v) std::cout << n << " ";
std::cout << "\n";
return 0;
}
Output:
count = 13
1 2 99 4 5
#include <iostream>
#include <string>
struct Big { char data[1024]; int id; }; /* 1028 bytes */
int copies = 0;
struct Tracked {
int id;
Tracked(int i) : id(i) {}
Tracked(const Tracked &o) : id(o.id) { ++copies; }
};
void by_value(Tracked t) { (void)t; }
void by_cref (const Tracked &t) { (void)t; }
void by_ref (Tracked &t) { t.id = 99; }
int main() {
Tracked t(1);
copies = 0; by_value(t);
std::cout << "by_value copies: " << copies << "\n";
copies = 0; by_cref(t);
std::cout << "by_cref copies: " << copies << "\n";
copies = 0; by_ref(t);
std::cout << "by_ref copies: " << copies
<< ", t.id now " << t.id << "\n";
std::cout << "\nsizeof(Big) = " << sizeof(Big)
<< " bytes copied by value\n";
std::cout << "sizeof(Big&) = " << sizeof(Big*)
<< " bytes passed by reference\n";
return 0;
}
Output:
by_value copies: 1
by_cref copies: 0
by_ref copies: 0, t.id now 99
sizeof(Big) = 1032 bytes copied by value
sizeof(Big&) = 8 bytes passed by reference
The copy counter makes it visible: pass by value invokes the copy constructor, both reference forms do not. Note sizeof(Big) is 1032, not 1028 β the int id after char[1024] needs 4-byte alignment and the total rounds up, which is the padding rule from the struct topic showing up again.
T&&) which bind only to temporaries β the basis of move semantics. It is why returning a big std::vector by value is now free rather than a copy: the compiler moves the internal buffer pointer instead of duplicating the data. This single feature is the biggest performance change in C++'s history. Search "C++ move semantics rvalue reference", and specifically "return value optimisation" β you will find that the "always return by reference for speed" advice you may read online is now outdated advice that makes code less safe for no gain.swap in both styles. State when returning a reference is safe and give the dangling reference to a local as the classic error. Know that a const& binds to temporaries and a non-const reference does not, and be able to justify pass-by-const& with the copy cost.Create a free account to tick topics off, take notes as you read, watch the video lessons and get a day-by-day study plan built around your exam date.
Loadingβ¦