본문으로 건너뛰기
Previous
Next
C++ Functions: Parameters, Return Values, Overloading,

C++ Functions: Parameters, Return Values, Overloading,

C++ Functions: Parameters, Return Values, Overloading,

이 글의 핵심

Functions are the building blocks of C++ programs. This guide covers everything beginners need: how to define and call functions, when to pass by value vs reference, how overloading works, and the pitfalls to avoid.

Why Functions?

Functions give a name to a block of code so you can reuse it without copying. They break large programs into smaller, testable pieces.

// Without functions — repeated code
double area1 = 3.14159 * 5.0 * 5.0;
double area2 = 3.14159 * 3.0 * 3.0;

// With a function — write once, use anywhere
double circleArea(double radius) {
    return 3.14159 * radius * radius;
}

double area1 = circleArea(5.0);
double area2 = circleArea(3.0);

Function Anatomy

// Return type  Name  Parameters
double          add   (double a, double b) {
    return a + b;  // body
}

Every part matters:

  • Return type: what type the function produces (void if nothing)
  • Name: what you call it
  • Parameters: inputs (can have zero or many)
  • Body: the code that runs
  • return: sends a value back to the caller

Declaration vs Definition

A declaration tells the compiler a function exists (its name, return type, parameter types). A definition provides the actual code. You can declare a function before defining it:

// Declaration (prototype) — put in header files or at top of file
double squareRoot(double x);
void printLine(const std::string& text);

// Use the function before the definition
int main() {
    std::cout << squareRoot(16.0) << '\n';  // 4.0
    printLine("Hello");
}

// Definition — the actual implementation
double squareRoot(double x) {
    return std::sqrt(x);
}

void printLine(const std::string& text) {
    std::cout << text << '\n';
}

In larger projects, declarations go in .h header files and definitions go in .cpp source files.


Parameters: Value, Reference, and Pointer

Pass by Value

A copy of the argument is made. Changes inside the function don’t affect the caller:

void doubleIt(int x) {
    x *= 2;          // modifies the copy, not the caller's variable
}

int main() {
    int n = 5;
    doubleIt(n);
    std::cout << n;  // still 5
}

Use when: the type is small (int, double, char, small structs), or you intentionally want a local copy to modify.

Pass by Reference

An alias to the caller’s variable — no copy, changes affect the caller:

void doubleInPlace(int& x) {    // & means reference
    x *= 2;                     // modifies the caller's variable
}

int main() {
    int n = 5;
    doubleInPlace(n);
    std::cout << n;  // 10
}

Use when: you need to modify the caller’s variable (output parameter).

Pass by const Reference

Efficient read-only access — no copy, but cannot be modified:

double sumVector(const std::vector<double>& v) {  // const & — no copy, no modification
    double total = 0;
    for (double x : v) total += x;
    return total;
}

Use when: the type is large (string, vector, any struct) and the function only reads it. This is the most common pattern for large types in C++.

Pass by Pointer

Similar to reference, but allows nullptr (no argument):

void process(const std::string* text) {
    if (text == nullptr) {
        std::cout << "(nothing)\n";
        return;
    }
    std::cout << *text << '\n';
}

int main() {
    std::string s = "hello";
    process(&s);     // pass address
    process(nullptr); // no argument
}

Use when: null is a valid state (optional parameter). Prefer std::optional<T> in modern C++.

Quick Reference

TypeSyntaxCopy?Modifiable?Use for
Valueint xYesLocal onlySmall types
Const refconst T& xNoNoLarge read-only types
RefT& xNoYesOutput parameters
PointerT* xNoYesOptional parameters

Return Values

Returning a Value

int add(int a, int b) {
    return a + b;
}

std::string greet(const std::string& name) {
    return "Hello, " + name + "!";
}

Every non-void function must return a value on all code paths. Modern compilers warn or error when a path exits without returning.

Returning Large Objects (RVO)

You can return large objects by value — the compiler eliminates the copy:

std::vector<int> makeRange(int start, int end) {
    std::vector<int> result;
    for (int i = start; i < end; i++) {
        result.push_back(i);
    }
    return result;  // RVO: constructed directly in caller's memory — no copy
}

int main() {
    auto v = makeRange(0, 1000);  // efficient — no extra copy
}

Never Return References to Locals

A local variable is destroyed when the function returns. Returning a reference to it is undefined behavior:

// WRONG — dangling reference
int& getLocal() {
    int x = 42;
    return x;   // x is destroyed here — reference is dangling
}

// CORRECT — return by value
int getLocal() {
    int x = 42;
    return x;   // copy returned, x destroyed — caller gets the copy
}

void Functions

Functions that perform actions but produce no value use void:

void printDivider(char ch, int count) {
    for (int i = 0; i < count; i++) {
        std::cout << ch;
    }
    std::cout << '\n';
}

// void functions use return; with no value to exit early
void processIfValid(int value) {
    if (value < 0) return;  // early exit
    // ...process...
}

Default Arguments

Parameters can have default values when not provided:

void printBox(int width, int height = 5, char fill = '*') {
    for (int row = 0; row < height; row++) {
        for (int col = 0; col < width; col++) {
            std::cout << fill;
        }
        std::cout << '\n';
    }
}

int main() {
    printBox(10);          // width=10, height=5, fill='*'
    printBox(8, 3);        // width=8, height=3, fill='*'
    printBox(6, 4, '#');   // width=6, height=4, fill='#'
}

Rules:

  • Defaults apply right-to-left — all parameters to the right of a default must also have defaults
  • Put defaults in the declaration (header), not in the definition
// Header — declaration with defaults
void connect(const std::string& host, int port = 8080, bool secure = false);

// Source — definition without defaults (they're already in the header)
void connect(const std::string& host, int port, bool secure) {
    // ...
}

Function Overloading

Multiple functions with the same name but different parameter types:

// Three versions of print — different parameter types
void print(int value) {
    std::cout << "int: " << value << '\n';
}

void print(double value) {
    std::cout << "double: " << value << '\n';
}

void print(const std::string& value) {
    std::cout << "string: " << value << '\n';
}

int main() {
    print(42);           // calls print(int)
    print(3.14);         // calls print(double)
    print("hello");      // calls print(const string&)
}

You cannot overload on return type alone — the parameter list must differ:

int getValue();     // OK
double getValue();  // compile error — same name and parameters

Recursion

A function that calls itself. Classic examples:

// Factorial: n! = n * (n-1) * ... * 1
long long factorial(int n) {
    if (n <= 1) return 1;           // base case
    return n * factorial(n - 1);   // recursive case
}

// Fibonacci — note: O(2^n) time, impractical for large n
int fib(int n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
}

// Better: iterative Fibonacci — O(n) time
long long fibIterative(int n) {
    if (n <= 1) return n;
    long long a = 0, b = 1;
    for (int i = 2; i <= n; i++) {
        long long c = a + b;
        a = b;
        b = c;
    }
    return b;
}

Every recursive function needs a base case that stops the recursion. Without it, the program runs until the stack overflows.


Common Mistakes

Calling before declaring:

int main() {
    result = add(2, 3);  // error: add not declared yet
}

int add(int a, int b) { return a + b; }

// Fix: declare add before main, or define add before main

Missing return on some path:

int sign(int x) {
    if (x > 0) return 1;
    if (x < 0) return -1;
    // compiler may warn: control reaches end without return
    // Fix: add return 0;
}

Passing large types by value:

double sumAll(std::vector<int> v) { ... }        // copies the entire vector
double sumAll(const std::vector<int>& v) { ... } // no copy — correct

Returning reference to local:

const std::string& getName() {
    std::string name = "Alice";  // local variable
    return name;                  // UB: name destroyed at end of function
}

Practical Example: String Utilities

#include <string>
#include <algorithm>
#include <sstream>
#include <vector>

// Count occurrences of a character
int countChar(const std::string& s, char target) {
    int count = 0;
    for (char c : s) {
        if (c == target) count++;
    }
    return count;
}

// Check if string is a palindrome
bool isPalindrome(const std::string& s) {
    std::string lower = s;
    std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
    std::string reversed = lower;
    std::reverse(reversed.begin(), reversed.end());
    return lower == reversed;
}

// Split string by delimiter
std::vector<std::string> split(const std::string& s, char delimiter) {
    std::vector<std::string> result;
    std::stringstream ss(s);
    std::string token;
    while (std::getline(ss, token, delimiter)) {
        result.push_back(token);
    }
    return result;
}

int main() {
    std::cout << countChar("mississippi", 's') << '\n'; // 4
    std::cout << isPalindrome("racecar") << '\n';       // 1 (true)
    std::cout << isPalindrome("hello") << '\n';         // 0 (false)

    auto words = split("one,two,three", ',');
    for (const auto& w : words) {
        std::cout << w << '\n';  // one / two / three
    }
}

Key Takeaways

  • Pass small types by value, large types by const reference — this is the most common function parameter pattern
  • Const reference (const T&) gives efficient read-only access without copying
  • Return by value for most things — RVO eliminates the copy overhead
  • Never return a reference to a local variable — it’s undefined behavior (dangling reference)
  • Overloading lets you use the same name for functions that do the same thing with different types
  • Default arguments go in the declaration (header), not the definition
  • Recursion needs a base case — always think about when it stops

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Complete C++ function guide for beginners: declaration vs definition, pass by value/reference/pointer, return rules, RVO… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, functions, parameters, return, beginner 등으로 검색하시면 이 글이 도움이 됩니다.