1. 函数基础
1.1 函数示例
1.编写函数
一个标准的函数包括以下部分:返回类型、函数名字、由 0 个或多个形参组成的列表和函数体,函数的功能在函数体中实现。
int fact(int val){
int ret = 1;
while(val > 1){
ret = ret*val;
val--;
}
return ret;
}
2.调用函数
调用函数时完成两项工作:一是用实参初始化函数对应的形参,如果没有形参则该工作不执行,二是将控制权转移给被调函数,此时主调函数的执行暂时中断,被调函数开始执行。调用函数时 return 语句完成两项工作:一是返回 return 语句中的值,二是将控制权从被调函数转移回调用处。函数的返回值用于初始化调用函数的结果。
#include <iostream>
int fact(int val) {
int ret = 1;
while(val > 1) {
ret *= val;
val--;
}
return ret;
}
int main() {
int number = 5; // 你想要计算阶乘的数字
int factorial = fact(number); // 调用 fact 函数计算阶乘
cout << "阶乘 " << number << " 是 " << factorial << endl;
return 0;
}
3.形参和实参
实参是形参的初始值,第一个实参初始化第一个形参,第二个实参初始化第二个形参,以此类推。
int fact(int val); // val是形参
int number = 5;
int factorial = fact(number); // number为实参
注意: 实参的类型必须与对应的形参类型匹配。
4.函数的形参列表
函数的形参列表可以为空,但是不能省略,可以用关键字void置于形参列表内表示函数没有形参。形参列表中的形参通常用逗号隔开,其中每个形参都含有一个声明符的声明。即使两个形参类型一样,也必须把类型都写出来。任意两个形参不能同名,而且函数外层作用域的局部变量也不能使用与函数形参一样的名字。
void myFunction(int a, double b, char c) {
// 函数体
}
1.2 局部对象
在C++语言中,名字有作用域,对象有生命周期。名字的作用域是程序文本的一部分,名字在其中可见。对象的生命周期是程序执行过程中该对象存在的一段时间。函数体是一个语句块,每个块构成一个新的作用域,我们可以在其中定义变量。形参和函数体内部定义的变量统称为局部变量。
1.自动对象
我们把只存在于块执行期间的对象称为自动对象。当块的执行结束后,块中创建的自动对象的值就变成未定义的了。形参是一种自动对象。函数开始时为形参申请储存空间,因为形参定义在函数体作用域之内,一旦函数终止形参也就被销毁。
void myFunction(int a, double b) // 定义一个函数,带有两个形参
{
// 函数内的局部变量
int x = 5;
double y = 7.3;
cout << "形参 a 的值为:" << a << endl;
cout << "形参 b 的值为:" << b << endl;
cout << "局部变量 x 的值为:" << x << endl;
cout << "局部变量 y 的值为:" << y << endl;
}
2.局部静态对象
有时候需要让局部变量的生命周期贯穿函数调用及以后的时间,可以将局部变量定义成static类型从而获得这样的对象。局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。
#include <iostream>
void myFunction() {
// 定义局部静态变量
static int count = 0;
// 每次调用增加静态变量的值
count++;
std::cout << "局部静态变量 count 的值为:" << count << std::endl;
}
int main() {
// 多次调用函数以展示静态变量的持久性
myFunction();
myFunction();
myFunction();
return 0;
}
1.3 函数声明
函数只能定义一次,但可以多次声明。如果一个函数永远也不会被我们用到,那么它可以只有声明没有定义。函数的声明和函数的定义非常相似。唯一区别是函数声明无需函数体,用一个分号结尾即可。需要注意的是函数应该在头文件中声明,在源文件中定义。
2. 参数传递
参数传递有以下三种方式:
1.按值传递: 将参数的值复制一份传递给函数,函数内对参数的修改不会影响原始变量的值。
2.按引用传递: 将参数的引用传递给函数,函数内对参数的修改会影响原始变量的值。这样可以避免复制大块数据,提高效率。
3.按指针传递: 将参数的地址传递给函数,在函数内通过指针操作参数的值。和按引用传递一样,按指针传递可以改变原始变量的值。
2.1 按值传递
#include <iostream>
// 按值传递的函数
void increment(int num) {
num ++; // 修改传入的参数值的副本
std::cout << "函数内部的值为:" << num << std::endl;
}
int main() {
int value = 5;
std::cout << "函数调用前的值为:" << value << std::endl;
// 调用函数,并传递变量 value 的值
increment(value);
std::cout << "函数调用后的值为:" << value << std::endl;
return 0;
}
2.2 按引用传递
#include <iostream>
// 按引用传递的函数
void increment(int &num) {
num++; // 修改传入参数的值
std::cout << "函数内部的值为:" << num << std::endl;
}
int main() {
int value = 5;
std::cout << "函数调用前的值为:" << value << std::endl;
// 调用函数,并传递变量 value 的引用
increment(value);
std::cout << "函数调用后的值为:" << value << std::endl;
return 0;
}
1.使用引用避免拷贝
拷贝大的类类型对象或者容器时程序运行效率较低,甚至有的类类型不支持拷贝操作,使用引用可以提高效率。
2.使用引用形参返回额外信息
一个函数只能返回一个值,然后有时函数需要同时返回多个值,引用形参为我们一次返回多个结果提供了有效途径。
#include <iostream>
// 函数使用引用形参返回多个值
void calculate(int a, int b, int &sum, int &product) {
sum = a + b;
product = a * b;
}
int main() {
int x = 5, y = 3;
int resultSum, resultProduct;
// 调用函数,传递参数并接收结果
calculate(x, y, resultSum, resultProduct);
std::cout << "和:" << resultSum << std::endl;
std::cout << "积:" << resultProduct << std::endl;
return 0;
}
2.3 指针传递
// 指针传递
void exchange3(int* a, int* b)
{
int c = *a;
*a = *b;
*b = c;
}
int a = 3;
int b = 4;
exchange3(&a, &b);
cout << a << " " << b << endl;
注意: 指针形参也是传递的指针副本,指针副本和指针指向同一片内存地址,地址中的值修改,那么解引用指针后原来的值也被修改。 故可以通过指针形参来修改变量的值。
3. 函数形参详解
3.1 const形参和实参
当形参有顶层const时,传给他常量对象或者非常量对象都是可以的,但不可以将一个顶层const实参传给一个普通形参。
#include <iostream>
// 使用普通引用作为形参
void modifyValue(int& num) {
num = 10; // 这个函数可以修改传入的实参值
}
// 使用常量引用作为形参
void displayValue(const int& num) {
// 该函数不能修改传入的实参值
std::cout << "传入的值为: " << num << std::endl;
}
int main() {
int x = 5;
const int y = 10;
modifyValue(x); // 可以修改x的值
// modifyValue(y); // 这行代码会导致编译错误,因为不能修改常量对象的值
displayValue(x); // 可以接受非常量对象
displayValue(y); // 也可以接受常量对象
return 0;
}
注意: 把函数不会改变的形参定义成普通的引用是一种比较常用的错误,这会给函数的调用者一种错误,即函数可以修改它的实参的值。此外使用引用而非常量引用也会限制函数所能接受的实参类型,const对象、字面值或者需要类型转换的对象不能传递给普通的引用形参。
3.2 数组形参
数组的两个特殊性质对我们定义和使用对数组进行操作的函数有影响。这两个性质分别是:
不允许拷贝数组: 因为不能拷贝数组,所以我们无法以值传递的方式使用数组参数,但是我们可以把形参写成类似数组的形式。
使用数组时通常会将其转换成指针: 因为数组会被转换成指针,所以当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针;
void print(const int *);
void print(const int[]);
void print(const int[10]);
尽管表现形式不同,但上面的三个函数是等价的,每个函数的唯一形参都是const int * 类型的。如果我们传给 print 函数的是一个数组,则实参自动转换成指向数组首元素的指针。
注意: 因为数组是以指针的形式传递给函数的,所以一开始函数并不知道数组的确切尺寸,调用者应该为此提供一些额外信息。
1.使用标记指定数组长度
管理数组实参的第一种方法是要求数组本身包含一个结束标记,使用这种方法的典型示例是C风格字符串。函数在处理C风格字符串时遇到空白符停止,这种方法适用于那些有明显结束标记的情况。
#include<iostream>
using namespace std;
// 以空白符为结束标记,打印字符串中每个单词的首字母
void printWords(const char* str) {
while (*str != '\0') {
// 忽略空白符
while (*str == ' ' || *str == '\t' || *str == '\n') {
str++;
}
// 打印单词
if (*str != '\0') {
cout << *str << endl;
}
// 移动到下一个单词的开头
while (*str != '\0' && *str != ' ' && *str != '\t' && *str != '\n') {
str++;
}
}
}
int main() {
const char str[] = "Hello World! This is a string.";
printWords(str);
return 0;
}
2.使用标准库规范
管理数组参数的第二种方法是传递指向数组首元素和尾后元素的指针:
#include<iostream>
using namespace std;
void test(const int* beg,const int* end)
{
while (beg != end) {
cout << *beg << endl;
beg++;
}
}
int main()
{
int a[10] = { 0,1,2,3,4,5,6,7,8,9 };
auto beg = begin(a);
auto last = end(a);
test(beg, last);
return 0;
}
3.显式传递一个表示数组大小的形参
第三种方法是专门定义一个表示数组大小的形参。
#include<iostream>
using namespace std;
void print(const int* ia, size_t size)
{
for (size_t i = 0; i!= size; i++) {
cout << *ia << endl;
ia++;
}
}
int main()
{
int ia[10] = { 1,2,3,4,5,6,7,8,9,10 };
print(ia, 10);
return 0;
}
4.数组引用形参
C++语言允许将变量定义成数组的引用,基于同样的道理,形参也可以是数组的引用。此时引用形参绑定到对应的实参上,也就是数组上。
#include <iostream>
// 函数接受数组引用作为参数,并修改数组的值
void modifyArray(int (&arr)[5]) {
// 将数组中的每个元素乘以2
for (int i = 0; i < 5; ++i) {
arr[i] *= 2;
}
}
int main() {
int myArray[5] = {1, 2, 3, 4, 5};
std::cout << "原始数组:";
for (int i = 0; i < 5; ++i) {
std::cout << myArray[i] << " ";
}
std::cout << std::endl;
// 调用函数,传递数组引用
modifyArray(myArray);
std::cout << "修改后的数组:";
for (int i = 0; i < 5; ++i) {
std::cout << myArray[i] << " ";
}
std::cout << std::endl;
return 0;
}
3.3 main函数形参
有时我们需要给main传递实参:
int main(int argc, char *argv[])
第一个形参argc表示数组中字符串的数量,第二个形参argv是一个数组,它的元素是指向C风格字符串的指针。
4. 返回类型和return语句
函数的返回类型置于函数名之前,大多数类型都能用作函数的返回类型,此外一种特殊的返回类型是void,它表示函数不返回任何值。但函数的返回类型不能是数组类型或函数类型。
4.1 无返回值函数
没有返回值的return语句只能用在返回类型是void的函数中。void 函数如果想在它的中间位置提前退出可以使用 return 语句。
// 一个不带返回值的函数,可以在中间位置提前退出
void processNumbers(int num) {
for (int i = 1; i <= num; ++i) {
if (i == 5) {
std::cout << "遇到数字 5,提前退出函数" << std::endl;
return; // 在这里使用 return 提前退出函数
}
std::cout << "当前数字:" << i << std::endl;
}
std::cout << "函数执行结束" << std::endl;
}
4.2 有返回值函数
return 语句提供函数的结果,只要函数的返回类型不是 void,该函数内的每条 return 语句必须返回一个值。return 语句返回值的类型必须与函数的返回类型相同。main 函数可以没有return 语句直接结束。编译器会隐式的插入一条返回 0 的 return 语句。
#include <iostream>
// 函数返回整数类型的值
int multiply(int a, int b) {
int result = a * b;
return result; // 返回 result 的值
}
// 函数返回字符串类型的值
std::string concatenate(std::string str1, std::string str2) {
std::string result = str1 + str2;
return result; // 返回 result 的值
}
int main() {
int x = 5, y = 3;
std::string word1 = "Hello, ";
std::string word2 = "world!";
// 调用返回整数类型值的函数
int product = multiply(x, y);
std::cout << "乘积为: " << product << std::endl;
// 调用返回字符串类型值的函数
std::string combined = concatenate(word1, word2);
std::cout << "合并后的字符串为: " << combined << std::endl;
return 0;
}
注意: 返回一个值的方式和初始化一个变量完全一样,返回值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。
1.不要返回局部对象的引用或指针
函数完成后,它所占用的储存空间也随之被释放掉。因此,函数终止意味着局部变量的引用将指向不再有效的内存区域。如前所述,返回局部对象的引用是错误的,同样的返回局部对象的指针也是错误的。一旦函数完成,局部对象被释放,指针将指向一个不存在的对象。
#include <iostream>
int* createLocalVariable() {
int num = 10; // 局部变量
return # // 返回指向局部变量的指针
}
int& getLocalVariableReference() {
int num = 5; // 局部变量
return num; // 返回局部变量的引用
}
int main() {
// 获取指向局部变量的指针,并尝试使用它
int* ptr = createLocalVariable();
std::cout << "指向局部变量的指针: " << *ptr << std::endl;
// 获取局部变量的引用,并尝试使用它
int& ref = getLocalVariableReference();
std::cout << "局部变量的引用: " << ref << std::endl;
// 注意:以上两种情况都会导致潜在问题
return 0;
}
2.返回类类型的函数和调用运算符
调用运算符的优先级与点运算符和箭头运算符相同,并且也符合左结合律。如果函数返回类的对象,我们就能使用函数调用的结果访问结果对象的成员。
#include <iostream>
class MyClass {
private:
int value;
public:
MyClass(int val) : value(val) {}
// 函数返回类的对象
MyClass add(int x) {
value += x;
return *this; // 返回当前对象
}
void display() {
std::cout << "当前值为: " << value << std::endl;
}
};
int main() {
MyClass obj(5);
// 连续调用函数并访问结果对象的成员
obj.add(3).add(7).add(10).display();
return 0;
}
3.列表初始化返回值
C++11新标准规定,函数可以返回花括号包围的值的列表。类似于其他返回结果,此处的列表也用来对表示函数返回的临时量进行初始化。返回的值由函数的返回类型决定。如果函数返回的是内置类型,则花括号包围的列表最多包含一个值。如果函数返回的是类类型,由类本身定义初始化值如何使用。
#include <iostream>
using namespace std;
// 自定义结构体,表示包含两个数的列表
struct Number {
private:
int first;
int second;
public:
Number(int a,int b):first(a),second(b){}
Number() = default;
Number print(int a,int b)
{
return{ a,b };
}
void test(Number& num)
{
cout << first << endl;
cout << second << endl;
}
};
int main() {
Number num;
num = num.print(100,4);
num.test(num);
return 0;
}
4.递归函数
如果一个函数调用了它自身,不管这种调用是直接还是间接的,都称该函数为递归函数。
#include <iostream>
unsigned int factorial(unsigned int n) {
if (n == 0 || n == 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
int main() {
unsigned int num = 5;
std::cout << "5的阶乘为:" << factorial(num) << std::endl;
return 0;
}
4.3 返回数组指针
因为数组不能被拷贝,所以函数不能返回数组。不过可以返回数组的指针或引用。方法如下:
1.使用尾置返回类型
C++中还有一种可以简化上述 func 声明的方法就是使用尾置返回类型。任何函数的定义都能使用尾置返回,但是这种形式对于返回类型比较复杂的函数最有效:
#include <iostream>
// 返回一个数组的函数,尾置返回类型
auto createArray(int size) -> int(*)[5] {
// 创建一个大小为 size 的数组,并返回指向该数组的指针
static int arr[5]; // 静态数组,其生命周期将持续到程序结束
for (int i = 0; i < 5; ++i) {
arr[i] = i * size;
}
return &arr; // 返回数组的指针
}
int main() {
int size = 2;
int(*result)[5]; // 定义一个指向包含5个整数的数组的指针
result = createArray(size);
std::cout << "返回的数组为: ";
for (int i = 0; i < 5; ++i) {
std::cout << (*result)[i] << " ";
}
std::cout << std::endl;
return 0;
}
2.使用decltype
如果我们知道函数返回的指针将指向什么类型的数组,就可以使用decltype关键字声明返回类型。
#include <iostream>
int odd[] = { 1, 3, 5, 7, 9 };
int even[] = { 0, 2, 4, 6, 8 };
decltype(odd)* arrptr(int i) {
return (i % 2) ? &odd : &even;
}
int main() {
// 调用arrptr函数,根据用户输入获取指向奇数数组或偶数数组的指针
int (*result)[5] = arrptr(0);
for (int i = 0; i < 5; ++i) {
std::cout << (*result)[i] << " ";
}
return 0;
}
5. 函数重载
如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数。
5.1 定义重载函数
#include<iostream>
using namespace std;
// 定义重载函数
void display(int x)
{
cout << "显示整数" << x << endl;
}
void display(double x)
{
cout << "显示浮点数" << x << endl;
}
int main()
{
int x = 10;
double y = 10;
display(x);
display(y);
return 0;
}
注意: 只有形参类型不同才能使函数重载有效,使用类型别名无法建立函数重载。
1.重载和const形参
顶层const不影响传入函数的对象。一个拥有顶层 const 的形参无法和一个没有顶层 const 的形参区分开来。
#include <iostream>
void displayValue(int value) {
std::cout << "非 const 形参版本: " << value << std::endl;
}
void displayValue(const int value) {
std::cout << "const 形参版本: " << value << std::endl;
}
int main() {
int num = 5;
const int constNum = 10;
displayValue(num); // 调用非 const 形参版本
displayValue(constNum); // 也调用了非 const 形参版本,而不是 const 形参版本
return 0;
}
如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,
#include <iostream>
// 针对非常量对象的重载版本
void processValue(int* ptr) {
std::cout << "处理非常量指针" << std::endl;
(*ptr)++; // 可以修改指针指向的值
}
// 针对常量对象的重载版本
void processValue(const int* ptr) {
std::cout << "处理常量指针" << std::endl;
// 不能修改指向的值
// (*ptr)++; // 这行代码会导致编译错误
}
int main() {
int num = 5;
const int constNum = 10;
int* ptr = #
const int* constPtr = &constNum;
processValue(ptr); // 调用处理非常量指针的版本
processValue(constPtr); // 调用处理常量指针的版本
return 0;
}
2.重载与作用域
如果我们在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体。因此在不同的作用域中无法重载函数名。
#include <iostream>
void displayValue(int x) {
std::cout << "外层作用域函数: " << x << std::endl;
}
int main() {
void displayValue(double x); // 在内层作用域中声明同名函数
displayValue(5); // 这里调用的是内层作用域中的函数
return 0;
}
// 内层作用域中的函数定义
void displayValue(double x) {
std::cout << "内层作用域函数: " << x << std::endl;
}
5.2 重载函数匹配
函数匹配是指我们把函数调用与一组重载函数中的某一个关联起来。函数匹配的具体流程如下:
1.确定候选函数和可行函数: 选定本次调用对应的重载函数集,集合中的函数称为候选函数。候选函数具备两个特征:
1.与被调用的函数同名;
2.其声明在调用点可见。
2.寻找最佳匹配
从可行函数中选择与本次调用最匹配的函数。在这一过程中逐一检查函数调用提供的实参,寻找形参类型与实参类型最匹配的那个可行函数。
3.最佳函数匹配
编译器依次检查每个实参以确定那个函数是最佳匹配,如果有且只有一个函数满足下列条件则匹配成功:
1.该函数每个实参的匹配都不劣于其他可行函数需要的匹配;
2.至少有一个实参的匹配优于其他可行函数提供的匹配。
5.3 函数匹配结果
当调用函数重载时可能有三种结果:
1.编译器找到一个与实参最佳匹配的函数,并生成调用该函数的代码;
2.找不到任何一个函数与调用的实参匹配;
3.有多于一个函数可以匹配,但是每一个都不是最佳选择,此时发生二义性调用。
6. 函数指针
6.1 使用函数指针
当我们把函数名作为一个值使用时,该函数自动地转换成指针。
#include <iostream>
// 一个简单的函数,用于打印消息
void showMessage(const char* message) {
std::cout << "Message: " << message << std::endl;
}
int main() {
// 声明一个函数指针,指向返回类型为 void、参数为 const char* 的函数
void (*funcPtr)(const char*);
// 将函数指针指向 showMessage 函数
funcPtr = showMessage;
// 使用函数指针调用函数
funcPtr("Hello, using function pointer!");
return 0;
}
注意: 如果定义了指向重载函数的指针,编译器通过指针类型决定选用那个函数,指针类型必须与重载函数中的某一个精确匹配。
6.2 函数指针形参
和数组相似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。
#include <iostream>
// 一个简单的函数,接受一个整数和一个函数指针作为参数,并使用函数指针调用函数
void process(int num, void (*funcPtr)(int)) {
std::cout << "处理前:" << num << std::endl;
funcPtr(num); // 使用函数指针调用函数
}
// 两个不同的函数,用于处理传入的整数参数
void increment(int x) {
std::cout << "增加 1 后:" << x + 1 << std::endl;
}
void decrement(int x) {
std::cout << "减少 1 后:" << x - 1 << std::endl;
}
int main() {
int number = 10;
// 调用 process 函数,传入不同的函数指针作为参数
process(number, increment); // 传入 increment 函数的指针
process(number, decrement); // 传入 decrement 函数的指针
return 0;
}
6.3 返回指向函数的指针
和数组相似,虽然不能返回一个函数,但是可以返回指向函数的指针。
#include <iostream>
using namespace std;
// 返回一个函数指针
int (*getOperation(bool add))(int, int) {
if (add) {
return [](int a, int b) { return a + b; }; // 返回一个加法函数的指针
}
else {
return [](int a, int b) { return a - b; }; // 返回一个减法函数的指针
}
}
int main() {
bool chooseAddition = true;
// 根据条件选择返回加法函数或减法函数的指针
int (*operation)(int, int) = getOperation(chooseAddition);
int result = operation(10, 5); // 调用选择的函数
cout << "结果为:" << result << endl;
return 0;
}
7. 特殊用途语言特性
本节介绍默认实参、内联函数和 constexpr 函数,以及在程序调试过程中常用的一些功能。
7.1 默认实参
某些函数有这样一种形参,在函数的很多次调用中都赋予一个相同的值,此时我们把这个反复出现的值称为函数的默认实参。调用含有默认实参的函数时,可以包含该实参,也可以省略该实参。
#include <iostream>
// 带有默认实参的函数
void greet(std::string name = "Guest") {
std::cout << "Hello, " << name << "!" << std::endl;
}
int main() {
greet(); // 不提供实参,将使用默认实参 "Guest"
greet("Alice"); // 提供实参,将使用提供的值 "Alice"
return 0;
}
注意: 如果我们想使用默认实参,只要在调用函数的时候省略该实参就可以了。函数调用时实参是按其位置解析,默认实参负责填补函数调用缺少的尾部实参,即靠右侧位置。
1.默认实参声明
在给定的作用域中一个形参只能被赋予一次默认实参。换句话说,函数的后续声明只能为之前那些没有默认值的形参添加默认实参,而且该形参右侧的所有形参都必须有默认值。
string screen(sz, sz, char = ' ');
string screen(sz, sz, char = ' * '); // 错误
string screen(sz = 18,sz = 21,char); // 正确,添加默认实参
注意: 局部变量不能作为默认实参。除此之外,只要表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参。
7.2 内联函数和constexpr函数
1.内联函数可避免函数调用的开销
将函数指定为内联函数,通常就是将它在每个调用点上内联地展开。内联机制用于优化规模较小、流程直接、频繁调用的函数。
#include <iostream>
// 内联函数定义在头文件或者在定义它的源文件中
inline int add(int a, int b) {
return a + b;
}
int main() {
int x = 5, y = 3;
int result = add(x, y); // 调用内联函数
std::cout << "结果为:" << result << std::endl;
return 0;
}
注意: 内联函数的定义通常放在头文件中,这些定义会在每个调用这个函数的地方被复制进去,而不是像普通函数一样在程序的某个地方单独存储一份。因此,多个源文件包含同一个内联函数的定义不会引起重定义错误。
2.constexpr函数
constexpr函数是指能用于常量表达式的函数。定义 constexpr 函数的方法与其他函数类似,不过要遵循几项约定:函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条 return 语句。
#include <iostream>
// 返回两个整数的和作为 constexpr 函数
constexpr int add(int a, int b) {
return a + b;
}
int main() {
constexpr int result = add(3, 5); // 在编译时求值
std::cout << "结果为:" << result << std::endl;
return 0;
}
// 在 main 函数中,我们在编译时计算出了 add(3, 5) 的值,并将结果存储在 result 中。
// 这种方式使得函数能够在编译时求值,提高了代码的性能和灵活性。
注意: constexpr 函数被用于在编译期进行计算。由于这种函数的特性是在编译时求值,所以即使它们在多个源文件中定义,编译器会在编译期间对其进行计算。这意味着多次定义 constexpr 函数也不会引起重定义错误。
本文围绕C++函数展开,介绍了函数基础,包括示例、局部对象和声明;阐述了参数传递的三种方式;详解函数形参,如const形参、数组形参等;还讲解了返回类型和return语句、函数重载、函数指针以及特殊用途语言特性,如默认实参、内联函数和constexpr函数。

被折叠的 条评论
为什么被折叠?



