目录
一、内联函数
1、定义
2、特性
二、auto
1、定义
2、使用场景
3、不能使用场景
三、范围for(C++11)
1、定义
2、使用条件
四、nullptr
接上一小节C++入门(2)—函数重载、引用
内联函数(Inline Function)是C++中的一个特性,主要用于优化小型、频繁调用的函数。内联函数的主要思想是将函数调用替换为函数体的内容,从而减少函数调用的开销。
在C++中,可以通过在函数声明或定义前加上关键字`inline`来声明一个函数为内联函数,例如:
inline int Max(int a, int b) {
return a > b ? a : b;
}
当编译器看到内联函数的调用时,它会尝试将函数调用替换为函数体的内容,而不是生成函数调用的代码。这样可以消除函数调用的开销,包括参数传递、返回地址的保存和恢复等。
然而,需要注意的是,`inline`关键字只是向编译器发出一个建议,编译器可以选择忽略这个建议。例如,如果函数体很大,或者函数包含循环、递归等复杂结构,编译器可能会选择不进行内联。
下面来看一个问题:
假设swap函数代码有十行,1000个调用swap的地方
swap不是inline时:
swap+调用swap指令,合计是多少行指令?
直接调用:10+1000。
swap是inline时:
swap+调用swap指令,合计是多少行指令?
inline展开:10*1000。
- C++11引入了auto关键字,它可以用于自动推导变量的类型。auto关键字可以让编译器根据变量的初始化表达式来推导变量的类型,从而简化代码。
- auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
下面这段代码演示了C++11中引入的auto关键字的用法。类型很长时,可以考虑auto关键字自动推导变量的类型,从而简化代码。
#include
#include
1、下面这个例子是auto常规操作,使用
auto
关键字来声明b
和c
变量的类型。
int main()
{
int a = 0;
auto b = a;
auto c = &a;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
return 0;
}
使用 typeid
运算符来输出变量 b
和 c
的类型信息,VS中输出结果如下,左为64位环境,右为32位环境 。
使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
2、auto与指针和引用结合起来使用 ,auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。
int main()
{
int a = 0;
auto b = &a;
auto* c = &a;
auto& d = a;
return 0;
}
auto
关键字来声明 b
、c
和 d
变量的类型。auto
关键字可以让编译器自动推断变量的类型,根据变量的初始化表达式来确定类型。b
的类型被推断为 int*
,因为它被初始化为 &a
,即 a
的地址。c
的类型被显式声明为 int*
,与 b
的类型相同。d
的类型被推断为 int&
,因为它被初始化为 a
,即 a
的引用。b
和 c
的类型虽然都是指针类型,但它们的声明方式不同。b
的类型是 auto
推断出来的,而 c
的类型是显式声明的指针类型。这两种方式在语义上是等价的,但在代码风格上有所不同。d
的类型是引用类型,它可以看作是 a
的别名,d
可以直接访问 a
的值。3、当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
void TAuto()
{
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}
void TAuto(auto a)
{}
void TestAuto()
{
int a[] = {1,2,3};
auto b[] = {4,5,6};
}
我们来对比一下输出数组元素的两种方式。
int main()
{
int a[] = { 1,2,3,4,5 };
for (int i = 0; i < sizeof(a) / sizeof(int); i++)
{
cout << a[i] << " ";
}
cout << endl;
for (auto& e : a)
{
cout << e << " ";
}
cout << endl;
return 0;
}
二者输出结果相同,其中第二种方式就叫范围for。
C++11 引入了一种新的循环语句——范围
for
循环(Range-based for loop),它可以用于遍历容器类对象或数组。
范围 for
循环的语法如下:
for (auto& var : container) {
statement
}
var
是一个变量名,用于存储容器中的元素;container
是一个容器类对象或数组,用于存储要遍历的元素;statement
是要执行的语句块,可以包含多条语句。auto&
或 const auto&
声明一个引用变量或常量引用变量,它会依次引用容器或数组中的每个元素。在循环体中,直接使用该变量访问容器或数组元素,并执行相应的操作。for
循环的优点在于语法简洁明了,可以避免下标越界等问题,同时也可以提高代码的可读性和可维护性。它适用于遍历容器或数组中的所有元素,但不适用于需要跳过或删除元素的情况。来看一下下面例子中有什么问题?
void TestFor(int array[])
{
for (auto& e : array)
cout << e << endl;
}
数组作为函数参数:在C++中,当数组作为函数参数时,它会被解析为指向其首元素的指针。因此,你无法在函数内部获取数组的大小。这就导致了你无法在范围for循环中使用数组,因为范围for循环需要知道数组的开始和结束。
使用auto&
:在这个上下文中,auto&
表示引用类型,但是由于数组被解析为指针,所以这里的e
实际上是一个指针,而不是数组中的元素。
在C++编程中,为变量赋予一个初始值是一种良好的编程习惯,否则可能会引发不可预知的错误,尤其是在处理未初始化的指针时。
如果一个指针没有有效的指向,我们通常会按照以下方式进行初始化:
void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
// ……
}
在这里,NULL实际上是一个宏,在传统的C头文件(stddef.h)中可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
具体来看,NULL在C++被定义为字面常量0,在C被定义为无类型指针(void*)的常量。但在C++中使用空指针NULL时会引发一些问题。
例如:
#include
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;
}
在这个例子中,我们希望通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义为0,所以实际上调用的是f(int)函数,这与我们的初衷相反。
在C++98中,字面常量0既可以被视为一个整型数字,也可以被视为无类型的指针(void*)常量。然而,编译器默认将其视为一个整型常量。如果我们希望将其作为指针使用,必须进行强制类型转换,即(void *)0。
需要注意的是: