【C++入门】引用、内联函数、auto 关键字

文章目录

  • 一、引用
    • 1. 语法概念
    • 2. 注意事项
    • 3. 使用场景
      • 引用传参
      • 引用返回
    • 4. 引用和指针的区别
  • 二、内联函数
    • 1. 基本概念
    • 2. 使用习惯
    • 3. 注意事项
  • 三、auto 关键字
    • 1. 基本概念
    • 2. 注意事项

一、引用

1. 语法概念

从语法上理解,引用就是给变量取一个别名,它没有独立空间,和它引用的变量共用一块空间。

例子:

int a = 10;
int &b = a;//b是a的别名
//对b的操作都相当于对a操作

值得注意的是,C++ 中的引用一经初始化,后面是改变不了指向的(这点与Java有所不同),而且引用是必须初始化的。
引用的类型也必须和原变量对应,显然,你肯定不能用 int& 的类型引用 double 类型的变量。

2. 注意事项

在引用的过程中,权限可以平移,可以缩小,但是不能放大

怎么理解这句话呢?举个例子,假设你声明了一个 const 修饰的整型变量:

const int a = 10;

现在你要对其进行引用,如果你用 int& 类型:

int &b = a;//权限的放大

此时编译器会报错,这属于引用的权限放大。被引用的变量是有 const 修饰的,而你忽略了这个,直接用 int& 类型来引用,也就是说你用一个变量当一个常量的别名,显然是不合法的。
引用时权限不能放大,但是可以平移,可以缩小。因此上述代码我们可以改成权限的平移:

const int &b = a;

这种就属于权限的平移,也就是用一个常量当作一个常量的别名,这样是合法的。
那权限的缩小是什么样的呢?例子:

int a = 10;
const int &b = a;

此时我们用一个常量当作一个变量的别名,这样做是合法的。也就是说我们不能通过这个别名来改变原变量,这属于权限的缩小。

3. 使用场景

引用到底有什么用,用引用又有什么好处呢?

引用传参

传参分两种,传值和传引用。
传值传参,形参相当于实参的拷贝,有拷贝的消耗,并且对形参的操作不会影响实参。
而传引用则有所不同,引用传参相当于传了一个别名,对引用的操作会影响被引用的变量。另外引用传参不用将实参拷贝一份,大大提高了效率。

例子1:

//引用传参,形参影响实参
void swap(int &a, int &b)
{
	int t = a;
	a = b;
	b = t;
}

例子2:

//引用传参,不用拷贝效率高
void func(vector<string> &x, vector<string> &y);

引用传参在任何情况下都可以使用,如果我们想形参影响实参或者提高效率的话。

引用返回

返回值也分两种,传值返回和引用返回。
传值返回,返回值是先拷贝到临时变量里的,等出了函数作用域,函数栈帧销毁,再把临时变量里的值传回函数调用的地方。
因此传值返回也是有拷贝消耗的,效率较低,并且传回的只是临时拷贝的内容,不能修改返回的对象。另外临时拷贝具有常属性,不能作左值,所以传值返回的返回值不能作左值。
例子:

//传值返回
int func(...)
{
	...
	return n;//传值返回的是n的拷贝,无法直接修改n
}

int main()
{
	...
	func(...) = 10;//报错,不能作左值
}

而传引用返回则有所不同,首先引用返回的是一个别名,没有拷贝的消耗,效率较高。
其次引用返回可以直接修改返回对象,因为引用返回的正是返回对象的别名,但这也带来一个问题,就是要保证出了函数作用域后,返回对象还存在,否则结果是未定义的。
另外,传引用返回的是一个变量的别名,变量是可以作左值的。
例子:

//引用返回
int& func(...)
{
	...
	return n;//引用返回的是n的别名
	//但是要保证出了函数,n还存在
}

int main()
{
	...
	func(...) = 10;//合法,返回的是变量的别名,可以作左值
	
	int& ret1 = fun(...);//用另一个别名接收
	ret1 = 20;//可以直接修改返回对象n
	
	int ret2 = fun(...);//这个是把n的值赋给一个新的变量
	//ret2是一个新开辟的变量,对ret2的操作不会影响n
}

因此,引用返回的好处有提高效率、可直接修改返回对象。但是必须保证出了函数作用域,返回对象依然存在,才能使用引用返回。

4. 引用和指针的区别

从语法上理解,引用就是变量的别名,没有独立空间。
但底层上引用是用指针实现的,现阶段不用深究,理解了引用的语法概念并且能够正确使用即可,毕竟引用被发明出来就是为了方便使用的。

下面来看看引用在使用的时候和指针的区别:
1.引用定义一个变量的别名,指针存储一个变量的地址。
2.引用在定义时必须初始化,指针没有要求。
3.引用初始化后不能改变指向,指针可以。
4.没有 NULL 引用,但有 NULL 指针。
5.sizeof 引用的结果是引用类型的大小,sizeof 指针的结果是地址大小。
6.引用自加是引用的实体自加,指针自加是指针向后偏移一个类型的大小。
7.有多级指针,但是没有多级引用。
8.访问实体时,指针需要显式解引用,引用则是编译器自己处理。
9.使用引用比使用指针更安全。

二、内联函数

1. 基本概念

inline 修饰的函数叫内联函数,编译后会在调用内联函数的地方展开,没有调用函数的开销,可以提高程序运行的效率。
例子:

//inline 修饰的函数叫内联函数
inline void swap(int& a, int& b)
{
	int t = a;
	a = b;
	b = t;
}

但是inline只是给编译器的一个建议,编译器自己会决定内联函数是否在调用处展开。

内联函数展开是一种空间换时间的做法,函数体替换函数调用,可能会使目标文件变大,但是运行时少了函数调用的开销。

2. 使用习惯

习惯上,我们对规模较小(一般不超过十行)、调用频繁且不含递归的函数,可以使用inline修饰,以提高效率。但是内联函数具体会不会展开,取决于编译器。

内联函数的使用场景让我们很容易想到 C 语言中的宏函数(传送门),在 C++ 中,我们一般用内联函数取代宏函数,因为内联函数更安全、更好维护。

3. 注意事项

内联函数的声明和定义必须在同一个文件中,否则会导致链接错误。
因为内联函数经过编译如果展开,就不会出现在内联函数定义文件的符号表中。此时如果函数的声明在另外一个文件中,这个文件的符号表上就会有内联函数的函数名和无效地址。这样在链接的时候合并符号表,找不到内联函数的定义体(也就是找不到该符号的有效地址),就会报错。

对上述编译链接的过程有不清楚的,可以参考这篇文章:传送门

一般来说,我们建议直接在头文件中定义内联函数,不用单独声明。

三、auto 关键字

1. 基本概念

auto关键字是 C++ 11 的新特性。
在早期 C / C++ 中,auto的含义是自动销毁的局部变量,由于局部变量本来就是自动销毁的,所以我们一般忽略不写。
而在 C++ 11 中,auto变量的含义是我们不告诉编译器这个变量的类型,让编译器在编译时根据变量的初始化表达式(auto修饰的变量必须初始化),自己去猜变量的类型。

例子:

int a = 0;
auto b = a;  //int b = a;
auto c = &a; //int* c = &a;
auto& d = a; //int& d = a;

2. 注意事项

auto不能用作返回值、形参、数组的类型,这个记住就行。

另外,使用auto一行声明多个变量时,变量的类型必须相同。因为编译器只对第一个变量进行推导,然后用推导出来的类型定义同一行其他变量。
例子:

auto a = 0, b = 0;//变量的类型必须相同
auto c = 0, d = 0.1;//编译失败,因为c和d的初始化表达式类型不同

你可能感兴趣的:(C++入门,c++,开发语言)