Cpp复习 Chapter 1 内联函数、引用变量、函数重载、函数模板
Cpp复习 Chapter 1 内联函数、引用变量、函数重载、函数模板
【Cpp筑基】一、内联函数、引用变量、函数重载、函数模板
【Cpp筑基】二、声明 vs 定义、头文件、存储持续性作用域和链接性、名称空间
【Cpp筑基】三、对象和类
【Cpp筑基】四、重载运算符、友元、类的转换函数
【Cpp筑基】五、类的继承、虚函数、抽象基类
【Cpp筑基】一、内联函数、引用变量、函数重载、函数模板
1. 内联函数
C++提供了一种内联函数,在 C++ 中,内联函数(inline function)是一种特殊的函数,其定义使用 inline
关键字来提示编译器将函数调用直接替换为函数体,以减少函数调用的开销。内联函数通常用于简短、频繁调用的函数。
要使用内联函数,必须:
- 在函数声明前加上关键字
inline
- 在函数定义前加上关键字
inline
注意内联函数不能递归。内联函数的基本语法:1
2
3inline return_type function_name(parameters){
// 函数体
}
举个例子:1
2
3
4
5
6
7
8
9
10
11
12
13
using namespace std;
// 定义一个内联函数
inline int add(int a, int b) {
return a + b;
}
int main() {
int result = add(3, 4);
cout << "Result: " << result << endl;
return 0;
}
输出结果1
Result: 7
inline
工具是C++新增的特性,C语言使用预处理器语句#define
来提供宏——内联代码的原始实现。例如:1
inline
和#define
的主要区别是:
- 实现机制:
- 内联函数是由编译器在编译时展开的,是一种编译时的语言特性。编译器会在调用内联函数的地方直接替换函数体。
- 而
#define
宏是在预处理阶段展开的,是一种文本替换的机制。预处理器会在编译前把宏展开。
- 类型检查:
- 内联函数有类型检查,编译器会检查参数类型是否匹配。
#define
宏是简单的文本替换,没有类型检查,容易出现类型错误。
- 时间和空间开销:
- 内联函数在编译时展开,没有函数调用的开销,但会增加程序的大小,增加空间成本。
#define
宏在预处理阶段展开,没有函数调用开销,但可能会导致代码膨胀。
2. 引用变量
C++新增了一种复合类型——引用变量,使用运算符&
,引用变量的主要用途是作为函数参数列表中的形参。例如,创建一个引用变量1
2int rats;
int & rodents = rats;
==注意==:引用必须在声明时进行初始化,而不能像指针一样,先声明再赋值。引用一旦与变量进行关联,就一直指向这个变量。
C++11中新增了另一种引用——==右值引用==(rvalue reference),这种引用可以指向右值,使用&&
进行声明。
在 C++ 中,“左值”(lvalue)和“右值”(rvalue)是用来描述表达式值类型的一对术语。理解它们的概念对于掌握 C++ 语言的赋值、引用、移动语义等方面的内容非常重要。
左值(lvalue,locatable value)是指能够定位的值,它表示存储在内存中的某个位置的对象。因此,左值是可以取地址的,可以出现在赋值操作的左侧。例如:
1 | int x = 10; // x 是左值 |
右值(rvalue,readable value)是指不具有持久存储位置的临时值,它通常是表达式求值的结果。右值不能取地址,也不能出现在赋值操作的左侧。例如:
1 | int y = 10; // 10 是右值 |
左值引用是对左值的引用,用于绑定左值。例如:
1 | int a = 10; |
右值引用是对右值的引用,用于绑定右值。这是 C++11 引入的特性,主要用于实现移动语义和完美转发,以提高性能。
1 | int &&rref = 10; // 右值引用 |
新增右值引用主要是用于移动语义和完美转发,理解左值和右值的区别是掌握 C++ 高级特性(如移动语义和完美转发)的基础。
==引用常用于函数的参数传递==,这样可以避免不必要的拷贝,并且允许函数修改传入的参数值。例如:1
2
3
4
5
6
7
8
9
10void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 10, y = 20;
swap(x, y); // x 和 y 的值被交换
}
注意,在使用引用进行函数的参数传递的时候,我们应该尽可能使用const
,将引用参数声明为常量数据的引用的理由有三个:
- 使用
const
可以避免无意中修改数据的编程错误 - 使用
const
使函数能够处理const
和非const
实参,否则将只能接受非const
数据 - 使用
const
引用使函数能够正确生成并使用临时变量
举个例子:
1 |
|
输出如下:1
2
3
4Non-const reference: hello
After printString(): modified
Const reference: modified
After printConstString(): modified
什么时候使用引用和指针呢?
使用引用参数的主要原因有两个:
- 程序员能够修改调用函数中的数据对象
- 通过传递引用而不是整个数据对象,可以提高程序的运行速度。
对于使用传递的值而不做修改的函数:
- 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为
const
3. 函数重载
C++实现==多态==有两种方式,
- 编译时多态Compile-time Polymorphism(通过函数重载和模板实现)
- 运行时多态Runtime Polymorphism(通过继承和虚函数实现)
函数重载的关键是函数的参数列表(也称为特征标),如果两个函数的参数数目和类型相同,同时参数的排列顺序也相同,则它们的特征标相同,而变量名是不重要的。C++允许定义相同名称的函数前提是他们的特征标不同。
举个例子,编译时多态可以通过函数重载或者是通过模板进行实现:
通过函数重载进行实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// 使用函数重载实现多态
void print(int i) {
std::cout << "Integer: " << i << std::endl;
}
void print(double d) {
std::cout << "Double: " << d << std::endl;
}
void print(const std::string& str) {
std::cout << "String: " << str << std::endl;
}
int main() {
print(42); // 调用 void print(int)
print(3.14); // 调用 void print(double)
print("Hello"); // 调用 void print(const std::string&)
return 0;
}通过函数模板进行实现
1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename T>
void print(T value) {
std::cout << "Value: " << value << std::endl;
}
int main() {
print(42); // T 被推断为 int
print(3.14); // T 被推断为 double
print("Hello"); // T 被推断为 const char*
return 0;
}
C++中的运行时多态(Runtime Polymorphism)是通过继承和虚函数(virtual functions)实现的。这种多态性允许在运行时根据对象的实际类型调用适当的方法,而不是在编译时决定调用哪个函数。运行时多态的核心是使用基类指针或引用来操作派生类对象。
运行时多态主要依赖以下的几个概念:
- 继承(Inheritance):允许一个类继承另一个类的属性和方法。
- 虚函数(Virtual Functions):在基类中声明为virtual的函数,可以在派生类中被重写。
- 虚函数表(Virtual Table, vtable):编译器为包含虚函数的类生成的一个表,表中存储了类的虚函数指针。每个对象包含一个指向其类的虚函数表的指针。
1 |
|
4. 函数模板
函数模板有两种定义方式,第一种使用关键字template
和typename
,例如
1 | template<typename T> |
第二种是使用关键字template
和class
,例如
1 | template<class T> |
其中,template<typename T>
是模板头,表示这个函数是一个模板函数,T
是一个类型参数,可以是任意合法的类型。T
可以用在函数的返回类型、参数列表和函数体内。
模板类型可以有多种形式,不仅限于一个,例如:
1
2
3
4
5
6
7
8
9
10template<typename T1, typename T2>
void print(T1 a, T2 b) {
std::cout << a << " " << b << std::endl;
}
int main() {
print(10, " apples"); // T1 被推断为 int,T2 被推断为 const char*
print(3.14, 42); // T1 被推断为 double,T2 被推断为 int
return 0;
}除了类型参数,模板还可以具有非类型参数,例如:
1 | template<typename T, int N> |
在这个例子中,N
是一个非类型模板参数,它表示数组的大小。
- 模板参数也可以有默认值,例如
1 | template<typename T = int> |
- 模板还允许提供具体化版本,即对特定的类型提供不同的版本,例如:
1 |
|
函数模板具有显式具体化机制,显式具体化就是为特定的类型提供函数模板的特化版本,这里针对std::string
类型提供了一个不同版本的add
函数。
Reference
《C++ Prime Plus》