前言
在前文我们讲解了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的诞生原因以及使用。
最后感谢您能阅读完此片文章,如果有任何建议或纠正欢迎在评论区留言,也可以前往我的主页看更多好文哦(点击此处跳转到主页)。
如果您认为这篇文章对您有所收获,点一个小小的赞就是我创作的巨大动力,谢谢!!!