命名空间
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::string”
1> c:\users\administrator\desktop\classdemo\classdemo\string.h(6): note: 或 “PoEdu::string”
1>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::string”
1> 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变量和引用都必须在初始化的时候就得赋值,否则编译时不会通过的。