通讯录是一个可以记录亲人、好友信息的工具。
本教程主要利用C++来实现一个通讯录管理系统
系统中需要实现的功能如下:
创建项目步骤如下:
打开vs2017后,点击创建新项目,创建新的C++项目
为什么要用移动语义?
我们回顾一下之前模拟的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能够复用临时对象申请的空间。
这其实也可以视为是一种隐式转换。
模板是一种通用的描述机制,使用模板允许使用通用类型来定义函数或类。在使用时,通用类型可被具体的类型,如 int、double 甚至是用户自定义的类型来代替。模板引入一种全新的编程思维方式,称为“泛型编程”或“通用编程”。
像C/C++/Java等语言,是编译型语言,先编译后运行。它们都有一个强大的类型系统,也被称为强类型语言,希望在程序执行之前,尽可能地发现错误,防止错误被延迟到运行时。所以会对语言本身的使用造成一些限制,称之为静态语言。与之对应的,还有动态语言,也就是解释型语言。如javascript/python/Go,在使用的过程中,一个变量可以表达多种类型,也称为弱类型语言。因为没有编译的过程,所以相对更难以调试。
如图,变量a表达了int数据,然后又去表达了字符串,python中允许这样的做法。
强类型程序设计中,参与运算的所有对象的类型在编译时即确定下来,并且编译程序将进行严格的类型检查。为了解决强类型的严格性和灵活性的冲突,也就是在严格的语法要求下尽可能提高灵活性,有以下3种方式:
例如,想要实现能够处理各种类型参数的加法函数
多态( polymorphism )是面向对象设计语言的基本特征之一。仅仅是将数据和函数捆绑在一起,进行类的封装,使用一些简单的继承,还不能算是真正应用了面向对象的设计思想。多态是面向对象的精髓。多态可以简单地概括为“一个接口,多种方法”。比如说:警车鸣笛,普通人反应一般,但逃犯听见会大惊失色,拔腿就跑。
通常是指对于同一个消息、同一种调用,在不同的场合,不同的情况下,执行不同的行为 。
我们知道,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类)。它们的目的都是为了代码重用。而多态除了代码的复用性外,还可以解决项目中紧偶合的问题,提高程序的可扩展性。
如果项目耦合度很高的情况下,维护代码时修改一个地方会牵连到很多地方,会无休止的增加开发成本。而降低耦合度,可以保证程序的扩展性。而多态对代码具有很好的可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
C++支持两种多态性:编译时多态和运行时多态。
编译时多态:也称为静态多态,我们之前学习过的函数重载、运算符重载就是采用的静态多态,C++编译器根据传递给函数的参数和函数名决定具体要使用哪一个函数,又称为静态联编。
运行时多态:在一些场合下,编译器无法在编译过程中完成联编,必须在程序运行时完成选择,因此编译器必须提供这么一套称为“动态联编”(dynamic binding)的机制,也叫动态联编。C++通过虚函数来实现动态联编。接下来,我们提到的多态,不做特殊说明,指的就是动态多态。