1-->类 命名空间
1.0 复习构造函数:1 与类同名 2 没有返回值 3 自动生成 4 手动后,不会自动生成 5 不在特定的情况下,不会私有
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 ;
}
看图
所以在主函数所在的界面,最好不要写全局的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();
std::cout << demo.GetNum() << std::endl;
std::cout << demo1.GetNum() << std::endl; return ;
}
运行:
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 ;
}
答案是:没有~!
5.2 改下代码
#include "ClassDemo.h"
#include <iostream> int main()
{
using namespace PoEdu; ClassDemo *demo = new ClassDemo; delete demo; return ;
}
运行:
调用带参数的: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 ;
}
6.3 如上图,malloc()它不会调用构造函数,它只会开辟空间。所以生成一个对象,用new是准确的。同样,delete会调用析构函数,free()不会调用析构函数。
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 = ; demo = ; //赋值函数
std::cout << demo.GetNum() << std::endl; return ;
}
断点,运行
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 = ; //赋值函数
std::cout << demo.GetNum() << std::endl; return ;
}
运行:没有了 转换构造函数,再看拷贝赋值函数的结果:
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 = ; //赋值函数
std::cout << demo.GetNum() << std::endl; return ;
}
运行:
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 = ; demo = ; ClassDemo demo1();
demo = demo1; demo = ; //赋值函数
std::cout << demo.GetNum() << std::endl; return ;
}
运行:
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时,初始化可以赋初值,还有当变量为引用时,初始化时就能给其赋值,不然就不能赋值了。
作业:做好这几天的笔记。