C++命名空间,函数重载与引用

目录

目录

一、命名空间 

namespace用法:

1.正常的命名空间定义

2.命名空间可以嵌套

3.命名空间可以合并

命名空间的使用的三种方式 

 1.加空间名称及作用域限定符

2.使用using将命名空间中某个成员引入

3.使用using namespace命名空间引入

C++输入&输出

二、函数重载

函数重载是什么:

 函数重载的三种使用情况

1.参数类型不同

2.参数个数不同

3.参数类型顺序不同

C++底层如何支持函数重载

三、引用

引用概念

引用特性

常引用

引用场景

1.做参数

2.做返回值

传值,传引用效率比较

传值传址和传引用

引用和指针的区别


一、命名空间 

在C语言中经常出现这种情况,库中定义了rand,我们自己如果也定义一个rand变量,就会出现命名冲突问题 ,并且在大型项目中,每个人负责的模块不同,定义的函数名可能会相同,项目整合到一起后发生冲突,为了解决C语言中存在的这种问题,C++进行了优化。

C++中使用了命名空间namespace,目的是对标识符的名称进行本地化,以避免命名冲突或名字污染。

namespace用法:

1.正常的命名空间定义

命名空间中可以定义变量,函数,类型

namespace mids
{
	int rand = 10;
	int Add(int left, int right)
	{
		return left + right;
	}
	struct Node
	{
		struct Node* next;
		int val;
	};
}

2.命名空间可以嵌套

就算是一个命名空间在另一个命名空间中,也不会发生命名冲突

namespace mids
{
	int rand = 10;
	int Add(int left, int right)
	{
		return left + right;
	}
	struct Node
	{
		struct Node* next;
		int val;
	};
	namespace mids1
	{
		int rand = 3;
		double Add(double left, double right)
		{
			return left+right;
		}
		struct Node
		{
			struct Node* next;
			double val;
			
		};
	}
}

3.命名空间可以合并

同一个工程中允许存在多个相同名称的命名空间编译器最后会合成同一个命名空间中

比如下面这个例子,在同一个项目中的test.h定义了mids,在test.c也定义了mids

//test.h
#pragma once
namespace mids
{
	int Mul(int left, int right)
	{
		return left * right;
	}
}
//test.c
#include<iostream>
using namespace std;
#include"test.h"
namespace mids
{
	int rand = 10;
	int Add(int left, int right)
	{
		return left + right;
	}
	struct Node
	{
		struct Node* next;
		int val;
	};
}
using namespace mids;
int main()
{
	int a = 10;
	int b = 3;
	int ret = Mul(a, b);
	cout << ret << endl;
	return 0;
}

运行结果:

命名空间的使用的三种方式 

 1.加空间名称及作用域限定符

int main()
{
	printf("%d\n", mids::rand);
}

2.使用using将命名空间中某个成员引入

#include<iostream>
using std::cout;
using std::endl;
using mids::a;
int main()
{
	cout << a << endl;
	return 0;
}

3.使用using namespace命名空间引入

#include<iostream>
using std::cout;
using std::endl;
using namespace mids;
int main()
{
	cout << a << endl;
	return 0;
}

C++输入&输出

std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中

#include<iostream>
using namespace std;
int main() 
{
	cout << "Hello world! " << endl;
	return 0;
}

说明:

1.使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含<iostream>头文件以及按命名空间方法使用std。

2.cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含<iostream>头文件中。

3.<<是插入运算符,>>是流提取运算符。

4.使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。C++的输入输出可以自动识别变量类型。

5.实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识,目前只是简单的使用,后面会更深入学习

6.日常练习直接使用using namespace std即可,比较方便。但是在项目开发中代码较多,规模大,容易出现和库函数命名相同的情况,我们在项目中一般用std::cout这样指定命名空间使用,或者使用using std::cout 展开常用的库对象/类型。

二、函数重载

函数重载是什么:

是函数的一种特殊情况,C++允许在同一个作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。

分析:

C语言不支持函数重载,那么在命名的时候就需要避免相同,特意规避费时又费脑。

比如一个简单的swap函数:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
void swap(double* a, double* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	swap(&a, &b);
	return 0;
}

因为命名一样就会报错,这样我们就得将他们命名成swap1,swap2,不仅命名麻烦,调用也麻烦,还得记住swap1是int类型参数的函数名,swap2是double类型参数的函数名。C++为了解决这样的问题,允许函数重载,同样的代码在C++中就可以编译运行:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
void swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
void swap(double* a, double* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	swap(&a, &b);
	cout << a << endl;
	cout << b << endl;
	return 0;
}

 

 函数重载的三种使用情况

1.参数类型不同

void swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
void swap(double* a, double* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

2.参数个数不同

void func()
{
	cout << "func()" << endl;
}
void func(int a)
{
	cout << "func(int a)" << endl;
}
int main()
{
	func();
	return 0;
}

3.参数类型顺序不同

void func(int a,char b)
{
	cout << "func(int a,char b)" << endl;
}
void func(char a,int b)
{
	cout << "func(char a,int b)" << endl;
}
int main()
{
	func(10,'a');
	func('a',10);

	return 0;
}

除此之外,其他情况就会报错了,比如

int add(int a,int b)
{
	return a + b;
}
double add(int a, int b)
{
	return a + b;
}
int main()
{
	int a = 10;
	int b = 20;
	int ret=add(a, b);
	cout << ret << endl;
	return 0;
}

原因是C++无法重载以返回类型区分的函数,因为传参过程中,不可能再传一个返回类型,所以函数没办法区分,并且这个返回类型就是函数去控制的

C++底层如何支持函数重载

 

   C语言中参数不同直接取不一样的名字                                  

 Windows VC++修饰后的格式为    ?+函数名+@@YA+返回值+参数1+参数2+@Z 

Linux下的命名规则:_Z+函数长度+函数名+类型首字母 

三、引用

引用概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用开辟内存空间,它和它引用的变量共用同一块内存空间(比如宋江,又叫“及时雨”)

int main()
{
	int a = 10;
	int& ra = a;
	printf("%p\n", &a);
	printf("%p\n", &ra);
	return 0;
}

注意:引用类型必须和引用实体同种类型

引用特性

void TestRef()
{
int a = 10;
// int& ra; // 该条语句编译时会出错
int& ra = a;
int& rra = a;
printf("%p %p %p\n", &a, &ra, &rra);
}

1.引用在定义时必须初始化

2.一个变量可以有多个引用

3.引用一旦引用一个实体,再不能引用其他实体

常引用

void TestConstRef()
{
const int a = 10;
//int& ra = a; // 该语句编译时会出错,a为常量
const int& ra = a;
// int& b = 10; // 该语句编译时会出错,b为常量
const int& b = 10;
double d = 12.34;
//int& rd = d; // 该语句编译时会出错,类型不同
const int& rd = d;
}

引用场景

1.做参数

void Swap(int& left, int& right)
{
int tep = left;
left = right;
right = tep;
}

2.做返回值

int& Count()
{
static int n = 0;
n++;
// ...
return n;
}

注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没给系统),则可以使用引用返回,如果已经还给了系统,则必须使用传值返回,否则会出现下面的后果:

int& Count()
{
	int n = 0;
	n++;
	return n;
}

int main()
{
	int& ret = Count();
	cout << ret << endl;
	cout << ret << endl;
	cout << ret << endl;
	
	return 0;
}

分析:static int n是静态变量,在堆上创建的,函数结束后不会被销毁,这个时候我们引用作为返回值,返回的相当于n的别名,n一直存在,没有问题。但是当n是普通变量时,在栈帧开辟,那么出了函数作用域就会被销毁,这个时候我们再访问这个n就相当于越界访问了,因为这个栈帧空间有可能赋给别的函数,本来的值就可能发生改变。就好比我们离开了一个酒店,落下一瓶水,但是这个酒店别人又住过了,我们之前留了钥匙偷偷溜进去发现水已经没了,只有一瓶可乐。那么有没有可能这个酒店没人住过呢,有可能,但是只要运行过一个函数,那么这个值就变了。包括printf 和cout <<(后面可能会理解的更深刻一点)

传值,传引用效率比较

以值作为参数或返回值类型,在传参和返回期间,函数不会直接传递实参或将变量本身直接返回,而是传递实参或者返回变量的一份临时拷贝,因此用值作为参数或者返回值类型,效率是十分低下的,尤其是当参数或者返回值类型非常大时,效率就更低

分析:拷贝需要浪费时间,不拷贝直接对数进行操作要快很多

下面是网上的一些我觉得有用的话,因为我对这个return有一些困惑,return返回为什么还要拷贝呢,传参拷贝我理解,但是返回为什么还要拷贝,先留下一个疑问吧

C++ 17开始,返回变量会有优化,会直接构造外部变量,而非构造->复制构造\移动构造

什么是RVO优化
RVO的全称是Return Value Optimization。RVO是一种编译器优化技术,可以把通过函数返回创建的临时对象给”去掉”,然后可以达到少调用拷贝构造的操作目的,
它是C++11标准的一部分。

如果编译器明确知道函数会返回哪一个局部对象,那么编译器会把存储这个局部对象的地址和存储返回值临时对象的地址进行复用,也就是说避免了从局部对象到临时对象的拷贝操作。这就是RVO。

传值传址和传引用

1.传值

这种传递方式中,实参和形参是两个不同的地址空间,参数传递的实质是将原函数中变量的值,复制到被调用函数形参所在的存储空间中,这个形参的地址空间在函数执行完毕后,会被回收掉。整个被调用函数对形参的操作,只影响形参对应的地址空间,不影响原来函数中的变量的值,因为这两个不是同一个存储空间。

即使形参的值在函数中发生了变化,实参的值也完全不会受到影响,仍为调用前的值。

2.传址

这种参数传递方式中,实参是变量的地址,形参是指针类型的变量,在函数中对指针变量的操作,就是对实参(变量地址)所对应的变量的操作,函数调用结束后,原函数中的变量的值将会发生改变。

被调用函数中对形参指针所指向的地址中内容的任何改变都会影响到实参。

3.传引用

这种参数传递方式中,形参是引用类型变量,其实就是实参的一个别名,在被调用函数中,对引用变量的所有操作等价于对实参的操作,这样,整个函数执行完毕后,原先的实参的值将会发生改变。

被调函数对形参做的任何操作都影响了主调函数中的实参变量。

引用和指针的区别

语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间


底层实现上实际是有空间的,因为引用是按照指针方式来实现的

看一下汇编代码就明白了 

 给上面的代码加了一点注释

int a = 10;
00FA215F  mov         dword ptr[a], 0Ah     //0Ah是16进制的10的意思,就是把5送入变量a中
int& ra = a;                                   
00FA2166  lea         eax, [a]              //将变量a的地址放入寄存器eax
00FA2169  mov         dword ptr[ra], eax    //将寄存器的内容(a的地址)送入变量ra
ra = 20;
00FA216C  mov         eax, dword ptr[ra]    //将变量ra的值送入寄存器eax
00FA216F  mov         dword ptr[eax], 14h   //将数值20送入以eax的内容为地址的单元中
return 0;

可以看到指针和引用的的汇编代码完全一样

int& ra=a 类似  int* const pa=a

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值