PoEdu - C++阶段班【Po学校】- 第6天 |
课堂选择题目:
1 关于转换构造函数 ClassDemo demo = 1; 调用转换构造函数
2 关于拷贝赋值函数 demo =2;
首先创建一个临时对象,再调用operator=
3 自己手动加了一个函数在头文件:ClassDemo& operator=(const int other); 那么demo =2 ;调用了什么:
这里此时不会调用构造函数,而是直接调用operator=
4 ClassDemo demo1 = demo; 这行是不是赋值?
不是赋值,它相当于ClassDemo demo1(demo);是构造函数。偶有一个新的对象产生,100%就需要调用构造函数。
5 demo =20;这句代码相当于: demo.operator=(ClassDemo(20));
1--> 拷贝构造函数
1.0 拷贝构造函数,是默认生成的,你不写编译器会自动生成。注意它的参数,一定是一个引用。
1.1 来看下面示例:
ClassDemo.h
#ifndef _ClASSDEMO_H_
#define _CLASSDEMO_H_ namespace PoEdu
{
class ClassDemo
{
public:
ClassDemo();
ClassDemo(int num);
ClassDemo(const ClassDemo& other);
~ClassDemo(); ClassDemo& operator=(const ClassDemo& other); int GetNum();
private:
int _num;
};
}
#endif //!_CLASSDEMO_H_
ClassDemo.cpp
#include "ClassDemo.h"
#include <iostream> namespace PoEdu
{
ClassDemo::ClassDemo()
{
std::cout << "ClassDemo()" << std::endl;
} ClassDemo::ClassDemo(int num )
{
_num = num;
std::cout << "转换构造ClassDemo(" << num << ")" << std::endl;
} 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;
} ClassDemo& ClassDemo::operator=(const ClassDemo& other)
{
std::cout << "operator=()……被调用 " <<_num << std::endl;
_num = other._num;
return *this;
} int ClassDemo::GetNum()
{
return _num;
}
}
main.cpp
#include "ClassDemo.h"
#include <iostream> int main()
{
using namespace PoEdu; ClassDemo demo ();
demo = ; ClassDemo demo1 = demo; std::cout << demo.GetNum() << std::endl; return ;
}
F10运行:
1.2 看ClassDemo.cpp那边代码图
继续F10,拷贝赋值
继续F10,
看看调用的是哪里:
接着就是打印,最后析构
1.4 拷贝构造函数的格式,要注意:参数里面一定是一个引用,如果不是引用,会发生什么结果:
1.5 编译器编译不成功,为什么会编译失败呢?这里面会有一个“无限递归”的问题出现。 答:如果手动写拷贝构造时,一定要注意参数,它一定是一个引用,如果不用引用,会产生一个严重的问题:无限递归
函数的形参,会被实参化,怎么实参化呢,它会调用一个拷贝,那么,又回到了本身,我们要写的就是拷贝构造函数,这里无限递归就形成了。编译一定是通不过的。
1.6 再看示例:
1.7 方法1,产生一个临时对象,这个临时对象是拷贝构造直接生成的,使用了这个临时对象后,立即销毁了。
1.8 再看方法2 f2完全没有对象的产生。
1.9 方法2传递的是当前对象的一个引用。没有调用构造函数,产生对象。
1.10 注意:能用引用就要用引用,但纯引用会改变变量的值,很危险,那么,就要加const,所以引用要配合const使用。
2-->空类 生成默认函数
2.0 一个空类,编译器会自动生成哪几种默认函数:
2.1 构造
2.2 析构
2.3 拷贝构造
2.4 赋值
2.5 有了参数后,哪些会发生改变:1 构造 不改变,2 析构不改变 3 拷贝构造改变 4 拷贝赋值 改变
2.6 注意4 个自动生成的函数中,当手动写了一个构造,那么默认的构造函数就没了。
3--> 浅拷贝 & 深拷贝
3.0 MyString类的示例:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream> class MyString
{
public:
MyString()
{
_len = ;
_str = new char[_len];
}
MyString(char* str):_len(strlen(str))
{
_str = new char[_len + sizeof(char)];
strcpy(_str, str);
}
~MyString()
{
if (_str)
delete[]_str;
}
char* GetString()
{
return _str;
} private:
char *_str;
int _len; }; int main()
{
MyString str("I love Mark!!");
MyString sb = str;
std::cout << sb.GetString() << std::endl; return ;
}
3.1 以上代码一点问题也没有,但如果我要加一句呢?请看:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream> class MyString
{
public:
MyString()
{
_len = ;
_str = new char[_len];
}
MyString(char* str):_len(strlen(str))
{
_str = new char[_len + sizeof(char)];
strcpy(_str, str);
}
~MyString()
{
if (_str)
delete[]_str;
}
char* GetString()
{
return _str;
} private:
char *_str;
int _len; }; int main()
{
MyString str("I love Mark!!");
MyString sb = str;
std::cout << sb.GetString() << std::endl;
sb = "I Need Mark!!";
std::cout << sb.GetString() << std::endl;
return ;
}
运行结果:
I love Mark!!
葺葺葺葺葺葺葺葺葺0
乱码出现。分析为什么乱码会出现 :
代码 sb = "I Need Mark!!";这里可以分解为3个步骤,1构造临时对象并传递参数(“I Need Mark!!”),参数是一个指针
2调用拷贝赋值函数:operator(MyString&)(sb._str = temp._str; _len = temp._len);注意高亮部分sb._str = temp._str;这是指针的赋值,指针是内存地址。
vs编译器在debug版本下,把野指针赋值为 0xdddddddd .在Vs2015编译器debug版本下面,未经初始化的值赋值为:0xCCCCCCCC,已经被delete的值赋值为:0xDDDDDDDD.
直接对指针赋值的拷贝,就是浅拷贝。浅拷贝没有维护参数的生命周期,给别人维护。
深拷贝就是考虑了数据的完整度的拷贝动作,对比浅拷贝,它维护了对象所有参数的生命周期,所有参数的生命周期和我的对象是同步的:
学习方法,反复嘱咐:基础很重要,基础知识点,要做到了如指掌。