一、内敛函数
1.1内联函数的意义
1.1.1C语言中的宏
在C语言中,我们对于多次使用的常量和函数往往会使用宏来定义,但宏使用起来往往十分容易出错,例如我们要定义一个宏函数来求两个整数变量的和
#define ADD(a,b) ((a)+(b))
在这一过程中有几处细节需要注意
①函数内容定义完成以后不可以加分号;
②宏函数内每个变量都要加括号
③宏函数外部总体需要加括号
一旦有哪个细节遗漏,就有可能遇到问题。
1.1.2解释宏函数的细节
我们之前列举了三个需要注意的细节,可是为什么呢?
①宏本质时替换,如果加了分号,可能会出现替换完成后
a=a1+a2;;
这一多重分号的问题,但这还不至于报错,只是如果要打印呢?
cout<<a+b;<<endl;
这种逆语法的语句一定会直接报错。
②宏函数内部如果不加括号,那么倘若出现
ADD(x&y,x|y);
这一类参数中包含运算优先级小于”+“的宏函数调用,就会出现
x&y+x|y;
从而导致x+y被提前计算,结果与预期不符
③宏函数外如果不加括号,那么配合宏函数调用的表达式中出现运算优先级比+高的符号,就会出现问题,如
int n=ADD(x,y)*6;
运算起来会变成这样
int n=x+y*6;
与预期不符。
1.1.3总结宏的优缺点
优点:
①增强代码的复用性
②提高代码性能
缺点:
①不方便调试
②导致代码可读性和可维护性变差
③无安全类型的检查,还容易出错
1.1.4应需求而生的内联函数
C语言中宏的问题带来了种种困扰,因此在C++中,我们替代了它
①用const enum关键字来代替宏定义常量
②用内联函数来代替宏定义短小函数
1.2内联函数使用时的注意问题
1.2.1内联函数使用方式
内联函数本质上是在函数调用的位置把函数内容直接展开,使用inline作为内联标识
inline int Add(int a,int b){//...}
余下的使用与函数一致
1.2.2要明确内联函数只是一个建议
内联函数只适合于语句内容很少且频繁被调用的函数,不包括递归等等复杂函数。
若编译器发现内联函数不符合内容很少的预期,会忽视inline,把函数作为普通函数来对待;
若编译器将函数作为内联函数来处理,那么会在编译阶段用函数体来替代函数调用。
1.2.3其余的注意问题
①本质上内联函数是一种以空间换时间的做法
②内联函数不支持声明和定义分离,因为调用内联函数时本质上不需要跳转操作(编译阶段直接复制指令到调用位置,汇编层面不涉及函数调用),没有对应地址,因此链接的过程会出现无法寻找的问题,因此它的声明和定义我们都放在.h文件中。
二、auto关键字
2.1auto关键字的作用
auto本意为自动推导,根据初始化的值自动推导类型。
2.1-补充说明
谈到替代类型实现定义的作用,C中的typedef也可以达到,但需要注意一些情况下是有问题的
typedef char* pstring;
int main()
{
const pstring p1;//报错
const pstring* p2;//此处const修饰的是*p2,即二级指针p2指向的内容,不报错
//但此处p2为二级指针,*p2为一级指针且被const修饰,**p2
//为char类型的值,可以被修改,与本意仍然不符合
}
这个代码关于p1是会报错的,我们想要定义的其实是
const char* p1;
但是因为pstring是由typedef得来的,所以决定const修饰的内容的指令在寻找到typedef目标之前执行,导致const修饰内容变为了p1,也就是
char* const p1;
此时按规则我们是必须要初始化p1的,所以报错了。
2.2与auto配合使用的函数
函数typeid可以检查auto出来的类型并打印,使用方式
auto b='c';
cout<<typeid(b).name()<<endl;
可以打印出来类型,预计为char
2.3auto与指针和引用的搭配
auto,auto*和auto&使用举例
例如:
int x=10;
auto a=&x;
auto* b=&x;
auto& c=x;
其中
auto a=&x;
auto* b=&x;
这两句其实作用是一致的,但是
①auto*意味着等式右侧必须传的是一个指针,
同理,
②auto&也意味着等式左边一定会是一个别名。
2.4auto不能推导的情况
①不可以一个auto跟多个不同类型变量同时定义
auto a=7,b=6.0;//不被允许
②auto不可以做函数参数
无法对实际类型进行推导,确定传参是否正确
③不可以用来声明数组
auto a={1,2,3};//不被允许
④定义的时候一定要初始化
初始值是推导类型的唯一依据
三、范围for
3.1范围for的意义
加入我们要打印一个数组array,我们要这样做:
for(int i=0;i<sizeof(array)/sizeof(array[0]);i++)
{
cout<<array[i]<<" ";
}
cout<<endl;
但是当我们用了范围for:
for(auto e:array)
{
cout<<e<<" ";
}
cout<<endl;
简化了很多很多,实际上范围for自动帮我们把数组中的数据给了e,自动++,自动判断了是否结束。
3.1-补充说明
使用范围for是可以改变数组类型和自动赋值目标变量名的,比如
for(int a:array){
//...
}
也是可以的
3.2使用范围for修改数组中的值
假如我们要把array数组中的每个值扩大为原来的二倍,只需要
for(auto& e:array)
{
e*=2;
}
在类型后面加上一个引用即可达到目的。
四、nullptr
在C++中,对NULL定义是有缺陷的,查询可见这样的语句
#define NULL 0;
我们知道NULL本身还可以是指针变量的值,那么下述情况
f(int a)
{
cout<<int<<" ";
}
f(int* a)
{
cout<<int*<<" ";
}
int main()
{
f(0);
f(NULL);
return 0;
}
会打印成int int的,这很明显不是正常情况。
若要正常我们需要
f((int*)NULL);
为了解决这一问题,C++后期引入了nullptr
它的sizeof值等同于(void*)0,只要使用nullptr就解决了NULL定义为int类型的0导致的一系列矛盾问题