Giao diện
🔧 Functions — Hàm trong C++
Functions là building blocks của mọi chương trình. Hiểu cách truyền tham số quyết định performance và tính đúng đắn của code.
Declaration vs Definition
Declaration (Prototype) — Chỉ khai báo
cpp
// Declaration: Nói "hàm này tồn tại"
// Thường đặt trong header file (.h, .hpp)
int add(int a, int b); // OK
double calculateArea(double radius); // OK
void greet(std::string name); // OK - tên param là optional
void process(int, int); // OK - không cần tên paramDefinition — Cài đặt đầy đủ
cpp
// Definition: Cài đặt logic của hàm
// Thường đặt trong source file (.cpp)
int add(int a, int b) {
return a + b;
}
double calculateArea(double radius) {
return 3.14159 * radius * radius;
}Tại sao tách Declaration và Definition?
┌─────────────────────────────────────────────────────────────────┐
│ math.h (Header) math.cpp (Source) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ // Declaration only #include "math.h" │
│ int add(int a, int b); │
│ int multiply(int a, int b); // Definitions │
│ int add(int a, int b) { │
│ return a + b; │
│ } │
│ │
│ int multiply(int a, int b) { │
│ return a * b; │
│ } │
│ │
└─────────────────────────────────────────────────────────────────┘
▼
main.cpp chỉ cần #include "math.h"
→ Compile nhanh hơn (không cần recompile math.cpp nếu main thay đổi)Pass by Value — Truyền bản sao
cpp
#include <iostream>
void doubleValue(int x) {
x = x * 2; // Chỉ modify bản sao local
std::cout << "Inside function: x = " << x << std::endl;
}
int main() {
int num = 10;
std::cout << "Before: num = " << num << std::endl;
doubleValue(num); // Truyền bản sao của num
std::cout << "After: num = " << num << std::endl;
return 0;
}Output:
Before: num = 10
Inside function: x = 20
After: num = 10 ← num không thay đổi!Ưu/Nhược điểm Pass by Value
| ✅ Ưu điểm | ❌ Nhược điểm |
|---|---|
| An toàn — không thay đổi original | Tốn memory cho copy |
| Dễ hiểu, predictable | Chậm với objects lớn |
| Thread-safe (mỗi thread có bản sao riêng) |
Pass by Reference — Truyền tham chiếu
cpp
#include <iostream>
void doubleValue(int& x) { // Note: int& (reference)
x = x * 2; // Modify trực tiếp biến gốc!
}
int main() {
int num = 10;
std::cout << "Before: num = " << num << std::endl;
doubleValue(num); // Truyền reference
std::cout << "After: num = " << num << std::endl;
return 0;
}Output:
Before: num = 10
After: num = 20 ← num đã thay đổi!Pass by Const Reference — Read-only, không copy
cpp
#include <iostream>
#include <string>
#include <vector>
// ❌ BAD: Copy toàn bộ vector (có thể hàng triệu elements!)
void printBad(std::vector<int> v) {
for (const auto& x : v) {
std::cout << x << " ";
}
}
// ✅ GOOD: Không copy, chỉ reference, không modify
void printGood(const std::vector<int>& v) {
for (const auto& x : v) {
std::cout << x << " ";
}
// v.push_back(100); // ❌ Error: v is const
}
// ✅ GOOD: Với string
void greet(const std::string& name) {
std::cout << "Hello, " << name << "!" << std::endl;
}📌 HPN Standard: Khi nào dùng gì?
| Situation | Syntax | Reason |
|---|---|---|
| Modify original | T& | Thay đổi biến gốc |
| Read large object | const T& | Tránh copy, không modify |
| Small primitives | T (by value) | Copy rẻ, đơn giản |
| Optional/nullable | T* (pointer) | Có thể nullptr |
Golden Rule: Mặc định dùng const T& cho objects. Chỉ dùng by-value cho primitives (int, double, char, bool).
Pass by Pointer vs Reference
cpp
#include <iostream>
// Pass by pointer — Có thể null
void processPtr(int* ptr) {
if (ptr != nullptr) {
*ptr = 100;
}
}
// Pass by reference — Guaranteed non-null
void processRef(int& ref) {
ref = 100;
}
int main() {
int x = 42;
processPtr(&x); // Phải dùng &
std::cout << "After ptr: x = " << x << std::endl;
processRef(x); // Không cần &, tự động bind
std::cout << "After ref: x = " << x << std::endl;
// Pointer có thể null
processPtr(nullptr); // OK, hàm handle được
// Reference KHÔNG thể null
// processRef(nullptr); // ❌ Compiler error
return 0;
}| Aspect | Pointer (T*) | Reference (T&) |
|---|---|---|
| Nullable? | ✅ Có thể nullptr | ❌ Không thể |
| Rebindable? | ✅ ptr = &other; | ❌ Một lần mãi mãi |
| Syntax | *ptr để access | Trực tiếp ref |
| Use case | Optional params | Required params |
Default Arguments
cpp
#include <iostream>
#include <string>
// Default arguments: Từ phải sang trái!
void greet(const std::string& name, const std::string& greeting = "Hello") {
std::cout << greeting << ", " << name << "!" << std::endl;
}
// ❌ Invalid: default argument không ở cuối
// void bad(int a = 10, int b) { ... }
int main() {
greet("Alice"); // Hello, Alice!
greet("Bob", "Xin chào"); // Xin chào, Bob!
return 0;
}Function Overloading
cpp
#include <iostream>
#include <string>
// Cùng tên, khác parameters
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
std::string add(const std::string& a, const std::string& b) {
return a + b;
}
int main() {
std::cout << add(1, 2) << std::endl; // 3 (int version)
std::cout << add(1.5, 2.5) << std::endl; // 4.0 (double version)
std::cout << add("Hello, ", "World!") << std::endl; // Hello, World!
return 0;
}⚠️ Overloading không dựa trên return type
cpp
int getValue();
double getValue(); // ❌ Error: chỉ khác return type
// Compiler không biết gọi cái nào khi:
// auto x = getValue();Return by Value, Reference, and Pointer
cpp
#include <iostream>
#include <vector>
#include <string>
// Return by value — OK cho small objects
int getAnswer() {
return 42;
}
// Return by reference — DANGEROUS nếu return local!
// ❌ BAD: Dangling reference
std::string& badGetName() {
std::string name = "Alice";
return name; // name bị destroy sau khi function ends!
}
// ✅ GOOD: Return reference đến parameter hoặc member
std::string& getLonger(std::string& a, std::string& b) {
return (a.size() > b.size()) ? a : b;
}
// ✅ GOOD: Return by value (Move semantics xử lý efficiency)
std::vector<int> createVector() {
std::vector<int> v = {1, 2, 3, 4, 5};
return v; // Move, không copy (C++11)
}🐛 Bug Hunt Challenge
🐛 Bug Hunt
Đoạn code sau có bug gì?
cpp
#include <iostream>
void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 10, y = 20;
swap(x, y);
std::cout << "x = " << x << ", y = " << y << std::endl;
return 0;
}💡 Gợi ý
Hàm swap nhận parameters như thế nào?
🔍 Giải thích & Fix
Output: x = 10, y = 20 — Không swap!
Bug: Parameters a và b được pass by value → chỉ swap bản copy.
Fix:
cpp
void swap(int& a, int& b) { // Pass by reference
int temp = a;
a = b;
b = temp;
}Hoặc dùng std::swap:
cpp
#include <utility>
std::swap(x, y);📚 Tổng kết
| Concept | Syntax | Use Case |
|---|---|---|
| Pass by value | func(T x) | Small types, cần copy |
| Pass by reference | func(T& x) | Modify original |
| Pass by const ref | func(const T& x) | Read-only, avoid copy |
| Pass by pointer | func(T* x) | Nullable, optional |
| Default arguments | func(int x = 10) | Optional params |
| Overloading | Same name, diff params | Polymorphism |
➡️ Tiếp theo
Tiếp theo: Scope & Lifetime — Local, Global, và Static variables.
🧠 Quiz
Câu 1: Đoạn code sau in ra kết quả gì?
cpp
void change(int x) { x = 100; }
int main() {
int a = 5;
change(a);
std::cout << a;
}- [x] A) 5
- [ ] B) 100
- [ ] C) 0
- [ ] D) Lỗi biên dịch
💡 Giải thích: Hàm
changenhận tham số pass-by-value, nghĩa làxlà bản copy củaa. Thay đổixbên trong hàm không ảnh hưởng đếnagốc. Muốn thay đổia, cần dùngvoid change(int& x).
Câu 2: Hàm nào sau đây là overloading hợp lệ với int calc(int a, int b)?
- [ ] A)
double calc(int a, int b) - [x] B)
int calc(int a, int b, int c) - [ ] C)
int calc(int x, int y) - [ ] D)
long calc(int a, int b)
💡 Giải thích: Function overloading yêu cầu danh sách tham số khác nhau (số lượng hoặc kiểu). Chỉ khác return type (A, D) hoặc chỉ khác tên tham số (C) là KHÔNG đủ. Lựa chọn B có 3 tham số thay vì 2, nên hợp lệ.
Câu 3: Đoạn code sau có vấn đề gì?
cpp
void print(int x = 10, int y) {
std::cout << x + y;
}- [ ] A) Không có vấn đề gì
- [ ] B) Thiếu return type
- [x] C) Default argument phải từ phải sang trái
- [ ] D) Không thể có 2 tham số
💡 Giải thích: Trong C++, default arguments phải bắt đầu từ tham số bên phải nhất. Nếu
xcó default value thìycũng phải có. Cách đúng:void print(int x, int y = 0)hoặcvoid print(int x = 10, int y = 0).