第三课--之三(类和对象)

命名空间

C++中使用命名空间来避免命名的冲突(用来区分组织的),我们在写类的时候,一般都需要加上一个命名空间。

命名空间的三种用法

// 代表当前文件要导入PoEdu命名空间的所有内容
// 第一种用法
using namespace PoEdu;

// 第二种用法
namespace PoEdu
{
    ClassDemo::ClassDemo()
    {
    }

    ClassDemo::~ClassDemo()
    {
    }
}
// 第三种用法
PoEdu::ClassDemo::ClassDemo()
{
}

PoEdu::ClassDemo::~ClassDemo()
{
}

以上三种用法在功能上都是等价的,但是在细节上还是有所区别的,主要区别在于命名空间的污染

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

我们自己写一个string类,.h中的内容如下

namespace PoEdu
{
    class string
    {

    };
}

我们这样使用:
第一种使用方法

#include "string"
#include <string>

int main()
{
    std::string stdstring;
    PoEdu::string poedustring;

    return 0;
}

这样的话编译是可以通过的,没有问题,还没有被命名空间所污染。

我们再看下面的情况,第二种使用方法

#include "string"
#include <string>

int main()
{
    string stdstring;
    PoEdu::string poedustring;

    return 0;
}

此时的命名空间也还没有被污染。
第三种使用方法
有时候我们为了方便,把另一个命名空间PoEdu也直接包含过来了,代码如下

#include "string.h"
#include <string>

using namespace std;
using namespace PoEdu;

int main()
{
    string stdstring;
    string poedustring;

    return 0;
}

此时编译就会失败
提示信息如下所示

1>------ 已启动生成: 项目: classDemo, 配置: Debug Win32 ------
1>  main.cpp
1>c:\users\administrator\desktop\classdemo\classdemo\main.cpp(9): error C2872: “string”: 不明确的符号
1>  c:\program files (x86)\microsoft visual studio 14.0\vc\include\xstring(2634): note: 可能是“std::basic_string<char,std::char_traits<char>,std::allocator<char>> std::string1>  c:\users\administrator\desktop\classdemo\classdemo\string.h(6): note: 或  “PoEdu::string1>c:\users\administrator\desktop\classdemo\classdemo\main.cpp(10): error C2872: “string”: 不明确的符号
1>  c:\program files (x86)\microsoft visual studio 14.0\vc\include\xstring(2634): note: 可能是“std::basic_string<char,std::char_traits<char>,std::allocator<char>> std::string1>  c:\users\administrator\desktop\classdemo\classdemo\string.h(6): note: 或  “PoEdu::string”
========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ==========

上面就是我们所说的命名空间污染。所以我们最好的做法是第一种,该是谁的就用谁的。

关于包含头文件的位置

如果在.h文件中包含了本地的.h文件时,它在编译的时候会反复不停地去包含,给编译器造成了很大的压力;
但是系统级别的头文件可以包含到.h文件中;
.cpp文件中想怎么包含头文件就怎么包含头文件

内容在代码里面,就先把代码贴上去吧

#ifndef _CLASSDEMO_H_
#define _CLASSDEMO_H_

// 下面相当于是初始化列
// int _num = num;
// int _otehr = num;
// 下面相当于是计算列
// int _num;
// _num = num;

// 然而上面的貌似看上去没什么区别,难道对于效率的提高有多少吗?没有看出来!!!
// 其实初始化列表还有其他的作用,对于const的变量,只能在初始化列的时候进行赋值,而在
// 计算列的时候就无法进行赋值了
// 不仅仅是const变量需要在初始化的时候就赋值,我们的引用在初始化的时候也需要进行赋值
namespace PoEdu
{
    class ClassDemo
    {
    public:
        ClassDemo();    // 一个都不写的时候会生成默认构造函数
        explicit ClassDemo(int num);    // 只需要传递一个参数的这一类的构造函数,我们称为 转换构造函数
        ~ClassDemo();
        // 如果我们不写赋值函数,编译器会默认的为我们生成一个赋值函数
        //ClassDemo &operator=(const ClassDemo &other); // 默认生成的就是这个样子的,它的做法就是一个简单的赋值
        // 默认赋值函数与我们的默认构造函数不同,不管我们自己实现不实现赋值函数,
        // 编译器都会为我们生成一个默认的赋值函数
        //ClassDemo &operator=(const int other);

        // 初始化列表
        explicit ClassDemo(int lhs, int rhs) : _num(lhs), _other(rhs)   // 初始化列(属于初始化的过程)
        {
            // 计算列(属于赋值的过程)
            _num = lhs;
            _other = rhs;
        }

        int GetNum();

    private:
        int _num;
        int _other;
    };
}

#endif // !_CLASSDEMO_H_
#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;
    //}

    //ClassDemo& ClassDemo::operator=(const int other)
    //{
    //  std::cout << "operator=( int = " << _num << ")" << std::endl;
    //  _num = other;
    //  return *this;
    //}

    int ClassDemo::GetNum()
    {
        return _num;
    }
}
#include <iostream>
#include "ClassDemo.h"


int main()
{
    using namespace PoEdu;


    // 下是构造函数的例子
    //ClassDemo demo;
    //ClassDemo demo1(10);
    //std::cout << demo.GetNum() << std::endl;
    //std::cout << demo1.GetNum() << std::endl;
    /*ClassDemo *dm = new ClassDemo();
    std::cout << dm->GetNum() << std::endl;
    ClassDemo *dm1 = new ClassDemo(20);
    std::cout << dm1->GetNum() << std::endl;
    delete dm;
    delete dm1;*/
    //ClassDemo array[10];          // 会调用10次无参的构造函数
    //ClassDemo array[10] = { 1 };  // 1次有参的构造函数,会调用9次无参的构造函数
    //ClassDemo array[10] = { 1,1,1,1 };    // 同理 4次有参的构造函数,会调用6次无参的构造函数
    //////////////////////////////////////////////
    //ClassDemo *pArray = new ClassDemo[10];    // 会调用10次无参的构造函数
    //delete[]pArray;
    ////////////////////////////////////
    // 此时就什么也不会调用了,这是为什么呢?
    // 使用malloc是不会调用我们的构造函数的,而new能够调用构造函数
    // ClassDemo *demo = static_cast<ClassDemo*>( malloc(sizeof(ClassDemo)));
    // 同样的道理,delete和free的区别是:delete会调用析构函数,而free不会调用析构函数

    // 下面是转换构造函数的例子
    ClassDemo demo = 10;    // 此时已经调用了我们的一个构造函数了,这个构造函数是ClassDemo(int num),只有一个参数
                            // 需要注意的是:此时的"="不是赋值,它会调用我们的构造函数
                            // 它等价于 ClassDemo demo(10),但是这句话非常让我们费解,因为它非常容易让我们和demo = 20
                            // 这句话混淆,和这句话demo = demo1也非常的容易产生混淆
                            // 有的时候,我们并不想让他们进行隐式的转换,我们可以在构造函数前面加上explicit,此时
                            // ClassDemo demo = 10的这种写法就失效了,我们必须这样来写ClassDemo demo(10)
    demo = 20;  // 这种我们称为赋值函数    (如果我们将ClassDemo &operator=(const int other)这个函数干掉,那么此时也不能编译通过了)
    // 生成了一个临时对象,然后又把临时对象给析构了
    // 上面的代码相当于下面的3个步骤
    // 1. 新建一个临时对象(为了传递参数)  ClassDemo temp(20);
    // 2. 将临时对象赋值给demo对象            demo = temp;
    // 3. 再将临时对象析构掉
    // 相当于
    //{
    //  ClassDemo temp = 20;    // 之所以要创建这个临时变量,目的就是为了传递参数
    //  demo.operator=(temp);
    //}
    // 20(int)赋值给demo,这样是不行的,因为类型不一样ClassDemo &operator=(const ClassDemo &other);
    // 但是,我们有一个叫做隐式转换,即20 -> ClassDemo(2) 这是编译器的思考过程
    // 然后拿着ClassDemo(20)调用ClassDemo &operator=(const ClassDemo &other)这个函数
    // 如果将转换构造函数ClassDemo(int num)去掉,那么此时会发生什么情况呢?
    // 首先 会找operator=(int),没有找到
    // 然后,又会找ClassDemo(int),又没有找到,所以就会编译通不过
    //ClassDemo demo1(30);  // 这个例子主要是想说明自己实现了operator=(int)的函数之后,
                            //编译器还会不会为我们生成一个默认的operator=(ClassDemo)的函数
                            // 答案是编译器还是会默认的为我们生成,这是和默认构造函数所不同的地方
    //demo = demo1;

    // 构造函数的总结
    // 1. 如果没有写任何一个构造函数,会生成默认构造函数(无需传参),
    //                              1.1 如果写了任何一个,都不会再生成
    //                              1.2 只需要传递一个参数即可的构造函数,会提升为转换构造函数,用于隐式转换
    //                                  Test t = int;   // 这句话不是赋值,而是转换构造函数
    //                                      默认的赋值函数 接受的参数是当前类的对象的引用

    return 0;
}

主要的总结

构造函数的总结
1. 如果没有写任何一个构造函数,会生成默认构造函数(无需传参),
1.1 如果写了任何一个,都不会再生成
1.2 只需要传递一个参数即可的构造函数,会提升为转换构造函数,用于隐式转换
Test t = int; 这句话不是赋值,而是转换构造函数
默认的赋值函数 接受的参数是当前类的对象的引用

初始化列表的总结:
初始化列表是用来初始化const变量和引用的,因为const变量和引用都必须在初始化的时候就得赋值,否则编译时不会通过的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值