1. 内联函数
1.1. 宏的介绍
在介绍内联函数之前,我们先说一下为什么c++中会有这个东西。
- 宏
在c语言中,有宏这个概念
#define ADD(x,y) ((x)+(y))
int Add(int x, int y)
{
return x+y;
}
宏函数相较于一般函数,有以下优点:
- 没有严格类型的检查
- 提高效率,针对频繁调用的小函数,不需要建立函数栈帧(主要)
但是宏对于使用者的体验来说就不是很好
缺点:
- 容易出错,语法点多
写函数不容易出错,但是写宏很容易出错
#define ADD(int x,int y) {return x+y;}
#define ADD(x,y) (return x+y;)
#define ADD(x,y) return x+y;
#define ADD(x,y) x+y;
#define ADD(x,y) x+y;
#define ADD(x+y) (x+y)
上面的写法都是有问题的,最后一个看起来好像正确 ,但是对 x 和 y 没有括号的约束,很容易出现运算顺序的错误。
2. 不能调试
宏会在预编译阶段在函数中进行替换,而 vs 在调试的时候,不会展示预编译的操作。
3. 没有安全类型的检查
int main()
{
int a = 1, b = 2;
ADD( a|b , a&b );
return 0;
}
这里的位运算传入宏函数中,如果是(x+y),x和y都没有括号的限制,最后运算就会改变,“+”的运算的等级高于 “|” 和 “&”。
1.2. 内联函数的使用
因为宏函数的缺点,祖师爷在创建c++的时候引入了内联函数的概念。
关键字: inine
在定义函数前,加上上面的关键字就可以了。
#include<iostream>
inline int add(int x,int y)
{
return x+y;
}
int main()
{
int ret=add(1,2);
return 0;
}
使用方式很简单,就是加上 inline 这个关键字。
一般函数,在程序预编译的链接环节,会用用函数的地址调用函数
在运行的过程中,打开反汇编会有这句 “call”,就说明了程序通过调用函数来使用函数(debug版本下函数会默认调用,而不是在函数内展开)
但是在我们使用 release 版本,通过反汇编
属性---优化---内联函数拓展---只适用于 ——inline(/ob1)
属性---常规---调试信息格式---程序数据库(/Zi)
如果还是没有显示出来可以通过上面的步骤设置。
在这里,我们可以看见,虽然我们调用了这个函数,但是并没有通过地址寻找这个函数,而是直接在主函数内部展开进行操作。
- 这样的方法继承了宏 的优点 不需要建立函数栈帧的优点,能提高程序的效率。
- 而且写出来是函数的样子,大大降低了出错率。
1.3. 内联函数注意事项
使用内联函数,不是说什么时候都可以使用,还需要注意以下几点:
1. 内联函数是由编译器决定的
#include<iostream>
inline namespcae std;
inline void f()
{
int a=10;
a=20;
aa=30;
a=40;
a=50;
a=60;
a=70;
cout<<a<<endl;
}
int main()
{
f();
return 0;
}
在运行这段代码的时候,我们查看反汇编
此时,会发现虽然我们定义的是内联函数,但是程序还是去找了函数的地址。
这是因为虽然定义的时候设置的是内联函数,但是它能不能当内联函数还是要看编译器,而不是你。
就像是你递交了一份简历,决定你能不能进公司的不是你有没有交简历这件事,而是看公司答不答应。
为什么编译器会这样搞呢?
我们可以分析一下代码数量:
我定义了一个内联函数 Func() 100行
在程序中 调用了 100次这个函数
- 如果编译器没有自动识别的操作
最后程序会多出: 10000行代码。
内联函数会在调用的地方展开,展开后会加入这么多的代码,会造成代码膨胀。
所以程序一般会默认通过内容只有几行(2,3行)的代码作为内联函数 - 如果程序有这个功能
最后对多出: 200行
100行是定义这个函数的行数,另外的100行是程序在运行的过程中,会自动生成调用函数的语句,也就是每调用一次生成一句。
2. 内联函数的声明和定义不能分离在两个文件中
Func.h
#pragma once
#include<iostream>
using std::cout;
using std::endl;
inline void f(int i);
Func.cpp
#include"Func.h"
inline void f(int i)
{
cout << i << endl;
}
test.cpp
#include"Func.h"
int main()
{
int ret = add(2, 3);
printf("%d ", ret);
f(1);
return 0;
}
在上面的三个文件中,我们分别将内联函数 f 的声明和定义分别放入两个文件中。此时我们检查错误
LNK2019 是预编译过程中链接过程的错误
为什么会出现这样的错误?
首先,我们知道,c++在预编译过程中,会在test.cpp中展开头文件,但是头文件中只有函数的声明,test.cpp中只有函数的调用,但是能通过编译汇编环节。
而test.cpp中没有定义,就回去其他文件中找,也就是在链接过程中区寻找函数,但是是通过函数修饰名去寻找地址,而 内联函数在符号表里没有地址,也就是找不到调用的函数,所以在链接的时候无法找到内联函数。
但是如果我们在 Func.cpp 中调用函数,就会通过
因为在链接过程中,头文件展开有函数的声明,Func.cpp有函数的定义和调用,也就是说在 Func.cpp中会直接找到函数,可以运行。
所以为了避免出现上面的错误,我们可以在定义内联函数的时候,将内联函数的声明和定义都写在头文件中。
Func.h
#include<iostream>
using std::cout;
using std::endl;
inline void f(int i)
{
cout << i << endl;
}
void fx();
test.cpp
#include"Func.h"
int main()
{
int ret = add(2, 3);
printf("%d ", ret);
fx();
f(1);
return 0;
}
这样的话,不管是哪个文件使用该头文件,展开后都会有函数的声明,定义,调用,就不会 出现上面的情况。
一般我们用
- inline 内联函数来代替宏函数
- const enum 枚举来替换宏常量
2. auto关键字
2.1. auto的使用介绍
作用:根据右边的值,自动推导左边值的类型
int main()
{
int a = 0;
auto b = 1 ;
auto c = &a;
auto& d = a;
return 0;
}
普通场景下没有什么价值
对于类型很长,就有价值,可以起到简化代码的作用
- auto 不能作为函数的参数
- auto不能直接用来声明函数
typdeif().name()
显示类型
#include<iostream>
using namespace std;
int main()
{
int a = 10;
auto c = &a;
auto& b = a;
cout<<typdeif(c).name()<<endl;
cout<<typdeif(b).name()<<endl;
return 0;
}
2.2. 范围for的使用
作用:自动输出数组中的内容
#include<iostream>
using namespace std;
int main()
{
int array[]={ 2, 4, 6, 8, 10};
for(auto e:array)
{
cout<<e<<" ";
}
cout<<endl;
for(auto e: array)
{
e*=2;
}
return 0;
}
注意:
- 会一次一次取原数组中的数据,赋值给e
- 会自动判断结束,自动迭代
- 这里的 e 只是数组中数据的拷贝,改变 e 的值原数组中的值不会改变
- 如果想要改变数组中的值,需要引用
- 这里加上 auto 就不需要判断数据类型,如果里面是 int e,数组是double 类型还需要换类型,麻烦。
for(auto& e: array)
{
e*2=2;
}
同时注意
- 语法固定范围for每次只会取数组中的值,也就是直接把数字的值赋给e,直接使用指针接受非指针的值,所以这里不允许使用指针
- 数组名传入函数后,不会代表整个数组,运行会报错
3. 指针空值
c++中对 NULL 定义,不是 c 语言中的指针类型,而是直接定义为 0
c++中 NULL 的定义
#define NULL 0;
c++中的 NULL 是作为宏定义的
我们可以测试一下:
#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((int*)NULL);
return 0;
}
NULL 一般作为指针空值,但是当我们将 NULL 作为参数传入,它会进入参数 int 为类型的函数,也就是说c++中默认 NULL 是0;
如果我们队 NULL 强制类型转换后,NULL 才会进入参数为 int* 类型的函数。
在 c++ 中,后序的更新,c++不敢将 NULL 的宏定义修改,有些人的程序可能将 NULL 作为空值,修改后会导致一些人的程序出现问题,所以就引入了 nullptr 在c++中作为指针空值
所以在c++中,我们使用 nullptr 来表示空指针
nullptr 类型(void*)
使用时,需注意:
- 使用nullptr 时,不需要包含头文件,nullptr 是c++11 作为新关键字引入的。
- 在 c++11 中,sizeof(nullprt)与size((void*)0) 所占的字节数相同。
- 为了提高代码以后不出问题,最好在使用指针空值的时候使用 nullptr。