以inline修饰的函数叫做内联函数,在编译期间编译器会用函数体替换函数的调用,没有函数调用建立栈帧的开销,提升程序运行的效率。
当我们使用宏来进行加法运算时:
#include
using namespace std;
#define ADD(x,y) ((x)+(y))
int main()
{
cout << ADD(1, 2) << endl;
return 0;
}
可以看到当我要进行加法运算时,并没有调用函数,而是通过宏的形式实现文本替换。
如果函数不加inline修饰,那么它就会建立函数栈帧。
如果函数加上inline修饰,那么它就不会建立函数栈帧。
注意,在debug环境下,需要对项目进行属性配置才能有效使用inline关键字。
在我们使用C语言手写快排的时候,Hoare法中调用了多次Swap函数。而Swap函数仅有短短的三行,且出现的频率不低,那么这个时候,让Swap被inline修饰是最好的选择。 inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用。
inline void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void QuickSort_Hoare()
{
int x = 1000;
//...
while (x--)
{
int left = 3;
int right = 5;
Swap(&left, &right);//Swap函数规模小,且被频繁调用
}
//...
}
其原因是因为,inline修饰的函数不会被写进符号表(即使编译器忽略了inline的特性),那么要调用此函数的另一目标文件就找不到此函数的地址。
总结:
1.内联函数是用函数体替换函数调用的方法来减少栈帧的开销,提升运行效率
2.在编译器看来,inline只是一个建议。具体需要考虑inline修饰的函数的规模与性质
3.多文件操作中,inline修饰的函数声明与定义分离会导致连接错误
在早期的C/C++程序中,使用auto修饰的变量,是具有自动存储器的局部变量,但是很遗憾,一直没有人去使用它。
在C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。我们可以将它理解为,自动推导类型。为了与早期的auto定义不发生冲突,在C++11中丢弃了早期的auto定义。
int main()
{
auto a = 10;
auto b = a;
auto& c = a;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
auto x = 1.55;
auto px = &x;
cout << typeid(x).name() << endl;
cout << typeid(px).name() << endl;
//auto z;//未初始化将无法推导z的类型
return 0;
}
可以看到,auto修饰的变量能够根据等号右边的类型推导出修饰的变量的类型。并且auto推导出的变量与常用的int、char、double等没有差异,都会在内存中开辟一块对应大小的空间。
在我们的学习的编程过程中,程序会越来越复杂。就像在C语言中会使用大量的结构体,在C++中会使用大量的类。这就会导致类型复杂,类型名长度太长,在以后使用此类型定义变量时将会是一份不轻松的工作。所以类似于前面描述的情况,我们都可以考虑使用auto关键字自动推导类型。
void f(auto x)
{
}
void f(double x)
{
}
int main()
{
double a = 5;
f(a);
return 0;
}
如果我们碰到这种情况,程序会调用哪个函数?所在C++中,使用auto作为函数参数是禁止的。
auto关键字不能直接去声明数组,数组元素的合法类型应当由数组名前面的类型决定,而使用auto则与数组的定义发生冲突。所以在C++中,auto声明数组也是禁止的。
int main()
{
int arr1[] = { 1,2,3 };
auto arr2[] = { 1,2,3 };//不能确定数组元素的类型
return 0;
}
在早期的学习中,我们都是这样打印数组的:
int main()
{
int arr[] = { 123,45,124,56,123,67,76,234,758,234,65,246 };
for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
{
cout << arr[i] << " " ;
}
return 0;
}
那么在C++11中,我们可以这么打印数组:
int main()
{
int arr[] = { 123,45,124,56,123,67,76,234,758,234,65,246 };
/*for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
{
cout << arr[i] << " " ;
}*/
for (auto tmp : arr)
{
cout << tmp << " ";
}
return 0;
}
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
我们可以理解为,arr的数组范围被确定了,那么就不需要我们打代码的人再确定循环的范围。将arr的每个元素放入一个迭代变量tmp中,然后语法会自动进行数组的迭代。并且,这种循环与普通循环一样,都可以使continue、break等关键字。
现在我们只针对数组。数组的范围必须要被确定,也就是第一个元素和最后一个元素的范围。迭代的变量可以是普通变量、指针、引用等。在目前的入门状态,我们仅仅使用普通变量打印,使用引用修改某一些值。
int main()
{
int arr[] = { 123,45,124,56,123,67,76,234,758,234,65,246 };
for (auto tmp : arr)
{
cout << tmp << " ";
}
cout<<endl;
for (auto& x : arr)
{
x *= 2;
}
for (auto tmp : arr)
{
cout << tmp << " ";
}
return 0;
}
事实上在我们看来这就是一个空指针。不过在早期的版本中,NULL是宏定义的,它的值是0。这就是为什么C++11中会出现nullptr这样的空指针,因为NULL会产一些歧义。
void f(int)
{
cout << "f(int)" << endl;
}
void f(int*)
{
cout << "f(int*)" << endl;
}
int main()
{
f(0);
f(NULL);
f(nullptr);
return 0;
}
我们自然认为NULL是空指针,那么它就是一个指针类型,实际上不是。或许这也是早期的不严谨之处,C++11或许就是为了填这个坑。
C++11为了去填这个坑,将nullptr变得更加严格,它是void* 类型的 0 。它并不是宏定义,而是一个关键字。