前言
在前文我们讲解了C++的诞生与历史,顺便讲解一些C++的小语法,本文会继续讲解C++的基础语法知识。
1.inline(内联函数)
inline
是C++新加入的关键字,用inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方将函数展开,这样每次调用内联函数就不需要建立新的栈帧,就可以提高效率
#include<iostream>
using namespace std;
inline int Add(int& a, int& b)
{
int ret = a + b;
return ret;
}
int Sub(int& a, int& b)
{
int ret = a - b;
return ret;
}
int main()
{
int a = 10;
int b = 5;
// 可以通过汇编观察程序是否展开
// 有call Add语句就是没有展开,没有就是展开了
int ret = Add(a, b);
cout << ret << endl;
int tmp = Sub(a, b);
cout << tmp << endl;
return 0;
}
只要在有call一个地址就是没有展开。
我们看到不管是有inline修饰的函数,还是没有inline修饰的函数,他们都有在call一个地址(也就是没有展开函数),那不就和inline会展开函数的定义相悖吗。
其实是因为我使用的是VS编译器,VS编译器在debug版本下默认是不展开inline函数的,这样是为了方便调试,如果我们在VS中想要展开,那么就要设置一下两个地方。
修改了这些之后,我们就可以看到inline的展开了
- 但是我们想要知道的是,inline对于编译器而言只是一个建议,也就是说,即使你在函数前加了inline,编译器也有可能不展开,不同编译器关于inline在什么情况下展开各不相同,因为C++标准并没有规定,所以inline适合的场景是使用频繁的短小函数,对于递归函数、代码比较多的函数,即使加上了inline,编译器也不会展开。
为什么要这样做呢,因为是防止有些程序员不靠谱,在每个函数前都加上inline,如果都展开,那么这个代码量就太大了。
假设我有一个一百行代码的函数a
,并且我在函数前加上了inline,这时,我调用了1万次a
,如果编译器在每次调用a
的时候就展开,那么这个工程编译处理下来,至少要执行
100
∗
10000
100*10000
100∗10000(一百万)条可执行程序,这样效率就会下降。
如果不展开,那么编译处理下来,就只要执行 10000 + 100 10000+100 10000+100(call一百次)条可执行程序,比起前面的一万行,这时不展开的效率就会高很多。
//该函数在编译时就不会展开了
inline int Add(int& a, int& b)
{
int ret = a + b;
ret += 1;
ret += 1;
ret += 1;
ret += 1;
ret += 1;
ret += 1;
ret += 1;
return ret;
}
int main()
{
int a = 10;
int b = 5;
// 可以通过汇编观察程序是否展开
// 有call Add语句就是没有展开,没有就是展开了
//inline修饰的函数
int ret = Add(a, b);
//cout << ret << endl;
return 0;
}
-
C++设计inline的目的是为了替代C的宏函数,虽然C语言的宏函数也会在预处理的时候替换展开,但是宏函数实现很复杂很容易出错,并且不好调试。
-
inline不建议声明和定义分离到两个文件,分离会导致链接错误;因为inline被展开,就没有了函数地址,那么链接时就会报错。
//Func.h
#pragma once
#include<iostream>
using namespace std;
inline int Add(int& a, int& b);
//Func.cpp
#include"Func.h"
int Add(int& a, int& b)
{
int ret = a + b;
return ret;
}
//test.cpp
#include"Func.h"
int main()
{
int a = 10;
int b = 5;
int ret = Add(a, b);
}
2.nullptr
在C语言表述一个函数为空函数是使用NULL,但NULL其实是一个宏函数,在C头文件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
这段代码的意思是,如果是在C语言环境下,那么NULL就会被定义成((void) * 0)
,如果是在C++环境下,那么就直接将NULL定义为0。无论采取何种定义,在使用空值的指针时,都不可避免的遇到一些问题,例如我想通过f(NULL)
来调用指针版的f(int*)
函数,但由于NULL被定义成了0,那么就会调用f(int x)
,这样就会于程序的目的相悖
#include<iostream>
#include<stdlib.h>
using namespace std;
void F(int x)
{
cout << "void F(int x)" << endl;
}
void F(int* x)
{
cout << "void F(int* x)" << endl;
}
int main()
{
F(0);
F(NULL);
return 0;
}
并且我们调用f((void*)NULL)
时也会报错,因为C++检查的更严格,C++环境下,void*不会自动转换成对于的类型*,如果要转换,就必须使用强制类型转换(C语言会自动转换)。
既然NULL在定义上已经不是一个指针了,那么我们就需要一个真正意义上的空指针。
那么C++11就引入了nullptr,nullptr是一个特殊的关键字,是一种特殊类型的字面量,他可以转换成任意类型的指针类型。使用nullptr定义空指针也可以避免类型转换的问题,因为nullptr只能被隐式转换为指针类型,不能被上转换成其他类型。
#include<iostream>
#include<stdlib.h>
using namespace std;
void F(int x)
{
cout << "void F(int x)" << endl;
}
void F(int* x)
{
cout << "void F(int* x)" << endl;
}
int main()
{
F(0);
F(NULL);
//F((void*) NULL);
F(nullptr);
int* a = nullptr;
double* b = nullptr;
//int x = nullptr;
return 0;
}
结语
本文进一步讲解了c++基础知识,讲解了关键词inline的使用,如何在VS下看到以及他的目的和角色;讲解了nullptr的诞生原因以及使用。
最后感谢您能阅读完此片文章,如果有任何建议或纠正欢迎在评论区留言,也可以前往我的主页看更多好文哦(点击此处跳转到主页)。
如果您认为这篇文章对您有所收获,点一个小小的赞就是我创作的巨大动力,谢谢!!!