函数重载
规则
- 函数名相同
- 参数个数不同、参数类型不同、参数顺序不同
注意
- 函数返回值类型与函数重载无关
- 调用函数时,实参的隐式类型转换可能会产生二义性
函数重载的本质:采用了name mangling技术,会对函数名进行改变修饰,生成多个不同的函数名,不同的编译器规则不一样。
例如win平台下:(设置属性中C++优化一项,禁用release优化)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KUKlRwFB-1619342327420)(media/16189751668924/16190076999745.jpg)]
mac平台下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MXGlGGSx-1619342327423)(media/16189751668924/16190078310876.jpg)]
static 和 extern区别
static:在定义函数时,在函数的最左边加上static可以把该函数声明为内部函数(又叫静态函数),这样该函数就只能在其定义所在的文件中使用。如果在不同的文件中有同名的内部函数,则互不干扰。可以用来声明内部函数。
extern:在定义函数时,如果在函数的最左边加上关键字extern,则表示此函数是外部函数,可供其他文件调用。C语言规定,如果在定义函数时省略extern,则隐含为外部函数。在一个文件中要调用其他文件中的外部函数,则需要在当前文件中用extern声明该外部函数,然后就可以使用,这里的extern也可以省略。
#pragram once
- #ifndef #define #endif 受C\C++标准的支持,不受编译器的任何限制,可以修饰一个文件中的部分代码
- 有些编译器不支持#pragma once(较老编译器不支持,如GCC 3.4版本之前),兼容性不够好,而且只能够修饰整个文件的内容
extern “C”
- 被
extern "C"
修饰的代码会按照C语言的方式去编译。 - 如果函数同时有声明和实现,要让函数声明被extern "C"修饰,函数实现可以不修饰
- C++在调用C语言API时,需要使用extern "C"修饰C语言的函数声明,为了适用性,一般会添加宏定义
#ifdef __cpluscplus
extern "C" {
#endif // __cpluscplus
#ifdef __cpluscplus
}
#endif // __cpluscplus
内联函数(代码体积不大,频繁调用)
- 使用inline修饰函数的声明或者实现,可以使其变成内联函数。建议声明和实现都添加上inline
- 编译器会将函数调用直接展开为函数体代码,减少函数的调用开销,但是增加代码体积
- 内联函数不建议过长(例如超过10行),有些函数即使添加inline也不是内联函数,比如递归函数
int sum(int a, int b) {
return a + b;
}
正常的函数调用开辟函数栈空间,将参数压栈后调用函数进行执行
#sum(10, 20)
009710C8 6A 14 push 14h
009710CA 6A 0A push 0Ah
009710CC E8 AF FF FF FF call sum (0971080h)
009710D1 83 C4 08 add esp,8
009710D4 50 push eax
修改sum为内联函数的话,可以观察到编译器直接进行了函数的替换
00151004 B8 0A 00 00 00 mov eax,0Ah
00151009 83 C0 14 add eax,14h
0015100C 89 45 FC mov dword ptr [c],eax
如果设置成内联函数的话,那么生成的可执行文件中不会存在相应的函数。
内联函数与宏
- 内联函数和宏,都可以减少函数调用的开销
- 对比宏,内联函数多了语法检测和函数特性
C++表达式赋值
int a = 1;
int b = 2;
(a = b) = 3; // 等效于a = 3
(a < b ? a : b) = 4; //等效于b = 4
const常量
// const只修饰它右边的,看的就是*p 或者 p 与类型无关
int age = 10;
int height = 10;
// *p1是常量 p1不是
const int * p1 = &age;
// *p2是常量 p2不是
int const * p2 = &age;
// p3是常量 *p3不是
int * const p3 = &age;
// p4是常量 *p4也是
const int * const p4 = &age;
// p5是常量 *p5也是
int const * const p5 = &age;
引用
注意
- 引用相当于是变量的别名(基本数据类型、枚举、结构体、类、指针、数组等,都可以有引用) (只观察&符号左边的是什么类型即可)
- 对引用做计算,就是对引用所指向的变量做计算
- 在定义的时候就必须初始化,一旦指向了某个变量,就不可以再改变,“从一而终”
- 可以利用引用初始化另一个引用,相当于某个变量的多个别名
- 不存在【引用的引用、指向引用的指针、引用数组】
引用的本质就是指针,只是编译器削弱了它的功能,所以引用就是弱化了的指针
机器码证明 引用就是指针
观察机器码都是一样子的
47: int *p = &age;
89 45 F4 mov dword ptr [p],eax
48: *p = 20;
8B 4D F4 mov ecx,dword ptr [p]
C7 01 14 00 00 00 mov dword ptr [ecx],14h
50: int &ref = age;
89 45 F4 mov dword ptr [ref],eax
51: ref = 20;
8B 4D F4 mov ecx,dword ptr [ref]
C7 01 14 00 00 00 mov dword ptr [ecx],14h
常引用
引用可以被const修饰,这样就无法通过引用修改数据了,可以称为常引用
const必须写在&符号的左边,才能算是常引用
//*p不能修改内容 但是p可以修改指向
int const *p = &age;
*p = 20; //error
p = &height;
//p1不能修改指向 但是能利用*p1间接修改指向的变量
int * const p1 = &age;
*p1 = 10;
p1 = &height; //error
//ref不能修改指向 也不能利用ref间接修改指向的变量
int const &ref = age;
ref = 20; //error
//ref1不能修改指向 但是能利用ref1间接修改指向的变量
int & const ref1 = age;
ref1 = 20;
const引用的特点
- 可以指向临时数据(常量、表达式、函数返回值等)
int const &ref = 30;
int a = 1;
int b = 2;
int const &ref1 = a + b;
int func1() {
return 8;
}
int const &ref2 = func1();
- 可以指向不同类型的数据
double const &ref3 = 30;
- 作为函数参数时(此规则也适用于const指针)
- 可以接受const和非const实参(非const引用,只能接受非const实参)
int test1(int &v1, int &v2) {
return v1 + v2;
}
int test2(const int &v1, const int &v2) {
return v1 + v2;
}
int a = 10;
int b = 20;
test1(a, b);
test1(10, 20); //error
test2(a, b);
test2(10, 20);
- 可以跟非const引用构成重载
int test2(int &v1, int &v2) {
return v1 + v2;
}
int test2(const int &v1, const int &v2) {
return v1 + v2;
}
int a = 10;
int b = 20;
test2(a, b);
test2(10, 20);
- 当常引用指向了不同类型的数据时,会产生临时变量,即引用指向的并不是初始化时的那个变量
int age = 10;
const long &ref = age;
age = 30;
// age = 30 ref = 10
//在栈上开辟了单独的空间存放常引用long变量
C7 45 F4 0A 00 00 00 mov dword ptr [ebp-0Ch],0Ah
8B 45 F4 mov eax,dword ptr [ebp-0Ch]
89 45 F8 mov dword ptr [ebp-8],eax
8D 4D F8 lea ecx,[ebp-8]
89 4D F0 mov dword ptr [ebp-10h],ecx
C7 45 F4 1E 00 00 00 mov dword ptr [ebp-0Ch],1Eh