深入理解内联函数+auto+范围for+nullptr

在这里插入图片描述

一、内联函数

调用函数需要建立栈帧,栈帧中要保存寄存器,结束后就要恢复,这其中都是有 消耗 的

int add(int x, int y)
{
    
    
	int ret = x + y;
	return ret;
}

int main()
{
    
    
	add(2, 2);
	add(3, 2);
	add(4, 2);
	add(5, 2);
	add(1, 2);

	return 0;
}

然而对于C语言来说,针对 频繁调用 的 小函数,可以用 宏 优化

因为宏是在预处理阶段完成替换的,并没有执行时的开销

对于上面的add函数来说,用宏的话可以这样子

#define ADD(x, y) ((x) + (y)) 

int main()
{
    
    
	cout << ADD(1, 2) << endl;
	cout << ADD(1, 2) << endl
	return 0;
}

但是宏也有缺点:
1.不能调试
2. 没有类型安全的检查
3.有些场景下非常复杂
而且宏的定义很容易写错
可能一不小心就会写成 #define ADD(x + y) x + y 的样子;=所以写宏时出错,要么是替换出错,要么是因为优先级出错

所以C++当中引入内联函数

1.内联函数的定义

内联函数是在编译时将函数的代码插入到调用点的地方,而不是通过函数调用的方式执行,这样可以减少函数调用的开销

内联函数通常适用于函数体较小、频繁调用的情况,不适用于递归函数,函数体大的情况

使用 inline 关键字可以将函数声明为内联函数

内联函数的地址不会进入符号表

准确来说内联函数没有地址

2. 内联函数的声明和定义

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

int main() {
    
    
    int x = 5;
    int y = 3;
    
    int result = add(x, y);
    
   cout << "Result: " << result << endl;
    
    return 0;
}

3.特性

  1. inline 是一种以 “空间换时间” 的优化策略。它建议编译器将函数展开为内联函数,在编译阶段用函数体替换函数调用,以减少函数调用开销,提高程序运行效率。

  2. 编译器对 inline 只是一个建议,具体实现机制可能因编译器而异。不同的编译器可能采用不同的策略来决定哪些函数应该被展开为内联函数。

  3. 使用 inline 修饰函数有优点和缺点。优点是可以减少函数调用开销,提高程序的执行效率。缺点是可能会使目标文件变大,因为函数被展开在每个调用点处。因此,需要权衡是否使用 inline 根据具体情况进行选择。
    在这里插入图片描述
    4.inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到

4. 内联函数的注意点

  1. 内联函数通常将定义和声明放在一起更好,因为内联函数没有地址,如果只有声明而没有定义,调用内联函数时会找不到定义从而导致错误,所以应该在头文件中同时包含内联函数的声明和定义
  2. 如果将内联函数的声明放在头文件中,定义放在其他源文件中,调用内联函数时可能会找不到定义,导致编译错误。
  3. 对于内联函数是否在调用点展开是由编译器决定的。即使将函数声明为内联函数,也无法确保它一定会被展开。编译器会根据函数体的复杂度、调用频率等因素进行优化,决定是否展开内联函数。
  4. 内联函数的展开与编译器决策相关,我们可以使用 inline 关键字来建议编译器将函数展开为内联函数,但实际的决策权在编译器手中。

结论:简短,频繁调用的小函数建议定义成 inline .

二、范围for

范围for循环是C++11引入的一种新的循环语法,用于简化对容器、数组等可迭代对象的遍历操作。它提供了一种更加简洁、易读的方式来进行元素的迭代访问

1. 基本语法

范围for循环的基本语法如下:

for (auto element : iterable) {
    
    
    // 循环体
}

其中,element 是迭代过程中当前元素的副本,iterable 是一个可迭代对象,例如容器或数组。

2. 遍历容器

范围for循环可以很方便地遍历各种容器,例如vectorlist等。我们不再需要使用迭代器或者索引来进行遍历,而是直接使用范围for循环:

vector<int> nums = {
    
    1, 2, 3, 4, 5};

for (auto num : nums) {
    
    
    cout << num << " ";
}

// 输出:1 2 3 4 5

3. const和引用

通过使用const关键字,范围for循环还可以实现只读遍历,保护容器中的数据不被修改:

vector<int> nums = {
    
    1, 2, 3, 4, 5};

for (const auto& num : nums) {
    
    
    cout << num << " ";
}

// 输出:1 2 3 4 5

上述代码中,num被声明为const auto&,表示对容器中的元素进行只读访问。

4. 数组的遍历

范围for循环同样适用于数组的遍历,使得代码更加简洁:

int arr[] = {
    
    1, 2, 3, 4, 5};

for (auto e : arr) {
    
    
    cout << e << " ";
}

// 输出:1 2 3 4 5

5. 自定义类型的遍历

对于自定义类型,只需要提供begin()end()成员函数或者非成员函数的重载,就可以使用范围for循环进行遍历。例如:

class MyContainer {
    
    
public:
    int* begin()
    {
    
     
    return &data[0];
    }
    int* end() 
    {
    
     
    return &data[size]; 
    }

private:
    int data[5] = {
    
    1, 2, 3, 4, 5};
};

MyContainer container;

for (auto e : container) {
    
    
    cout << e << " ";
}

// 输出:1 2 3 4 5

上述代码中,MyContainer类提供了begin()end()函数,使得对象可以像容器一样使用范围for循环遍历。

范围for循环是C++中一个强大而又简洁的特性,可以极大地简化迭代操作的代码,提高代码的可读性

6.使用条件

for循环迭代的范围必须是确定的

对于数组的范围就是数组中第一个元素和最后一个元素的范围;

对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。

函数传参,数组就会退化为指针

#include<iostream>
using namespace std;
void TestFor(int array[])
{
    
    
    for (auto& e : array)
    {
    
    
        cout << e << endl;
    }
}

int main()
{
    
    
	return 0;
}

在这里插入图片描述

三、auto关键字

1. 自动类型推断

使用auto关键字可以自动推断变量的类型,无需显式指定类型。编译器会根据变量的初始化表达式来推断其类型。

auto num = 10; // 推断num的类型为int
auto name = "John"; // 推断name的类型为const char*
auto prices = {
    
     1.99, 3.49, 2.99 }; // 推断prices的类型为std::initializer_list<double>

使用auto关键字可以简化代码,减少类型的重复书写,提高代码的可读性。

typeid 可以看对象类型,用法为 typeid(c).name() ,由此可以打印变量的类型在这里插入图片描述

2. 函数返回值类型推断

使用auto关键字可以让编译器推断函数的返回值类型。这对于返回复杂类型或涉及泛型编程的函数特别有用。

auto add(int a, int b) {
    
    
    return a + b;
}

auto compute() {
    
    
    // 复杂类型的计算逻辑
    return result;
}

在这里,编译器会根据实际返回的值来推断函数add和compute的返回值类型。
在这里插入图片描述

4.auto注意

1.auto不能独立定义
在这里插入图片描述

2.auto不能定义数组
在这里插入图片描述

3.auto不能接受函数参数

因为编译器无法推导 auto 的类型,没有根据。

但是在 C++11 后的版本中,auto 可以作为参数
在这里插入图片描述

四、nullptr(C++11)

对于 c 来说,空指针为 NULL,是一个宏。

在 C++98/03 时,只能使用 NULL ;而 C++11 后,推荐使用 nullptr 。

NULL(stddef.h):

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

实际上 NULL 就是个宏,所以说·写成 int* p = 0· ,也可以

但是特殊情况

#include<iostream>
using namespace std;

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

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

int main()
{
    
    
	f(0);
	f(NULL);
	return 0;
}


在这里插入图片描述

将NULL换为nullptr
在这里插入图片描述

五、总结

本次主要概括了内联函数的特性,auto关键字的运用,范围for的运用,nullptr的补坑

猜你喜欢

转载自blog.csdn.net/2201_76062618/article/details/132012475