提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
一、类class
1. 一些定义
类是一系列具有共同属性和行为的事物抽象。对象是类的具体化(实例化)。
属性:数据成员(int,char。。。),一系列事物公有特征
行为:成员函数的描述,一系列公有事物共同操作
类的特点:封装性,继承性,多态性,抽象性,隐藏性
封装性:顾名思义,将属性,行为封装在一个抽象类里,便于管理操作。
继承性:类的继承特性,下面会详说。
多态性:同一种行为的不同结果,下面也会详说。
抽象性:c++通过纯虚函数实现抽象类,将事物的每一部分抽象出来,在需要用到时才实例化(纯虚函数需要先继承下来才能实例化)。
隐藏性:使用者不会知道内部的实现代码,只提供功能接口。用private限制外部用户访问内部数据。
2.构造函数
构造函数的一些定义
- 类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时 执行。
- 构造函数的名称和类的名称是完全相同的,并且不会返回任何类型,也不会返回void。构造函数可以带参数,用于为某些成员变量设置初始值
class MyClass
{
public:
MyClass(char name[], int age); //构造函数定义
int m_age;
char m_name[20];
};
MyClass::MyClass(char name[], int age)
{
strcpy(m_name, name);
m_age = age;
}
如何使用构造函数?
int main()
{
char name[] = "Jack";
char name2[] = "Lily";
MyClass temp = {name, 18}; //创建变量并初始化
MyClass tempArr[2] = {{name, 18}, {name2, 19}}; //数组对象
return 0;
}
考虑到并不是每次都需要使用到里面的属性,会增加一个无参构造方便使用(函数重载)。
MyClass::MyClass() //无参构造
{
memset(m_name, 0, sizeof(m_name));
m_age = -1; //初始化一些无用值
}
//创建对象时就不用传参数了
//c++提供了这么一种定义的方式,不用再写声明 MyClass() = defalut;
当然,你也可以使用默认参数来代替这种方式。
MyClass::MyClass(char name[], int age = 18){}
委托构造
实际上这种方式并不常用,它的作用也同样是方便不传参数使用对象。甚至会有点麻烦。
Tips: 为了方便展示委托构造,我需要修改一下上面的例子。(将数组name的类型换成const char*)
class MyClass
{
public:
MyClass(const char* name, int age); //构造函数定义
MyClass():MyClass("委托", 18){} //委托构造
int m_age;
const char* m_name;
};
MyClass::MyClass(const char* name, int age)
{
m_name = name;
m_age = age;
}
注意到,委托的参数(“委托”, 18)是和我们定义的有参构造相同的,顺序也是一样,那它们就一定有联系。
实际上,在我们创建一个无参的对象时,它会调用两次构造函数,首先是有参构造,把委托的2个参数传给成员变量,然后再调用无参构造。
拷贝构造函数
拷贝构造主要是为了实现通过一个对象创建一个新的对象。
增加一个构造函数
MyClass(const MyClass& object) //该构造函数成为拷贝构造
{
m_age = object.m_age;
m_name = object.m_name;
}
匿名对象:没有具体的对象,常常用来作为函数返回值,如果调用了函数不接受返回的匿名对象,那么它就会被释放掉。
拷贝构造和普通构造(上述除了拷贝外的构造)的区分:
MyClass(“匿名对象”, 20);-----------------------------------------------调用普通构造
MyClass newClass = MyClass(“匿名对象”, 20);----------------匿名对象转正,不调用任何构造
MyClass newClass((对象)oldClass);-----------------------------调用拷贝构造
MyClass newClass = oldClass;---------------------------------------调用拷贝构造
void function(MyClass object){} -----函数传参---------------------调用拷贝构造
引用传参(由于没有产生新对象,不会调用)
区分的技巧:看是否是通过另一个class来创建新的class。
移动构造函数
增加一个构造函数
MyClass(MyClass&& object) //该构造函数成为移动构造函数
{
//该构造函数主要实现的是资源权限转移,将目标内存拿过来,并干掉目标
}
MyClass newClass = move(oldClass);//这行代码调用移动构造,oldClass为目标对象,newClass代oldClass
Tips: move的功能是将左值变为右值
3.析构函数
一些结论
- 析构函数一般跟构造函数相反
- new出来delete掉会自动调用析构函数,以某种方式释放掉也会调用。
深浅拷贝
经典的深浅拷贝问题: 当数据成员是指针,并做了内存申请。浅拷贝会把oldClass的内存地址一同复制过去,在释放内存时造成重复释放的问题。这时就需要深拷贝来解决。
浅拷贝
MyClass::MyClass()
{
m_pInt = new int[3]; //给成员变量m_pInt,new了3个int类型
memset(m_pInt, 0, sizeof(int) * 3); //清空
}
MyClass::MyClass(const MyClass& object)//浅拷贝
{
m_pInt = object.m_pInt; //直接将地址赋给新的class
}
MyClass class1; //class1
MyClass class2 = class1; //用class1创建class2
此时我们看看地址两个class的指针指向的地址
cout << &class1.m_pInt[0] << endl; //输出指向内存的首地址
cout << &class2.m_pInt[0] << endl;
发现是一样的。这就导致了一个问题,当调用析构函数(通常释放动作写在析构函数里)时,会释放掉内存,内存释放两次就会出现问题。
深拷贝
我们只需要修改一下拷贝函数
MyClass::MyClass(const MyClass& object) //深拷贝
{
m_pInt = new int[3]; //重新给新class申请内存
memcpy(m_pInt, object.m_pInt, sizeof(int) * 3); //内存拷贝
}
这时,释放时就是释放各自指向的内存了,不会产生冲突了。
二、如何访问类中数据
实际上,我们习惯性的将一些重要成员变量放到private中,但有时候又需要用到,那该如何处理?
1.利用构造函数初始化
class MyClass
{
public:
MyClass(int age); //构造函数定义
private:
int m_age;
};
MyClass::MyClass(int age) //构造函数实现
{
m_age = age; //将传进来的参数age赋值给成员变量m_age
}
2.提供接口(函数)
class MyClass
{
public:
MyClass(int age); //构造函数定义
int returnAge() //接口
{
return m_age;
}
private:
int m_age;
};
这个方法只是获取,并不能修改。
3.提供一个功能(函数)传参修改
class MyClass
{
public:
MyClass(int age); //构造函数定义
void setAge(int age) //返回引用
{
m_age = age;
}
private:
int m_age;
};
MyClass::MyClass(int age)
{
m_age = age;
}
4.返回引用修改
class MyClass
{
public:
MyClass(int age); //构造函数定义
int& returnAge() //返回引用
{
int& age = m_age;
return age;
}
private:
int m_age;
};
MyClass::MyClass(int age)
{
m_age = age;
}
最后
如果文章有错误,或者有什么补充,欢迎各位指正讨论。