当前位置: 首页 > article >正文

CD8.【C++ Dev】auto、范围for、内联函数和nullptr

目录

1.auto和范围for的知识回顾

2.auto的注意事项

1.无表达式无法做自动推导

2.返回表达式类型的函数typeid

3.使用auto在同一行定义多个变量,这些变量的类型必须相同

4.auto无法推导的情况

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

6.对比auto、auto*和auto&

3.范围for的注意事项

4.内联函数

为什么会引入内联函数?

回想调用一般函数的缺点

C语言的解决方法:宏

C++的解决方法:内联函数

内联函数的例子

反汇编分析

先做以下配置,准备查看内联函数

main函数部分

内联函数的5点提醒

5.NULL和nullptr

使用C++编译器测试NULL

使用C++编译器测试nullptr

结论


1.auto和范围for的知识回顾

参见C7.【C++ Cont】范围for的使用和auto关键字文章

auto和范围for的知识点大部分讲过,下面提醒一些注意事项

2.auto的注意事项

1.无表达式无法做自动推导

auto的格式为:auto 变量 = 表达式;(表达式不可省略)

2.返回表达式类型的函数typeid

返回表达式类型的名称使用typeid的name()接口,使用方式:typeid(表达式).name()

#include <iostream>
using namespace std;
int main()
{
    auto i = 4;
    cout << typeid(i).name() << endl;
    return 0;
}

 运行结果

3.使用auto在同一行定义多个变量,这些变量的类型必须相同

正确写法示例:

auto a = 1, b = 2;

注意:编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量

例如上方代码的a和b,编译器只对a推导,推导出的类型用来定义变量b

错误写法示例:

auto c = 'c', d = 3.14;

4.auto无法推导的情况

1.不可作为函数的参数

2.不可直接声明数组

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

6.对比auto、auto*和auto&

auto:一般情况下的推导

auto*:推导的类型必须为指针

auto&:推导的类型必须为别名

错误写法示例:

auto* p = 1;//错误
auto& p = 123;//错误,123是字面量,无法修改

正确写法示例:

const auto& p = 123;//正确,123为字面量,无法修改,需要const修饰
auto* pm = &main;//取出main函数的地址

3.范围for的注意事项

范围for的迭代范围必须是确定的,即有begin()和end()方法

像下面的写法就是错误的

void function(int* p)
{
    for (auto a : p)
    {
        //do_something
    }
}

int main()
{
    int arr[] = { 1,2,3,4 };

    return 0;
}

function传的参数是指针,没有begin()和end()方法,因此范围for无法使用

4.内联函数

为什么会引入内联函数?

回想调用一般函数的缺点

调用一般函数需要开辟栈帧空间,保存寄存器的状态,使用参数的值等等,准备工作较多,如果频繁调用会消耗大量的时间,而且占用过多的资源

C语言的解决方法:宏

使用宏则直接按照替换规则进行(复习宏的知识参见18.【C语言】初识#define定义常量和宏文章)

虽然宏不用建立栈帧,提高调用效率,但宏的明显缺陷是:容易写错且复杂,例如某项目GitHub Cuik/tb/hash.c下的部分代码,非常容易写错

// leftrotate function definition
#define LEFTROTATE(x, c) (((x) << (c)) | ((x) >> (32 - (c))))

C++的解决方法:内联函数

内联函数的例子
inline int add(int a,int b)
{
    return a + b;
}

 和普通函数的区别是:1.内联函数多了inline关键字 2.编译器在编译阶段会展开内联函数(即嵌入代码,不需要像普通函数那样进行繁琐的准备工作)

反汇编分析

运行环境:release(debug为调试功能,编译器默认不展开内联函数)

先做以下配置,准备查看内联函数

在项目的属性页中更改以下选项:

main函数部分
int main()
{
    int ret = add(2, 3);
    return 0;
}

反汇编结果:(画框的为add内联函数)

结论:内联函数是优于宏的

内联函数的5点提醒

1.内联函数适用于短小的频繁调用的函数,如果过大会导致代码膨胀

例如某项目中有10000处调用为20行的内联函数,经过编译器展开后(其实编译器根本不会展开)行数增加了10000*20=2*10^5行,如果采用普通函数调用,仅仅增加(10000+20)行

2.由1推出:过长的函数不会内联

例如:提高add函数的代码长度

inline int add(int a,int b)
{
    cout << "123" << endl;
    cout << "123" << endl;
    cout << "123" << endl;
    cout << "123" << endl;
    cout << "123" << endl;
    cout << "123" << endl;
    cout << "123" << endl;
    cout << "123" << endl;
    return a + b;
}

反汇编的结果出现call意味着没有内联,是调用函数

3.递归函数不会内联

例如以下代码:

#include <iostream>
using namespace std;
inline long long f(size_t n)
{
    if (n < 3)
        return 1;

    return f(n - 1) + f(n - 2);
}

int main()
{
    int ret=f(5);
    return 0;
}

反汇编的结果出现call意味着没有内联,是调用函数

4.由1~3可以推出:inline对于编译器仅仅只是一个建议,最终是否成为inline,由编译器自己决定

5.inline不建议声明和定义分离,无法展开内联函数

例如以下代码:

main.cpp写入:

#include "cppheader.h"
using namespace std;
int main()
{
    int ret = function(1, 2);
    return 0;
}

cppheader.h写入:

#pragma once
inline int function(int a, int b);

function.cpp写入:

inline int function(int a, int b)
{
	return a + b;
}

编译器报的是链接错误:

原因:当内联函数的声明和定义分离,头文件被展开时没有找到定义,因此不能链接

可以在Linux看一下细节:虽然能生成test.i,但并没有包含function的定义

将main.i编译为main.s文件会报错

解决方法:将function函数的定义写在cppheader.h中,直接展开,无链接问题

面试题:宏的优缺点

优点
1.增强代码的复用性
2.提高性能
缺点:
1.不方便调试宏(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用
3.没有类型安全的检查
C++有哪些技术替代宏?
1. 常量定义换用const enum
2. 短小函数定义换用内联函数

5.NULL和nullptr

NULL的知识回顾:之前在C语言中讲过NULL的含义:代表值为0的void*的指针

具体可以通过如下方法查看:

NULL的定义在stdio.h中,右击NULL后转到定义

看到如下代码:

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

复习:在下面文章中讲过#ifndef、#define、#else和#endif的含义以及怎样阅读

93.【C语言】解析预处理(1)

94.【C语言】解析预处理(2)

95.【C语言】解析预处理(3)

96.【C语言】解析预处理(4)

这里直接解释含义:对于NULL,如果是C++,NULL的含义为数字0,如果为C语言,NULL的含义为((void *)0),即类型为void*的空指针

使用C++编译器测试NULL

#include <iostream>
using namespace std;
void function(int)//可不需要参数的名称,下面并没有使用接收到的参数
{
    cout << "call function(int)" << endl;
}

void function(int*)//可不需要参数的名称,下面并没有使用接收到的参数
{
    cout << "call function(int*)" << endl;
}

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

运行结果验证了上述的说法,函数重载过后,function(int*)没有调用,C++下NULL等同于数字0

不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,如果在C++使用NULL来表示空指针必须强制类型转换,如下:

int main()
{
    function(0);
    function((int*)(NULL));//强制类型转换
    return 0;
}

运行结果:

为了解决这一问题,C++11引入了新关键字(不需要包含头文件)nullptr来表示空指针,sizeof(nullptr)==sizeof((void*)0)

使用C++编译器测试nullptr

#include <iostream>
using namespace std;
void function(int)
{
    cout << "call function(int)" << endl;
}

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

int main()
{
    function(0);
    function(nullptr);
    return 0;
}

运行结果:

结论

为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr


http://www.kler.cn/a/565137.html

相关文章:

  • Linux上用C++和GCC开发程序实现不同PostgreSQL实例下单个数据库的多个Schema之间的稳定高效的数据迁移
  • CSS默认样式
  • 吃一堑长一智
  • 智能生活综合平台需求规格说明书
  • 通过命令启动steam的游戏
  • vue3+naiveUI开关switch
  • PHP实现国密SM4算法,银行系统加密算法,JAVA和PHP可相互转换(附完整源码)
  • vue3.2 + vxe-table4.x 实现多层级结构的 合并、 展开、收起 功能
  • 嵌入式Qt的动平衡仪完整设计方案
  • 大模型的工作原理:分布式训练入门
  • 【FL0088】基于SSM和微信小程序的奶茶点餐小程序
  • Linux的部分常用基础指令
  • Windows AD组策略完整实战文档 | 企业级安全配置指南
  • 35. Spring Boot 2.1.3.RELEASE 应用监控【监控信息可视化】
  • STM32内存五区及堆栈空间大小设置(启动文件浅析)
  • Island架构与部分水合技术解析:下一代Web应用的性能突围
  • 基于C语言模拟一个简易的冯诺依曼式计算机CPU的工作(bupt计导)
  • 无人机的最长悬停时间为什么短于最长飞行时间
  • 蓝桥杯 Java B 组之记忆化搜索(滑雪问题、斐波那契数列)
  • Redis哨兵集群搭建