6. 函数重载·内联函数·const

本文最后更新于 2024年1月28日 下午

函数重载·内联函数·const

这是浙江大学翁恺老师的公开课,《面向对象设计C++》
视频地址:
https://www.bilibili.com/video/BV1yQ4y1A7ts/?spm_id_from=333.337.search-card.all.click&vd_source=3074f6f6ab43a114c5af8727fa4f7255

本节对应视频17-20部分。

函数重载(function overloading)指的是一些函数可以有相同的函数名,但是有不同的参数表,这些参数表中的参数的个数和类型可能都不一样,构成重载关系。在调用函数的时候,给出的不同的参数决定了编译器会调用哪一个函数。

1
2
3
4
5
6
//如下的print函数都是重载的关系
void print(char *str, int width);
void print(double d, int width);
void print(long l, int width);
void print(int i, int width);
void print(char *str);

需要注意的是,如果两个函数的函数名相同,但是返回类型不同,这两个函数并不能构成重载的关系。
如果存在重载的函数,如果变量需要进行强制转换,那么编译器会试图找到完全匹配的类型。

缺省值

缺省值(default arguments)是C++中的一个功能,在定义函数时可以在函数的参数表中预先给一个值,表示这个参数的默认值。

1
Stash(int size, int initQuantity = 0); //如果调用Stash函数时没有指定initQuantity,那么它自动为0
需要注意的是,含有缺省值的变量需要在定义时放在没有缺省值的本地变量之后:
1
2
int harpo(int n, int m =4, int j=5);
int chico(int n, int m=6, int j); // 这种写法是不合法的。
需要注意的是,只能在函数原型.h文件中才能设置函数的缺省值,在函数具体的.cpp文件中不能存在缺省值。

f的函数原型在a.h中:

1
void f(int i, int j=0);
创建a.cpp
1
2
3
4
5
6
7
8
9
# include "a.h"
# include <iostream>
using namespace std;

void f(int i, int j)
{
cout << i << " "<< j << endl;
}

接着创建一个main.cpp
1
2
3
4
5
# include "a.h"
int main() {
f(10);
return 0;
}
运行该程序,输出为:
1
2
3
$ g++ main.cpp a.cpp
$ ./a.out
>> 10 0
下面的写法是不合法的:
1
2
3
4
5
6
7
8
# include "a.h"
# include <iostream>
using namespace std;

void f(int i, int j = 0) // 在a.cpp中设置函数的缺省值
{
cout << i << " "<< j << endl;
}
编译器会报错:
1
2
3
4
5
$ g++ main.cpp a.cpp
$ ./a.out
>> a.cpp: In function 'void f(int, int)':
>> a.cpp:6: error: default argument given for parameter 2 of 'void f(int, int)'
>> a.h:1: error: after previous specification in 'void f(int, int)'
即使a.h中不设置缺省值,编译也无法通过。
1
2
3
4
5
$ g++ main.cpp a.cpp
$ ./a.out
>> a.cpp: In function 'void f(int, int)':
>> a.h:1: error: too few arguments to function 'void f(int, int)'
>> main.cpp:5: error: at this point in file

现在思考这样的尝试,如果在main.cpp中不使用a.h而直接定义f

1
2
3
4
5
6
// # include "a.h"
void f(int i, int j = 10)
int main() {
f(10);
return 0;
}
运行该程序,输出为:
1
2
3
$ g++ main.cpp a.cpp
$ ./a.out
>> 10 10
所以缺省值的机制是在编译过程中,编译器发现原型声明的缺省后自动补充这个值。

缺省值会造成程序的阅读困难而且不安全,因此不要使用缺省值。

内联函数

在函数调用的过程中会出现额外的开销(overhead)。这些开销与堆栈操作有关:每个程序都有自己独立的堆栈,用于存放本地变量和返回地址。在函数调用的过程中,堆栈做的操作有:
- 函数的参数入栈 - 返回地址入栈 - 准备返回值 - 出栈所有

这些堆栈操作额外的开销可以通过C++中的特性内联函数(inline function)解决:如果一个函数是内联函数,其在被调用的时候会将函数的代码嵌入到调用的地方,保持函数的独立性。

比如:

1
2
3
4
5
6
7
8
9
// f 是一个内联函数
inline int f(int i){
return i*2;
}

main(){
int a=4;
int b=f(a);
}
实际上在调用的时候,主函数中真正的操作是:
1
2
3
4
main(){
int a=4;
int b=a+a; //将f的实际指令嵌入其中
}
如此内联函数只会出现在编译器中,不会出现在可执行代码(由C++翻译的汇编代码)中。

用法是在需要设置为内联的函数前加入关键字inline,且必须在函数的声明*.h和定义*.cpp中都设置为inline

在函数声明中:

1
inline int plusOne(int x);
在函数定义中:
1
inline int plusOne(int x){return ++x;};
需要注意的是,使用内联函数时如果在函数声明的位置给出了函数体的部分(如此可以便于以便编译器插入),此时的函数声明其实是一个函数的定义,此时*.cpp文件可以不被需要,否则会出现重复定义。

内联函数牺牲代码空间,但是会降低调用函数时候额外的开销,从而降低运行时间。但是在大多数时候,这是值得的。
如果成员函数在class声明时就给出函数体,那么这些函数都是内联函数。

1
2
3
4
5
6
7
8
9
10
11
class Point {
int i,j,k
public:
Point() {i=j=k=0};
Point(int ii, int jj, int kk){i=ii,j=jj,k=kk;}//内联函数
void print(string& msg = ''){ //内联函数
cout<<"i= "<<i<<","
<<"j="<<j<<","
<<"k="<<k<< endl;
}
};

被频繁调用或者非常小的函数(2-3行)值得做成内联函数。非常大的函数(20行)以及递归的函数不要做内联。

const

const用于变量之前,表示这个变量被赋值之后其值不可以做任何修改:

1
2
3
const int x=1;
x =2; //非法
x++; //非法
需要注意的是,这个变量仍然是变量,也遵循scope rule。const变量的值需要在编译时让编译器知道(才能提前为本地变量分配内存大小)才能够通过编译:
1
2
3
4
5
6
7
8
const int class_size = 12
int finalGrade[class_size]; // OK
```
```cpp
int x;
cin >> x; // 编译时编译器并不知道x的值
const int size = x;
double classAverage[size]; //error

const指针

当一个指针变量是const时,有两种写法:

1
2
3
* const q = 'abc'; // q is const
*q = 'c'; // ok
q++; // error
在这一种写法中,指针q本身是const*q指向的内存的内容并不是const,因此对应内存的内容可以改变,这种写法称为常量指针。
1
2
3
const char *p = 'abc' // *p is a const char
*p='b'; // error
p++; // ok
在如上的写法中,*p指向的内存地址中的内容是const的,不能通过p去修改指向的内存单元,这种写法是指针常量。

const的对象和成员函数

如果一个对象是cosnt,表明这个对象内的值是不能被修改的。

1
const Currency the_raise(42,38);
但是如此可能会引发一些问题,一些成员函数也许会修改其中成员变量的值。 因此需要在成员函数的声明和变量后增加const表示这个函数不会修改成员变量的值。
1
int get_day() const;
1
2
3
int get_day() const {
return day;
};
这个const其实加在了this前,表示的是thisconst:
1
2
3
4
5
6
7
8
9
10
11
12
13
class A {
int i;
public:
A() : i(0) {} // 初始化i
void f() { cout << 'f()'<< endl;} //相当于f(A* this)
void f() const {cout << 'f() const'<< endl;} //相当于 f(const A* this)
// 因此两者参数表不同,构成重载关系
};
int main(){
const A a; // a是const,因此选择调用const的f()
a.f();
return 0;
}
1
>> f() const
另外,如果一个成员变量是const,其一定要被初始化(因为其他没有再修改它的值的机会了)。


6. 函数重载·内联函数·const
https://l61012345.top/2024/01/05/学习笔记/C++面向对象设计/6. 函数重载/
作者
Oreki Kigiha
发布于
2024年1月5日
更新于
2024年1月28日
许可协议