C++介绍
C++介绍
参考南软2024秋C++高级程序设计课件introduction, introduction2
C++历史
今年不考,以后应该也不会考了
C++的历史:1979-1991
在真实世界中为其进化一门语言:1991-2006
改变世界:2006-至今
C和C++的关系
- C++完全包含C语言成分,C++支持C所支持的全部编程技巧(C的超集);同时C++还添加了OOP支持
- 任何C程序都能被C++用基本相同的方法编写,并具备相同的运行效率和空间
- C++还引入了重载、内联函数、异常处理等功能,对C中的过程化控制及其功能进行了扩充
- C++由以下4部分有机组成
- C
- OOP
- STL
- Inside-Model
语言的类别
C/C++是静态强类型语言,C++比C”更“静态
结构化编程
程序 = 数据结构 + 算法
ADT
Abstract Data Type 抽象数据类型
结构化编程需要关注抽象数据类型、联系、优先级、类型转换(强制、隐式、溢出?)、求值顺序、副作用
cast
此处介绍各种类型的cast(类型转换)
static_cast
用法:static_cast<>()
,效果类似于直接用(类型)
的强制类型转换
const_cast
用法:const_cast<>()
,用于去除const变量的修饰符const
const int c = 10; |
输出
0012FF74 10 |
个人看法:在编译器眼中,c
是一个编译时常量,可能维护了一个类似常量表的东西,凡是遇到读c
的地方统统换成这个对应的常量,因为const
类型的变量不允许修改,因此在编译器眼中,这段代码cout << &c << ": " << c << endl;
实际上是cout << &c << ": " << 10 << endl;
;而p
是一个指向c
的地址的指针,它的的确确把这个内存中的值改了,const
只是保证了c
这个变量不被修改,这个变量只是用来解释一个特定内存中存储的二进制序列,并不保证内存一定不会被修改。那么编译器保证变量c
始终是个常量的做法应该就是维护一个常量表来做替换。
reinterpret_cast
用法:reinterpret_cast<>()
reinterpret_cast
是 C++ 中的一个强制类型转换操作符,用于在不同类型之间进行转换,尤其是指针和整数之间的转换。它是 C++ 中最强大、最危险的类型转换操作符之一。使用 reinterpret_cast
可以实现低级别的内存操作,绕过类型系统的类型检查。
// float -> int |
当然也可以在不同类型的指针之间转换,比如int*
和char*
dynamic_cast
用法:dynamic_cast<>()
动态类型转换一般用于多态,即基类指针和派生类指针之间的转换,且基类中必须要有虚函数(virtual
关键字)才能使用动态类型转换,面向对象编程中常用。
auto
auto
关键字可以根据值的实际类型决定变量的类型
C++为什么需要auto?
- 减少冗余:有些类型特别长,写起来容易出错,提供auto关键字可以减轻程序员的负担,把类型判断交给编译器
- 简化模板编程
- 类型推导支持泛型编程
- 支持返回类型推导(即可以把返回值声明为
auto
) - 提高编程的灵活性,更优雅
但也有缺点,在大量使用auto
的代码中,调试起来的可读性可能会变差。
union和struct
与C
语言相同,只需要注意struct
默认访问权限是public
,class
默认是private
tuple
std::tuple
是 C++11 引入的一种数据结构,用于存储多个不同类型的元素。与 std::pair
只能存储两个元素不同,std::tuple
可以存储任意数量和类型的元素。它是一个模板类,可以存储多个不同类型的数据。
使用 std::get<index>(tuple)
来访问元组中的元素,其中 index
是元素的索引(从 0 开始)。
std::tuple_size
结合decltype()
用于获取元组中元素的个数。std::tuple_element
用于获取指定索引的元素类型。
std::tie
可以将元组的元素与一组变量进行绑定,使得你能够解构元组。常用于返回多个值的函数
std::make_tuple
是一个方便的函数,可以用来创建一个元组,并且自动推导元素类型
示例:
using namespace std; |
optional
std::optional
是 C++17 引入的标准库类型,用于表示可能为空的值。它是一种封装类型,可以表示“存在值”或“无值”的状态,避免必须要用指针中的空指针代表值不存在的概念。
std::optional
的默认构造函数是“无值”的;可以通过成员函数std::optional::has_value()
判断是否有值
using namespace std; |
variant
std::variant
表示可以存储多种可能类型的数据,但同一时刻只能存储一种类型的值,可以通过std::get
或std::visit
访问存储的值
using namespace std; |
std::holds_alternative
检查当前 variant
是否存储了某种类型
cout << holds_alternative<int>(v); // 1 |
std::visit
可以访问std::variant
中的元素并做相应的操作
struct Visitor { |
相比传统的union
,variant
的优点有:
- 类型安全:不像传统的
union
,std::variant
是类型安全的,编译器会确保你只在正确的类型上进行操作 - 灵活
- 简化代码
any
类似variant
但不需要提前声明存储的类型,可以存储任意类型;也可以视作是类型安全的void*
(直接使用void*
可能引发问题)
通过 std::any_cast<>()
来访问存储的数据
示例:类似auto
?
// 使用 std::any 存储不同类型的值 |
std::any::has_value()
检查 std::any
是否包含有效的值。如果 std::any
中存储了一个值,返回 true
,否则返回 false
std::any::reset()
清除 std::any
中存储的值,使其不再包含任何值
cout << std::a.has_value();; // 1 |
智能指针
RAII
Resource Acquisition Is Initialization
RAII是C++中重要的思想,即资源在初始化时分配,也可以理解成在构造函数(初始化时)中获取资源并初始化,在析构函数中释放资源(归还)。
智能指针通过多套一层来管理指针资源,减少了内存泄漏的可能性
class A { |
unique_ptr
unique_ptr
独占资源,只能有一个指向内存的指针
make_unique<>()
也可以创建一个独享智能指针
unique_ptr
不允许拷贝但允许移动,可以通过std::move
将资源的所有权从一个unique_ptr
转移到另一个
auto p1 = unique_ptr<A>(new A(1)); |
shared_ptr, weak_ptr
shared_ptr
std::shared_ptr
是一个智能指针,它允许多个指针共同拥有同一个对象的所有权。当最后一个 shared_ptr
被销毁时,它所管理的对象会自动销毁(即调用析构函数并释放内存)。它通过引用计数机制来跟踪有多少个 shared_ptr
指向同一个对象,但存在一个潜在的循环引用的问题。
*循环引用问题:*两个或多个对象之间相互持有对方的引用,从而导致它们的引用计数永远不会归零
std::shared_ptr::use_count
返回 shared_ptr
指向同一个对象的引用计数
std::shared_ptr::reset
使当前共享指针不再指向管理的对象,引用计数 - 1
和unique_ptr
一样可以通过shared_ptr<>()
和make_shared<>()
创建共享智能指针,不同的是,shared_ptr
可以直接通过赋值运算符创建一个指向同一对象的shared_ptr
using namespace std; |
weak_ptr
为了避免循环引用问题,引入weak_ptr
它本身不管理对象的生命周期,也不增加对象的引用计数。weak_ptr
只是观察一个对象,但不拥有它。可以通过 std::weak_ptr
来访问 shared_ptr
指向的对象,前提是该对象仍然存在(即至少有一个 shared_ptr
在管理该对象)
使用 std::weak_ptr::lock
可以尝试从 weak_ptr
获取一个 shared_ptr
,如果对象已被销毁,lock()
将返回一个空的 shared_ptr
应用场景:避免循环引用,如果两个对象相互引用 shared_ptr
,就会导致循环引用,导致内存无法释放。使用 weak_ptr
可以避免这个问题。
例如,两个对象互相持有 shared_ptr
,但其中一个对象通过 weak_ptr
引用另一个对象,这样就不会导致引用计数增加,从而避免循环引用。
auto sp = make_shared<int>(1); |
总结
特性 | std::shared_ptr |
std::weak_ptr |
---|---|---|
引用计数 | 增加引用计数,管理对象的生命周期 | 不增加引用计数,不能管理对象生命周期 |
访问对象 | 直接访问对象 | 通过 lock() 转换为 shared_ptr ,访问对象 |
对象生命周期管理 | 当最后一个 shared_ptr 被销毁时,自动删除对象 |
不管理对象生命周期,不能删除对象 |
避免循环引用 | 会导致循环引用问题 | 可以避免循环引用 |
数组
高维数组的本质:用一维数组组织,用高维数组解释
|
事实上计算机中没有“维度”的概念,数据在其中并非立体,因此看似有空间感的高维数组,在实现层面都是以一维线性数组组织的。
(•‿•)