C++ Tutorial
C++ Function Parameters
Pass by value copies. Pass by reference shares. Default arguments fill in when the caller omits them.
Parameters are the inputs
The names in the parentheses of a definition are parameters. The values you write at the call site are arguments. C++ matches them in order: the first argument initializes the first parameter, the second argument the second parameter.
How that initialization works is the subject of this chapter. A plain type copies. A type with &shares the caller’s object. A default value stands in when the caller leaves an argument out.
Pass by value copies
void bump(int n) receives its own int. Assigning to n changes the copy. The variable in main stays the same. That is safe and simple for small types.
Example
#include <iostream>
using namespace std;
void bump(int n) {
n = n + 1;
cout << "inside: " << n << endl;
}
int main() {
int coins = 10;
bump(coins);
cout << "after: " << coins << endl;
return 0;
}The program prints inside: 11 and after: 10. bump never sawcoins. It saw a copy of the number 10. Use pass by value when the function should read a number (or compute from it) and leave the caller’s variable alone.
Pass by reference shares
int& n is the same alias rule as int& r = x;. The parameter is another name for the argument. Writes go through to the caller. That is how you update an object without returning it.
Example
#include <iostream>
using namespace std;
void bump(int& n) {
n = n + 1;
}
void report(const int& n) {
cout << "coins: " << n << endl;
}
int main() {
int coins = 10;
bump(coins);
report(coins);
return 0;
}After bump(coins), coins is 11. report takesconst int&: it shares the object but must not assign to it. For a large string or struct, const Type& is the usual way to read without copying. For a single int, either a copy or a const reference is fine; the const reference shows the pattern you will reuse.
Choose copy or share
| Parameter | What happens | Use when |
|---|---|---|
int n | Copy | Small input you will not write back |
int& n | Share, writable | The caller’s variable must change |
const int& n | Share, read-only | Avoid a copy, promise not to assign |
Returning a value is still the cleanest way to produce a new result: int next = coins + 1; inside the function, then return next;. Reach for a writable reference when several fields of a struct must change together, or when a swap has to exchange two existing variables.
Default arguments
A default sits on the parameter list. If the caller omits that argument, the default is used. Defaults can only trail: once you give a default, every parameter after it needs a default too. Put the defaults on the declaration the compiler sees first (the prototype, or the definition if there is no prototype).
Example
#include <iostream>
#include <string>
using namespace std;
void label(string name, string tag = "guest", int seats = 1) {
cout << name << " (" << tag << "), seats: " << seats << endl;
}
int main() {
label("Rae");
label("Rae", "member");
label("Rae", "member", 3);
return 0;
}First call fills tag and seats from the defaults. Second call overridestag and keeps seats at 1. Third call supplies every argument. You cannot skip tag and still pass seats; C++ fills from the right by omitting trailing arguments, not by jumping over a hole.
Put the pieces together
Write the cheap inputs by value. Write outputs you must mutate as non-const references. Write large read-only inputs as const references. Add defaults only for arguments that have a sensible fallback, and keep them at the end of the list.
If a function both needs a default and is defined below main, put the default on the prototype above main. Do not repeat the default on the definition — g++ will reject the duplicate.
Next: two functions can share a name if their parameter lists differ. That is overloading.
Worked examples
The short listings above are there so you can see the grammar. The programs here use the same statements on quantities that already have units: a speed, a pH, a count of bases. They are classroom numbers. Air resistance is ignored. g is 9.81 m/s² unless a line says otherwise.
Open them in the C++ editor at /cpp/try. Change one measurement and check whether the result still has the right unit.
Maths
Hypotenuse by value
A 5-12-13 triangle is another integer right triangle: 25 + 144 = 169. Passing a and b copies the numbers. The function cannot change the caller’s a. It only returns c.
c = √(a² + b²)
Example
#include <iostream>
#include <cmath>
using namespace std;
double hypot_leg(double a, double b) {
return hypot(a, b);
}
int main() {
cout << "c = " << hypot_leg(5.0, 12.0) << endl;
return 0;
}Physics
Impulse on a momentum
Impulse J equals change in momentum: J = F Δt = Δp. Adding 4 N·s to 10 kg·m/s leaves 14 kg·m/s. A reference parameter changes the caller’s p. A copy would add 4 to a temporary and throw it away.
Δp = F Δt
Example
#include <iostream>
using namespace std;
void apply_impulse(double &momentum, double joule_s) {
momentum += joule_s;
}
int main() {
double p = 10.0;
apply_impulse(p, 4.0);
cout << "p = " << p << " kg m/s" << endl;
return 0;
}