c++【补充关键字inline,auto,nullptr】



补充关键字:

inline(内联函数):

特性:

inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用
inline对于编译器而言只是一个建议不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、频繁调用的函数采用inline修饰否则编译器会忽略inline特性

以下是一个简单的 C++ 内联函数的示例:

#include <iostream>

// 定义内联函数,计算两个数的和
inline int add(int a, int b) {
    return a + b;
}

int main() {
    int x = 5;
    int y = 10;
    
    // 调用内联函数,直接将函数体替换到调用处
    int result = add(x, y);
    
    std::cout << "Sum: " << result << std::endl;
    
    return 0;
}

在这里插入图片描述

我们来观察一下,这时就没有call指令了,表示没有去调用add函数,也就是没有开辟add的栈。
原因:在这段汇编代码中,add 函数被内联展开,因此没有产生独立的 add 函数的栈帧。add 函数的代码直接嵌入到 main 函数的汇编代码中,因此所有的操作都在 main 函数的栈帧内完成。

出现原因:

前面的c++初阶提到:函数调用是会出现压栈销毁栈,某些函数虽然很小,但是它们的调用仍然会引入额外的开销,如函数参数的传递、局部变量的创建等, 这种开销可能会显著影响程序的性能。

作用:

1.减少函数调用开销: 函数调用会引入一定的开销,包括压栈、跳转等操作,尤其是在频繁调用的小型函数中,这种开销可能会显著影响程序的性能。内联函数通过直接将函数体插入调用处,避免了这种开销,从而提高了程序的执行效率。
2.减少函数开销: 某些函数虽然很小,但是它们的调用仍然会引入额外的开销,如函数参数的传递、局部变量的创建等。内联函数可以避免这些开销,因为它在调用处直接插入了函数体,而不需要真正地调用函数。
3.增加程序的可读性: 内联函数可以使代码更加紧凑,减少了函数调用的层级,使得程序的逻辑结构更清晰,易于理解和维护。
4. 编译器优化: 内联函数使得编译器可以更好地进行优化,例如进行更有效的代码重排、消除冗余操作等,从而进一步提高程序的性能。

注意事项:

频繁调用:

内联函数最适合于在程序中频繁调用的函数。频繁调用的函数使用内联可以减少函数调用的开销,从而提高程序的执行效率。但是会增加内存

#include <iostream>
using namespace std;
// 内联函数定义
inline int add(int a, int b) {
    return a + b;
}

int main() {
    int x = 5;
    int y = 10;
    int i = 0;
    // 调用内联函数
    for (i = 0; i < 100000; i++) {
        cout<< add(i, i+1)<<endl;
    }
    
    return 0;
}

在这里插入图片描述

由于函数体较小且频繁,编译器觉得inline没问题

函数规模:

内联函数适用于函数体较小且频繁调用的情况。如果函数体过大,内联化可能会导致代码膨胀,增加程序的体积,并且可能会降低缓存的效率。

#include <iostream>
using namespace std;
// 内联函数定义
inline int add(int a, int b) {
    cout << "破环内联函数" << endl;
    cout << "破环内联函数" << endl;
    cout << "破环内联函数" << endl;
    cout << "破环内联函数" << endl;
    cout << "破环内联函数" << endl;
    cout << "破环内联函数" << endl;
    cout << "破环内联函数" << endl;
    cout << "破环内联函数" << endl;
    cout << "破环内联函数" << endl;
    cout << "破环内联函数" << endl;
    return a + b;
}

int main() {
    int x = 5;
    int y = 10;
    int i = 0;
    // 调用内联函数
    for (i = 0; i < 100000; i++) {
        cout<< add(i, i+1)<<endl;
    }
    
    return 0;
}

在这里插入图片描述

此时强行展开,那不得了要把add函数循环100000次,相当于开100000add函数

在这里插入图片描述

inline 仅仅是给编译器建议,具体怎么做由编译器决定,这种情况,编译器就会优化:还不如开点栈帧给add用,浪费时间就浪费时间。

在这里插入图片描述

声明定义分离:

inline不建议声明和定义分离,分离会导致链接错误。 因为inline被展开,就没有函数地址了,链接就会找不到。

看这个例子:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

此时出现错误:链接错误:

在这里插入图片描述

**加粗样式

本质:在编译阶段print.h展开,给test.cpp表示有Print_t这个函数,此时的Print_t(a)底层会执行call指令,但此时没有函数地址,一般想调用函数是call加函数的地址,而得到这个地址是链接时发生的事情。但是因为inline被展开,就没有函数地址了,链接就会找不到,显示链接错误;

处理办法,声明链接不分开: 一般是头文件实现:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

inline与宏函数的比较:

补充宏函数的用法:

宏函数(Macro Function)是一种在编程语言中使用的一种宏定义,用于在编译时将一段代码替换为另一段代码。它们通常在预处理阶段展开,而不是在运行时执行。在C语言及其衍生语言(如C++)中,宏函数使用#define指令来定义,可以将一系列语句或表达式替换为一个标识符。以下是宏函数的基本用法:

定义宏函数:

使用#define指令来定义宏函数。语法如下:

#define MACRO_NAME(arguments) replacement_code

其中,MACRO_NAME是宏函数的名称arguments是宏函数的参数(可选),replacement_code是要替换的代码

宏函数的调用:

在代码中使用宏函数时,可以像调用普通函数一样使用宏函数调用时,会在编译时被展开为替换代码。

宏函数的替换规则:

宏函数替换代码中的参数和其他文本。在替换过程中,参数会被实际传入的值所替换,而其他文本则保持不变。

注意事项:

1.宏函数通常用于简单的替换和代码重用,但它们可能会导致一些问题,如副作用、>不易调试等。因此,应谨慎使用。
2.在定义宏函数时,通常使用括号将参数括起来,以避免优先级问题。
3.替换代码通常不需要分号,因为它会与调用宏函数的分号合并。

示例:

#include <stdio.h>

#define SQUARE(x) ((x) * (x))

int main() {
    int num = 5;
    printf("Square of %d is %d\n", num, SQUARE(num));
    return 0;
}

在这里插入图片描述

mov 指令用于将一个值移动到寄存器中。在这里,mov eax,dword ptr [num] 将变量 num 的值移动到寄存器 eax 中。imul 指令用于执行整数乘法。在这里,imul eax,dword ptr [num] 用于计算 eax * eax,即 num * num。push 指令用于将一个值压入栈中。在这里,push eax 和 push ecx 分别将 num * num 和 num 的值压入栈中。我们看到是直接进行((x) * (x))运算

inline与宏函数的比较:

类型安全性:
inline 函数在编译时会进行类型检查,因此它们提供了类型安全性。
宏函数在编译时只是简单地进行文本替换,不进行类型检查,因此它们不提供类型安全性。
可读性和调试性:
inline 函数通常比较容易阅读和调试,因为它们在代码中以函数的形式存在。
宏函数在代码中进行文本替换,可能会使代码变得难以理解和调试。
参数求值次数:
inline 函数中的参数在调用时只会被求值一次,与普通函数类似。
宏函数中的参数会在每次使用时进行文本替换,可能会导致参数被多次求值。
作用域和命名空间:
inline 函数像普通函数一样,具有自己的作用域和命名空间,不会污染全局命名空间。
宏函数在文本替换时直接将替换内容插入到调用位置,可能会导致命名冲突和意外的副作用。
代码大小:
inline 函数会在每个调用点复制其代码,可能会增加可执行文件的大小。
宏函数在编译时进行文本替换,不会增加可执行文件的大小。
编译器优化:
inline 函数的优化可能会受到编译器的限制,有些编译器可能会选择不将其内联。
宏函数通常可以强制进行代码展开,因为它们不受编译器的限制。

auto关键字:

作用:

使用auto定义变量时必须对其进行初始化,编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”, 编译器在编译期会将auto替换为变量实际的类型。

接下来的使用场景大家就看看,只是举例:

声明变量:使用 auto 关键字声明变量时,编译器会根据初始化表达式推断出变量的类型。例如:

auto x = 5;      // x 的类型被推断为 int
auto y = 3.14;   // y 的类型被推断为 double
auto z = "Hello"; // z 的类型被推断为 const char*

遍历容器元素:在 C++11 中引入了基于范围的 for 循环,auto 可以与其一起使用,自动推断容器中元素的类型。(后面具体讲)例如:

std::vector<int> numbers = {1, 2, 3, 4, 5};
for (auto num : numbers) {
    // num 的类型被推断为 int
    std::cout << num << std::endl;
}

声明函数返回类型:C++14 中引入了函数返回类型的自动推断,可以使用 auto 来声明函数的返回类型。(不推荐,实际返回值建议由自己的想法来)例如:

auto add(int x, int y) {
    return x + y; // 返回类型被推断为 int
}

在这里插入图片描述

建议自己控制返回值。

**与模板结合使用:**在模板编程中,auto 可以与模板结合使用,以简化代码并提高灵活性。(后面详细讲)例如:

template<typename T, typename U>
auto multiply(T x, U y) {
    return x * y; // 返回类型被推断为 T
}

注意事项:

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

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

#include <stdio.h>
#include<iostream>
using namespace std;
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;
}

在这里插入图片描述

auto在同一行定义多个变量:

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

在这里插入图片描述
在这里插入图片描述

auto不能作为函数的参数:

auto不能作为形参类型,因为编译器无法对a,b的实际类型进行推导

在这里插入图片描述
在这里插入图片描述

auto不能直接用来声明数组:

在 C++ 中,auto 关键字用于进行类型推断,让编译器根据初始化表达式推断变量的类型。然而,数组在 C++ 中是一种比较特殊的数据类型,其类型信息包含了元素类型 以及 数组的大小, 这使得数组的类型推断相对复杂,不能直接使用 auto 关键字来声明数组。

在这里插入图片描述
在这里插入图片描述

nullptr:

出现原因:

在 C++11 中引入了 nullptr 关键字,用于表示空指针常量。nullptr 的作用主要是解决了 C++ 中使用 NULL 或 0 表示空指针时可能出现的二义性和类型安全性问题。

在早期版本的 C++ 中空指针常常使用 NULL 或 0 来表示,但这种做法可能导致一些二义性,因为 0 同时也可以表示整数值。而 nullptr 是一个明确的空指针常量,可以消除指针和整数之间的二义性。

NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码

在这里插入图片描述

可以看到,NULL可能被定义为字面常量0, 或者被定义为无类型指针(void*)的常量。

#include <stdio.h>
#include<iostream>
using namespace std;
void f(int)
{
    cout << "f(int)" << endl;
}

void f(int*)
{
    cout << "f(int*)" << endl;
}

int main()
{
    f(0);
    f(NULL);
    f(nullptr);

    return 0;
}

在这里插入图片描述

我们传NULL本来是想调用f(int )的,但是却走的f(int);
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void)常量
但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转

在这里插入图片描述

注意:

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
  2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
  • 25
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值