PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

时间:2022-02-18 22:05:50

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

1-->类  命名空间

1.0  复习构造函数:1  与类同名   2  没有返回值   3  自动生成    4  手动后,不会自动生成    5  不在特定的情况下,不会私有

1.1   新建    两种方法示范  其一:在vs中选择类,编译器帮我们生成一部分默认代码   其二:手动添加头文件,添加cpp文件。
PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

1.2  域作用符::

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

1.3  看上去生成了空类,注意不能跨平台的代码。

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

1.4  自动生成的头文件,是要手动修改的 :  #pragma once    //这是windows中的特有表示,要替换成下面的写法。

#ifndef _ClASSDEMO_H_
#define _CLASSDEMO_H_ class ClassDemo
{
public:
ClassDemo();
~ClassDemo();
}; #endif //!_CLASSDEMO_H_

1.5  最佳的写法,还要加上命名空间:

#ifndef _ClASSDEMO_H_
#define _CLASSDEMO_H_ namespace PoEdu
{class ClassDemo
{
public:
ClassDemo();
~ClassDemo();
};
}
#endif //!_CLASSDEMO_H_

1.6  加入命名空间,能很好的避免类同名的问题。


2-->类  命名空间 污染

2.0  命名空间不建议写成using name space XXXXX ,因为要考虑命名空间污染的问题:

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

2.1  什么是命名空间污染呢?  

2.2  关于#include <>和#include " " 这两种运用的解释:<>表示先在系统目录里面查找头文件,“ ”则表示先在当前目录(工程目录)查找头文件。一般标准库用<>号,我们自己的头文件用“”号。

2.3  string.h里面还加一个命名空间:namespace PoDdu

#ifndef _STRING_H_
#define _STRING_H_ namespace PoEdu
{
class string
{ };
} #endif //!_STRING_H_

  string.cpp里面写上:

#include "ClassDemo.h"
#include "string.h"
#include <iostream>
#include <string>
using namespace std;
using namespace PoEdu; int main()
{
string stdstring;
PoEdu::string poedustring;
cout << stdstring; return ;
}

看图

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

所以在主函数所在的界面,最好不要写全局的using namespace XXXX

2.4  一定要写,可以写在别的cpp里面,如:ClassDemo.cpp

#include "ClassDemo.h"
using namespace PoEdu; ClassDemo::ClassDemo()
{
} ClassDemo::~ClassDemo()
{
}

这样子就不会影响全局,出现命名空间污染。命名空间其实就是用来区分“组织”的。

2.5  再举例:微软和谷歌都开发了一套库函数,里面都有string和system,那么想要同时使用两个组织的库,如果没有一套命名规则,那么肯定的重名,因为重名了,永远编译不过去。

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

加上命名空间,就避免了重名:

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

所以全局界面,少用using namespace XXXX;

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天


3-->类  头文件  include包含原则

3.0  再来看:ClassDemo.cpp这个include<>它写在cpp文件里面的,为什么不写头文件那边呢?

#include "ClassDemo.h"
#include <iostream> namespace PoEdu
{
ClassDemo::ClassDemo()
{
std::cout << "ClassDemo()" << std::endl;
} ClassDemo::~ClassDemo()
{
}
}

3.1  我们的头文件编译器最终在主函数之前,遇到#include “xxx” 的时候会展开,如果头文件开头就包含了太多的东西,编译器的压力就大了很多,特别是包含了很多本地的如:反复的#include "xxx" 多个,那么就都要一个个的展开来,一个个来判断它是否重复。所以头文件里面不需要使用的,就不写在头文件里面。  但系统级的#include <xxx>可以有限使用,要用到就包含。如果一定要包含我们自己写的头文件,最好的写法是:

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天


4-->类  构造函数  

ClassDemo.h

#ifndef _ClASSDEMO_H_
#define _CLASSDEMO_H_ namespace PoEdu
{
class ClassDemo
{
public:
ClassDemo();
ClassDemo(int num);
~ClassDemo();
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 )
{
std::cout << "ClassDemo(" << num << ")"<< std::endl;
} ClassDemo::~ClassDemo()
{
} int ClassDemo::GetNum()
{
return _num;
}
}

main.cpp

#include "ClassDemo.h"
#include <iostream> int main()
{
using namespace PoEdu;
ClassDemo demo;
ClassDemo demo1();
std::cout << demo.GetNum() << std::endl;
std::cout << demo1.GetNum() << std::endl; return ;
}

运行:

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

4.1  ClassDemo.cpp里面代码少了赋值,加上_num = num ;

#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()
{
} int ClassDemo::GetNum()
{
return _num;
}
}

再次运行:

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

4.2  在debug版本下面,未定义默认在内存给出0xcccccccc方便调试 ,所以得出同一个数值。在Release版本下面:

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天


5--> new  delete

5.0  有没有这种方式:ClassDemo *demo = new ClassDemo ;  答:有的。这种方式也会调用构造函数。

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

5.1  重点来了:这里用一个对象指针,new 了一个ClassDemo对象,构造函数如期运行了,析构函数有被调用吗?

#include "ClassDemo.h"
#include <iostream> int main()
{
using namespace PoEdu; ClassDemo *demo = new ClassDemo; return ;
}

答案是:没有~!

5.2  改下代码

#include "ClassDemo.h"
#include <iostream> int main()
{
using namespace PoEdu; ClassDemo *demo = new ClassDemo; delete demo; return ;
}

运行:

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

调用带参数的:ClassDemo *demo = new ClassDemo(10);

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

5.3  加上命名空间的写法:PoEdu::ClassDemo *pdemo2 = new Poedu::ClassDemo();  //没有参数的写法

5.4  还可以简化成:PoEdu::ClassDemo *pdemo2 = new Poedu::ClassDemo ;  //简化后面的括号。


 6-->  定义对象数组

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

6.0  这里会调用多少个构造函数?是有参还是无参?答:调用10次无参的构造。

6.1  那么这个写法会调用几次?都调用了无参还是有参?分别几次?ClassDemo arry[10] = {1};  答:1次有参,9次无参。如图:

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

6.2  再看这个:ClassDemo *pArray = new ClassDemo[10];  答:调用10次无参。

 PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天
 
#include "ClassDemo.h"
#include <iostream> int main()
{
using namespace PoEdu; ClassDemo *demo = static_cast<ClassDemo*>(malloc(sizeof(ClassDemo)));
free(demo); return ;
}

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

6.3  如上图,malloc()它不会调用构造函数,它只会开辟空间。所以生成一个对象,用new是准确的。同样,delete会调用析构函数,free()不会调用析构函数。

 

7-->  转换构造函数
 
PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

7.0  看这个:ClassDemo demo = 10; 编译下,成功通过~!运行:

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

7.1  当C++看到  类  对象名  = 数值; 会调用转换构造函数。此时的=并不是赋值操作,它会调用构造函数。基于这种特性,我们将只有一个参数的构造函数称为:转换构造函数。

7.2  转换构造函数,就是只需要输入一个参数的函数。ClassDemo demo =10;  可以把它看成:ClassDemo demo(10);

7.3  赋值是这样:demo = 20;这里一定要与转换构造函数区别对待。这里调用的是“拷贝赋值函数”。

7.4  再看示例:ClassDemo.h代码如下

#ifndef _ClASSDEMO_H_
#define _CLASSDEMO_H_ namespace PoEdu
{
class ClassDemo
{
public:
ClassDemo();
ClassDemo(int num,int other =100);
~ClassDemo(); //ClassDemo& operator=(const ClassDemo& other); int GetNum();
private:
int _num;
};
}
#endif //!_CLASSDEMO_H_

7.5  如果把cpp里面的代码改这样:

#include "ClassDemo.h"
#include <iostream> namespace PoEdu
{
ClassDemo::ClassDemo()
{
std::cout << "ClassDemo()" << std::endl;
} ClassDemo::ClassDemo(int num ,int other )
{
_num = num;
std::cout << "ClassDemo(" << num << ")" << std::endl;
} ClassDemo::~ClassDemo()
{
std::cout << "~ClassDemo( )" << std::endl;
} int ClassDemo::GetNum()
{
return _num;
}
}

运行:

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

7.6  说明:转换构造函数,它只需要一个参数正确的传递,只要能符合一个参数的赋值,它就能形成转换构造函数。只要符合ClassDemo demo(20);这样子语法的条件的函数,都可以形成转换构造函数。ClassDemo demo =20;这行代码的本质就是:ClassDemo demo(20);只是代码书写的方式稍有改变而已。它也是构造函数的一种。


8--> 拷贝赋值函数 

看代码:

#include "ClassDemo.h"
#include <iostream> int main()
{
using namespace PoEdu; ClassDemo demo = ; demo = ; //赋值函数
std::cout << demo.GetNum() << std::endl; return ;
}

断点,运行

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

8.1  ClassDemo& operator = (const ClassDemo& other);   operator操作符的意思。

8.2  新对象生成时,如果没有写构造函数,编译器会帮我们生成构造函数和析构函数,其实里面还有一系列默认函数被自动隐藏的生成了,当对象调用时,这些函数就被调用到,这里面就有一个很重要的函数:拷贝赋值函数。

8.3  ClassDemo& operator=(……);  这个是拷贝赋值函数 。上句代码不是重载,是默认生成的,拷贝赋值。这个函数里面做些什么事情呢?其实里面做的事情很简单,就是把每个参数进行拷贝。

代码:ClassDemo.h

#ifndef _ClASSDEMO_H_
#define _CLASSDEMO_H_ namespace PoEdu
{
class ClassDemo
{
public:
ClassDemo();
ClassDemo(int num);
~ClassDemo(); ClassDemo& operator=(const ClassDemo& other);
int GetNum();
private:
int _num;
};
}
#endif //!_CLASSDEMO_H_

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

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()
{
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;
}
}

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

运行:

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

8.5  再细说demo =2;这行代码,2是一个int整数,赋值给demo,它们之间的类型是不一致的,那么,这个等号呢,C++给出了默认的修改:operator=(const ClassDemo& other);因为有了这个默认的修改,等号在这就是拷贝赋值函数,注意,“=”现在是函数了。

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

8.6  在这里,C++提供了一个隐式转换的机制,把2提升为:ClassDemo()2;这样子就符合了“=”函数operator=(ClassDemo(2))的调用条件。

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

8.7  注意,这里如果我们的一个int类型2,它没办法构成ClassDemo(2);类型,后面的隐式转换,参数的传递,就会不成立。

8.8  ClassDemo.h    注释了    转换构造函数

#ifndef _ClASSDEMO_H_
#define _CLASSDEMO_H_ namespace PoEdu
{
class ClassDemo
{
public:
ClassDemo();
//ClassDemo(int num);
~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()
{
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 = ; //赋值函数
std::cout << demo.GetNum() << std::endl; return ;
}

运行:没有了 转换构造函数,再看拷贝赋值函数的结果:

1>------ 已启动生成: 项目: PoEdu_MyClass, 配置: Release Win32 ------
1> main.cpp
1>main.cpp(10): error C2679: 二进制“=”: 没有找到接受“int”类型的右操作数的运算符(或没有可接受的转换)
1> e:\c_code\poedu_myclass\poedu_myclass\ClassDemo.h(13): note: 可能是“PoEdu::ClassDemo &PoEdu::ClassDemo::operator =(const PoEdu::ClassDemo &)”
1> main.cpp(10): note: 尝试匹配参数列表“(PoEdu::ClassDemo, int)”时
========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ==========

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

8.9  编译器在这里,会先找默认的operator=,看operator=这个函数里面有没有能接收int类型的,如果没有,那么,再找后面的(const ClassDemo& other),看ClassDemo& other能不能匹配传一个int,也就是ClassDemo(int)这样子能不能成功。如果两次都失败,则编译器抛出错误异常。

8.10  可以得出,转换函数会使得 operator=得到实行。

8.11  是不是可以更改 operator=的参数为int型,让demo =2;这行代码编译通过呢?那是肯定的:

ClassDemo.h

#ifndef _ClASSDEMO_H_
#define _CLASSDEMO_H_ namespace PoEdu
{
class ClassDemo
{
public:
ClassDemo();
//ClassDemo(int num);
~ClassDemo(); //ClassDemo& operator=(const ClassDemo& other);
ClassDemo& operator=(const int 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()
{
std::cout << "~ClassDemo( " << _num << ")" << std::endl;
} ClassDemo& ClassDemo::operator=(const int other)
{
_num = other;
return *this;
} /*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 = ; //赋值函数
std::cout << demo.GetNum() << std::endl; return ;
}

运行:

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

8.12  其实这里的符号=,其本质是调用了某个带参数的函数。然而,现在此时,再来一个转换函数的代码:

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

8.13  反复的验证“=”加“参数类型”为一个函数。

8.14  还有一点,构造函数写了,就不自动默认生成,然而赋值函数却是,不管你写不写,始终都在~!一个构造函数都不写的时候,会生成默认构造函数。

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

8.15  下面验证一下:赋值函数,在手动写了以后,还会自动生成默认的拷贝赋值函数吗?

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()
{
std::cout << "~ClassDemo( " << _num << ")" << std::endl;
} ClassDemo& ClassDemo::operator=(const int other)
{
std::cout << "operator=int (" << _num << ")" << std::endl;
_num = other;
return *this;
} /*ClassDemo& ClassDemo::operator=(const ClassDemo& other)
{
std::cout << "operator=(" <<_num << ")" << std::endl;
_num = other._num;
return *this;
}*/ int ClassDemo::GetNum()
{
return _num;
}
}

ClassDemo.h

#ifndef _ClASSDEMO_H_
#define _CLASSDEMO_H_ namespace PoEdu
{
class ClassDemo
{
public:
ClassDemo();
ClassDemo(int num);
~ClassDemo(); //ClassDemo& operator=(const ClassDemo& other);
ClassDemo& operator=(const int other); int GetNum();
private:
int _num;
};
}
#endif //!_CLASSDEMO_H_

main.cpp

#include "ClassDemo.h"
#include <iostream> int main()
{
using namespace PoEdu; ClassDemo demo = ; demo = ; ClassDemo demo1();
demo = demo1; demo = ; //赋值函数
std::cout << demo.GetNum() << std::endl; return ;
}

运行:

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

8.16  看有没有调用默认的自动生成的赋值函数,当我有了一个手写的赋值函数的时候,看编译器还提不提供自动生成的默认赋值函数;

8.17  分析上例,一步步的调试,得出结论:不管你有没有写赋值函数,默认生成的赋值函数它都存在。它的做法就是帮我们做一个简单的赋值。赋值函数就相当于把“=”做了重载。

8.18  小结:这三行代码,很容易让人搞混语义 ,  1   ClassDemo demo = 1;     2   demo = 2;      3    demo = demo1;

8.19  记住了,第1个代码语义相当于:ClassDemo demo(1);   第2句和第3句都是拷贝赋值,“=”这里调用了拷贝赋值函数。他们之间的区别就是:第1它有创建一个新的对象。如果不想让第1种写法出现,那么,要怎么写呢,要加一个关键字:explicit

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天


 9--> 总结

9.0    如果没有写任何一个构造函数,会生成默认(无需传参)的构造函数;如果写了任何一个构造,它都不再生成默认构造  还会生成一个默认的赋值函数,它接收的参数是当前类的对象引用;

9.1     只需要传递一相参数即可的构造 函数,会提升为转换构造函数,用于隐式转换;如:Test t = int; 这不是赋值,是转换构造函数。

9.2    默认生成拷贝赋值函数,不管你有没有手写一个,默认的赋值函数始终存在。

        


 10--> 初始化列表  :

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天

10.0  好处:当一个变量为const时,初始化可以赋初值,还有当变量为引用时,初始化时就能给其赋值,不然就不能赋值了。

PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天


作业:做好这几天的笔记。

 
PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天