2025-07-20
C++
00

10.移动语义与资源管理

移动语义与智能指针

移动语义

为什么要用移动语义?

我们回顾一下之前模拟的String.cc

cpp
展开代码
#include <string.h> #include <iostream> using std::cout; using std::endl; class String { public: // 默认构造函数:初始化空字符串 String() : _pstr(new char[1]()) { cout << "String()" << endl; } // 有参构造函数:接受 C 风格字符串并复制 String(const char *pstr) : _pstr(new char[strlen(pstr) + 1]()) { cout << "String(const char *)" << endl; strcpy(_pstr, pstr); } // 拷贝构造函数:深拷贝字符串 String(const String &rhs) : _pstr(new char[strlen(rhs._pstr) + 1]()) { cout << "String(const String &)" << endl; strcpy(_pstr, rhs._pstr); } // 赋值操作符重载:深拷贝字符串并处理自我赋值 String &operator=(const String &rhs) { cout << "String &operator=(const String &)" << endl; if (this != &rhs) { if (_pstr) { delete[] _pstr; // 删除旧的内存 } _pstr = new char[strlen(rhs._pstr) + 1](); // 分配新内存 strcpy(_pstr, rhs._pstr); // 拷贝内容 } return *this; } // 获取字符串长度 size_t length() const { size_t len = 0; if (_pstr) { len = strlen(_pstr); } return len; } // 返回 C 风格字符串 const char *c_str() const { if (_pstr) { return _pstr; } else { return nullptr; } } // 析构函数:释放分配的内存 ~String() { cout << "~String()" << endl; if (_pstr) { delete[] _pstr; _pstr = nullptr; } } // 打印字符串内容 void print() const { if (_pstr) { cout << "_pstr = " << _pstr << endl; } else { cout << endl; } } private: char * _pstr; // 存储 C 风格字符串的指针 }; // 测试函数:演示 String 类的使用 void test0() { String s1("hello"); // 使用有参构造函数 // 拷贝构造 String s2 = s1; // 使用临时对象创建 s3,并深拷贝 String s3 = "hello"; }

创建s3的过程中实际创建了一个临时对象,也会在堆空间上申请一片空间,然后把字符串内容复制给s3的pstr,这一行结束时临时对象的生命周期结束,它申请的那片空间被回收。这片空间申请了,又马上被回收,实际上可以视作一种不必要的开销。我们希望能够少new一次,可以直接将s3能够复用临时对象申请的空间。

这其实也可以视为是一种隐式转换。

2025-07-20
C++
00

9.模板

第九章 模板

模板是一种通用的描述机制,使用模板允许使用通用类型来定义函数或类。在使用时,通用类型可被具体的类型,如 int、double 甚至是用户自定义的类型来代替。模板引入一种全新的编程思维方式,称为“泛型编程”或“通用编程”。

为什么要定义模板

像C/C++/Java等语言,是编译型语言,先编译后运行。它们都有一个强大的类型系统,也被称为强类型语言,希望在程序执行之前,尽可能地发现错误,防止错误被延迟到运行时。所以会对语言本身的使用造成一些限制,称之为静态语言。与之对应的,还有动态语言,也就是解释型语言。如javascript/python/Go,在使用的过程中,一个变量可以表达多种类型,也称为弱类型语言。因为没有编译的过程,所以相对更难以调试。

如图,变量a表达了int数据,然后又去表达了字符串,python中允许这样的做法。

强类型程序设计中,参与运算的所有对象的类型在编译时即确定下来,并且编译程序将进行严格的类型检查。为了解决强类型的严格性和灵活性的冲突,也就是在严格的语法要求下尽可能提高灵活性,有以下3种方式:

例如,想要实现能够处理各种类型参数的加法函数

2025-07-20
C++
00

8.多态

第八章 多态

  1. 什么叫多态?

多态( polymorphism )是面向对象设计语言的基本特征之一。仅仅是将数据和函数捆绑在一起,进行类的封装,使用一些简单的继承,还不能算是真正应用了面向对象的设计思想。多态是面向对象的精髓。多态可以简单地概括为“一个接口,多种方法”。比如说:警车鸣笛,普通人反应一般,但逃犯听见会大惊失色,拔腿就跑。

通常是指对于同一个消息、同一种调用,在不同的场合,不同的情况下,执行不同的行为 。

  1. 为什么需要多态性?

我们知道,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类)。它们的目的都是为了代码重用。而多态除了代码的复用性外,还可以解决项目中紧偶合的问题,提高程序的可扩展性。

如果项目耦合度很高的情况下,维护代码时修改一个地方会牵连到很多地方,会无休止的增加开发成本。而降低耦合度,可以保证程序的扩展性。而多态对代码具有很好的可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。

C++支持两种多态性:编译时多态和运行时多态。

编译时多态:也称为静态多态,我们之前学习过的函数重载运算符重载就是采用的静态多态,C++编译器根据传递给函数的参数和函数名决定具体要使用哪一个函数,又称为静态联编。

运行时多态:在一些场合下,编译器无法在编译过程中完成联编,必须在程序运行时完成选择,因此编译器必须提供这么一套称为“动态联编”(dynamic binding)的机制,也叫动态联编。C++通过虚函数来实现动态联编。接下来,我们提到的多态,不做特殊说明,指的就是动态多态

2025-07-20
C++
00

7.继承

第七章 继承

继承的基本概念

在学习类和对象时,我们知道对象是基本,我们从对象上抽象出类。但是,世界可并不是一层对象一层类那么简单,对象抽象出类,在类的基础上可以再进行抽象,抽象出更高层次的类。

而C++ 中模拟这种结构发展的方式就是继承,它也是代码重用的方式之一。通过继承,我们可以用原有类型来定义一个新类型,定义的新类型既包含了原有类型的成员,也能自己添加新的成员,而不用将原有类的内容重新书写一遍。原有类型称为“基类”或“父类”,在它的基础上建立的类称为“派生类”或“子类”。

定义派生类时,需要要在派生类的类派生列表中明确的指出它是从哪个基类继承而来的。

cpp
展开代码
class 基类 {}; class 派生类 : public/protected/private 基类 {};

如上述代码所示,有三种继承方式,其“继承效果”如图:

2025-07-20
C++
00

6.关联式容器

第六章 关联式容器

学到这里,我们可以提前学习一些STL的内容了,以帮助我们完成作业。本章我们介绍两个容器set、map,它们属于STL中的关联式容器。

set

set的构造

包含在头文件< set >,打开C++参考文档,主要关注这样的几个构造函数

image-20231031160917134

  1. 无参构造
  2. 迭代器方式进行构造,传入一个first迭代器,传入一个last迭代器
  3. 拷贝构造
  4. 标准初始化列表(大括号的形式)
cpp
展开代码
set<int> number; set<int> number2 = {1,3,9,8,9};