构造函数精讲:
1:构造函数:
是一个特殊的成员函数,一般情况下为public:在一些特定的情况下可能会为private:这会导致我们无法新建对象。 构造函数会被自动调用,确保我们能够正常的初始化,使我们构造函数保证的功能。如果没写构造函数, 会默认自动生成。构造函数可以重载。但是如果自己写了任意一个构造函数,就不会自动生成默认的构造函数了。
2:#pragma once防止头文件被二次编译(多次包含),这个是windows编译器所特有的,不支持跨平台。一般还是使用#ifndef,#define,#endif比较好。把上面的 #parag once改成
#ifndef _CLASSDEMO_H_
#define _CLASSDEMO_H_
#endif//!_CLASSDEMO_H_
3:#include<>和#include""的区别:
<>先引用的是编译器的类库路径里面的头文件,""引用的是自己程序目录的相对路径中的头文件,如果里面没有,他还是会在对应的引用目录查找这个头文件(一般系统头文件用<>,自己写的头文件用"")。如果自己写的库名字与系统的一样(eg:stdio.h),那就必须使用""才可以调用自己写的库了。
4:命名空间:
可以把类写在一个命名空间里面。这样会不重名。using namespace PoEdu;//表示直接开放PoEdu里面所有的代码。但一般不要这样用。可能会导致命名空间污染,最好不要全部开放。用那些就开放那些。否则他可能不知道调用哪一个命名空间里面的函数。命名空间是用来区分组织的,不同公司写的库里面很可能有重名的函数,所以有命名空间来区分这些组织。在一个函数里面,全部开放还好,但最好不要把他开放到全局中。很容易导致命名空间污染。命名空间作用域只在本cpp文件或者库文件类。出了文件就得重新定义。
5:可文件包含库:
#include""在被包含的时候会被展开,如果头文件里面也包含有很多库,就会导致编译变慢。头文件里面最好不要包含很多库,系统级的库可以包含在里面问题不大。cpp里面可以包含比较多都没问题。如果库函数里面要使用自己写的类,也不用包含那个类的头文件,可以直接在前面做前置申明即可。做前置申明后,后面的数据要做成指针或者引用。总之不能再头文件里面包含过多自己写的头文件。
6:初始化类成员可以初始化成指针,也会调用构造函数,但是要手动delete才会调用析构函数。
#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;//10
ClassDemo *demop = new ClassDemo;//也会调用构造函数
ClassDemo *demop1 = new ClassDemo();//也会调用默认构造函数
ClassDemo *demop2 = new ClassDemo(20);//调用带参构造函数
delete demop;//以指针申请的类成员需手动删除才可以
delete demop1;
delete demop2;
ClassDemo array[10];//会调用10次无参的析构函数
ClassDemo array1[10] = { 1 };//会调用1次有参的构造函数,9次无参的析构函数
ClassDemo array2[10] = { 1, 2, 3 };//会调用3次有参的构造函数,7次无参的析构函数
ClassDemo *pArray = new ClassDemo[10];//会调用无参10次
delete[]pArray;//一定要加[],否则删除会出错。
ClassDemo *demo2 = static_cast<ClassDemo*>(malloc(sizeof(ClassDemo)));
//不会调用构造函数,malloc只能单纯的申请内存空间。必须使用new才可以开辟类指针的正确方式。
free(demo2);
return 0;
}
7:转换构造函数:
重载的构造函数中只有一个参数的时候(或则第二个参数后面的全部有默认值,只需要传递第一个参数)就会自动提升成为转换构造函数。申请类成员的时候直接使用等号就可以:
ClassDemo demo = 10;//这个等号不是赋值,而是提升为转换构造函数了。也会调用构造函数。
ClassDemo demo1 = static_cast<char>(0x31);
//ClassDemo demo1 = 'c';//字符等其他类型都可以!
赋值函数为:
demo = 20;//这种成为赋值函数。
但执行这个的时候会执行新的构造函数,生成一个临时对象,同时又调用析构函数。而且是临时对象的析构函数。就为了传递一个参数。没写构造函数和析构函数,系统会默认生成,同时还默认生成了赋值函数的。与下面的类似。
classDemo& classDemo::operator=(const classDemo& other)
{
//拷贝全部参数。
return *this;//返回本身。
}
像demo = 20这种赋值,就会默认用20用转换构造函数生成一个临时对象(前提是有适合的转换构造函数),然后调用上面的方法对对象拷贝。也可以将两个对象直接相等就是直接调用这个赋值函数。相当于对整个对象的值赋值为另一个对象的值。默认的赋值函数只能适用于本类型之间的对象赋值。上面能成功是因为C++有隐式转换,利用转换构造函数把20构造成一个对象。然后赋值的!
也可以自己重载这个默认的赋值函数。重载了可以传递整型,字符型等数据类型的参数。自己重载这个函数后,默认的函数并不会消失。他还是会有默认的(这里与构造函数和析构函数不一样)。
可不可以使用ClassDemo demo = 10;只取决于有没有对应的转换构造函数(前提是对应的转换函数前面没有加explicit),与赋值完全无关。能否使用demo = 20;取决于要么有对应的转换构造函数(前提是对应的转换函数前面没有加explicit),要么自己重载了赋值函数!如果两个条件都达到,默认会调用自己重载的赋值函数,而不会隐式转换。
explicit关键字:只作用于构造函数。抑制构造函数的隐式转换。在构造函数前面加上explicit的时候,这个构造函数就不能提升为转换构造函数了,只能按默认的方式来用这个构造函数申请对象,不能直接用=号了。
总结:1:如果没有写任何一个构造函数,会生成默认的构造函数(无需传参的)
1.1:如果写了任何一个,都不会在生成。
1.2:只需要传递一个参数的构造函数会提升为转换构造函数,用于隐式转换。
TEST t = int;//隐式转换不是赋值,而是转换构造函数。
默认的赋值函数 接收的参数是当前类的对象引用。
初始化列表:
classDemo(int num):_num(num),_other(num)//称为初始化列,直接就赋值了
{
_num = num; _other = num;//称为计算列
}//
上面的和括号里面的区别在于:
初始化列表为:int _num = num; int_other = num;
括号里面的相当于:int _num; _num = num; int _other; _other = num;
最大的问题不在于提高效率,而是类里面的数据如果是const的话,就必须用上面那种才可以,相当于初始化的时候就赋值了。不光是const,引用也是必须在初始化的时候赋值,只能用上面那种。