【C++要笑着学】内联函数 inline | auto关键字(C++11) | 范围for | 关键字 nullptr(一)

简介: 本章将继续讲解C++入门部分的知识,将对内联函数、改版后的auto关键字、范围for,以及指针空值nullptr 等知识点进行讲解。

💭 写在前面


本章将继续讲解C++入门部分的知识,将对内联函数、改版后的auto关键字、范围for,以及指针空值nullptr 等知识点进行讲解。


Ⅰ.  内联函数

0x00 问题引入

调用函数,需要建立栈帧,栈帧中要保留一些寄存器,结束后又要恢复。


这就可以看出这些都是有消耗的,对于频繁调用的小函数,有没有方法可以优化呢?


💬 比如下面这个两数相加的函数:


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

💡 我们可以写一个两数相加的宏:


#include<iostream>
using namespace std;
int Add(int x, int y) {
    int ret = x + y;
    return ret;
}
// 写一个两数相加的宏
#define ADD(X, Y) ((X) + (Y))
int main(void)
{
    cout << "函数: " << Add(1, 2) << endl;
    cout << "宏: " << ADD(1, 2) << endl;
    // 写宏的技巧:记住宏原理是替换,你替换一下看看对不对
    // cout << "M: " << ((1) + (2)) << endl;
    cout << "宏: " << 10 * ADD(3, 4) << endl;
    return 0;
}


🚩 运行结果如下:

5733c59a779f7c9e8e4bccc18cfd3572_30d9e33b4eae467d9a42eb386164a6ed.png



0x01 内联函数的概念

宏有时候用起来似乎比较复杂,也容易出错。我们可以使用C++中的内联函数来解决。


📚 概念:以 inline 修饰的函数叫做内联函数。


语法:inline 数据类型 [函数名]


💬 代码演示:


#include <iostream>
using namespace std;
inline int Add(int x, int y) {
    int ret = x + y;
    return ret;
}
int main(void)
{
    cout << "内联函数: " << Add(1, 2) << endl;
    return 0;
}

0x02 内联函数的特性

 内联函数的特性:以空间换时间,省去了调用函数的开销。


编译时 C++ 编译器会在调用内联函数的地方展开,是没有函数压栈的开销的。


(内联内敛,内部关联)


因为他会在编译的时候展开,所以代码很长。举个例子:


inline void func() {
    // 假设有10行代码
}

如果不展开,假设有1000个调用,编译后台就会有 10 + 1000 条指令。


如果展开,编译后台合计会有 10 * 1000 条指令,


所以,这是一场以空间换取时间的交易。


因为没有了函数压栈的开销,所以能提高程序运行的效率。


📌 注意事项:


① inline 既然是以空间换时间的做法,所以代码很长、循环或递归的函数不适宜成为内联函数。


②  inline 对于编译器而言只是一个建议,编译器会自动优化,如果定义为 inline 函数的函数体内油循环或递归等等,编译器优化时会忽略掉内联。


③ inline 申明和定义不建议分离。内联函数会认为在调用的地方展开,导致不生成地址。test.cpp -> test.o  符号表中不会生成 Func 函数的地址,因为 inline 函数是不需要地址的(都在调用的地方展开了还要个毛线地址)。我们来验证一下:


💬 Add.h:


#include <iostream>
using namespace std;
inline int Func(int x, int y);

💬 Add.cpp:


#define _CRT_SECURE_NO_WARNINGS 1
#include "Add.h"
int Func(int x, int y) {
  return x + y;
}

💬 test.cpp:


#include "Add.h"
int main(void) 
{
  Func(1, 2);
  return 0;
}

🚩 运行结果如下:

50955af52f0ccd9591ecac221af13b92_1d305306ec4b472daa07497394538784.png


0x03 浅谈宏的优缺点和替代方法


❓ 宏的优缺点是什么?


优点:


① 宏可以增强代码的复用性


② 宏有助于提高性能


缺点:


① 宏调试起来很不方便(因为宏在程序预编译阶段进行替换)。


② 宏的大量使用可能会导致代码的可读性差,可维护性差,容易误用。


③ 宏没有类型安全的检查。


有哪些技术可以替代宏?


① 常量定义:换用 const


② 函数定义:换用内联函数


Ⅱ.  auto关键字(C++11)


0x00 改版前的auto

改版前的 auto 指的是在早期 C/C++ 中 auto 关键字的含义。


📚 旧的含义:使用 auto 修饰的变量,是具有自动存储器的局部变量。


遗憾的是,大家都懒得去用它。这是为什么呢?


auto int a = 0;   // 表示a是一个自动存储类型,会在函数结束后自动销毁。

当使用 auto 修饰后,表示 a 是一个自动存储类型,它会在函数结束以后自动销毁。


但是因为后来 C 把标准给改了,不加是自动销毁:


int a = 0;   // 标准改了之后,不加也是自动销毁。

这么一来,这个 auto 关键字就没有意义了,因为都是自动销毁。


auto:这就尴尬了,我的存在没有意义了,用和不用都一样。


0x01 改版后的auto

C++标准委员会觉得这 auto 也太尴尬了,我们得给它来一波加强。


为了缓解 auto 的尴尬,C++ 标准委员会把 auto 原来的功能给废弃了。


并赋予了 auto 全新的含义!


📚 游戏更新补丁(bushi):auto 现在不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器。auto 声明的变量必须由编译器在编译时推导而得。


💬 也就是说,它可以自动推导出数据的类型:


int a = 0;
auto c = a;  // C++11给auto关键字赋予了新的意义:自动推导c的类型

你的右边是什么,它就会推导出相应的类型


任何类型都可以实现,包括但不限于:


auto ch = 'A';
auto e = 10.11;
auto pa = &a;

为了方便测试,我们来打印一下对象的类型看看:


#include<iostream>
using namespace std;
int main()
{
    int a = 0;
    auto c = a;  // 自动推导c的类型
    auto ch = 'A';
    auto e = 10.11;
    auto pa = &a;
    // typeid - 打印对象的类型
    cout << typeid(c).name() << endl;   // i
    cout << typeid(ch).name() << endl;  // c
    cout << typeid(e).name() << endl;   // d
    cout << typeid(pa).name() << endl;  // Pi
    return 0;
}


🚩 运行结果如下:

d184d1a3483cac020ed3917f235064ed_b256f241e15e4116b25a18ff1447573b.png

emmm... 确实


这时候可能有人会觉得,这一波操作好像也没啥意义啊,


直接写数据类型不香吗?  int c = a;


我们继续往下看~


0x02 auto 的使用场景

处理又臭又长的数据类型


💬 遇到这种场景,就能体会到 auto 的香了:


#include <iostream>
#include <map>
int main(void) 
{
    std::map<std::string, std::string> dict = {{"sort", "排序"}, {"insert", "插入"}};
    std::map<std::string, std::string>::iterator it = dict.begin();
    // 这个类型又臭又长,写起来太麻烦了。。。
    auto it = dict.begin();   // 可以改成这样,爽!
    // ↑ 根据右边的返回值去自动推导it的类型,写起来就方便多了
    return 0;
}

像遇到这种又臭又长的类型,而且还要经常使用,


这时候使用 auto 帮你自动推到类型,就很爽了!


auto 与指针结合起来使用:


📚 auto 非常聪明,它在推导的时候其实是非常灵活的:


int main(void)
{
    int x = 10;
    auto a = &x;  // int*
    auto* b = &x; // int*
    auto& c = x;  // int
    return 0;
}

在同一行定义多个变量


当在同一行声明多个变量时,这些变量必须是相同的类型!


否则编译器将会报错,因为编译器实际只对第一个类型进行推导,


然后用推导出来的类型定义其他变量。


auto a = 1, b = 2;
auto c = 3, d = 4.0;  ❌ 该行代码会编译失败,因为c和d的初始化表达式类型不同

0x03 使用auto的注意事项

注意!


使用 auto 是必须要给值的!


int i = 0;
auto j;  ❌
auto j = i;  必须给值!!

这就意味着,auto 是不能做参数的!


auto 不能作为函数的参数


void TestAuto(auto a); ❌

此处代码编译失败,auto 不能作为形参类型,因为编译器无法对a的类型进行推导!


auto 不能直接用来声明数组


auto b[3] = {4,5,6};   ❌

📌 为了避免与 C++98 中的 auto 发生混淆,C++11 只保留了 auto 作为类型指示符的用法。


auto 在实际中最常见的优势用法就是 C++11 提供的新式 for 循环,


还有 lambda 表达式等进行配合使用。我们可以继续往下看~


相关文章
|
1月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
1月前
|
存储 编译器 程序员
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(二)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
1月前
|
程序员 C++ 开发者
C++入门教程:掌握函数重载、引用与内联函数的概念
通过上述介绍和实例,我们可以看到,函数重载提供了多态性;引用提高了函数调用的效率和便捷性;内联函数则在保证代码清晰的同时,提高了程序的运行效率。掌握这些概念,对于初学者来说是非常重要的,它们是提升C++编程技能的基石。
21 0
|
3月前
|
安全 编译器 C++
C++入门 | 函数重载、引用、内联函数
C++入门 | 函数重载、引用、内联函数
33 5
|
2月前
|
C语言 C++
C++(三)内联函数
本文介绍了C++中的内联函数概念及其与宏函数的区别。通过对比宏函数和普通函数,展示了内联函数在提高程序执行效率方面的优势。同时,详细解释了如何在C++中声明内联函数以及其适用场景,并给出了示例代码。内联函数能够减少函数调用开销,但在使用时需谨慎评估其对代码体积的影响。
|
4月前
|
存储 编译器 C++
C++从遗忘到入门问题之float、double 和 long double 之间的主要区别是什么
C++从遗忘到入门问题之float、double 和 long double 之间的主要区别是什么
|
5天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
27 5
|
12天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
40 4
|
13天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
36 4
|
1月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
27 4