内联函数是什么,我们来一起看看吧!
在C语言中,我们通常会把完成特定功能的代码封装为一个函数,这样的函数可能完成者复杂的功能从而具有较多的代码长度,同时也有着许许多多的只完成简单功能的函数,这些函数内部通常只有几行代码。 比如: 完成交换功能的函数
//交换两个整数
void Swap(int* x, int* y) {
int tmp = *x;
*x = *y;
*y = tmp;
}
int main() {
int a = 10;
int b = 20;
Swap(&a, &b);
return 0;
}
完成两个整数相加的函数
//两个整数相加
int Add(int a, int b) {
return a + b;
}
int main() {
int a = 10;
int b = 20;
int ret = Add(a, b);
return 0;
}
我们知道,调用函数时会在栈区开辟栈帧空间,返回后函数栈帧销毁,所以存在着一定的且不可忽略的系统开销。 对于复杂或代码较多的函数我们只能选择调用函数,在C语言中一般不规避上述开销; 但是对于功能简单的函数,代码可能只有几行,并且经常被其他函数调用,我们其实是有方式来规避掉调用函数时的栈帧开销的。在C语言中是有着宏的,我们可以利用宏来定义宏函数来解决这个问题。 因为功能简单的函数代码一般只有几行,转换为宏函数的代码也只有几行,所以转换比较容易。
//宏 - 交换两个整数
#define SWAP(x, y) int tmp = x;\
x = y;\
y = tmp
//宏 - 两个整数相加
#define ADD(x, y) ((x) + (y))
宏定义之后,出现宏定义的地方都会在预处理阶段被直接替换,相当于在出现宏定义的地方展开。 优点:
可见C语言使用宏已经能够初步解决小函数(代码少)的调用开销问题,但是宏定义是存在挺明显的缺点的:
C++从C而来,也对C做出了一些改进。那么C++是否选择了C语言的这种采用宏的方法呢? 显然是没有的,宏的缺点太过显眼了,C++中便引入了新的方式 -** 内联函数** 来解决小函数多次调用时存在的系统开销问题。
以关键字
inline
修饰的函数称为内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,从而内联函数能够提升程序运行的效率。 这里展开的意思就是直接用函数体替换函数被调用的位置。
//内联函数 - 交换两个整数
inline void Swap(int* x, int* y) {
int tmp = *x;
*x = *y;
*y = tmp;
}
int main() {
int a = 10;
int b = 20;
Swap(&a, &b);
return 0;
}
//inline 两个整数相加
inline int Add(int a, int b) {
return a + b;
}
int main() {
int a = 10;
int b = 20;
int ret = Add(a, b);
return 0;
}
内联函数inline
是一种以空间(编译时进行)换时间(运行时进行)的做法,如果编译器将函数当做内联函数处理,在编译阶段会用函数体替换函数调用。
优点:减少了函数调用的系统开销,提高了程序的运行效率;
缺点:如果内联函数被调用太多次,会产生代码膨胀,导致编译生成的目标文件过大(安装包过大)。
先说结论:不一定,取决于编译器。
inline
对于编译器来说只是一个建议或请求,不同的编译器堆inline的实现机制可能不同,编译器是否接受我们发出的请求也不受我们控制,而是由编译器自己决定。
inline
一般用于修饰函数规模较小(一般是几行代码)、非递归、调用频繁的函数。
对于函数规模较大(几十行或上百行代码)、递归函数,即使我们使用inline修饰,编译器也不会再调用这些函数的地方展开,而是像普通函数调用那样call
。
//11行代码输出
inline void function() {
cout << "hello C++!\n" << endl;
cout << "hello C++!\n" << endl;
cout << "hello C++!\n" << endl;
cout << "hello C++!\n" << endl;
cout << "hello C++!\n" << endl;
cout << "hello C++!\n" << endl;
cout << "hello C++!\n" << endl;
cout << "hello C++!\n" << endl;
cout << "hello C++!\n" << endl;
cout << "hello C++!\n" << endl;
cout << "hello C++!\n" << endl;
}
int main() {
function();
return 0;
}
先说结论:内联函数一般定义在需要调用内联函数的源文件内,或者直接定义在头文件内,在包含头文件即可。 来看这个错误:
为什么? 为什么内联函数不能像普通函数那样声明和定义分离呢?
这里将会涉及:
而内联函数呢,在编译时,inline
修饰函数并没有也不需要进入符号表(而是直接在编译时被编译器用函数体给替换了),
在编译时由于test.cpp
中只有内联函数的声明,而不知道Add
函数具体定义,所以编译器没有办法在main
函数中调用Add
函数处展开。
但是这并没有报错,如果这里报错应该是编译错误,但现在报的是链接错误,所以编译没问题。
在链接阶段test.o
会到其他目标文件中寻找Add
函数大的有效地址。
那么看链接阶段:
在链接阶段,test.o
符号表中只有Add
函数的无效地址(因为只是声明),而Add.o
符号表中也没有Add
函数的地址,导致了main
函数调用了Add
函数,却怎么都找不到Add
函数的地址,这发生在链接阶段,所以是链接错误。
对于内联函数前面已经知道:内联函数与其主调函数在同一源文件或内联函数在头文件中,内联函数都可以正常展开。因为在不需要再去找被调内联函数在哪了,可以直接展开内联函数了。
声明和定义分离,就会找不到内联函数的地址了。
C++中除了可以用内联函数代替宏定义之外,还可以使用const常变量、enum常量
来代替宏常量。
auto
关键字C语言原本就有,含义是auto
修饰的变量,是具有自动存储器的局部变量。
早期C++也沿用了C的auto
,不过很鸡肋,没啥用。
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一
个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得到。
使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto
的实际类型。
auto
并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto**替换为变量实际的类型。 **
auto
与指针和引用结合使用
用auto
声明指针类型时,用auto
和auto*
没有任何区别,但用auto
声明引用类型时则必须加&#include <iostream>
using namespace std;
int main() {
int a = 10;
auto b = a;
auto c = &a;
auto* d = &a;
auto& e = a;
//auto f;
cout << "a: " << a << endl;
cout << "b: " << b << endl;
cout << "c: " << c << endl;
cout << "d: " << d << endl;
cout << "e: " << e << endl;
return 0;
}
#include <iostream>
using namespace std;
int main() {
int a = 10;
auto b = 1, c = a;
//auto d = 10, f = 3.14;//error
cout << "a: " << a << endl;
cout << "b: " << b << endl;
cout << "c: " << c << endl;
return 0;
}
**typeid(变量名).name() ** 头文件
#include <typeinfo>
返回储存变量类型的字符串的地址,
#include <iostream>
using namespace std;
int main() {
int a = 10;
auto b = a;
auto c = &a;
auto* d = &a;
auto& e = a;
//auto f;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
cout << typeid(e).name() << endl;
return 0;
}
auto
不适用的情况:
auto
不能作为函数参数auto
不能用来声明数组在C语言和C++98中如果想要遍历一个数组,我们可以使用for循环:
#include <iostream>
using namespace std;
int main() {
int array[] = { 1,2,3,4,5,6,7,8,9,10 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i) {
cout << array[i] << " ";
}
cout << endl;
return 0;
}
对于一个有范围的集合而言,以前都是我们明确给出循环的范围,C++11
中则引入了基于范围的for循环,不需要我们指定,而是范围for循环自动控制范围:
for循环后的括号由冒号
:
分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
#include <iostream>
using namespace std;
int main() {
int array[] = { 1,2,3,4,5,6,7,8,9,10 };
for (auto e : array) {
cout << e << " ";
}
cout << endl;
return 0;
}
#include <iostream>
using namespace std;
int main() {
int array[] = { 1,2,3,4,5,6,7,8,9,10 };
for (auto& e : array) {
e *= 2;
cout << e << " ";
}
cout << endl;
return 0;
}
与普通for循环类似,可以用
continue
来结束本次循环,也可以用break
来跳出整个循环
int main() {
int array[] = { 1,2,3,4,5,6,7,8,9,10 };
for (auto& e : array) {
if (e == 8)
break;
e *= 2;
cout << e << " ";
}
cout << endl;
return 0;
}
int main() {
int array[] = { 1,2,3,4,5,6,7,8,9,10 };
for (auto& e : array) {
if (e == 8)
continue;
e *= 2;
cout << e << " ";
}
cout << endl;
return 0;
}
for循环迭代的范围必须是确定的。
对于数组范围是第一个元素和最后一个元素的范围; 错误举例:
int main() {
int array[] = { 1,2,3,4,5,6,7,8,9,10 };
int* p1 = array;//数组首元素的地址
for (auto e : p1)//error
cout << e << " "
cout << endl;
int(*p2)[10] = &array;//整个数组的地址
for (auto e : p2)//error
cout << e << " "
cout << endl;
return 0;
}
对于类来说,begin和end是for循环的范围。
迭代的对象要实现++和==的操作
我们在定义一个变量时可能并不知道该变量应该赋予的初值是什么,这时我们往往可以给其一个简单的初值。
int a = 0;
double b = 0.0;
char c = 0;
int* p = NULL;
int* p = 0;
在C++98中存在这样的一个有关NULL
问题:
NULL
是#define
定义的宏常量,一般用于为没有有效指向的指针赋值,表示指针空值。
在C语言中它是(void*)0
整型字面值0再强制类型转换为void*
的指针
在C++98中,字面常量0既可以是一个整型数字,也可以是无类型的指针(void*)
常量,但是编译器
默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void*)0
。
这样就会引起一些问题。
void func(int)
{
cout << "f(int)" << endl;
}
void func(int*)
{
cout << "f(int*)" << endl;
}
int main()
{
func(0);
func(NULL);
func((int*)NULL);
return 0;
}
但是C++并不好修改这个问题,只能保留这个问题,因为有很多人和企业使用这C++。
于是C++11便引入了一个关键字nullptr
来解决这个问题:
在C++11中,
sizeof(nullptr)
与sizeof((void*)0)
所占的字节数相同。 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr
void func(int)
{
cout << "f(int)" << endl;
}
void func(int*)
{
cout << "f(int*)" << endl;
}
int main()
{
func(0);
func(nullptr);
func((int*)NULL);
return 0;
}
本节主要介绍了内联函数的概念,并稍微了解了重获新生的auto关键字和新引入的nullptr
关键字。
下次再见!