1.函数重载
1.1 函数重载概念
int Add(int x, int y)
{
cout << "int Add(int x, int y)" << endl;
return x + y;
}
double Add(double x, double y)
{
cout << "double Add(double x, double y)" << endl;
return x + y;
}
int main()
{
int ret1 = Add(1, 2);
double ret2 = Add(2.3, 3.7);
cout << ret1 << " " << ret2 << endl;
return 0;
}
2.参数个数不同
void fun(int a)
{
cout << "fun(int a)" << endl;
}
void fun()
{
cout << "fun()" << endl;
}
int main()
{
fun(5);
fun();
return 0;
}
3.参数类型顺序不同
void f(int x, double y)
{
cout << "f(int x, double y)" << endl;
}
void f(double x, int y)
{
cout << "f(double x, int y)" << endl;
}
int main()
{
f(1,2.5);
f(2.9,5);
return 0;
}
1.2C++支持函数重载的原理--名字修饰(name Mangling)
C++支持函数重载的原理,在很大程度上依赖于一种被称为名字修饰(Name Mangling)的过程。这种机制使得编译器能够区分同名但参数列表不同的函数,从而支持函数重载.
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
我们来看linux环境下不同编译器编译相同代码的结果:
结论:在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变
通过上面可以看出c语言没法支持重载是因为同名函数无法区分,然而C++就会对函数进行修饰,只要参数的类型不同,就可以修饰出不同的名字。故C++支持重载如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办 法区分。
2.引用
2.1引用概念
引用是一个重要的概念,通过引用完成对不同的两个标识符对同一块看空间进行操作。
在本质上,引用就像是数据对象的一个别名。使用引用时,对引用的任何操作都会直接反映到被引用的对象上。编译器不会为引用变量开辟内存空 间,它和它引用的变量共用同一块内存空间
我们来具体应用,如下图:
int main()
{
int x = 5;
int& y = x;
cout << x << " " << y << endl;
cout << &x << " " << &y << endl;
return 0;
}
可以看到在变量类型前使用&来声明一个引用类型,在该示例中,y就是x的别名,y就是x,故两个变量的地址也是相同的。
int main()
{
int x = 5;
int& y = x;
y++;
cout << x << " " << y << endl;
cout << &x << " " << &y << endl;
return 0;
}
同样对x或者y的改变也会同时收到影响。
2.2引用特性
int x = 5;
int &b = x; // 正确,b被初始化为x的引用
int &c; // 错误,引用必须在声明时被初始化
int main()
{
int a = 1;
int& b = a;
int& c = a;
int& d = a;
cout << a << " " << b << " " << c << " " << d<< endl;
return 0;
}
int main()
{
int a = 5;
int& b = a;
int c = a;
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
return 0;
}
可以看到a是直接赋值给c里面,这里的c并不是a的别名。
2.3使用场景
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int x = 5, y = 6;
Swap(x, y);
cout << x << endl << y << endl;//这里成功交换了
return 0;
}
c语言中Swap交换函数要用指针才能改变实参的值,而在C++中用引用来代替了指针,也同样完成了对实参的改变。
2.4传值、传引用效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者
返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低
#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
A a;
// 以值作为函数参数
size_t begin1 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc1(a);
size_t end1 = clock();
// 以引用作为函数参数
size_t begin2 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc2(a);
size_t end2 = clock();
// 分别计算两个函数运行结束后的时间
cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
TestRefAndValue();
return 0;
}
TestFunc1每次都会对实参进行拷贝,当数据大的时候,这将会是一种很大的消耗,而TestFunc2中是实参的别名,不会另外开辟空间,他们使用的是同一块空间。
在上述代码示例中,TestFunc1(按值传递)会因为每次调用时都需要复制一个大数组而显得非常慢,而TestFunc2(按引用传递)则会因为避免了这种拷贝,运行时间将大大减少。
2.5引用和指针的区别
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
int main()
{
int a = 5;
int& b = a;
cout << &a << endl;
cout << &b<< endl;
return 0;
}
但在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
int main()
{
int a = 5;
int& b = a;
b = 50;
int* pa = &a;
*pa = 50;
return 0;
}
lea是地址,mov是移动。他们的底层可以说是一样的。
2.6常引用
int main()
{
const int a = 5;
int& b = a;//会报错因为权限放大了,(a是只读,而b是可读可写)
const int& b = a;//不会报错因为权限平移了(a是只读,而b也是是可读)
int x = 6;
const int& y = x;//不会报错因为权限缩小了(x是可读可写,而y是只读)
return 0;
}
3.内联函数
3.1内联函数的概念
内联函数旨在减少函数调用的开销,这种方法适用于那些函数体较小、调用频繁的函数。
比如我们要调用10万次加法函数
int Add(int x,int y)
{
return x+y;
}
函数每次的调用都要开辟栈帧,而随着调用次数的增大,栈帧开辟的就越频繁,这样销毁就很大,针对这个问题c语言的解决方法是用宏函数来实现
#define Add(x,y) ((x)+(y))
C++就使用内联函数来实现
inline int Add(int x,int y)
{
return x+y;
}
![](https://i-blog.csdnimg.cn/blog_migrate/b8e97b3fd685091843475f85f064792d.png)
4.auto关键字(C++11)
auto 关键字是 C++11 中引入的一个特性,它可以让编译器自动推导出变量的类型,使用 auto 可以使代码更加简洁易读,特别是当处理复杂的类型。
int main()
{
auto x = 5;
auto y = 2.8;
auto a = 's';
auto s = "hello";
cout << x << endl;
cout << y << endl;
cout << a << endl;
cout << s << endl;
return 0;
}
注意:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto 的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编 译期会将auto替换为变量实际的类型。
4.1auto的使用细则
int main()
{
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
*a = 20;
*b = 30;
c = 40;
return 0; }
int main()
{
auto x = 5, y = 6;
auto a = 5.5, b = 5;//这里会报错,不同类型名字
return 0;
}
4.2.auto不能推导的场景
1.auto不能作为函数的参数
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
2. auto不能直接用来声明数组
4.3基于范围的for循环(C++11)
int main()
{
int arr[] = { 1,56,9,78,9,1,51,106,40 };
int sz = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < sz; i++)
{
cout << arr[i] << " ";
}
cout << endl;
for (auto e : arr)
cout << e << " ";
cout << endl;
return 0;
}
范围for的使用条件:
1.for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围
void TestFor(int array[])
{
for(auto& e : array)
cout<< e <<endl;
}
5.指针空值nullptr(C++11)
在 C++ 中,nullptr 是用于表示空指针。它在 C++11 标准中引入,用以替代 C 语言时代的 NULL 宏和 C++ 中的 0(零)。
void f(int x)
{
cout << "f(int)" << endl;
}
void f(int* a)
{
cout << "f(int* a)" << endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
return 0;
}
程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,与预期违背
注意:1. 在使用 nullptr 表示指针空值时,不需要包含头文件,因为 nullptr 是 C++11 作为新关键字引入 的 。2. 在 C++11 中, sizeof(nullptr) 与 sizeof((void*)0) 所占的字节数相同。3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。