【C++入门】 函数重载、引用、内联函数、auto、指针空值nullptr

1.函数重载

1.1 函数重载概念

函数重载:是函数的一种特殊情况,C++允许在 同一作用域 中声明几个功能类似的 同名函数
些同名函数的 形参列表(参数个数 或 类型 或 类型顺序)不同 ,常用来处理实现功能类似数据类型
不同的问题。
  在c语言函数是不能重命名的,这会报错,然而C++中就支持函数的重命名,但只有以下条件才可以。
我们来依次对形参列表进行举例
 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编译完成后,函数名字的修饰没有发生改变

结论:在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参
数类型信息添加到修改后的名字中。
通过上面可以看出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引用特性

1. 引用在定义时必须初始化
int x = 5;
int &b = x; // 正确,b被初始化为x的引用
int &c; // 错误,引用必须在声明时被初始化
2. 一个变量可以有多个引用
int main()
{
	int a = 1;
	int& b = a;
	int& c = a;
	int& d = a;
	cout << a << " " << b << " " << c << " " << d<< endl;

	return 0;
}

3. 引用一旦引用一个实体,再不能引用其他实体
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使用场景

1.做参数
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是移动。他们的底层可以说是一样的。

引用和指针的不同点:
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用在定义时必须初始化,指针没有要求
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
一个同类型实体
4. 没有NULL引用,但有NULL指针
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节)
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7. 有多级指针,但是没有多级引用
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全

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;
}
在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的
调用。

 

1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运
行效率。
2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同。
3.inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址
了,链接就会找不到。

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并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编 译期会将auto替换为变量实际的类型。

4.1auto的使用细则

1. auto与指针和引用结合起来使用
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须 加&。
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; }

2. 在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译 器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

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;
}
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因
此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范
围内用于迭代的变量,第二部分则表示被迭代的范围。

范围for的使用条件:

1.for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围

注意:以下代码就有问题,因为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,与预期违背

在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器
默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void
*)0。
注意:
1. 在使用 nullptr 表示指针空值时,不需要包含头文件,因为 nullptr C++11 作为新关键字引入
2. C++11 中, sizeof(nullptr) sizeof((void*)0) 所占的字节数相同。
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
感谢观看!
  • 19
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WEP_Gg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值