前言
C++定义了很多新的语法,可以在一定程度上补上C语言的短板,我们废话不多说,来了解一下这些特殊的语法吧
1.内联函数
1.1概念
我们之前在C语言中学习过宏,如果我们在C语言中想要用宏写一个ADD函数,那么它的写法如下:
#define ADD(x,y) ((x)+(y))
int main(void)
{
printf("%d\n", ADD(1, 2));
printf("%d\n", ADD(1, 2)*3);
int a = 0, b = 1;
printf("%d\n", ADD(a | b, a & b));
return 0;
}
如果我们没有按要求加入3个括号,在进行乘除等运算的过程中,由于优先级的问题,可能会出现计算错误
所以我们可以知道宏有以下的缺点:
1.容易出错,语法的坑比较多
2.不能调试,在预处理阶段就已经被替换了
3.没有类型的检查
但是宏也有它的优点:
1.没有类型的严格限制,可以在一定程度上兼容不同的类型
2.针对频繁调用的小函数,不需要建立栈帧,提高了效率
针对这一情况,为了减少栈帧的调用,C++创建了一个东西叫做内联函数,就解决了这个问题
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调 用建立栈帧的开销,内联函数提升程序运行的效率(debug版本因为考虑到调试的问题所以默认不会展开,在release版本中会直接展开,可以在设置中更改)
inline int add(int x, int y)
{
return x + y;
}
int main(void)
{
printf("%d\n", add(1, 2));
return 0;
}
1.2 内联函数的特征
1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用。缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率
2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性
3. inline不建议声明和定义分离在两个文件中,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到
2.auto关键字
2.1 概念
我们编写的程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在:
1. 类型难于拼写
2. 含义不明确导致容易出错
C++为了解决这个问题创建了auto关键字,auto的具体使用方式如下:
如果我们想要定义一个变量a,再定义一个变量b存入a的取值,我们在C语言中通常会这么写:
int a = 0;
int b = a;
而在C++中auto关键字会自动识别类型,所以我们可以这么写:
int a = 0;
auto b = a;
auto c = &a;
auto& d = a;
auto关键字在普通的场景没有太大的价值,当类型很长的时候auto关键字就能够发挥它的作用,能够简化代码,我们来举一个例子说明(不需要理解,仅仅作为示例,能看懂就行):
#include <vector>
#include <string>
int main(void)
{
std::vector<std::string> v;
std::vector<std::string>::iterator it = v.begin();
//这一串代码可以简化为 auto it = v.begin();
return 0;
}
2.2 使用auto的注意事项
1.auto不能作为函数的参数
//代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
2.auto不能直接用来声明数组
//这种情况下编译会错误
void TestAuto()
{
int a[] = {1,2,3};
auto b[] = {4,5,6};
}
额外补充
C++中有一个东西可以帮助我们查看实际类型,叫做 typeid() ,具体的使用方式如下:
int main(void)
{
int a = 0;
auto b = &a;
cout << typeid(b).name() << endl;
return 0;
}
3.基于范围的for循环(C++11)
我们用之前学过的知识在C++中访问数组会很麻烦:
int main(void)
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
array[i] *= 2;
for (int* p = array; p < array + sizeof(array) / sizeof(array[0]); ++p)
cout << *p << endl;
return 0;
}
而在C++11中有一种新语法叫做基于范围的for循环,它的具体使用方法如下:
int main(void)
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
array[i] *= 2;
for (int* p = array; p < array + sizeof(array) / sizeof(array[0]); ++p)
cout << *p << " ";
cout << endl;
for (auto e : array)
{
cout << e << " ";
}
cout << endl;
return 0;
}
基于范围的for循环会依次取数组中的数据赋值给e,它会自动判断结束和自动迭代,范围for一般会结合auto使用
我们来看一下下面这种情况:
int main(void)
{
int array[] = { 1, 2, 3, 4, 5 };
for (auto e : array)
{
cout << e << " ";
}
cout << endl;
for (auto e : array)
{
e *= 2;
}
for (auto e : array)
{
cout << e << " ";
}
cout << endl;
return 0;
}
此时我们可能会产生疑问,为什么我们这里使用了 *= 操作,但是打印出来的值没有发生变化呢?
因为我们是依次取出数组中的元素赋值给e,对e的修改并不会影响原数组,如果想要对e的修改影响原数组,我们可以这样修改:
int main(void)
{
int array[] = { 1, 2, 3, 4, 5 };
for (auto e : array)
{
cout << e << " ";
}
cout << endl;
for (auto& e : array)
{
e *= 2;
}
for (auto e : array)
{
cout << e << " ";
}
cout << endl;
return 0;
}
要想完成这个操作,我们这里只能使用引用而不能使用指针
4.指针空值nullptr(C++11)
我们需要知道的是:NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量,而C语言中不存在这样的情况。不论采取何 种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:
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,因此与程序的 初衷相悖
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器 默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强制转换为 (void *)0
为了解决这个问题,我们可以使用nullptr:
void f(int)
{
cout << "f(int)" << endl;
}
void f(int*)
{
cout << "f(int*)" << endl;
}
int main()
{
f(0);
f(nullptr);
f((int*)NULL);
return 0;
}
在使用nullptr的时候我们需要注意:
1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的
2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr
结尾
本节我们学习了一些C++的新语法,下一节我们就要进入C++中一个很重要的内容——类和对象,希望能给你带来帮助,谢谢您的浏览!!!