PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天
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时,初始化可以赋初值,还有当变量为引用时,初始化时就能给其赋值,不然就不能赋值了。
作业:做好这几天的笔记。
PoEdu- C++阶段班【Po学校】-Lesson03_构造函数精讲 - 第5天的更多相关文章
- PoEdu - C++阶段班【Po学校】- Lesson03-4_构造函数&赋值函数&拷贝构造函数&学习方式 - 第6天
PoEdu - C++阶段班[Po学校]- 第6天 课堂选择题目: 1 关于转换构造函数 ClassDemo demo = 1; 调用转换构造函数 2 关于拷贝赋值函数 demo =2; 首 ...
- PoEdu - C++阶段班【Po学校】- 第3天
引用 C中指针的功能强大,使用起来繁杂,因为指针要控制的东西太多:有指针的类型,指针的解引用,指针空间内的值,它本身是有空间的,有自己的地址等.指针也是强大的,比如:我们要在函数之内,修改方法之外的值 ...
- PoEdu - C++阶段班【Po学校】- Lesson02_类与对象_第4天
复习:上节作业讲解 注意点: 设计SetString()的时候,要注意重置原来的空间. char * SetString(const char *str) { _len = strlen(str); ...
- PoEdu - C++阶段班【Po学校】- 第1课
1 C++开讲 C ++ 伟大的编程语言:能提高程序运行效率,节约更多的资源,"正确的使用C++,能够抑制全球变暖问题". 2 C++能力雷达图 通过 1效率 2灵活度 3 抽象 ...
- PoEdu - C++阶段班- Lesson07 To Lesson10_C to C++
07 重载导致的二义性 问题:为什么一定要重载呢?重载能方便我们注重函数的功能,当参数类型不确定时,我们能很便捷的利用重载的机制达到目的. 重载注意点:二义性 看代码: #include <c ...
- PoEdu - C++阶段班- Lesson02_C to C++
1 原生bool类型 c++里面的bool类型才是真正原生的true和faul,比如常见的大写的"BOOL",它就不是原生的. 原生的与非原生的bool,它们的区别: 详细说下原 ...
- PoEduo - C++阶段班【Po学校】-Lesson03-5_运算符重载- 第7天
PoEduo - Lesson03-5_运算符重载- 第7天 复习前面的知识点 空类会自动生成哪些默认函数 6个默认函数 1 构造 2 析构 3 赋值 4 拷贝构造 5 oper ...
- 3.PO如何给开发团队讲好故事
敏捷开发系列文章目录 讲出符合开发团队味口的故事. 上一章说了敏捷开发团队的构成与迭代过程,本章重点说一下迭代第一天的计划会议.熟话说“好的开始就成功了一半”,一个迭代的计划会议做得好不好确实直接注定 ...
- 清北学堂2018DP&图论精讲班 DP部分学习笔记
Day 1 上午 讲的挺基础的--不过还是有些地方不太明白 例1 给定一个数n,求将n划分成若干个正整数的方案数. 例2 数字三角形 例7 最长不下降子序列 以上太过于基础,不做深入讨论 例3 给定一 ...
随机推荐
- ipython notebook设置工作路径和自动保存.py文件 ipython_notebook_config.py
在安装完Anaconda,选择了配置环境变量后,打开cmd命令行 1. 打开命令行, 键入 ipython profile create 2. 键入 , 根据这个地址, 打开profile所在的文件夹 ...
- Centos下安装Mongodb
转自:http://nnzhp.cn/article/10/ Mongodb是一种nosql类型的数据库,高性能.易部署.易使用的特点在IT行业非常流行. 下面介绍一下mongodb的安装方式,这里我 ...
- python-etcd
Client 对象 ['_MDELETE', '_MGET', '_MPOST', '_MPUT', '__class__', '__contains__', '__del__', '__delatt ...
- R 统计学工具部署和使用
由于公司内部对于市场数据分析的需求,要求引入R统计工具,并集成到报表工具中.对于R的介绍,大家请百度一下,当然,最好能去看官方的说明 https://www.r-project.org/ 下面简单介绍 ...
- maven本地安装jar包同时生成pom文件
maven 本地安装jar包:mvn install:install-file -Dfile=本地路径/ojdbc12.jar -DgroupId=com.oracle -DartifactId=oj ...
- Python: 字典的基本操作
字典是Python里唯一的映射类型.字典是可变的.无序的.大小可变的键值映射,有时候也称为散列表或关联数组. 例子在下面: dic = {"apple":2, "oran ...
- Redis从基础命令到实战之字符串类型
字符串类型是Redis中最基本的数据类型,能存储任何形式的字符串和和二进制数据.本文以代码形式列举常用的操作命令,并在实践部分演示一个简单的商品管理功能,实现了通常使用关系型数据库开发的增改查功能,注 ...
- 《深度探索C++对象模型(Inside The C++ Object Model )》学习笔记
转载:http://dsqiu.iteye.com/blog/1669614 第一章 关于对象 使用class封装之后的布局成本: class并没有增加成本,data members直接内含在每一个c ...
- 315. Count of Smaller Numbers After Self
You are given an integer array nums and you have to return a new counts array. The counts array has ...
- iOS开发XCODE5 SVN配置 使用办法 (转) 收藏一下
标签: xcode5svn xcodesvn使用 xcode自带的svn xcodesvn版本操作 xcode自带svn版本 这两天响应老板要求,把所有代码放到公司的SVN服务器上,按照我的想法肯 ...