C++入门钥匙(引用+内联函数+函数重载+)

 鼠鼠来喽,今天鼠鼠带你走进C++!

你间歇性的努力和蒙混过日子,都是对之前努力的清零!

目录

一、命名空间(namespace)

1.1 为什么C++使用命名空间?

1.2 命名空间使用

1.2.1 筑墙

1.2.2 拆墙 

二、缺省参数

2.1 概念

2.2 全缺省参数

2.3 半缺省参数

2.3 缺省参数注意事项

三、函数重载

3.1 函数重载概念

3.2 C++如何实现函数重载

四、引用

4.1 引用的概念

 4.2 引用的使用规则

4.3 常引用

4.4 引用使用场景

4.4.1 做参数

4.4.2 传值、传引用效率比较

4.4.3 做返回值 (!!!C语言知识基础)

4.5 引用与指针的区别

五、内联函数

5.1 内联函数的概念

5.2 内联函数使用环境

六、auto关键字

6.1 出现背景

6.2 概念

6.3 使用规则

6.3.1 auto与指针和引用结合起来使用

6.3.2  auto不能作为函数的参数,不能声明数组

7. 指针空值nullptr(C++11)


一、命名空间(namespace)

1.1 为什么C++使用命名空间?

命名空间是用来存储数据的空间。我们知道在C语言中我们接触到局部,全局命名空间,不同的命名空间,他们的作用域和生命周期不同。在C语言中,我们会遇到定义变量冲突问题,也就是重命名,例如我在全局域定义一个整形变量tmp,然后我又定义一个整形指针tmp,在C语言中编译器会直接报错,可是有些时候就要需要同一变量不同类型的情况,使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。

1.2 命名空间使用

我将用筑墙、拆墙、挖墙缝这三个动作带你了解命名空间的使用~

1.2.1 筑墙

顾名思义,筑墙即创建命名空间,现在上代码~

namespace N
{
	int a = 20;//变量
	typedef struct Node
	{
		int Data;
		struct Node* next;
	}Node;//结构体
	int Add(int x, int y)
	{
		return x + y;
	}//函数
}
int a = 0;
int main()
{
	N::Node node;
	node.Data = 10;
	node.next = NULL;
	std::cout << a<< std::endl;
	std::cout <<N::a<< std::endl;//cout为标准输出流,endl为换行符,本质是函数,它们都来自空间std,而std来自头文件iostream
	std::cout << node.Data << std::endl;
	std::cout << N::Add(2, 3) << std::endl;
}

通过namespace创建空间N,使域内空间的全局a与N域外的a互不干扰,相当于筑墙隔绝彼此!我们还可以看出域内可以存放变量、结构体、函数,也可以域内继续筑墙(这里没提及)!


1.2.2 拆墙 

拆墙就是暴露墙内存放的数据,使其成为程序默认的命名空间!

我们用using namespace 空间名称 进行拆墙,下面用代码实现~

#include <iostream>//打印函数头文件
namespace N
{
	int a = 20;//变量
	typedef struct Node
	{
		int Data;
		struct Node* next;
	}Node;//结构体
	int Add(int x, int y)
	{
		return x + y;
	}//函数
}
using namespace std;
int main()
{
	cout << N::a << endl;
}

 这里我们让std成为默认命名空间,所以就不用指定cout,endl空间来源

using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对
象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模
大,就很容易出现。所以引出部分拆墙,就是挖墙缝!

1.2.3 挖墙缝

简单来说,就是根据需求暴露部分数据!

它们形如 using std::cout;  后面跟上面差不多,我这里不多复述了。


二、缺省参数

2.1 概念

缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参

2.2 全缺省参数

void Func(int a = 10, int b = 20, int c = 30)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}

2.3 半缺省参数

void Func(int a, int b = 10, int c = 20)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}

2.3 缺省参数注意事项

1.半缺省参数必须从右往左依次来给出,不能间隔着给

2.缺省参数不能在函数声明和定义中同时出现

//a.h
void Func(int a = 10);
// a.cpp
void Func(int a = 20)
{}
// 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该
用那个缺省值。

3. 缺省值必须是常量或者全局变量

4. C语言不支持(编译器不支持)


三、函数重载

3.1 函数重载概念

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

#include<iostream>
using namespace std;
// 1、参数类型不同
int Add(int left, int right)
{
    cout << "int Add(int left, int right)" << endl;
    return left + right;
}
double Add(double left, double right)
{
   cout << "double Add(double left, double right)" << endl;
   return left + right;
}

// 2、参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
   cout << "f(int a)" << endl;
}

// 3、参数类型顺序不同
void f(int a, char b)
{
   cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
  cout << "f(char b, int a)" << endl;
}

int main()
{
   Add(10, 20);
   Add(10.1, 20.2);
   f();
   f(10);
   f(10, 'a');
   f('a', 10);
   return 0;
}

3.2 C++如何实现函数重载

C++是通过函数修饰规则来区分,只要参数不同,修饰出来的函数名字(汇编)就不一样,就支持了重载。

我们比较Linux下C语言和C++中编译后的细节!

C语言:

 在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变


C++:

 在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。

四、引用

4.1 引用的概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间

我们用 类型&(符号) 引用变量名(对象名) = 引用实体 

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

void TestRef()
{
int a = 10;
int& ra = a;//<====定义引用类型
printf("%p\n", &a);
printf("%p\n", &ra);
}


从上我们可以看出引用的共用空间

 4.2 引用的使用规则

1. 引用在定义时必须初始化且引用类型必须和引用实体是同种类型
2. 一个变量可以有多个引用(一对多
3. 引用一旦引用一个实体,再不能引用其他实体(一对一


4.3 常引用

在C中我们知道变量类型的活泼性>常量类型,类型引用只有偏移,降低!也就是说变量能被常量引用,常量不能被变量引用

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;
}

4.4 引用使用场景

4.4.1 做参数

上代码~

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

4.4.2 传值、传引用效率比较

引用做参数后,函数调用就没有形参的拷贝,形参是实参的引用,在函数体中形参的改变即是实参的改变。这样的改变就减少了拷贝时间,提高了代码的执行效率!

下面用参数是大数组为例

#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;
}


4.4.3 做返回值 (!!!C语言知识基础)

int& Count()
{
	static int n = 0;
	n++;
	// ...
	return n;
}
int& Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int& ret = Count();
	int& _ret = Add(1, 2);
	Add(3, 4);
	cout << "ret=" << ret << endl;
	cout << "Add(1, 2) is :" << _ret << endl;
	return 0;
}

 我们注意到上面两次都是引用做返回值,但是达到的效果不同,第二个出现了随机值。为什么呢?停下来,自己从引用定义出发想想!

!!!

上面两个函数定义变量的区别是一个用了static,在静态区开辟,一个在栈上开辟。函数调用完后,栈空间使用权返回给操作系统,造成后续函数调用可以覆盖原空间,而静态区安然无事。引用是共用空间,第二次调用Add时理论有两种可能,第一种栈帧正好巧妙避开变量c的空间,未覆盖,导致_ret依旧是原值;第二种是覆盖c原来空间,_ret变成第二次Add两数相加值;这次出现的随机数,是因为打印函数再次覆盖,导致值变成随机数!而static修饰的n在静态区,不用担心空间被覆盖问题,所以值依旧为原值!

 总结:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用
引用返回
,如果已经还给系统了,则必须使用传值返回


4.5 引用与指针的区别

1. 引用概念上定义一个变量的别名,指针存储一个变量地址
2. 引用在定义时必须初始化指针没有要求
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
一个同类型实体

4. 没有NULL引用,但有NULL指针
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节)
6. 引用自加即引用的实体增加1指针自加即指针向后偏移一个类型的大小
7. 有多级指针,但是没有多级引用
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全


五、内联函数

5.1 内联函数的概念

inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。

对比:

未+内联 (Call代表函数调用

+内联

5.2 内联函数使用环境

1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
用函数体替换函数调用
,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运
行效率。
2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建
议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、
是递归、且频繁调用
的函数采用inline修饰,否则编译器会忽略inline特性。

《C++prime》第五版关于inline的建议:

3.inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址
,链接就会找不到(链接形成符号表,未在本文件定义函数,调用函数时通过符号表在不同文件寻找函数地址,找到后才能调用函数,内联函数不会形成符号,自然不会进入符号表

// F.h
#include <iostream>
using namespace std;
inline void f(int i);
// F.cpp
#include "F.h"
void f(int i)
{
  cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{
  f(10);
  return 0;
}
// 链接错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl
f(int)" (?f@@YAXH@Z),该符号在函数 _main 中被引用

六、auto关键字

6.1 出现背景

随着程序越来越复杂,变量类型也会越复杂,其中体现在类型名称复杂,含义不明确导致错误,例如下面一个类名称:

#include <string>
#include <map>
int main()
{
	std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange",
	"橙子" },
	{"pear","梨"} };
	std::map<std::string, std::string>::iterator it = m.begin();
	while (it != m.end())
	{
		//....
	}
	return 0;
}

有人会说用typedef 重新命名变量名称,但是其定义常变量时必须初始化,这就引出C++11使用auto关键字 

6.2 概念

C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一
个新的类型指示符来指示编译器
,auto声明的变量必须由编译器在编译时期推导而得。我们在使用auto 定义变量时必须进行初始化,因为在编译阶段编译器需要根据初始化表达式来推导auto
的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编
译期会将auto替换为变量实际的类型

6.3 使用规则

6.3.1 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;
}


6.3.2  auto不能作为函数的参数,不能声明数组


7. 指针空值nullptr(C++11)

我们之前习惯用NULL初始化指针,而NULL实际是一个宏!在C++98中对NULL的定义如下:

 可以看出NULL可能被定义为字面常量0,或者定义为无类型指针(void*)的常量,这样会造成二义性的麻烦,比如:

 

 程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖

在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void*)0。这时C++11定义了nullptr表示空指针!sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同!

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值