目录
C++版本
|-------|-------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 时间 | 阶段 | 内容 |
| 1998年 | C++98 | C++官方第一个版本,绝大多数编译器都支持,得到了国际标准化组织(ISO)和协会认可,以模板方式重写C++标准库,引入了STL(标准模板库) |
| 2003年 | C++03 | C++标准第二个版本,语言特性无大改变,主要:修订错误、减少多异性 |
| 2011年 | C++11 | 增加了许多特性,使得C++更像一种新语言,比如:正则表达式、基于范围for循环、auto关键字、新容器、列表初始化、标准线程库等 |
| 2014年 | C++14 | 对 C++11 的扩展,主要是修复 C++11 中漏洞以及改进,比如:泛型的 lambda 表 达式, auto 的返回值类型推导,二进制字面常量等 |
| 2017年 | C++17 | 在 C++11 上做了一些小幅改进,增加了 19 个新特性,比如: static_assert() 的文 本信息可选,Fold 表达式用于可变的模板, if 和 switch 语句中的初始化器等 |
| 2020年 | C++20 | 自 C++11 以来最大的发行版 ,引入了许多新的特性,比如: 模块 (Modules) 、协 程 (Coroutines) 、范围 (Ranges) 、概念 (Constraints) 等重大特性,还有对已有特性的更新:比如Lambda 支持模板、范围 for 支持初始化等 |
| 2023年 | C++23 | C++23是一个小版本的更新,进一步完善和改进现有特性,增加了if consteval、falt_map,import std导入标准库等 |
| 2026年 | C++26 | 制定ing |
C++参考文档
Reference - C++ Reference (cplusplus.com)
第一个链接是个人比较喜欢使用的C++文档,因为它是以头文件形式呈现,所以它的内容相较后两个比较易懂,但它并不是官方文档,而且只更新到了C++11版本
第二个和第三个是C++官方文档,第二个是中文版的文档,第三个是英文版的文档,信息很全,但是相较第一个来说有点不易看
C++输入输出
C++对比C
来看看C语言和C++输入输出的区别
输入:
cpp
#include<stdio.h>
int main()
{
printf("hello world\n"); //C语言
return 0;
}
cpp
#include<iostream>
using namespace std;
int main()
{
cout << "hello C++" << endl; //C++
return 0;
}
输出:
cpp
#include<stdio.h>
int main()
{
int a = 0;
scanf("%d", &a); //C语言
printf("%d", a);
return 0;
}
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 0;
cin >> a;
cout << a << endl; //C++
return 0;
}
我们可以看到C++包的头文件名字为 <iostream> ,该单词是Input Output Stream的缩写,是标准的输入、输出流库
using namespace是命名空间的语法,是在<iostream>文件中将std这个命名空间给展开的意思,关于命名空间的使用下面将叙述
cout是ostream类的对象,主要面向窄字符的标准输出流
cin是istream类的对象主要面向窄字符的标准输入流
endl是一个函数,相当于C语言在字符串里的\n的作用(换行)
<<是流插入运算符,>>是流提取运算符,当然也可以和C语言的运算符一样作为左右移位运算符
命名空间
相较C语言它存在的意义
cpp
#include<stdio.h>
#include<stdlib.h>
int rand = 10;
int main()
{
printf("%d\n", rand);
return 0;
}
在C语言中这样定义一个rand全局变量看起来没什么问题,但真正编译的时候就会发现编译不过去,理由如下
这说明了rand是一个函数,那它在哪呢?
它就在我们包的stdlib.h头文件里,当预处理完头文件展开后就会存在一个rand的函数,这时候就会出现两个rand从而无法编译通过,但去掉stdlib.h的时候就可以编译通过并运行了
若是在多人协同的一个项目当中,每个人都有自己的命名风格,当每个人做的部分最终合起来的时候,万一命名重复就会变得很麻烦,所以C++之父本贾尼博士就解决了这个问题
命名空间的使用
在C++中发明了命名空间这个东西
cpp
#include<iostream>
using namespace std;
namespace lyw
{
int a = 10;
}
int main()
{
int a = 5;
cout << a << endl;
return 0;
}
我们用namespace关键字创建了一个名为lyw的命名空间,这个命名空间里可以存储变量,函数,结构体等,这样就算名字相同也不会冲突
我们只需小小的修改就能访问到lyw命名空间里的a变量
cpp
cout << lyw::a << endl;
在前面输入输出的演示中,使用了标准输入、输出流库,并还使用了命名空间
cpp
#include<iostream>
using namespace std;
std就类似于上面lyw的一个命名空间,它封装着一些类,其中就包括cout,endl、cin,所以我们为了使用它我们就要访问到命名空间中
所以我们可以这样写
cpp
#include<iostream>
int main()
{
std::cout << "hello C++" << std::endl;
return 0;
}
所以using namespace的作用是将std这个命名空间展开,将它变成全局变量类似的效果,这样我们就能正常使用了,这种做法只推荐在几十行的那种小项目中进行,代码量大不推荐
如果我们需要多次使用cout、endl和cin但又不想将std展开,我们也可以这样
cpp
#include<iostream>
using std::cout;
using std::endl;
int main()
{
cout << "hello C++" << endl;
return 0;
}
只单独展开了cout和endl,就可以正常使用了
缺省参数
缺省参数就类似于我们给函数参数赋一个默认值,若我们有给函数传参,那么函数会使用我们传递的数值 ,但若没有传参则使用函数的默认值
全缺省
全缺省就是函数的所有参数都赋一个初始值
具体实例如下:
cpp
#include<iostream>
using namespace std;
void func(int a = 6)
{
cout << a << endl;
}
int main()
{
func(7);
return 0;
}
若没有传参
cpp
func();
半缺省
我们还可以半缺省
半缺省就是函数的部分参数具有初始值
但是它只能从右往左缺省(别问为什么,问就是C++之父规定的)
cpp
#include<iostream>
using namespace std;
void func(int a, int b, int c = 2, int d = 3)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
cout << d << endl;
}
int main()
{
func(9, 8, 7);
return 0;
}
函数重载
C++支持在同一作用域中出现同名函数,但是要求形参不同,可以是参数个数不同或者类型不同,在C语言中是不支持同名函数的
cpp
#include<iostream>
using namespace std;
int Add(int a, int b)
{
return a + b;
}
double Add(double a, double b)
{
return a + b;
}
int main()
{
cout << Add(1, 2) << endl;
cout << Add(1.1, 2.2) << endl;
return 0;
}
注:返回值不同并不能作为函数重载的一个条件
函数重载这里是有坑的,如下:
cpp
#include<iostream>
using namespace std;
void func()
{
cout << "func()" << endl;
}
void func(int a = 1)
{
cout << "func(int a = 1)" << endl;
}
int main()
{
func();
return 0;
}
这两个func是构成重载的,如果我们不调用func我们的编译就不会报错,但是如果我们调用func()的时候为什么会出错呢?
func()是可以对于上面两个函数来说都是可以调用的,如果我们去掉这两个函数任意一个,都能运行成功,但是如果有两个它都能调用,编译器怎么知道我们要调用的是哪个func呢?
引用
引用是给一个变量起一个别名,而不是定义一个新的变量,编译器不会为引用变量开辟内存空间,它和它引用的变量共用一块空间
它的使用方法是在类型后面加上一个&符号,像定义一个变量类似的方法给其他变量起一个别名
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 1;
int& b = a;
cout << a << endl;
cout << b << endl;
return 0;
}
再来看看地址
我们通过上图的调试界面可以看出
在取a和b的地址的时候它们的地址是相同的,说明它们是同一个"人"
注意事项
定义时必须初始化
cpp
int& b; // 错误写法,没有对这个别名初始化
一个变量能有多个引用,甚至可以引用别名
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 66;
int& b = a;
int& c = a;
int& d = c;
cout << a << endl;
cout << b << endl;
cout << c << endl;
cout << d << endl;
return 0;
}
引用一旦引用一个实体,就不能再引用其他的实体
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 66;
int& b = a;
int tmp = 55;
b = tmp;
cout << a << endl;
cout << b << endl;
return 0;
}
这里并不是b引用了tmp,而是tmp的值赋给了别名b,所以a也被影响了
权限放大问题
cpp
#include<iostream>
using namespace std;
int main()
{
const int a = 22;
int& b = a;
return 0;
}
上图的a是被const修饰的,只可读不可写,没有const限制的b自然也无法变成a的别名,因为b是可读可写,如果可以引用的话那a的权限就相当于被b放大了,这是不被允许的
cpp
const int& b = a;
当然改成这样就可以了
权限放大不行权限缩小是可以的,例如:
cpp
#include<iostream>
using namespace std;
int main()
{
int b = 20;
const int& rb = b;
return 0;
}
不能引用常量
cpp
#include<iostream>
using namespace std;
int main()
{
int& a = 5;
return 0;
}
也不能是这样
cpp
#include<iostream>
using namespace std;
int main()
{
int x = 2, y = 3;
int& a = x + y;
return 0;
}
一样的结果
引用和指针的关系(区别)
我们知道C++是在C的基础上诞生的,引用的出现自然解决了很多使用指针比较麻烦的场景,指针对大多数人来说是比较难写的一块知识
引用的底层是指针
但引用并不能代替指针,它们的功能有重叠,但它们也可以是相辅相成的关系
1. 在语法概念上引用是一个变量的别名,不开空间。而指针是存储一个变量的地址,要开空间
-
引用必须初始化,指针是建议初始化,不是必须的
-
引用初始化之后就不能再引用其他的对象,而指针可以不断改变指向
-
引用可以直接访问指向对象,而指针需要解引用才能访问指向对象
-
sizeof中含义不同,引用的结果为引用类型的大小,而指针是地址空间所占字符数(32位平台下占4byte,64位平台下占8byte)
-
指针比较出现空指针、野指针的问题,引用很少出现问题,相对安全
inline
用inline修饰的函数叫做内联函数,简单来说被inline修饰的函数像宏函数一样会被编译器展开,这样调用的函数就不需要再建立栈帧,提高效率
我们只需要将inline加在返回值类型的前面即可
cpp
#include<iostream>
using namespace std;
inline int Add(int a, int b)
{
return a + b;
}
int main()
{
int x = 3, y = 9;
int ret = Add(x, y);
cout << ret << endl;
return 0;
}
为什么要有inline?
众所周知,C语言中有一种东西叫做宏函数,例如:
cpp
#define Add(x, y) ((x) + (y))
这就是一个加法Add的宏函数
想更详细了解宏函数可以看看下面的博客
但是上面的宏函数写的就已经需要很多细节了,如果想写更复杂一些的宏函数那么就需要更多细节,很容易让人写出问题,写宏函数也是能提高效率
但我们写正常的函数就基本上很少出问题(相对来说),所以我们用了inline就可以像宏函数一样类似的效果展开代码,提高效率
C++设计inline的目的就是替代宏函数
注意事项
inline对于编译器来说只是一个建议,编译器并不一定要去执行,也就是说加了inline也并不一定会将函数展开
因为编译器不能随意的展开一段代码,假设我们的func函数有1000条代码来完成,但是我们在main函数或者其他地方调用了func函数100次,全部都展开的话就是1000*100的代码量,这样会使我们的内存负担极大,相反,不展开的话只需要一个个call func函数的地址即可
inline使用于频繁调用的短小函数(小于10行),对于递归函数,代码相对更多的就算加上inline也会被编译器忽略
vs编译器 debug版本下默认是不展开inline的,这样方便调试
inline不建议声明和定义分离,因为文件打开会导致地址不存在,从而导致链接错误
nullptr
nullptr:空指针
在以前的C语言中,使用空指针需要用到NULL宏,它是在头文件 stddef.h中定义的
cpp
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
这是条件编译的知识:
可以从上面的代码浅浅的看出,如果是cpp,NULL被定义为了0,否则NULL被定义为了((void *)0)
这有什么问题呢?看看如下代码的结果是?
cpp
#include<iostream>
using namespace std;
void func(int a)
{
cout << "func(int a)" << endl;
}
void func(int* a)
{
cout << "func(int* a)" << endl;
}
int main()
{
func(NULL);
return 0;
}
NULL不是应该是空指针的意思吗?结果没有调用int*的形参反而调用了int类型的形参,于程序的初衷相悖
说明在C++中的NULL定义是有问题的,为了解决这个问题,在C++11中诞生了nullptr
nullptr是一个特殊的关键字,它可以转换成任意其他类型的指针类型
使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式类型转换为指针类型,而不能被转换成整数类型
cpp
#include<iostream>
using namespace std;
void func(int a)
{
cout << "func(int a)" << endl;
}
void func(int* a)
{
cout << "func(int* a)" << endl;
}
int main()
{
func(nullptr);
return 0;
}
完