本章目标:把程序拆成函数;搞懂值/引用/指针三种传参;会组织多文件工程。引用是 C++ 相对 C 最大的语法增量,也是“避免拷贝”的技术基础。
3.1 函数:声明、定义与重载
先学会把一段逻辑装进函数,再学会把函数摆到正确的文件里。
声明 vs 定义
声明(原型)告诉编译器“有这么个函数”,可以放在头文件里;定义是函数体,放在 .cpp 里。int add(int a, int b); 是声明,int add(int a, int b) { return a + b; } 是定义。调用函数时只需要声明可见;链接时必须有定义。
重载:同名不同参数
int maxOf(int, int) 和 double maxOf(double, double) 可以同时存在,编译器根据实参类型选择版本,和 Java 的重载一样。注意两点:返回类型不能区分重载;默认参数只能从右往左连续写(int f(int a = 1, int b); 是错的)。
#include <iostream>
int maxOf(int a, int b) { return a > b ? a : b; }
double maxOf(double a, double b) { return a > b ? a : b; }
int main() { std::cout << maxOf(3, 5) << ' ' << maxOf(1.5, 2.5); }
5 2.5
✍️ 本节练习
写一个函数 int maxOf(int a, int b) 返回较大值,再写一个重载 double maxOf(double a, double b)。main 里读两个整数和一个浮点数对,分别调用并输出结果。
输入 一行两个整数;一行两个小数。
输出 两行,每行一个最大值。
3 5
2.5 1.55
2.5💡 提示 重载靠参数类型区分,返回类型不能区分重载。
✅ 查看参考答案与解析
#include <iostream>
int maxOf(int a, int b) { return a > b ? a : b; }
double maxOf(double a, double b) { return a > b ? a : b; }
int main() {
int x, y;
double p, q;
std::cin >> x >> y;
std::cin >> p >> q;
std::cout << maxOf(x, y) << '\n';
std::cout << maxOf(p, q) << '\n';
return 0;
}
解析 同名函数靠参数类型不同区分,编译器自动选对版本。
3.2 参数传递:值、引用、指针
这是全书最需要想清楚的一节。Java 只有值传递一种;C++ 有四种选择,你必须自己决定用哪种。
四种传参方式
byValue(int x):改的是副本,原变量不变;byRef(int& x):改的是原变量(引用=原变量的别名);byPointer(int* p):可能为空,用前先判空;byConstRef(const string& s):不拷贝也不能改——这是 C++ 里出现频率最高的参数写法。
#include <iostream>
#include <string>
void byValue(int x) { x = 100; (void)x; } // 改副本
void byRef(int& x) { x = 100; } // 改原变量
void byConstRef(const std::string& s) { (void)s; } // 零拷贝、只读
int main() {
int a = 1;
byValue(a); std::cout << a << '\n'; // 1
byRef(a); std::cout << a << '\n'; // 100
}
1
100
不要返回局部变量的引用或指针
局部变量在函数返回时就被销毁,返回它的地址得到一个悬垂引用,用它=未定义行为。编译器通常会给 -Wreturn-local-addr 警告。这是 C++ 独有的风险(Java 的 GC 没有这个问题)。
引用的三条铁律
int& r = a; 引用必须初始化(int& r; 编译不过);一旦绑定就不能改绑(r = b 是把 b 的值赋给 a,不是让 r 改指 b);不存在空引用(所以用引用的函数不需要判空)。
✍️ 本节练习
写一个函数 bool isEven(int n),然后读入 n,输出 1 到 n 之间所有偶数的和。
输入 一个整数 n(1 ≤ n ≤ 100000)。
输出 1 到 n 的偶数和。
1030💡 提示 函数只负责判断,求和写在 main 的循环里。
✅ 查看参考答案与解析
#include <iostream>
bool isEven(int n) { return n % 2 == 0; }
int main() {
int n;
std::cin >> n;
long long sum = 0;
for (int i = 1; i <= n; ++i) {
if (isEven(i)) { sum += i; }
}
std::cout << sum << '\n';
return 0;
}
解析 小函数让 main 更清晰;累加可能超 int,用 long long。
写一个函数 void swapRef(int& a, int& b) 交换两个整数,main 里读两个数调用它并输出交换后的结果。
输入 一行两个整数 a b。
输出 交换后的 a b(空格分隔)。
3 55 3💡 提示 int t = a; a = b; b = t;
✅ 查看参考答案与解析
#include <iostream>
void swapRef(int& a, int& b) {
int t = a;
a = b;
b = t;
}
int main() {
int x, y;
std::cin >> x >> y;
swapRef(x, y);
std::cout << x << ' ' << y << '\n';
return 0;
}
解析 引用就是原变量的别名,函数里改的就是调用方的变量。
写两个函数:void byValue(int x) 把参数改成 100;void byRef(int& x) 把参数改成 100。main 里把 a=1 分别传进去,输出 a 两次,观察区别。
输入 无
输出 两行,第一行 byValue 后 a 的值,第二行 byRef 后 a 的值。
(本题无输入)1
100💡 提示 值传递改的是副本,引用传递改的是原变量。
✅ 查看参考答案与解析
#include <iostream>
void byValue(int x) { x = 100; (void)x; }
void byRef(int& x) { x = 100; }
int main() {
int a = 1;
byValue(a);
std::cout << a << '\n'; // 1:副本被改,a 没变
byRef(a);
std::cout << a << '\n'; // 100:引用改的是原变量
return 0;
}
解析 这个实验值得亲手跑一遍,是理解引用意义的最小例子。
3.3 命名空间与多文件组织
为什么别人的 C++ 工程有 .h 和 .cpp 两种文件?什么时候放哪边?这一节解决这两个实际问题。
命名空间:给名字划分地盘
namespace math { ... } 包起来,外面用 math::square 访问(完全限定名,最安全)。using math::PI 只引入一个名字;using namespace std 引入整个命名空间,写在 .cpp 顶部可以接受,但绝不要写进头文件——头文件会被复制到很多源文件,等于强迫所有人接受 std 的全部名字。
头文件放什么、源文件放什么
规则:头文件放声明、类定义、constexpr 常量、inline 函数、模板(后面讲);源文件放普通函数的定义。头文件用 #pragma once 防止重复包含。编译时把 .cpp 一起交给 g++。
// ---------- util.h ----------
#pragma once
double square(double x); // 声明放头文件
// ---------- util.cpp ----------
#include "util.h"
double square(double x) { return x * x; } // 定义放源文件
// ---------- main.cpp ----------
#include "util.h"
#include <iostream>
int main() { std::cout << square(3.0); }
9
ODR:为什么有的东西能放头文件
单一定义规则(ODR):同一个名字在整个程序里可以有多个声明,但只能有一个定义,而且声明必须完全一致。inline 函数、constexpr 常量、类内定义的成员函数、模板,这些“可以写在头文件里”的东西,本质上都是标准给出的 ODR 豁免——它们允许在多处出现且保持一份定义。
✍️ 本节练习
定义命名空间 math,里面放 constexpr double PI 和函数 double square(double x)。main 里用 math:: 前缀调用,输出 PI 和 square(3.0)。
输入 无
输出 两行:PI 的值、9。
(本题无输入)3.14159265
9💡 提示 namespace math { ... } 包起来,外面用 math::square 访问。double 默认只显示 6 位有效数字,输出 PI 前用 std::setprecision(9)(9 位有效数字),不要用 fixed(会把 square 也变成 9.00000000)。
✅ 查看参考答案与解析
#include <iomanip>
#include <iostream>
namespace math {
constexpr double PI = 3.14159265;
double square(double x) { return x * x; }
}
int main() {
std::cout << std::setprecision(9) << math::PI << '\n';
std::cout << math::square(3.0) << '\n';
return 0;
}
解析 完全限定名 math:: 最安全;命名空间解决名字冲突。
把一个求平方的函数拆成三个文件:util.h(声明 + #pragma once)、util.cpp(定义)、main.cpp(调用),编译运行。这道题先不写进一个文件,按下面参考代码建三个文件,再用 g++ main.cpp util.cpp 编译。
输入 无
输出 9
(本题无输入)9💡 提示 编译命令:g++ -std=c++17 -Wall -Wextra -o app main.cpp util.cpp
✅ 查看参考答案与解析
#pragma once
double square(double x);
#include "util.h"
double square(double x) { return x * x; }
#include "util.h"
#include <iostream>
int main() {
std::cout << square(3.0) << '\n';
return 0;
}
解析 头文件放声明、源文件放定义;多个 .cpp 一起交给 g++,链接器把函数定义拼起来。
本章小结
- 声明放头文件、定义放源文件;编译期看声明,链接期看定义。
- const T& 是最高频参数写法:不拷贝、不改、能绑临时对象。
- 引用必须初始化、不能改绑、不存在空引用;不要返回局部变量的引用。
- using namespace std 别写进头文件;模板、inline、constexpr 可以放头文件(ODR 豁免)。
🧩 本章综合练习
这几道题把本章多个知识点串起来,建议合上资料独立完成,再展开答案对照。
写一个函数 void analyze(const std::vector<int>& v, int& minv, int& maxv, double& avg),把最小值、最大值、平均值写回调用方。main 读入数据后调用并输出。这是 C++ 参数设计最典型的组合:只读的用 const 引用(零拷贝),要写回的用引用。
输入 第一行整数 n;第二行 n 个整数。
输出 三行:min=、max=、avg=(2 位小数)。
6
5 3 9 1 7 4min=1
max=9
avg=4.83💡 提示 返回多个值就用引用输出参数;传入的容器不要按值传,否则整份拷贝。
✅ 查看参考答案与解析
#include <iomanip>
#include <iostream>
#include <vector>
void analyze(const std::vector<int>& v, int& minv, int& maxv, double& avg) {
minv = v[0];
maxv = v[0];
long long sum = 0;
for (int x : v) { // 只读遍历
if (x < minv) { minv = x; }
if (x > maxv) { maxv = x; }
sum += x;
}
avg = static_cast<double>(sum) / static_cast<double>(v.size());
}
int main() {
int n{};
std::cin >> n;
std::vector<int> v(n);
for (int i = 0; i < n; ++i) { std::cin >> v[i]; }
int lo{}, hi{};
double avg{};
analyze(v, lo, hi, avg);
std::cout << "min=" << lo << '\n';
std::cout << "max=" << hi << '\n';
std::cout << "avg=" << std::fixed << std::setprecision(2) << avg << '\n';
return 0;
}
解析 const 引用既避免拷贝又承诺不改;引用输出参数在写回前要先赋初值(这里从 v[0] 起步)。
引用 · 命名空间 · 头文件 · ODR 单一定义规则