###还是用代码来作为笔记吧,写的比较乱,见谅啊!
#ifndef _CLASSDEMO_H_
#define _CLASSDEMO_H_
namespace PoEdu
{
class ClassDemo
{
public:
ClassDemo();
ClassDemo(int num);
ClassDemo &operator=(const ClassDemo &other);
ClassDemo(const ClassDemo &other); // 拷贝构造函数,它的格式是固定的,它的格式无法发生改变,所以它是无法重载的
// 那么我们不写,编译器会默认帮我们生成一个,如果写了,编译器就不再生成了
//// 如果我们写了这样一个函数,那么就永远无法通过编译的,会涉及到一个东西是 无限递归
//// 那么为什么会进行无限递归呢?原因很简单,因为形参在被调用的时候,要被实参化,实参化就是进行一个拷贝,那么在拷贝的时候
//// 就会调用一个构造函数(即拷贝构造函数,那么拷贝构造函数就是它本身),所以就变成了无限的递归
//ClassDemo(const ClassDemo other)
//{
//
//}
~ClassDemo();
int GetNum() const;
private:
int _num;
};
}
#endif // !_CLASSDEMO_H_
#include "ClassDemo.h"
#include <iostream>
namespace PoEdu
{
ClassDemo::ClassDemo()
{
std::cout << "ClassDemo(" << _num << ")" << std::endl;
}
// 初始化列表
ClassDemo::ClassDemo(int num) : _num(num)
{
std::cout << "ClassDemo(" << num << ")" << std::endl;
}
ClassDemo& ClassDemo::operator=(const ClassDemo& other)
{
std::cout << "operator=(const ClassDemo& other)" << _num << std::endl;
_num = other._num;
return *this;
}
ClassDemo::ClassDemo(const ClassDemo& other)
{
std::cout << "ClassDemo(const ClassDemo& other)" << _num << std::endl;
_num = other._num;
}
ClassDemo::~ClassDemo()
{
std::cout << "~ClassDemo(" << _num << ")" << std::endl;
}
int ClassDemo::GetNum() const
{
return _num;
}
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include "ClassDemo.h"
空类
//class AAA
//{
// // 默认构造 AAA(){}
// // 默认析构 ~AAA(){}
// // 默认拷贝构造 AAA(const AAA& other){} // 这个函数就是一个空函数,只是针对于这个空类来说的
// // 默认赋值 AAA &operator=(const AAA &other){ return *this; } // 同样也是什么也不做
//};
不是空类
//class AAA
//{
//public:
// // 默认构造 AAA(){} 没有发生改变
// AAA(int num, int other) // 此时默认构造函数就没有了
// {
// _num = num;
// _other = other;
// }
// // 默认析构 ~AAA(){} 没有发生改变
// // 默认拷贝构造 AAA(const AAA& other){_num = other._num; _other = otehr._other;} // 这个函数就是一个空函数,只是针对于这个空类来说的 发生改变了
// // 因为拷贝构造函数是一个功能函数
// // 默认赋值 AAA &operator=(const AAA &other){ _num = other._num; _other = otehr._other; return *this; } // 同样也是什么也不做 发生改变了
//
// void SetNum(int num)
// {
// _num = num;
// }
// int GetNum()
// {
// return _num;
// }
//private:
// int _num;
// int _other;
//};
class MyString
{
public:
// 默认构造函数
MyString()
{
_len = 100;
_str = new char[_len];
}
MyString(char *str)
{
_len = strlen(str);
_str = new char[_len + sizeof(char)];
strcpy(_str, str);
}
// 默认析构函数
~MyString()
{
if (_str)
delete[]_str;
}
// 默认拷贝构造函数
// MyString(const MyString& other){_str = other._str; }
// 默认赋值函数
// MyString &MyString(cosnt MyString &other){ _str = other._str; }
// 那么此时的默认拷贝构造和默认的赋值函数就开始变得危险了,需要自己实现了
MyString(const MyString &other)
{
// 对象分为两种
// 1.面向对象
// 2. 使用对象
// 这种我们称之为深拷贝,这样才是安全的
// 深拷贝指的是在对象中,维护了所有参数的生命周期,所有参数的生命周期和我的对象的生命周期是一致的
delete[] _str;
_len = other._len;
_str = new char[_len + sizeof(char)];
strcpy(_str, other._str);
// 这种直接对指针赋值的行为我们称为浅拷贝,是存在风险的(当被delete的时候)
// 没有维护参数的生命周期,是由外部来维护的
//_str = other._str;
//_len = other._len;
// 但是如果没有指针的话,深拷贝和浅拷贝是一样的
// 我们并不是说深拷贝就一定比浅拷贝好,需要根据我们的业务来定
// 有的时候我们是不需要维护对象参数的生命周期的
}
MyString & operator=(const MyString &other)
{
delete[] _str;
_len = other._len;
_str = new char[_len + sizeof(char)];
strcpy(_str, other._str);
return *this;
//_str = other._str;
//_len = other._len;
}
char *GetString()
{
return _str;
}
private:
char *_str;
int _len;
};
void f1(PoEdu::ClassDemo demo)
{
demo.GetNum();
}
void f2(const PoEdu::ClassDemo &demo)
{
demo.GetNum();
}
// 上面的两个函数在使用上没有任何区别,但是在参数传递的时候,就会有区别
// f1产生了临时对象,并且销毁了临时对象,所以在函数内部使用的也是临时对象
// 然而f2没有产生临时对象,使用的外部对象
// 说明了传递引用会更加节约时间,提高效率,但是这样的引用传递的风险也大
// 那么怎么办呢?应该加上const,但是,const对象的引用在调用函数的时候,有一个限制条件
// 那就是必须调用常成员函数
int main()
{
using namespace PoEdu;
// 第3.4节课 测试3.3节课的内容
//ClassDemo demo = 10; // 相当于ClassDemo demo(10);
// A 构造函数 (更详细的说应该是转换构造函数)
// B 隐藏的operator=函数
// 但凡有一个新的对象的产生,100%就需要调用构造函数
//demo = 20; // 正确答案是 C
// A operator=
// B temp
// C temp 默认的operator=
// 然而,当我们加上ClassDemo &operator=(const int other)这个函数之后,那么上面的答案就会发生改变,答案就会变成A
// 为什么会这样呢?因为我们重载的ClassDemo &operator=(const int other)这个函数和demo=20是最匹配的,所以就会先调用
// 这个函数了,这和我们的重载函数的调用是一样的
//ClassDemo demo1 = demo; // 这句话说明我们肯定有一个ClassDemo(ClassDemo)的函数,否则的话,肯定编译不通过的
// 然而我们并没有写这个函数,那就是编译器帮我们生成了这个函数了
//ClassDemo demo(10);
//f1(demo);
//f2(demo);
//AAA aaa, bbb;
//aaa.SetNum(10);
//bbb = aaa; // 是一个赋值函数
// // 但是这句话能够编译通过是因为有一个默认的AAA &operator=(const AAA &other){ return *this; }函数
//std::cout << bbb.GetNum() << std::endl;
//AAA aaa;
//aaa.SetNum(10);
//AAA bbb = aaa; // 是一个默认构造函数
//std::cout << bbb.GetNum() << std::endl;
// 上面的例子说明了默认的拷贝构造函数和默认的赋值函数都是功能型函数
MyString str("I Love Mark!!!");
MyString sb = str; //拷贝构造
std::cout << sb.GetString() << std::endl;
sb = "I Need Mark!!!";
// 上面会做三件事情
// 1.构造临时对象 temp
// 2. operator=(const MyString& other) { sb._str = other._str; _len = other._len; } 这里的other就是temp
// 3. 析构temp,那么第二步的操作就会变得要命了 delete temp._str delete sb._str(野指针)
std::cout << sb.GetString() << std::endl;
// 使用野指针是一种未定义的行为
// debug 下面的 0xCCCCCCCC是未经过初始化的值
// 0xDDDDDDDD是已经删除的值 这些都是一般性的,不是百分之百的
// 而在release版本下面就是随机的
return 0;
}