目录
前言:
上一篇文章我们认识C++的关键字、命名空间、C++输出和输入、缺省参数、函数重载、引用,今天我们接着继续学习;
一、内联函数:
在C语言中,当我们需要频繁调用一个函数时,为了避免在栈开太多空间会使用宏函数来代替函数实现功能。但是在C++中一般不使用宏了,一般都是用const和enum去代替宏常量,用inline代替宏函数了。
为什么C++不是用宏了呢?
①宏不支持调试;②没有类型安全检查;③宏只能用来实现一些简单的场景,复杂场景实现不了,就算可以实现,语句也会十分复杂。
我们可以看下列代码
#define Add(x,y)((x)+(y))
int main()
{
//如果将宏定义成这样:#define Add(x,y) (x)+(y)
int ret=Add(10,2)*3;
//这时结果为(10)+(2)*3与我们预期的(10+2)*3不一样;
int a=1;
int b=3;
//如果将宏定义成这样:#define Add(x,y) x+y
int ab=Add(a&b,a|b);
//这时结果为a&(b+a)|b与我们预期的(a&b)+(a|b)不一样;
//因为+的优先级比&和|高,所以会先进行。
return 0;
}
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用时建立栈帧的消耗,内联函数提升程序运行的效率。
下面是内联函数的反汇编:
#include<iostream>
using namespace std;
inline int Add(int a, int b)
{
return a + b;
}
int main()
{
int a = 3;
int b = 2;
int ret=Add(a, b);
cout << "ret:" <<ret<< endl;
return 0;
}
普通函数:
#include<iostream>
using namespace std;
int Add(int a, int b)
{
return a + b;
}
int main()
{
int a = 3;
int b = 2;
int ret=Add(a, b);
cout << "ret:" <<ret<< endl;
return 0;
}
内联函数特性:
①inline是一种以空间换时间的做法,如果编译器将函数当作内联函数处理,在编译阶段会用函数体替换函数调用。
缺陷:可能会使目标文件变大。
优势:没有函数调用时建立栈帧的消耗,提高运行效率。
②inline对于编译器只是一个建议,最终是否会按内联函数处理取决于编译器。
一般内联函数适用于短小频繁调用的函数。
③inline不建议声明和定义分离,分离会导致链接错误,因为inline被展开,就没有地址了,链接会找不到,一般直接定义在头文件。
二、auto关键字:
使用auto关键字可以让编译器自动推导其类型。
int main()
{
int a = 0;
auto b = a;
auto c = &a;
//typeid().name()可以查看auto推导的是什么类型;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
return 0;
}
auto经常使用于:
1. 类型难于拼写
2. 含义不明确导致容易出错
我们之前也学过的typedef也可以起到简化代码的作用,但是typedef在一些场景下有很大的缺点,比如:
typedef char* ps;
int main()
{
const ps p1;
const ps* p2;
return;
}
上面这段代码运行时,会报错:
为什么p1会报错呢?
这是因为typedef重命名char* 后,p1实际上就是char* const p1;const去修饰p1很明显p1变成了一个常量,而常量的定义必须初始化。
auto也可以强制类型:
int main()
{
int a = 0l;
auto* aa = &a;//强制aa是指针类型;
char c = 'a';
auto& b = c;//auto后想要其实另一个变量的引用必须加上引用符号
return 0;
}
ps:
①使用auto定义变量时必须要初始化,因为在编译阶段编译器需要根据初始化表达式来推导auto的实际类型;
②用auto再用一行声明多个变量时,这些变量必须是相同的类型,否则会报错,因为编译器实际支队第一个类型进行推导,然后用推导出来的类型定义其它变量。
auto不能推导的场景:
①auto不能作为函数的参数
②auto不能直接用来声明数组
三、基于范围的for循环:
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误,因此C++11中引入基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则是表示迭代的范围,如下:
int main()
{
int arr[] = { 1,2,3,4,5,6,7 };
for (auto a : arr)
{
cout << a << "" << endl;
}
return 0;
}
如果赋值给数组中的元素可直接用a赋值即可:
int main()
{
int arr[] = { 1,2,3,4,5,6,7 };
cout << "+=1前:";
for (auto a : arr)
{
cout<< a << " ";
}
cout << endl;
cout << "+=1后:";
for (auto a : arr)
{
a += 1;
cout << a << " ";
}
return 0;
}
但是上面这种做法是不会改变原数组的,如要更改原数组只需要在定义迭代的变量使用引用即可:
int main()
{
int arr[] = { 1,2,3,4,5,6,7 };
cout << "+=1前:";
for (auto a : arr)
{
cout<< a << " ";
}
cout << endl;
for (auto& a : arr)
{
a += 1;
}
cout << "+=1后:";
for (auto a : arr)
{
cout << a << " ";
}
return 0;
}
范围for的使用条件:
fot循环迭代的范围必须是确定的,对于数组而言,就是数组中第一个元素和最后一个元素的范围。
四、指针空值nullptr:
C++中的nullptr实际上是一个补丁,因为C++中的NULL除了bug,如下:
void func(int)
{
cout << "int" << endl;
}
void func(int*)
{
cout << "int*" << endl;
}
int main()
{
func(0);
func(NULL);
return 0;
}
按照我们的预期是第一个func输出int,第二func输出int*;但是结果并没有,这是因为在C语言中NULL实际是一个宏(0);所以c++中加了一个关键字nullptr。
void func(int)
{
cout << "int" << endl;
}
void func(int*)
{
cout << "int*" << endl;
}
int main()
{
func(0);
func(nullptr);
return 0;
}
总结:
到此我们C++入门就算学完了,C++入门所讲解的都是针对C语言的缺陷进行改进,更多的是语法,会比较枯燥,需要大家动手去练习熟悉它们。