9.1 复制构造函数

例:
自定义string类,以简化字符串的操作


注:
如果不重载赋值运算符,对象str1的内容直接复制到新对象str2中,对于没有指针的简单类来说这足够了,但当我们拥有作为数据成员的指针时,逐字节的复制将会把指针从一个对象复制给另一个对象,而两个指针就会同时指向一个内存

解决方案:重载赋值运算符

注:

  1. 当重载赋值运算符时,务必确定将一个对象中的所有数据成员都复制到另一个对象中
  2. 如果包含多个数据成员,则每个成员都需要复制到目标对象中

另一个场景:同一个类的对象初始化另一个对象时。

String str2(str1);

解决方案:为类定义赋值 / 复制构造函数(即以对象为参数的构造函数)
//使用const关键字可以保证复制过程中不会改变被复制的对象:

String(const String & str);//复制构造 / 拷贝构造函数

注:在下面三种场景里面会调用复制的构造函数:

  1. 当类的对象被初始化为同一类的另一个对象时
  2. 当对象被作为参数传递给一个函数时
  3. 当函数返回一个对象时
//自定义的字符串包装类
class String
{public:String();String(char * str);String(const String & str);//复制构造 / 拷贝构造函数virtual ~String();//重载赋值运算符,将数组中每个元素都进行复制,而不是只复制数组指针const String & operator=(const String & str);friend ostream & operator << (ostream & out, const String & str);protected:private:int m_length;   //字符串的实际长度,不包括\0char * m_value;   //实际存储字符的字符数组};
String::String(): m_length(0)
{//char * str = "";    //长度为0,但是实际的字符数组中会存在唯一的元素:\0this->m_value = new char[m_length + 1];this->m_value[0] = '\0';
}
ostream & operator << (ostream & out, const String & str)
{out << str.m_value << "\n";//out << "m_value的长度:" << strlen(str.m_value);printf("%p", str.m_value);return out;
}
String::String(char * str)
{//将传入的字符串str的值赋给当前对象中的m_valueif(NULL == str){this->m_value = new char[1];this->m_value[0] = '\0';return;}m_length = strlen(str);//获得复制字符串的长度m_value = new char[m_length + 1];//为\0留出一个空间strcpy(m_value, str);
}String::String(const String & str)//复制构造 / 拷贝构造函数
{m_length = strlen(str.m_value);m_value = new char[m_length + 1];strcpy(m_value, str.m_value);
}//当重载赋值运算符,务必确定将一个对象中的所有数据都复制到另一个对象中(特别是有指针时)
//如果是包含多个成员,那么每个成员都需要复制到内存对象中 - 深复制
/* 如果一个成员拥有指针类型成员,那么大部分情况下,都需要使用深复制 - 才能将指针指向的内容复制一份出来,让原有的对象和新对象相互独立*/
//如果类的成员没有指针,那么一般使用浅复制
const String & String::operator=(const String & str)
{if(this == &str) return *this;delete[] m_value;   //首先要释放字符串的原始空间m_length = str.m_length;m_value = new char[m_length + 1];strcpy(m_value, str.m_value);return *this;
}String::~String()
{//析构时,释放字符数组所指向的空间delete[] m_value;
}
void TestString()
{String str1("我是你爹");//String str2 = "sadwea";String str2(str1);cout << str1 << endl;cout << str2 << endl;cout << "对象之间的赋值" << endl;str2 = str1;cout << str1 << endl;cout << str2 << endl;
}

·

·

·

·

·

自定义数组类:

//自定义的数组类
class Array
{public:Array(int length = 0);Array(const Array & arr);   //拷贝构造virtual ~Array();//int operator[](int index);  //获取元素(无法写入)const Array & operator=(const Array & arr);int & operator[](int index);friend ostream & operator<<(ostream & out, const Array & arr);private:int m_length;int * m_datas;
};
Array::Array(int length): m_length(length)
{if(m_length == 0){m_datas = NULL;}else{m_datas = new int[m_length];}
}
Array::Array(const Array & arr)
{if(arr.m_length == 0) return;m_length = arr.m_length;m_datas = new int[m_length];//1.使用循环组个元素赋值//2.memcpymemcpy(m_datas, arr.m_datas, m_length * sizeof(int));
}
const Array & Array ::operator=(const Array & arr)
{if(this == &arr) return *this;if(arr.m_length == 0) return *this;m_length = arr.m_length;m_datas = new int[m_length];//1.使用循环组个元素赋值//2.memcpymemcpy(m_datas, arr.m_datas, m_length * sizeof(int));return *this;
}
ostream & operator<<(ostream & out, const Array & arr)
{for(int i = 0; i < arr.m_length; i++){out << arr.m_datas[i] << ",";}out << endl;
}
/*
//重载中括号运算符
int Array::operator[](int index)
{if(m_length == 0){cerr << "数组为空,访问失败!" << endl;}if(index < 0 || index >= m_length){cerr << "数组下标越界!" << endl;}return m_datas[index];
}*/
int & Array::operator[](int index)
{if(m_length == 0){cerr << "数组为空,访问失败!" << endl;}if(index < 0 || index >= m_length){cerr << "数组下标越界!" << endl;}return m_datas[index];
}Array::~Array()
{delete[] m_datas;
}
//测试:
void TestArray()
{Array arr1(10);arr1[0] = 1234;cout << arr1 << endl;
}

·

·

·

·

·

自定义容器类:

//使用了模板技术,模板技术一般用来做算法,比如重载100次某个类型的算法
//注:如果使用模板技术,那么类的声明和方法实现都要放在同一个头文件中
template<typename T>class MyVector
{public:MyVector();MyVector(int len, T element); //填充len长度的元素elementMyVector(const MyVector<T> & vec);  //复制构造virtual ~MyVector();template<typename T2>friend ostream & operator<<(ostream & out, const MyVector<T2> & vec);MyVector<T> & operator=(const MyVector<T> & vec);T & operator[](int index);  //获取元素void push_back(T element);  //将元素element添加到内部数组中/**T pop_back();   // 返回并删除最后一个元素void insert(int pos, T elemnt); //在pos位置处,插入元素elementvoid clear();   //清空所有的元素 **/private:T * m_elements;//用来存放元素的数组int m_length;//所存放元素的实际个数int m_capacity;//当前元素数组的大小vec.m_elements
};template<typename T>
MyVector<T>::MyVector(): m_capacity(16), m_length(0)
{this->m_elements = new T[m_capacity];
}
template<typename T>
MyVector<T>::MyVector(int len, T element): m_capacity(16)
{m_capacity = len + m_capacity;m_length = len;m_elements = new T[m_capacity];for(int i = 0; i < m_length; i++){   //1.使用for循环复制//m_elements[i] = element;    //每次都会调用重载的赋值运算符//2.使用memcpy复制memcpy(&m_elements[i], &element, sizeof(T));}
}
template<typename T>
MyVector<T> & MyVector<T>::operator=(const MyVector<T> & vec)
{if(this = &vec) return *this;if(NULL != m_elements){delete[] m_elements;m_elements = NULL;}m_capacity = vec.m_length + vec.m_capacity;m_length = vec.m_length;m_elements = new T[vec.m_capacity];memcpy(m_elements, vec.m_elements, m_length * sizeof(T));return *this;
}
template<typename T>
void MyVector<T>::push_back(T element)
{//将元素element添加到内部数组的最后一个位置if(NULL == m_elements){m_capacity = 16;m_length = 0;m_elements = new T[m_capacity];}//判断当前数组的容量是否已满if(m_length == m_capacity){//如果满了,就扩容(当前容量* + 1)T * newElement = new T[m_capacity * 2 + 1];//把原来的元素拷贝到新空间memcpy(newElement, m_elements, m_length * sizeof(T));delete m_elements;  //释放空间m_elements = newElement;    //将元素数组指向修改为新空间}//m_elements[m_length++] = element;memcpy(&m_elements[m_length++], &element, sizeof(T));
}
template<typename T>
T & MyVector<T>::operator[](int index)
{return m_elements[index];
}
template<typename T2>
ostream & operator<<(ostream & out, const MyVector<T2> & vec)
{for(int i = 0; i < vec.m_length; i++){out << vec.m_elements[i] << ",";}out << endl;return out;
}
//拷贝构造
template<typename T>
MyVector<T>::MyVector(const MyVector<T> & vec)
{m_capacity = vec.m_capacity;m_length = vec.m_length;m_elements = new T[m_capacity];memcpy(m_elements, vec.m_elements, m_length * sizeof(T));
}
template<typename T>
MyVector<T>::~MyVector()
{delete[] m_elements;
}
//测试:
void TestVector()
{MyVector<int> vec1;  //默认构造MyVector<double> vec2(10, 99.9);cout << "vec1" << vec1 << endl;cout << "vec2" << vec2 << endl;MyVector<String> vec3(5, String("add"));vec3.push_back("123");cout << "vec3" << vec3 << endl;
}

9.2 类型的自动转换和强制转换

在C++中存在隐式类型转换语法(自动类型转换)

int a = 12;
a = 22.2 + a;

C++还提供了显式类型转换语法(强制转换)

类型名(变量)
int num = int(88.5);

C语言采用的语法格式
int num = (int)88.5;

注:

  1. 不管是自定类型转换还是强制类型转换,前提是编译器必须知道如何转换
    ① 将浮点型数据赋值给整型变量时,舍弃小数部分
    ② 将整型数据赋值给浮点型变量时,数值不变,但是以指数形式存储
    ③ 将double型数据赋值给float变量时,注意数值范围溢出
    ④ 字符型数据可以赋值给咱整型变量,此时存入的是字符的ASCLL码
    等等…

9.2.1 自定义类型之间的转换

C++允许我们自定义类型转换规则:我们可以将其它类型转换为当前类类型,也可以将当前类类型转换为其它类型,这种自定义的类型转换规则只能以类的成员函数的形式出现

转换构造:将其他类型转换为当前类型时调用

class Rectange
{public:Rectange();Rectange(float width, float height);     //带参构造Rectange(const Rectange & rect);    //拷贝构造Rectange(float width);          //转换构造(将其它类型转换为当前类型时使用void diaplay(){cout << "width:" << width << endl;cout << "height:" << height <<endl;}/*//将几个构造混合在一起的写法:Rectange(float width = 0, float height = 0): width(width), height(height){}*/virtual ~Rectange();private:float width;float height;
};

9.2.2当前类型转换为其他类型

类型转化函数:
类型转换函数的作用就是将当前类类型转换为其它类型;它只能以成员函数的形式出现(直接在.h文件中书写),也就是只能出现在类中。

//类型转换函数的语法格式:
operator type()
{return data;
}

特点:

  1. 类型转换函数看起来没有返回值类型,其实是隐式地指明了返回值类型
  2. 类型转换函数也没有参数,因为要将当前类的对象转换为其它类型(实际上编译器会把当前对象的地址赋值给this指针,这样在函数体内就可以操作当前对象了)
operator float() const{//将矩形转换成float类型return width;
}
operator Circle() const{//将矩形转换成Circle类型return Circle(width / 2);
}

注:

  1. type可以是内置类型,类类型以及由typedef定义的数据别名,任何作为函数返回类型都是被支持的(不允许转换为数组或函数类型,可以转换为指针或引用类型)
  2. 类型转换函数一般不会更改被转换的对象,所以通常被定义为const
  3. 类型转换函数可以被继承,可以是虚函数

老九学堂 学习C++ 第九天相关推荐

  1. 老九学堂 学习 C++ 第七、八天

    7.1 内联函数 内联(inline)函数:是C++为提高程序运行速度所做的一项改进:与常规函数的区别不在于编写方式,而在于被调用时的运行机制不同:编译使用函数代码替换 函数调用. 使用建议:如果执行 ...

  2. 老九学堂 学习C++ 第十天

    10继承 10.1 概念: 所谓 "继承(inheritance)" 就是在一个已经存在的类基础上,再建立一个新类 从已有的类派生出新的类,派生类就继承了原有类(基类)的特征,包括 ...

  3. 老九学堂 学习C++ 第六天

    函数 自定义函数的完整写法: 注: 函数原型与函数定义的头部类似,最后以分号结尾 函数原型中的参数名称可以省略,只写参数类型 C++中返回值类型不能是数组,但可以是其他任何类型(可以将数组作为结构或对 ...

  4. 【老九学堂】【C++】数组与指针

    不知道在通过前面的内容学习后,是否有很多小伙伴都会认为数组和指针是等价的,数组名表示数组的首地址呢?不幸的是,这是一种非常危险的想法,并不完全正确,前面我们将数组和指针等价起来是为了方便大家理解(在大 ...

  5. 老九学堂之分布式设计教材

    老九学堂之分布式设计教材 作者:老九-技术大黍 原文:分布式系统设计教材 社交:知乎 公众号:老九学堂(新人有惊喜) 特别声明:原创不易,未经授权不得转载或抄袭,如需转载可联系笔者授权 前言 本文由老 ...

  6. 【老九学堂】【C++】位运算符

    位运算是指按二进制进行的运算.在系统软件中,常常需要处理二进制位的问题.C语言提供了6个位操作运算符.这些运算符只能用于整型操作数,即只能用于带符号或无符号的char,short,int与long类型 ...

  7. 【老九学堂】【C++】C++的发展史

    为了让小伙伴们在学习过程中,能收获更多的知识,达到真正的零基础入门和深入了解C++,老九君特地收集了有关C++发展相关的一些资料供大家查阅和学习: C++语言发展大概可以分为三个阶段: 第一阶段从80 ...

  8. 【老九学堂】【初识C语言】C语言中的运算符

    1运算符和表达式 C语言运算符是说明特定操作的符号,它是构造C语言表达式的工具.C语言的运算异常丰富,除了控制语句和输入输出以外的几乎所有的基本操作都作为运算符处理.除了常见的三大类,算术运算符.关系 ...

  9. 【老九学堂】【初识C语言】C语言保留字(关键字)详解

    保留字(reserved word) 保留字又称关键字. 指在高级语言中已经定义过的字,使用者不能再将这些字作为变量名或过程名使用. 每种程序设计语言都规定了自己的一套保留字. 例如:BASIC语言规 ...

  10. 【老九学堂】【初识C语言】二维数组

    一维数组只有一个下标,称为一维数组,其数组元素也称为单下标变量.在实际问题中有很多量是二维的或多维的,因此C语言允许构造多维数组.多维数组元素有多个下标,以标识它在数组中的位置,所以也称为多下标变量. ...

最新文章

  1. OzymanDNS 使用——perl 5.22没有成功。。。
  2. DZ各个数据表详解(DZ论坛各表详细说明,二次开发用)
  3. 谷歌go语言课程讲解资源
  4. 正弦波 程序 角度传感器_激光位移传感器的原理及应用领域
  5. Android 屏幕灭屏亮屏广播,屏幕灭屏亮屏监听,广播实现按键监听
  6. android 7调用摄像头,Android调用摄像头拍照(兼容7.0)
  7. cam350怎么看顶层_厉害的人是怎么分析问题的?(实操干货)
  8. MySQL-Cluster7.2.5安装和配置
  9. 国家精品课程推荐|中山大学-地理信息系统概论
  10. 16s测序 | 如何轻松发表文章
  11. 机器学习笔记-激活函数关于零点对称问题
  12. 图像目标检测算法总结(从R-CNN到YOLO v3)
  13. ESP32 C3 使用ConfigWiFi配网
  14. Paypal联手信用卡Discover 打压Square和星巴克威风
  15. JavaScript 实现微信公众号内分享功能
  16. 专访仙工智能叶杨笙:工业产品如何提升研发效能?
  17. Java学习记录 类的高级特性篇
  18. mysql导入微信小程序云开发_微信小程序-云开发数据库上传json文件
  19. 【信管1.18】IT服务管理与监理
  20. linux镜像8.0,从ISO镜像安装Red Flag inWise V8.0

热门文章

  1. 基于Auto.js的淘宝抢购助手+京东双十一活动助手。亲测运行正常!
  2. 机器学习规则学习_使用机器学习发现动作规则
  3. 一米霜降肥牛,煎饼果子,all you can eat 牛油串串
  4. Eclipse - 取消英文拼写检查
  5. 什么是视频编码 编解码技术及压缩技术
  6. 1.3版走迷宫小游戏(C++)
  7. killer queen ctf
  8. 极限、连续、导数与微分
  9. 如何在AI中复制路径制作唯美动画
  10. 射频加热原理及其参数