类和对象(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 demo = 2; // 不是赋值, 而是转换构造函数
赋值函数
// 赋值函数例子
// 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;" ?编译器思考过程
- 一个int型的值 5 过来了, 要赋值给 ClassDemo 类型的 demo.
- 尼玛, 直接拒绝, 不能直接赋值, 因为类型不同!
- 哎? 我试一下
隐式转换, 做编译器要严谨... - 哎呦~, 有个
转换构造函数, 雾草, 正好把int转换成了一个ClassDemo. - 哎呀, 我了个去, 类型一样了!
- 哦哦, 我正好提供了一个默认的
赋值函数 - 那就调用一下子给赋值吧.....
- 幸好看了一眼
隐式转换, 不然又丢编译器了...
赋值函数是可以重载的!
我们可以自己写
赋值函数, 来进行我们的一些自定义操作.
编译器会生成默认的赋值函数, 接收的参数是当前类的对象引用.
扩展阅读:
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()
{
}前置声明中的陷阱
- 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();
} - 不能在CHouse的声明中使用CBed的方法
使用了未定义的类型CBed;
bed->Sleep的左边必须指向类/结构/联合/泛型类型class CBed; // 盖房子时:现在先不买,肯定要买床的
class CHouse
{
CBed* bed; // 我先给床留个位置
// CBed bed; // 编译出错
public:
CHouse(void);
virtual ~CHouse(void);
void GoToBed()
{
bed->Sleep(); // 编译出错,床都没买,怎么能睡
}
}; - 在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下节预告
类对象
拷贝构造函数
深拷贝 浅拷贝未完待续如有错误,请提出指正!谢谢.
本文由 花心胡萝卜 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: 2017-01-04 at 01:50 am