PoEdu培训 C++班 第五课 类和对象(3)
文章类别: 培训笔记 0 评论

PoEdu培训 C++班 第五课 类和对象(3)

文章类别: 培训笔记 0 评论

类和对象(3)

命名空间

命名空间主要作用就是用来区分组织的.
在相同的命名空间中, 是不可以有重复的类的.

一般我们都用命名空间包含类, 防止重名.

注意事项

使用 using namespace xxx; 很容易让命名空间被污染

什么是命名空间污染?

// 命名空间污染例子
// 由于文件较多 请注意观察

// string.h --> 自定义类
namespace Hades
{
    class string
    {
    public:
        char* GetString()
        {
            return _str;
        }
    private:
        char* _str;
    };
}

// main.cpp  --> 测试类
#include <string>
#include "string.h"

using namespace std;
using namespace Hades;

int main()
{
    // 命名空间被污染了
    // 编译不通过
    string stdStr;
    string hadesStr;

    return 0;
}

所以, 对using namespace xxx;不能太过于依赖, 要防止命名空间污染
using namespace xxx; 可以写在方法内部, 这样可以将作用域仅限于方法内部, 更加的安全

构造函数

它是我们的特殊的成员函数
一般为 public, 也可能为private, private的时候无法新建对象(不涉及其他技巧).
在新建对象的时候会被自动调用
如果不提供, 编译器会提供一个默认的构造函数
默认构造函数什么都没有, 什么参数都不需要传递
构造函数可以重载.
提供任意一个构造函数后, 编译器就不会提供默认的构造函数了

转换构造函数

只需要传入一个参数的构造函数, 会被提升为转换构造函数.
包括默认实参等技术手段在内, 能够使构造函数只需要传递一个参数的构造函数...
转换构造函数可以使用 = 进行赋值.

// 转换构造函数例子

// ClassDemo.h
#ifndef _CLASS_DEMO_H_
#define _CLASS_DEMO_H_

namespace Hades
{
    class ClassDemo
    {
    public:
        ClassDemo(){}

        ClassDemo(int num)
        {
            _num = num;
        }

        ~ClassDemo(){}

        int GetNum()
        {
            return _num;
        }
    private:
        int _num;
    };
}

#endif //!_CLASS_DEMO_H_

// main.cpp
#include <iostream>
#include "ClassDemo.h"

int main()
{
    using namespace Hades;
    ClassDemo demo = 2; // 转换构造函数
    // 此时, 这句话相当于
    // ClassDemo demo(2);

    return 0;
}

explicit关键字

explicit关键字只对构造函数起作用, 用来抑制隐式转换.

// explicit例子

// ClassDemo.h
#ifndef _CLASS_DEMO_H_
#define _CLASS_DEMO_H_

namespace Hades
{
    class ClassDemo
    {
    public:
        ClassDemo(){}

        explicit ClassDemo(int num)
        {
            _num = num;
        }

        ~ClassDemo(){}

        int GetNum()
        {
            return _num;
        }
    private:
        int _num;
    };
}

#endif //!_CLASS_DEMO_H_

// main.cpp
#include <iostream>
#include "ClassDemo.h"

int main()
{
    using namespace Hades;
    ClassDemo demo = 2; // 转换构造函数 报错
    ClassDemo demo2(2); // 成功

    return 0;
}

因为加了 explicit 关键字, 转换构造函数被抑制, 不能够在被隐式转换
所以, 转换构造函数失败.

构造函数总结

如果没有写任何构造函数, 会生成默认构造函数(无需传参).

赋值函数

// 赋值函数例子

// ClassDemo.h
#ifndef _CLASS_DEMO_H_
#define _CLASS_DEMO_H_

namespace Hades
{
    class ClassDemo
    {
    public:
        ClassDemo(){}

        ClassDemo(int num)
        {
            _num = num;
        }

        ~ClassDemo(){}

        int GetNum()
        {
            return _num;
        }
    private:
        int _num;
    };
}

#endif //!_CLASS_DEMO_H_

// main.cpp
#include <iostream>
#include "ClassDemo.h"

int main()
{
    using namespace Hades;
    ClassDemo demo = 2; // 转换构造函数
    // 此时, 这句话相当于
    // ClassDemo demo(2);
    demo = 5;   // 赋值函数
    // 它相当于如下操作:
    // {
    //     ClassDemo tmp(5);
    //     demo.operator=(tmp);
    // }

    return 0;
}
根据以上例子来看

赋值函数的样式就是这样的:

ClassDemo& operator=(const ClassDemo& other)
{
    if (this == &other)
        return *this;
    _num = other._num;
    return *this;
}

注: 关注点在函数样式, 代码是我自己编写的, 不确定提供的默认赋值函数中是否有自复制判断.

为什么可以 "demo = 5;" ?

编译器思考过程

赋值函数是可以重载的!

我们可以自己写赋值函数, 来进行我们的一些自定义操作.
编译器会生成默认的赋值函数, 接收的参数是当前类的对象引用.

扩展阅读:
MSDN微软帮助文档 - 复制构造函数和复制赋值运算符 (C++)").

初始化列表

构造函数有个特殊的初始化方式叫初始化表达式表(简称初始化表)
初始化表位于函数参数表之后, 在函数体 {} 之前.
该表里的初始化工作发生在函数体内的任何代码被执行之前.

类的 const 常量只能在初始化表中被初始化,
因为它不能在函数体内用赋值的方式来初始化.
引用类型的成员变量只能在初始化表中被初始化.

类的数据成员的初始化可以采用初始化表或函数体内赋值两种方式
这两种方式的效率不完全相同

非内部数据类型的成员对象应当采用初始化列表方式初始化, 以获取更高的效率
对于内部数据类型的数据成员而言, 两种初始化方式的效率几乎没有区别, 但函数体内赋值方式能使程序版式更清晰

// 初始化列
// 此种方式高效, 并且可以为 const, 引用类型进行初始化
// 等同于
// int _num = num;
// int _other = num1;
ClassDemo(int num, int other) : _num(num), _other(num1)
{

}

ClassDemo(int num, int other)
{
    // 计算列
    // int _num;
    // _num = num;
    // int _other;
    // _other = num1;
     _num = num;
     _other = num1;
}

几个小知识

小知识①

在使用默认构造函数的时候
如果使用 ClassDemo* demo = new ClassDemo; 来进行初始化
和使用 ClassDemo* demo = new ClassDemo(); 来进行初始化, 是不一样的.
后者会默认将成员变量初始化为 0.

小知识②

#include尽量不写到头文件中
因为在预编译时, 头文件会展开
在展开后, 如果头文件中包含了过多的头文件, 编译速度会变慢
尤其是自己编写的本地头文件

小知识③

可以使用预声明(前置声明)来解决不在头文件中include的问题

注意:
预声明之后, 只能使用该类型的指针或者引用.

以下 预声明(前置声明) 内容转载自: http://blog.csdn.net/yunyun1886358/article/details/5672574

举个形象点的例子,就是我要盖一个屋子(CHOuse),光有屋子还不行啊,我还得有床(CBed)。但是屋子还没盖好,总不能先买床吧,床的大小我定了,改天买。先得把房子盖好,盖房子的时候我先给床留个位置,等房子盖好了,我再决定买什么样的床。
前置声明就是我在声明一个类(CHouse)的时候,用到了另外一个类的定义(CBed),但是CBed还没有定义呢,而且我还先不需要CBed的定义,只要知道CBed是一个类就够了。
那好,我就先声明类CBed,告诉编译器CBed是一个类(不用包含CBed的头文件):

class CBed;

然后在CHouse中用到CBed的,都用CBed的指针类型代(因为指针类型固定大小的,但是CBed的大小只用知道了CBed定义才能确定)。
等到要实现CHouse定义的时候,就必须要知道CBed的定义了,那是再包好CBed的头文件就行了。

前置声明有时候很有用,比如说两个类相互依赖的时候要。还有前置声明可以减少头文件的包含层次,减少出错可能。
上面说的例子:

// House.h
class CBed; // 盖房子时:现在先不买,肯定要买床的
class CHouse
{
    CBed* bed; // 我先给床留个位置
public:
    CHouse(void);
    virtual ~CHouse(void);
    void GoToBed();
};

// House.cpp
#include "Bed.h"
#include "House.h" // 等房子开始装修了,要买床了

CHouse::CHouse(void)
{
    bed = new CBed(); // 把床放进房子
}

CHouse::~CHouse(void)
{
}

void CHouse::GoToBed()
{
    bed->Sleep();
}

// Bed.h
class CBed
{

public:
    CBed(void);
    ~CBed(void);
    void Sleep();
};

// Bed.cpp
#include "Bed.h"

CBed::CBed(void)
{
}


CBed::~CBed(void)
{
}

void CBed::Sleep()
{

}

前置声明中的陷阱

  1. CBed* bed; 必须用指针或引用

引用版本:

// House.h  
class CBed; // 盖房子时:现在先不买,肯定要买床的  
class CHouse  
{  
    CBed& bed; // 我先给床留个位置  
    // CBed bed; // 编译出错  
public:  
    CHouse(void);  
    CHouse(CBed& bedTmp);  
    virtual ~CHouse(void);  
    void GoToBed();  
};  
  
// House.cpp  
#include "Bed.h"  
#include "House.h" // 等房子开始装修了,要买床了  
  
CHouse::CHouse(void)  
    : bed(*new CBed())  
{  
    CBed* bedTmp = new CBed(); // 把床放进房子  
    bed = *bedTmp;  
}  
  
CHouse::CHouse(CBed& bedTmp)  
    : bed(bedTmp)  
{  
}  
  
CHouse::~CHouse(void)  
{  
    delete &bed;  
}  
  
void CHouse::GoToBed()  
{  
    bed.Sleep();  
}  
  1. 不能在CHouse的声明中使用CBed的方法
使用了未定义的类型CBed;
bed->Sleep的左边必须指向类/结构/联合/泛型类型
class CBed; // 盖房子时:现在先不买,肯定要买床的  
class CHouse  
{  
    CBed* bed; // 我先给床留个位置  
    // CBed bed; // 编译出错  
public:  
    CHouse(void);  
    virtual ~CHouse(void);  
    void GoToBed()  
    {  
        bed->Sleep();  // 编译出错,床都没买,怎么能睡  
    }  
};  
  1. 在CBed定义之前调用CBed的析构函数
// House.h  
class CBed; // 盖房子时:现在先不买,肯定要买床的  
class CHouse  
{  
    CBed* bed; // 我先给床留个位置  
    // CBed bed; // 编译出错  
public:  
    CHouse(void);  
    virtual ~CHouse(void);  
    void GoToBed();  
    void RemoveBed()  
    {  
        delete bed; // 我不需要床了,我要把床拆掉。还没买怎么拆?  
    }  
};  
  
// House.cpp  
#include "Bed.h"  
#include "House.h" // 等房子开始装修了,要买床了  
  
CHouse::CHouse(void)  
{  
    bed = new CBed(); // 把床放进房子  
}  
  
CHouse::~CHouse(void)  
{  
    int i = 1;  
}  
  
void CHouse::GoToBed()  
{  
    bed->Sleep();  
}  
  
// Bed.h  
class CBed  
{  
    int* num;  
public:  
    CBed(void);  
    ~CBed(void);  
    void Sleep();  
};  
  
// Bed.cpp  
#include "Bed.h"  
  
CBed::CBed(void)  
{  
    num = new int(1);  
}  
  
CBed::~CBed(void)  
{  
    delete num; // 调用不到  
}  
  
void CBed::Sleep()  
{  
  
}  
  
//main.cpp  
#include "House.h"  
  
int main()  
{  
    CHouse house;  
    house.RemoveBed();  
}  

前置声明在友元类方法中的应用

《C++ Primer 4 Edition》在类的友元一章节中说到,如果在一个类A的声明中将另一个类B的成员函数声明为友元函数F,那么类A必须事先知道类B的定义;
类B的成员函数F声明如果使用类A作为形参,那么也必须知道类A的定义,那么两个类就互相依赖了。
要解决这个问题必须使用类的前置声明。例如:

// House.h  
#include "Bed.h"  
class CHouse  
{  
    friend void CBed::Sleep(CHouse&);  
public:  
    CHouse(void);  
    virtual ~CHouse(void);  
    void GoToBed();  
    void RemoveBed()  
    {  
    }  
};  
  
// House.cpp  
#include "House.h"  
  
CHouse::CHouse(void)  
{  
}  
  
CHouse::~CHouse(void)  
{  
    int i = 1;  
}  
  
void CHouse::GoToBed()  
{  
}  
  
// Bed.h  
class CHouse;  
class CBed  
{  
    int* num;  
public:  
    CBed(void);  
    ~CBed(void);  
    void Sleep(CHouse&);  
};  
  
// Bed.cpp  
#include "House.h"  
CBed::CBed(void)  
{  
    num = new int(1);  
}  
  
CBed::~CBed(void)  
{  
    delete num;  
}  
  
void CBed::Sleep(CHouse& h)  
{  
  
}  
以上 预声明(前置声明) 内容转载自: http://blog.csdn.net/yunyun1886358/article/details/5672574

下节预告

类对象
拷贝构造函数
深拷贝 浅拷贝

未完待续

如有错误,请提出指正!谢谢.

回复