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