1、内联函数
1.1、内联函数的概念
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序的运inline关键字必须与函数的定义体放在一起,这样才能使函数成为内联函数。如果inline仅出现在函数声明前面,而不伴随定义体,那么它不会起任何作用。
#include <iostream>
using namespace std;
inline int Add(int left, int right)
{
return left + right;
}
int main()
{
int a = 3;
int b = 4;
int sum = Add(a, b); // 调用内联函数
cout << "Sum: " << sum << endl;
return 0;
}
///
Sum: 7
1.2内联函数的特性
1、减少函数调用开销:inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用;
2、编译器决策:inline对于编译器而言只是一个建议,不同的编译器对于inline的实现机制可能不同,一般建议是将函数规模较小、不是递归、调用频繁的函数采用inline修饰,否则编译器会忽略inline的特性;
3、优势:与宏定义相比,内联函数在代码展开时会进行语法安全检查和数据类型转换,避免了宏可能出现的错误和类型问题,省去了函数调用的时间,提高程序运行效率;
4、劣势:会导致目标文件变大,占用更多的内存空间
5、inline不建议定义和声明分离,分离会导致链接错误,因为inline被展开,就没有函数地址了,编译器就会找不到;
//F.h
#pragma once
#include<iostream>
using namespace std;
inline void f(int i);
//F.cpp
#include"F.h"
void f(int i)
{
cout << i << endl;
}
//main.cpp
#include "F.h"
int main()
{
f(100);
return 0;
}
/
严重性 代码 说明 项目 文件 行 禁止显示状态 详细信息
错误 LNK2019 无法解析的外部符号 "void __cdecl f(int)" (?f@@YAXH@Z),函数 main 中引用了该符号 7.2 D:\程序员\七月\7.2\7.2\main.obj 1
1.3面试题
1、宏的优缺点
优点:
- 增强代码的复用性;
- 提高程序性能;
缺点:
- 不方便调试宏(因为预编译阶段进行了替换);
- 导致代码可读性差,可维护性差,容易误用;
- 没有安全类型检查。
2、C++有哪些技术替代宏
- 常量定义:换用const enum;
- 短小函数定义:换用内联函数;
2、auto关键字
2.1、auto关键字简介
在C++11之前,auto关键字在C/C++中用来声明具有自动存储期的局部变量,但这个特性并不常用。随着C++11标准的推出,auto关键字被赋予了新的生命力,它现在可以用于自动类型推导,让编译器根据初始化表达式自动推断变量的类型。
#include<iostream>
using namespace std;
inline void f(int i);
int TestAuto()
{
return 0;
}
int main()
{
int a = 0;
auto b = a;
auto c = 'a';
auto d = TestAuto();
//typeid自动推导一个变量的类型
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
//auto e;无法通过编译,使用auto定义变量必须对其进行初始化
return 0;
}
int
int
char
int
//
std::vector<std::string> array;
std::vector<std::string>::iterator it = array.begin(); // 使用auto可以简化为 auto it = array.begin();
注意:使用auto定义变量必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期间会将auto替换为变量实际的类型 。
2.2、auto使用规则
1、auto与指针和引用结合起来使用
使用auto声明指针类型时,用auto*和auto没有任何区别,但是用auto声明引用变量类型时必须加&
#include<iostream>
using namespace std;
int main()
{
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
*a = 20;
*b = 30;
c = 40;
return 0;
}
/
int * __ptr64
int * __ptr64
int
2、在同一行定义多个变量
当在同一行定义多个变量时,这些变量必须是相同的类型,否则编译器会报错,因为编译器只对第一个类型进行推导然后用推导出来的类型定义其他变量。
void TestAuto()
{
auto a = 1, b = 3;
//这行代码会编译失败,因为C和d的初始化表达式的类型不同
auto c = 3, d = 4.0;
}
2.3auto不能推导的场景
1、auto不能作为函数参数
//此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{
}
2、auto不能直接用来声明指针
void TestAuto()
{
int a[] = { 1,2,3 };
auto b[] = { 2,3,4 };
}
3、为了比避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法;
4、auto在实际中最常见的优势用法就是C++11中提到的新式循环for循环与lambda表达式进行配合使用。
3、基于范围的for 循环(C++1)
3.1、范围for语法
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错,因此C++11中引入了基于范围的for循环,for循环后面的括号有冒号“:”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围
#include<iostream>
using namespace std;
void TestFor()
{
int arry[] = { 1,2,3,4,5 };
for (auto& e : arry)
{
e *= 2;
}
for (auto e : arry)
{
cout << e <<" ";
}
cout << endl;
}
int main()
{
TestFor();
return 0;
}
2 4 6 8 10
注意:与普通循环类似,可以使用continue来结束本次循环,也可以使用break来跳出本次循环
3.2范围for的使用条件
1、for循环迭代的范围必须是确定的,对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin 和 endl的方法,begin 和 endl 就是for循环的迭代范围。
注意:下一代码就有问题,因为for循环的范围不确定
2、迭代的对象要实现(++)和(==)的操作: 范围for循环需要使用迭代器来遍历容器或数组的元素,迭代器是一个指向容器中元素的指针或引用,它需要支持前缀自增运算符(++)来移动到下一个元素,以及等于运算符(==)来判断是否已经到达了容器的末尾。如果迭代的对象没有实现这两个操作符,那么范围for循环将无法正常工作。在这种情况下,你可能需要使用传统的for循环或其他迭代方式来遍历容器。
4、指针空值nullptr(C++11)
4.1、C++98中的指针空值
在C++98中,指针的空值是用NULL
宏来表示的,NULL是一个预定义的宏,它展开为整数常量0或者一个类型为void*的指针常量0,但是编译器默认情况下将其看成一个整形常量,具体取决于它的使用上下文。如果要将其按照指针的的方式来使用,必须对其进行强转(void*)0。
在良好的C/C++编译习惯中,声明一个变量时最好给一个合适的初始值,否则可能会出现不可预料的错误,比如为初始化的指针,如果一个指针没有一个合法的指向,我们基本都是按照如下方式对其进行初始化
#include<iostream>
using namespace std;
void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
//.........
}
4.2、C++1中的nullptr
在C++11中,指针的空值是用nullprt关键字来表示的,nullprt是一个类型为nullprt_t的常量,它可以用来初始化或赋值给任何类型的指针,使其表示空值。
#include<iostream>
using namespace std;
void f(int)
{
cout << "f(int)" << endl;
}
void f(int*)
{
cout << "f(int*)" << endl;
}
int amin()
{
f(0);
f(NULL);
f((int*)NULL);
return 0;
}
程序的本意是想通过f(NULL)调用指针f(int*)的函数,但是由于NULL被定义为0,因此与程序的初衷相勃。
4.3、nullptr的使用规定
1、在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++作为新关键字引入的;
2、在C++11中,sizeof(nullptr)与sizeof(void*)0所占的字节数相同;
3、为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr;
4、nullptr_t类型数据可以用于关系运算表达式,但仅能与nullptr_t类型数据或者指针类型数据进行比较,当且仅当关系运算符为==、>=、<=等时返回true
。
命运有如双刃剑