1 域

在 C++ 中,域包括了函数局部域,全局域,命名空间域,类域
函数局部域:由函数的大括号所包裹起来的区域,局部变量所处的位置
全局域:不受大括号影响的区域,全局变量所处的位置
命名空间域:由 namespace 关键字限制的区域
类域:由 class 或 struct 关键字限制的区域

2 namespace 关键字

2.1 命名空间的定义

使用 namespace 关键字,可以定义一个命名空间,命名空间本身也是一个域,命名空间内可以定义函数,变量等,并且命名空间只能在全局进行定义
具体语法为:

namespace 命名空间名称
{
    //命名空间的内容
}

命名空间也可以嵌套定义

namespace A
{
	int a = 0;
	namespace B
	{
		int b = 0;
	}
}

2.2 命名空间的使用

如果要访问命名空间中的内容,有三种方式:

使用命名空间名字访问
较为推荐的方法

#include <iostream>
using namespace std;
namespace A
{
	int a = 0;
	namespace B
	{
		int b = 0;
	}
}

int main()
{
    //使用命名空间名字
	cout << A::a << endl; // ::为域作用限定符
	cout << A::B::b << endl;
	return 0;
}

使用 using 将命名空间的某成员展开
如果某成员不会发生命名冲突,可以用这个方式展开

#include <iostream>
using namespace std;
namespace A
{
	int a = 0;
	namespace B
	{
		int b = 0;
	}
}
//展开成员
using A::a;
using A::B::b;
int main()
{
	cout << a << endl;
	cout << b << endl;
	return 0;
}

使用 using 将整个命名空间展开
这个方法在大项目中可能会存在命名冲突的问题,不太推荐

#include <iostream>
using namespace std;
namespace A
{
	int a = 0;
	namespace B
	{
		int b = 0;
	}
}

//展开整个命名空间
using namespace A;
using namespace B;

int main()
{
	cout << a << endl;
	cout << b << endl;
	return 0;
}

2.3 命名空间的作用

在 C 语言中,经常会因为调用了某个头文件而产生变量名与函数名的命名冲突问题,命名空间就是为了处理这种问题而存在的
比如:

#include <stdlib.h>
int rand = 0;

int main()
{
	cout << rand << endl;
	return 0;
}

这段代码中,定义了一个全局变量 rand,但是头文件 stdlib.h 中有一个函数的名称也是 rand,就导致命名冲突

可以用命名空间来解决这个问题:

#include <stdlib.h>

namespace A
{
	int rand = 0;
}

int main()
{
	cout << A::rand << endl; //使用命名空间A中的rand
	rand(); //调用rand函数

	return 0;
}

3 缺省参数

C++在定义函数时,可以在定义参数时给参数缺省值(默认值),这个参数就被叫做缺省参数
在调用函数时如果没有传参,那么就会使用该缺省值,如果传参了,就会使用传入的值
具体形式为:

int func(int x = 0) //x为缺省参数,有默认值0
{
	cout << x << endl;
}
int main()
{
	func();
	return 0;
}

缺省参数有全缺省半缺省
全缺省:函数的每个参数都有自己的缺省值

int Add(int x = 0, int y = 0) //全缺省
{
	return x + y;
}
int main()
{
	Add();
	return 0;
}

半缺省:函数的部分参数有自己的缺省值

int Add(int x, int y = 0) //半缺省
{
	return x + y;
}
int main()
{
	Add(1);
	return 0;
}

在语法规定中,半缺省参数一定要从右往左进行缺省,不能进行跳跃缺省

int Add(int x = 0, int y) //不能从左往右缺省
{
	return x + y;
}

int func(int a = 0, int b, int c = 0) //不能跳跃缺省
{
	//...
}

不管是全缺省还是半缺省,在传参时,参数列表必须是从左往右依次传入实参

int func(int a = 0, int b = 0, int c = 0)
{
	//...
}

int main()
{
	func(1,1); //从左往右依次传参
	func(,,1); //不能从右往左传参
	return 0;
}

函数的声明和定义中不能同时出现缺省值,必须在声明中给缺省值

4 函数重载

函数重载主要会在下面几种情况下发生:
函数名相同,形参列表变量个数相同但变量类型不同

int Add(int x, int y)
{
	return x + y;
}

double Add(double x, double y)
{
	return x + y;
}

函数名相同,形参列表变量类型相同但变量个数不同

int Add(int x, int y)
{
	return x + y;
}

int Add(int x)
{
	return x + 10;
}

函数名相同,形参列表变量类型,个数都不相同

int Add(int x, int y)
{
	return x + y;
}

double Add(double x)
{
	return x + 10.0;
}

不会发生重载的情况:
函数名相同,形参列表变量类型,个数都相同,但是返回值不同

int Add(int x, int y)
{
	return x + y;
}

double Add(int x, int y)
{
	return x + y;
}

会发生重载,但是无法判别函数的情况:
函数名相同,但是一个是无参,一个是全缺省参数

void func()
{
	//...
}

void func(int a = 0, int b = 0)
{
	//...
}

因为全缺省参数和无参的函数都可以不用传实参,所以在调用时,如果不传实参,编译器无法判定使用哪个函数

5 引用

5.1 引用的概念

定义一个引用就相当于是给一个变量取了别名,引用在定义时是不开辟新空间的,它和被引用的变量使用同一块内存空间,C++中,可以使用 & 来定义一个引用,一个变量可以有多个引用

int main()
{
	int a = 0;
	int& ra = a; //给a取别名为ra
	return 0;
}

在使用引用时,要注意:

引用必须要进行初始化

int& rb; //这种写法是错误的

引用不能更改它的指向

int main()
{
	int a = 0;
	int& ra = a;
	int b = 0;
	ra = b; //赋值,没有改变引用的指向
	return 0;
}

把 b 赋值给 ra 的行为不是改变了 ra 的指向,而是将 0 赋值给了 a

5.2 const 引用

用 const 修饰引用就是 const 引用,const 引用可以对被引用的对象进行读取,但是不能更改被引用对象的值
const 引用可以用来引用普通对象和 const 修饰的对象,但是不能用普通引用来引用 const 修饰的对象

const 引用指向普通对象

int main()
{
	int a = 0;
	const int& ra = a;
	return 0;
}

a 是普通的对象,它即可读又可写,ra 是 const 引用,对引用的对象可读不可写,权限进行了缩小

const 引用指向 const 修饰的对象

int main()
{
	const int a = 0;
	const int& ra = a;
	return 0;
}

a 是 const 修饰的对象,它可读不可写,ra 是 const 引用,对引用的对象可读不可写,权限进行了平移

普通引用指向 const 修饰的对象

int main()
{
	const int a = 0;
	int& ra = a;
	return 0;
}

a 是 const 修饰的对象,它可读不可写,ra 是普通引用,对引用的对象可读可写,权限进行了放大,无法进行编译

特殊情况
C++中如果发生了隐式类型转换或表达式求值,都会产生一个临时对象用来存储结果,临时对象具有常性,如果要让引用指向这个临时对象,那么该引用需要 const 修饰

int main()
{
	int a = 1;
	const int& ra = (a + 2);
	const double& rb = a;
	return 0;
}

在这里插入图片描述

5.3 引用的使用场景

函数在进行传值调用与返回的时候,会对参数进行拷贝,此时如果该参数占的空间较大,则会耗费不少的时间来处理
引用主要是在函数传参和返回时使用,用来减少函数传参和返回时产生的拷贝在函数体内改变对象

const int& Add(int& ra, int& rb)
{
	return ra + rb;
}

int main()
{
	int a = 0;
	int b = 1;
	Add(a, b);
	return 0;
}

在引用作返回值时,需要注意引用也会存在野引用的问题

int& func()
{
	int a = 0;
	int& ra = a;
	return a;
}

int main()
{
	func();
	return 0;
}

func 内定义了 a,返回了 a 的引用,但是 a 是局部变量,所以函数结束时会销毁,那么 ra 就会变成野引用

5.4 引用和指针的区别

在C++中,引用和指针相辅相成,各有各的特点:

  • 在定义时,引用是不开辟新空间的,只是一个变量的别名,指针则需要开辟空间来保存地址
  • 在初始化时,引用必须要进行初始化,否则会有编译错误,指针建议初始化,但不是必须的
  • 在修改时,引用不能修改指向,指针则可以通过修改保存的地址来变更指向
  • 在大小上,引用的大小就是指向的对象的大小,指针的大小在 32 位机器下是 4B,64位机器下是 8B
  • 在访问上,引用直接可以访问指向的对象,指针则需要解引用来访问
  • 在安全性上,引用一般不存在空引用和野引用的问题,指针则会存在空指针和野指针的问题,引用相对更为安全

6 inline 关键字

C 语言中可以把逻辑较为简短的函数写成宏的形式,减少函数调用时栈帧创建的时间损耗,但是写成宏容易出现问题,所以 C++ 引入了 inline 关键字
inline 是用来修饰函数的关键字,被它修饰的函数叫内联函数编译时会在调用内联函数的地方将它展开不需要创建栈帧即可使用函数,提升了效率也避免了宏的问题,毕竟,写函数一般都不会写错

inline int Add(int x, int y) //内联函数
{
	return x + y;
}
int main()
{
	Add(1, 2); //直接展开
	return 0;
}

需要注意的是:

  • 并不是所有加了 inline 的函数都会直接展开,这取决于编译器,编译器会根据函数内代码的多少,来斟酌展开
  • 代码量过多的函数如果成为内联函数并在很多地方展开,那么会导致整个项目的代码量急剧上升,效率下降,因此一般只建议让代码量少的函数成为内联函数
  • 内联函数不支持声明和定义分离

7 nullptr

C 语言中,NULL 是 void* 类型的常量,可以用来给任何类型的指针赋值,所以在函数调用时,就能够匹配形参为指针类型的函数
C++ 中,NULL 被定义为了 0,此时通过传 NULL 来调用函数时,就会匹配至形参为整形的函数,而非形参为指针类型的函数,使用与 C 语言有所区别,所以在C++中,定义了 nullptr 作为空指针,它可以用来给任何类型的指针赋值

int func(int* p)
{
	cout << "func(int*)" << endl;
}

int func(int x)
{
	cout << "func(int)" << endl;
}

int main()
{
	func(NULL); //调用func(int x)

	func(nullptr); //调用func(int* p)
	 
	return 0;
}
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐