目录
祖师爷——Bjarne Stroustrup
本贾尼·斯特劳斯特卢普(Bjarne Stroustrup,1950年6月11日-),丹麦人,计算机科学家,在德克萨斯A&M大学担任计算机科学的主席教授。他最著名的贡献就是开发了C++程序设计语言。
1982年,美国AT&T公司贝尔实验室的本贾尼博士在C语言的基础上引入并扩充了面向对象的概念,发明了—种新的程序语言。为了表达该语言与C语言的渊源关系,它被命名为C++。而本贾尼博士被尊称为C++语言之父。
什么是C++
C++(c plus plus)是一种计算机高级程序设计语言,是C语言的拓展。正所谓青出于蓝而胜于蓝,C++继承了 C 的大多数优点,并增加了面向对象编程(OOP)的功能以及其他的改进
C++的的一些特性可能会使它比 C 更适合某些类型的项目,特别是那些需要高级抽象和复杂系统设计的应用
C++应用领域
- 大型软件开发,如编译器,浏览器,数据库(相对而言,只有C++适合),操作系统等
- PC端,军工业,传统工业, C++ && QT等
- 后端服务开发
- 游戏后端开发
- 嵌入式及物联网领域(饭碗),嵌入式驱动(需对硬件了解),学完C++需单独学习嵌入式系统或说stm32
- 分布式应用
- 人工智能,上层基本都是用python封装
- 音视频处理,如ffmeg
- 测试开发\测试
C++版本更新
标准版本 | 发布时间 | 正式名称 | 更新内容 |
---|---|---|---|
C++ 03 | 2003年 | ISO/IEC 14882:2003 | 对C++ 98版本的漏洞做了部分修改。 [14] |
C++ 11 | 2011年8月12日 | ISO/IEC 14882:2011 | 对容器类的方法做了三项主要修改: 1、新增了右值引用,可以给容器提供移动语义。 2、新增了模板类initilizer_list,因此可将initilizer_list作为参数的构造函数和赋值运算符。 3、新增了可变参数模板(variadic template)和函数参数包(parameter pack),可以提供就地创建(emplacement)方法。 [15] |
C++ 14 | 2014年8月18日 | ISO/IEC 14882:2014 | C++11的增量更新。主要是支持普通函数的返回类型推演,泛型lambda,扩展的lambda捕获,对constexpr函数限制的修订,constexpr变量模板化等。 [18] |
C++ 17 | 2017年12月6日 | ISO/IEC 14882:2017 | 新增UTF-8 字符文字、折叠表达式(fold expressions):用于可变的模板、内联变量(inline variables):允许在头文件中定义变量;在if和switch语句内可以初始化变量;结构化绑定(Structured Binding):for(auto [key,value] : my_map){…};类模板参数规约(Class Template Argument Deduction):用pair p{1, 2.0}; 替代pair{1, 2.0};;>;static_assert的文本信息可选;删除trigraphs;在模板参数中允许使用typename(作为替代类);来自 braced-init-list 的新规则用于自动推导;嵌套命名空间的定义;允许命名空间和枚举器的属性;新的标准属性:[[fallthrough]], [[maybe_unused]] 和 [[nodiscard]];对所有非类型模板参数进行常量评估;Fold表达式,用于可变的模板;A compile-time static if with the form if constexpr(expression);结构化的绑定声明,允许auto [a, b]=getTwoReturnValues() |
C++ 20 | 2020年12月7日 | ISO/IEC 14882:2020 | 新增模块(Modules)、协程(Coroutines)、范围 (Ranges)、概念与约束 (Constraints and concepts)、指定初始化 (designated initializers)、操作符“<=> != ==”;constexpr支持:new/delete、dynamic_cast、try/catch、虚拟、constexpr向量和字符串;计时:日历、时区支持。 |
C++的第⼀个程序
C++兼容大部分C语言的语法,在Visual Studio2022中,编译.cpp文件使用C++编译器,.c文件使用C编译器;在 Linux 系统下,cpp使用g++编译器,c使用gcc编译器(本文使用vs2022)
//C语言
#include<stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
//C++
#include<iostream>
using namespace std;
int main()
{
std::cout << "hello world" << std::endl;
return 0;
}
以上两段代码的表现形式是一样的,但编写的代码有很大的差池。C++使用了 cout 对象和 endl 来完成这一任务。 cout 是C++标准库 iostream 中的一个对象,用于向屏幕输出数据;而 endl 则是一个流操纵符,它不仅会输出一个换行符,还会刷新缓冲区。注意,在C++中,我们通常会在 iostream 头文件之前加上 using namespace std; 声明,以便于直接使用 std 命名空间内的元素,比如 cout 和 endl 。
命名空间
上述代码中提到了命名空间
//使用命名空间std
using namespace std;
当你在程序中使用 using namespace std; 时,实际上是告诉编译器你打算使用标准库中的所有命名空间成员,并且不再每次使用它们时都加上 std:: 前缀。这意味着你可以直接使用 cout 、 endl 等标准库中的标识符,而不需要每次都写 std::cout 或 std::endl
💡tips:我比较建议加上 std:: 前缀以防止重定义错误(两个命名空间有同名函数)
命名空间的定义
特性
- 命名空间内可以定义变量 / 函数 / 结构体等
- 可以嵌套定义
- 本质是定义一个域,这个域跟全局域各自独⽴,不同的域可以定义同名变量
- 多文件定义同名域被认为是同一个域
namespace xxx
{
//命名空间中可以定义变量/函数/类型
int val =10;
int Add(int left,int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
命名空间成员的调用
namespace xxx
{
//......
}
xxx :: func(x,y);
//.......
但在定义结构体时需要注意
struct xxx :: MyStruct node;
xxx :: MyStruct node1;
将空间内某成员展开
using xxx::val;
C++的输入&输出
<iostream>:input output stream的缩写,标准的输入、输出流库,定义了相应对象
- std::cin 是 istream 类的一个对象
- std::cout 是 ostream 类的一个对象
以上都是针对窄字符的标准输入输出流
- std::endl 换行的同时,刷新缓存区
- << 流插入运算符,>> 流提取运算符(可以左移/右移)
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
int main()
{
int a = 0;
double b = 0.1;
char c = 'x';
cout << a << " " << b << " " << c << endl;
std::cout << a << " " << b << " " << c << std::endl;
// 可以⾃动识别变量的类型
cin >> a;
cin >> b >> c;
cout << a << endl;
cout << b << " " << c << endl;
return 0;
}
缺省参数
缺省参数(Default Arguments),也称为默认参数,在 C++ 中允许你在函数声明或定义时为函数参数提供一个缺省值。当调用函数时如果没有传递对应的实参,则会使用这个缺省值
注意事项
- 从右向左定义:缺省参数必须从函数参数列表的右侧向左侧定义。如果你为一个参数定义了缺省值,那么在其右侧的所有参数也必须有缺省值或提供实参。
- 重复定义:如果你在函数声明和定义中都指定了相同的缺省值,编译器通常会接受这种情况,但最好保持一致性,避免潜在的错误。
- 缺省参数的顺序:在调用函数时,如果没有提供某个参数,则该参数及其右侧的所有参数都必须使用缺省值或完全提供。
//缺省参数
namespace func
{
int func1(int a = 1)
{
return a++;
}
void func2(int a = 10, int b = 20, int c = 30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
}
using namespace func;
int main()
{
func2();
func2(1);
func2(1,2);
func2(1,2,3);
return 0;
}
缺省函数的定义
缺省参数在头文件的声明不可以出现缺省!!!会出现重定义错误,在声明给缺省的形参
头文件中
void func(int x, size_t y = 10);
源文件中
void func(int x, size_t y ) {
//..........
}
重载函数
基本概念
函数名相同:重载的函数必须拥有相同的函数名。
参数列表不同:至少一个参数的类型、数量或顺序不同。
返回类型无关:返回类型的不同不能作为函数重载的依据。即,仅靠返回类型的不同不能区分重载的函数
-
#include <iostream> using namespace std; // 函数重载示例 void display(int value) { cout << "整数值:" << value << endl; } void display(double value) { cout << "浮点数值:" << value << endl; } void display(int a, int b) { cout << "两个整数值:" << a << " 和 " << b << endl; } int main() { display(5); // 调用 display(int) display(5.0); // 调用 display(double) display(5, 10); // 调用 display(int, int) return 0; }
引用
一种特殊类型的变量,它为已经存在的变量提供了一个别名(alias)。一旦创建了引用,它就始终指向最初绑定的对象。引用本身并不是一个新的存储单元;它只是一个已存在对象的另一个名字。
注意
- 引⽤传参跟指针传参功能是类似的,引⽤传参相对更⽅便⼀些。
- 引⽤返回值的场景相对⽐较复杂,我们在这⾥简单讲了⼀下场景,还有⼀些内容后续类和对象章节中会继续深⼊讲解。
- 引⽤和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。C++的引⽤跟其他语⾔的引⽤(如Java)是有很⼤的区别的,除了⽤法,最⼤的点,C++引⽤定义后不能改变指向,
- Java的引⽤可以改变指向。
- ⼀些主要⽤C代码实现版本数据结构教材中,使⽤C++引⽤替代指针传参,⽬的是简化程序,避开复杂的指针,但是很多同学没学过引⽤,导致⼀头雾⽔。
特性:
- 定义必须初始化(否则编译出错)
- 一个变量可以有多个引用
- 一个引用一但绑定一个实体,不可再改变
int a = 0;
int& b = a;
引用的价值
引用传参、引用返回值,减少拷贝时的内存占用;改变引用对象,被绑定的对象发生变化
- 引用可绑定 const 修饰对象,但必须是 cons t引用
- const 修饰的对象具有常性,只可读不可写
- C++规定,相加 || 类型转换会产生临时变量
- 指针会出现空指针和野指针,引用相对较少,安全性更高
//可以
const int a = 10;
const int& ra = a;
//可以
int b = 10;
const int rb = b;
//不可以,编译无法通过
const int c = 10;
int& rc = c;
- 引用权限不可大于被绑定变量
- 引用可以给常量取别名,但是需要const修饰
inline(内联)
特性
- 用inline修饰的函数叫内联函数,内联类似于C语言的宏,但是比宏更加安全
- 内联函数在被调用时展开,因此不需要建立栈帧,可提高效率
- 内联函数确确实实是一个函数,在编译时会进行类型等检查,因此可提高安全性
- C语言的宏难以调试,但内联函数可调试
- inline对编译器来讲只是一个建议,在不同编译器在有不同的效果,有的编译器可以不展开,inline适合比较短小且频繁调用的场景;递归,较长篇幅的函数中,编译器会忽略inline
- 不建议声明定义分离,会导致链接错误。因为内联函数默认是展开,并没有地址,但头文件中需要拿到函数的地址,在汇编中需要call函数的地址,但内联函数无地址,所以导致链接错误。建议直接放到头文件(.h)
对内联函数进行定义需进行以下操作(以vs2022为例)
#include <iostream>
using namespace std;
// 内联函数声明
inline int max(int a, int b) {
return (a > b) ? a : b;
}
int main() {
int a = 20;
int b = 10;
int c = 15;
// 多次调用 max 函数
cout << "Max of " << a << " and " << b << " is " << max(a, b) << endl;
cout << "Max of " << a << " and " << c << " is " << max(a, c) << endl;
return 0;
}
nullptr
C语言中的NULL实际是⼀个宏,在传统的C头⽂件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#if defined(__cplusplus)
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
- C++中 NULL 可能被定义为字⾯常量0,或者 C 中被定义为⽆类型指针 (void*) 的常量。不论采取何种定义,在使⽤空值的指针时,都不可避免的会遇到⼀些⿇烦,本想通过 f(NULL) 调⽤指针版本的f(int*) 函数,但是由于 NULL 被定义成0,调⽤了 f(intx) ,因此与程序的初衷相悖。f((void*)NULL) ; 调⽤会报错。
- C++11中引⼊ nullptr ,nullptr 是⼀个特殊的关键字,nullptr 是⼀种特殊类型的字⾯量,它可以转换成任意其他类型的指针类型。使⽤nullptr 定义空指针可以避免类型转换的问题,因为nullptr 只能被隐式地转换为指针类型,⽽不能被转换为整数类型。
#include <iostream>
using namespace std;
void f(int x) {
cout << "f(int x)" << endl;
}
void f(int* ptr) {
cout << "f(int *ptr)" << endl;
}
int main()
{
f(0);
// 本想通过f(NULL)调用指针版本的f(int*)函数,
// 但由于NULL被定义成0,调用了f(int x),因此与程序初衷相悖。
f(NULL);
f((int*)NULL);
// 编译报错:error C2665:“f”:2 个重载中没有一个可以转换所有参数类型
// f((void*)NULL);
f(nullptr);
return 0;
}