本质:用函数代码替换函数调用
使用方式:在函数声明和函数定义前加上 inline 关键字
笔者感觉跟C语言中的宏定义差不多,但是内联函数更加“智能”(应该是编译器更加智能)。即使程序员将函数作为内联函数,但是编译器会检查是否满足一些要求,比如是否是递归调用,函数是否过大等。
笔者还是喜欢宏,当然因人而异
int a;
int &b = a;
int c = 20;
b = c; // ==> 这里是将 c 的值赋给 b 即 b = a = 20 [其实b的值是a的地址......]
注意:引用变量必须初始化;引用变量不得更换引用对象
本质:b 保存着 a 的地址;每次操作 b 时,会取出其保存的 a 的地址进行操作【是不是感觉跟指针很像,但是指针操作每次还得解引用,所以这里可以看作<指针+自解引用>】
考虑如下代码:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int add(int a, int b) { return a + b; }
inline int sub(int a, int b) { return a - b; }
int main()
{
int a = 10;
int &b = a;
b = 20;
cout << &a << " " << &b << endl;
cout << a << " " << b << endl;
return 0;
}
输出:
0x7ffffa5b781c 0x7ffffa5b781c
20 20
IDA 里面直接识别成的指针:
这里看网上很多人说变量a和变量b的地址是相同的,其实这里我感觉是错误的,变量a和变量b的地址并不相同,因为调试发现变量b保存的是变量a的地址,但是在进行相关操作时会进行特殊处理,比如:
?&b? ? ==> 其实取的是b保存的a的地址
?b=10?==> 其实是将变量b保存的a的地址pa取出来,然后执行 [pa] = 10
当然这里说了引用变量保存的是引用对象的地址,跟指针差不多,所以 int &b = a + 3; 是错误的
但好像早期的编译器是允许将表达式作为引用对象的,但是都2023年了,你懂的,其实笔者在函数传参啥的时候还是更喜欢直接用指针,但是后面类的设计,引用就是必不可少的了
当然也要注意当引用作为返回值时,别把局部变量引用返回了,比如如下代码:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int& func(int &a)
{
int b = a;
return b;
}
int main()
{
int a = 10;
int &b = func(a);
cout << &a << " " << &b << endl;
b = 100;
return 0;
}
输出如下:
0x7ffc6e6b2f7c 0
Segmentation fault (core dumped)
跟 python 的差不多,就没啥好说的了,注意点:
1)如果 i 位置为默认参数,则 i+x 位置应该都是默认参数
2)如果第 i 个默认参数被赋值,则第 i+x 个默认参数应当都被赋值
函数重载条件:
? ? ? ? 1)具有不同的函数特征标 ==> 人话就是参数列表不同
? ? ? ? 2)函数调用不具有二义性(或多义性)==> 最佳匹配情况下
其实关键的地方在于函数调用的二义性:考虑如下代码
include <fstream>
#include <string>
using namespace std;
int func(int a, int b, int c = 10)
{
return a + b + c;
}
int func(int a, int b)
{
return a + b;
}
int main()
{
int a = func(20, 30, 10);
int b = func(20, 30);
return 0;
}
这里的函数特征标确实不同,但是在调用函数时具有二义性,即 func(20, 30); 两个函数都可以匹配。如果把 int b = func(20,30); 去掉则可以成功编译
这里我把代码稍微改一下下:仅仅将第二个 func 的第二个参数类型改为 long long
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int func(int a, int b, int c = 10)
{
cout << "func 3 argc" << endl;
return a + b + c;
}
int func(int a, long long b)
{
return a + b;
}
int main()
{
int a = func(20, 30, 10);
int b = func(20, 30);
return 0;
}
这个时候又可以成功编译,为啥这时不具备二义性呢?这里的二义性是在最匹配的情况下,因为对于 int b = func(20, 30); 来说,30 为 int 类型(在 int 范围内的下常数为 int 类型),所以这里会匹配第一个 func 函数:输出如下
func 3 argc
func 3 argc
用法:template <typename TypeName> Function
本质:就是将函数展开
给个demo:
ing namespace std;
// typename 可以用 class 关键字代替
template <typename T>
void Swap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10, b = 20;
float fa = 20.0, fb = 30.0;
Swap(a, b);
Swap(fa, fb);
cout << a << " " << b << endl;
cout << fa << " " << fb << endl;
return 0;
}
IDA 中识别如下:
?但是 Swap<float> 还是用的 int(但是问题不大,都是32位的数据类型):
显式具体化
上面的模板实现存在一定问题,比如如果T为数组呢?所以可以具体化一个模板:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
// typename 可以用 class 关键字代替
template <typename T>
void Swap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
cout << "Swap 0" << endl;
}
// 具体化
template <> void Swap<float>(float &a, float &b)
{
cout << "Swap 1" << endl;
}
int main()
{
int a = 10, b = 20;
float fa = 20.0, fb = 30.0;
Swap(a, b);
Swap(fa, fb);
return 0;
}
输出如下:
Swap 0
Swap 1
如果把函数重载考虑进来,调用关系如下:
? ? ? ? 非模板函数>具体化函数>模板化函数?
decltype 关键字主要是争对模板类型的,考虑如下代码:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
// typename 可以用 class 关键字代替
template <typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b)
{
decltype(a + b) sum = a + b;
return sum;
}
int main()
{
int a = 10, b = 20;
float fa = 20.0, fb = 30.0;
int c = add(a, fb);
float fc = add(fa, b);
cout << c << " " << fc << endl;
return 0;
}
这里的问题就是由于 T1 和 T2 的类型我们事先都不知道,所以这里的 sum 和返回类型是啥呢?这里显然也是不知道的,为了解决这里问题,引入了 decltype 关键字
decltype (expression) var;
? ? ? ? 1)若?expression 是没用括号的标识符,则 var 类型与该标识符相同
? ? ? ? 2)若?expression 是一个函数调用,则 var 类型为函数返回值类型
? ? ? ? 3)若?expression 是用括号的左值,则 var 类型为引用类型
? ? ? ? 4)若以上都不满足,则 var 类型与?expression 相同
int a = 10;
decltype (a) b;
decltype ((a)) c = b;
注意这里的 c 为引用类型,所以记得初始化。?
这里仅仅回顾了一些 C++ 的最基本的知识,并没用深入,注意是为之后的逆向做准备。不熟悉或没接触过 C++ 的读者建议好好的去深入学习下上面所讲的知识,最好自己动手写写代码。