C++基础语法
1. 内联函数
在讲内联函数之前我们先回忆一下C语言宏的概念,我们可以用宏定义一个函数,但是在使用的过程,会发现有以下缺点:
- 代码可读性差,因为优先级的关系,容易用错
- 不能调试
- 没有类型检查不安全
优点是:
- 提高了代码的复用性
- 因为不用开辟栈帧,所以提高了程序的性能
而我们C++之父则用内联函数替代宏定义函数,内联函数不仅有宏定义函数的优点还没有宏定义函数的缺点,可以说是无敌了。
1. 概念
以inline修饰的函数叫做内联函数,编译时C++会在调用内联函数的地方展开(也不一定,后面来解释 ),不会开辟栈栈,提高了程序运行的效率。
眼见为实,我们通过汇编语言来看一下,是否展开了。
这里是普通函数,可以看到call调用了函数
这里我们使Add函数变成了内联函数,可以看到直接展开了
2. 特性
- 内联函数是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用。提高了效率但是使目标文件变大了。
- 内联函数只是对编译器的一个建议,不同编译器对内联函数的实现各有差异,所以编译器可以拒绝这个建议,这里就解释了上面说的可能不会展开内联函数,而是当做普通函数处理,一般建议:函数代码少(没有具体定义,取决于编译器)、不是递归、频繁调用的函数用inline修饰,否则编译器会拒绝这个请求。
- 内联函数不建议定义和声明分离,分离会导致链接错误,因为内联函数被展开,就没有函数地址,所以就会链接错误。
2. auto关键字
2.1 auto简介
在一些语言中,如:python等语言中,我们定义一个变量是不需要声明变量类型的,而是让编译器自己去推导,使得代码写起来更加的方便。所以C++11引入了auto关键字。
int main()
{
int a = 0;
auto b = a;
auto c = &a;
//typeid是用来识别变量类型的
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
return 0;
}
2.2 使用规则
- auto和指针与引用结合起来用
用auto声明指针类型的时候,用auto和auto*没有任何区别,但是用auto声明引用类型的时候必须加&
int main()
{
int a = 0;
auto b = a;
auto c = &a;
auto* d = &a;
auto& e = a;
//typeid是用来识别变量类型的
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
cout << typeid(e).name() << endl;
return 0;
}
2.当在同一行定义多个变量时,这些变量的类型必须一样,否则编译器会报错,因为编译器只对第一个数据的类型进行推导,然后用推导出来的类型来定义其他变量。
int main()
{
auto a = 1, b = 2.0;
return 0;
}
2.3 auto不能用的场景
- auto不能做函数的参数
- auto不能用来申明数组
- 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
- 正常定义变量的时候一般不使用auto,auto在实际中最常见的优势用法就是跟范围for循环,还有lambda表达式等进行配合使用,还有冗长变量名。
3. 基于范围的for循环
3.1 普通for循环 vs 范围for循环
int main()
{
int arr[] = { 1,2,3,4,5 };
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
cout << arr[i] << " ";
}
cout << endl;
for (int* a = arr; a < arr + sizeof(arr) / sizeof(arr[0]); a++)
{
cout << *a << " ";
}
cout << endl;
for (auto e : arr)
{
cout << e << " " ;
}
return 0;
}
可以看出,这几种遍历数组的方式都是可以的,但我们可以知道使用范围for,不用设置长度,避免了越界访问的问题,而且书写起来方便、简洁。
3.2 范围for的语法
for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的临时变量,第二部分则表示被迭代的范围。
int main()
{
int array[] = { 1, 2, 3, 4, 5 };
for (auto& e : array)
e *= 2;
for (auto e : array)
cout << e << " ";
return 0;
}
注:
- :前面的变量不是数组中的元素,而是临时拷贝,改变:前面的变量的值,数组中的值不会变,如果想要改变的话,用引用类型就行。
- 与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。
3.3 范围for的使用条件
- 迭代的范围必须明确
void Test(int array[])
{
for (auto& e : array)
e *= 2;
for (auto e : array)
cout << e << " ";
}
int main()
{
int array[] = { 1, 2, 3, 4, 5 };
Test(array);
return 0;
}
因为这里传过去的是指针,确定不了数组的范围。
- 迭代的对象要实现++和==的操作。
4. 指针空值nullptr
void f(int)
{
cout << "f(int)" << endl;
return;
}
void f(int*)
{
cout << "f(int*)" << endl;
return;
}
int main()
{
f(0);
f(NULL);
return 0;
}
该程序的目的是想通过f(0)调用void f(int),通过f(NULL)调用void f(int*),但运行结果和我们的想法却不一样。
这是为什么呢?其实NULL的本质就是宏,在传统的C头文件(stddef.h)中,可以看到如下代码。
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void*)0。
所以C++引入nullptr关键字表示空指针,为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr,使用该关键字不需要头文件。
void f(int)
{
cout << "f(int)" << endl;
return;
}
void f(int*)
{
cout << "f(int*)" << endl;
return;
}
int main()
{
f(0);
f(nullptr);
return 0;
}