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

1-->类  命名空间 

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

1.1   新建    两种方法示范  其一:在vs中选择类,编译器帮我们生成一部分默认代码   其二:手动添加头文件,添加cpp文件。

 

1.2  域作用符::

 

 

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

 

 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 ,因为要考虑命名空间污染的问题:

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 0;
}

看图

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

 

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

#include "ClassDemo.h"
using namespace PoEdu;

ClassDemo::ClassDemo()
{
}


ClassDemo::~ClassDemo()
{
}

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

 

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

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

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

 


 

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>可以有限使用,要用到就包含。如果一定要包含我们自己写的头文件,最好的写法是:

 

 


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(10);
    std::cout << demo.GetNum() << std::endl;
    std::cout << demo1.GetNum() << std::endl;
    
    return 0;
}

运行:

 

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;
    }
}

再次运行:

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

 

 


5--> new  delete

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

 

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

#include "ClassDemo.h"
#include <iostream>

int main()
{
    using namespace PoEdu;
    
    ClassDemo *demo = new ClassDemo;

    return 0;
}

答案是:没有~!

 

5.2  改下代码 

#include "ClassDemo.h"
#include <iostream>

int main()
{
    using namespace PoEdu;
    
    ClassDemo *demo = new ClassDemo;

    delete demo;

    return 0;
}

 运行:

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

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

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

 


 

 6-->  定义对象数组

 

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

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

 

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

 
 

#include "ClassDemo.h"
#include <iostream>

int main()
{
    using namespace PoEdu;
    
    ClassDemo *demo = static_cast<ClassDemo*>(malloc(sizeof(ClassDemo)));
    free(demo);

    return 0;
}

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

 

 
7-->  转换构造函数
 

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

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;
    }
}

运行:

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

 

 


 

8--> 拷贝赋值函数 

 看代码:

#include "ClassDemo.h"
#include <iostream>

int main()
{
    using namespace PoEdu;
    
    ClassDemo demo = 20;

    demo = 30;   //赋值函数
    std::cout << demo.GetNum() << std::endl;    

    return 0;
}

断点,运行

 

 

 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_

 

 

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;
    }
}

 

运行:

 

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

 

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

 

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 = 30;   //赋值函数
    std::cout << demo.GetNum() << std::endl;    

    return 0;
}

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

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 个 ==========

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 = 30;   //赋值函数
    std::cout << demo.GetNum() << std::endl;    

    return 0;
}

运行:

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

 

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

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

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 = 1;
    
    demo = 2;

    ClassDemo demo1(3);
    demo = demo1;
    
    demo = 3;   //赋值函数
    std::cout << demo.GetNum() << std::endl;    

    return 0;
}

运行:

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

 


 

 9--> 总结

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

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

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

        

 


 

 10--> 初始化列表  :

 

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


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

 


 

posted on 2016-12-30 14:16  zzdoit  阅读(738)  评论(1编辑  收藏  举报

导航