一、函数核心作用
把一段重复、独立的逻辑封装起来,一次定义、多处调用,实现代码复用、逻辑分层、降低维护成本。
C++函数分为:普通全局函数、类成员函数(构造/析构/普通成员/静态成员/常函数)、内联函数、模板函数、Lambda匿名函数。
二、函数基础语法
1. 完整结构
// 返回值类型 函数名(参数列表) { 函数体; return 返回值; }
int add(int a, int b) {
return a + b;
}
-
返回值类型:函数执行完返回的数据类型;无返回写 void
-
函数名:标识符,见名知意,不能是关键字
-
参数列表:0个/多个,逗号分隔;形参(定义处)、实参(调用处)
-
函数体:大括号内执行逻辑
-
return:终止函数,带回返回值;void函数可省略 return
2. 无参、无返回示例
void printHello() {
std::cout << "Hello C++" << std::endl;
}
// 调用
printHello();
3. 有参有返回
double mul(double x, double y) {
return x * y;
}
double res = mul(3.14, 2);
三、函数声明 vs 函数定义(对应.h/.cpp)
1. 声明(只告诉编译器函数长什么样,无实现)
放在头文件 .h,作用:提前告知接口,解决"调用在定义前"报错。语法:末尾加分号。
// 声明
int add(int a, int b);
2. 定义(完整实现逻辑)
放在 .cpp,只能定义一次。
int add(int a, int b) {
return a + b;
}
工程规范:
.h:放函数声明
.cpp:#include 对应头文件,写函数实现
四、函数参数四大传递方式
1. 值传递(拷贝,最常用)
实参拷贝一份给形参,函数内修改形参不影响外部变量。
void test(int x) {
x = 100; // 只改副本
}
int a = 10;
test(a); // a 依旧是 10
缺点:大对象(string、类)拷贝开销大。
2. 引用传递 & 常引用(&/const&)
(1)普通引用 T&
形参是实参别名,修改形参直接改外部变量,无拷贝开销。
void modify(int& x) {
x = 999;
}
int num = 10;
modify(num); // num = 999
(2)const 常引用 const T&(最优传参)
只读取、不修改,无拷贝、不能改原数据,传递字符串/类首选。
void showStr(const std::string& s) {
std::cout << s;
// s = "xxx"; // 报错,const 不可修改
}
3. 指针传递 T*
传递变量地址,通过解引用修改原值,兼容C语言。
void setVal(int* p) {
*p = 666;
}
int n = 1;
setVal(&n);
4. 数组参数
数组传参等价于指针,会丢失长度,需额外传大小。
void printArr(int arr[], int len) {
for (int i = 0; i < len; i++)
std::cout << arr[i];
}
五、默认参数
定义函数时给参数预设值,调用不传则使用默认值。规则:带默认值的参数必须靠右。
// 正确:默认参数放后面
void func(int a, int b = 10, double c = 3.14) {}
// 调用多种写法
func(1); // b=10, c=3.14
func(1, 20); // c=3.14
func(1, 20, 9.9);
// 错误:默认参数不能靠左
// void func(int a = 10, int b) // 编译报错
注意:声明和定义只能一处写默认值,推荐写在 .h 声明里。
六、函数重载 Overload
同一作用域,函数名相同,参数列表不同(个数/类型/顺序),编译器自动匹配对应函数。返回值不能作为重载区分依据。
// 重载1
int calc(int a) {
return a;
}
// 重载2
int calc(int a, int b) {
return a + b;
}
// 重载3
double calc(double a) {
return a * 2;
}
calc(10); // 调用重载1
calc(1, 2); // 调用重载2
calc(2.5); // 调用重载3
Qt 中大量使用重载(按钮点击、控件构造)。
七、特殊函数:类内成员函数
1. 构造函数 Constructor
函数名=类名,无返回值,可重载。创建对象自动调用,用于初始化成员变量。
class Person {
public:
Person() { // 无参构造
age = 0;
}
Person(int a) { // 有参构造(重载)
age = a;
}
private:
int age;
};
Person p1; // 调用无参构造
Person p2(18); // 调用有参构造
2. 析构函数 Destructor ~类名()
如:~MainWindow()
波浪号~+类名,无参数、无返回、只能一个。对象销毁时自动调用,释放堆内存、资源、句柄。
class MainWindow {
public:
MainWindow();
~MainWindow(); // 析构
};
3. 常成员函数 const
函数末尾加 const,函数内不能修改成员变量,只能读取。
class Test {
public:
int getNum() const {
// num = 10; // 报错
return num;
}
private:
int num = 5;
};
4. 静态成员函数 static
属于类,不属于单个对象,只能访问静态成员,没有 this 指针。
class Math {
public:
static int max(int a, int b) {
return a > b ? a : b;
}
};
// 直接类名调用,不用创建对象
Math::max(3, 9);
八、内联函数 inline
普通函数调用有压栈跳转开销,inline 提示编译器将函数代码直接嵌入调用处,省去调用开销。适用:代码很短(1~5行)、频繁调用的函数。
inline int square(int x) {
return x * x;
}
模板函数默认隐式 inline,必须写在头文件。
九、模板函数 template
通用函数,适配任意数据类型,解决重复写 int/double/string 同款逻辑。
template<typename T>
T add(T a, T b) {
return a + b;
}
add<int>(1, 2); // 返回 3
add<double>(1.1, 2.2); // 返回 3.3
强制放 .h 头文件,编译器需要完整定义才能实例化。
十、Lambda 匿名函数 C++11+
不用定义函数名,临时写一段逻辑,常用于 Qt 信号槽、排序、遍历。
auto func = [](int x) -> int {
return x * 2;
};
std::cout << func(5); // 输出 10
捕获列表 \[\]:= 值捕获、\& 引用捕获外部变量。
十一、函数递归
函数内部自己调用自己,必须有终止条件,否则死循环栈溢出。
// 阶乘
int fact(int n) {
if (n <= 1) return 1; // 终止条件
return n * fact(n - 1);
}
十二、返回值补充
-
void:无返回,可写 return; 提前退出
-
引用返回 int&:可以作为左值修改原变量
-
指针返回 int*:返回堆地址,记得外部释放
-
函数不能返回局部普通变量的引用/指针(局部变量销毁,悬空引用)
十三、常见易错点
-
函数声明加分号,定义不能加分号
-
模板、inline 放头文件;普通实现放 .cpp
-
重载只看参数,返回值不算区分条件
-
默认参数从右往左写
-
传大型对象优先 const&,减少拷贝
-
析构函数不要手动调用,对象销毁自动执行