类基础
类是一种自定义的数据类型,也就是一个新类型。类与类之间,并不是彼此孤立的。例如,一个类可以派生出子类,那么这个派生子类的类就变成了该子类的父类。
- 类的构成包含成员变量、成员函数。当然,类中有很多特殊的成员函数。
-
访问类的成员,如果是类的对象,就使用“
对象名.成员名
”来访问。如果是指向这个对象的指针,则使用“
指针名 -> 成员名
”来访问。 -
类中
public
修饰的成员可供外界调用,
priviate
修饰的成员只能类的内部调用。
class
默认是
private
。
在C中称呼“结构”,在C++中仍然可以称呼“结构”,当然也可以称呼“类”。
struct
(结构)是成员默认为
public
的
class
(类)。
举例说明:(这里采用定义和实现分别放在.h头文件和.cpp源文件中)
.h头文件
#ifndef _TIME_H
#define _TIME_H
class time
{
public:
unsigned Hour;
unsigned Min;
unsigned Sec;
private:
unsigned MillTime;
public:
void unsigtime(unsigned H,unsigned M,unsigned S);
private:
void unsigmilltime(unsigned Mill);
};
#endif // !_TIME_H
.cpp源文件
#include"time.h"
//这两个冒号叫 作用域运算符,表示unsigtime函数属于time类。避免多个类中相同的函数无法分别
void time::unsigtime(unsigned H, unsigned M, unsigned S)
{
Hour = H;
Min = M;
Sec = S;
unsigmilltime(7);
}
void time::unsigmilltime(unsigned Mill)
{
MillTime = Mill;
}
对象复制
-
time mytime1 = mytime;
-
time mytime2(mytime);
-
time mytime3{mytime};
-
time mytime4 = {mytime};
-
time mytime5;
mytime5 = mytime; // 通过赋值操作来复制对象
构造函数
-
在类中有一种特殊的成员函数,它的名字和类名相同,在创建对象的时候,这个特殊的成员函数会被自动调用,这个成员函数叫作“
构造函数
”。
- 构造函数无返回值。在构造函数的函数头什么也不写。
- 不可手工调用构造函数,否则编译器报错。
- 正常情况下 ,构造函数应被声明称
public
,因为创建一个对象时,系统要调用构造函数。- 构造函数中如果有参数,则创建对象时也要指定相应的参数。
举例说明:
在.h头文件中声明一个
public
类型的构造函数:
public:
time(int tmp_H,int tmp_M,int tmp_S);
在.cpp源文件中实现这个构造函数:
time::time(int tmp_H,int tmp_M,int tmp_S) // 函数体内赋值
{
Hour = tmp_H;
Minture = tmp_M;
Second = tmp_S;
unsigmilltime(7);
}
构造函数的初始化列表
在.cpp源文件中亦可使用下方构造函数的实现的方式。
time::time(int tmp_H,int tmp_M,int tmp_S)
:Hour(tmp_H),Minture(tmp_M),Second(tmp_S) // 这就叫做构造函数的初始化列表
{ }
上述两种构造函数的实现,第一个叫做赋值,第二个叫做初始化。构造函数初始化列表写法更显得专业。对内置类型如
int
类型的成员变量,使用两种方式来初始化差别并不大。但对于类类型的成员变量,使用初始化列表的方式初始化比使用赋值语句初始化效率更高。
同时需要避免写出如下的构造函数的初始化列表。
time::time(int tmp_H,int tmp_M,int tmp_S)
:Hour(tmp_H),Minture(Hour),Second(Minture) // 谁先有值,谁后有值是一个问题,会出现值不确定的情况
{ }
要注意的是:某个成员变量的值不要依赖于其他成员变量(如
Minture(Hour)
),因为成员变量的给值程序并不是根据初始化列表中从左到右的顺序,
而是依据类定义中成员变量的定义顺序(从上到下的顺序)
多个构造函数
- 一个类中可以有多个构造函数,如果有多个构造函数,就为该类对象的创建提供了多种创建方法。但这些构造函数总要有写区别,如参数数量或者参数类型上的区别。
下面再声明一个单参数的构造函数:
在.h头文件中声明一个
public
类型的构造函数:
public:
time(int tmp_H);
在.cpp源文件中实现这个构造函数:
time::time(int tmp_H) // 函数体内赋值
{
Hour = tmp_H;
Minture = 7;
Second = 7;
unsigmilltime(7);
}
函数默认参数
更改在.h头文件中构造函数的声明。将第三个参数的默认值改为7.
public:
time(int tmp_H,int tmp_M,int tmp_S = 7);
此时
tmp_S
参数就叫作函数默认参数。在创建类对象时,如果不给改参数赋值,则该参数的默认值就是7。
任何函数都可以有默认参数,对于传统函数,默认参数一般放在函数的声明中,而不放在函数的实现中,除非改函数没有声明只有定义。即一般写在.h头问价中。
默认参数必须出现在非默认参数的右侧。
同时也要避免以下构造函数的问题:
public:
time(int tmp_H,int tmp_M,int tmp_S = 7);
time(int tmp_H,int tmp_M); // 在创建类对象时,系统不知道该调用哪一个
隐式转换和explicit
当声明一个单参数的构造函数时。
在.h头文件中声明一个
public
类型的构造函数:
public:
time(int tmp_H);
在.cpp源文件中实现这个构造函数:
time::time(int tmp_H) // 函数体内赋值
{
Hour = tmp_H;
Minture = 7;
Second = 7;
unsigmilltime(7);
}
下面两种类对象的创建无语法错误。(没有单参数的构造函数时,下面两种类对象创建均会报错)
time mytimesig = 7
time mytimesig1 {1,2,3,4} // 最后一个参数当做参数传递进去
再看一下下面的一个普通函数:
void sigfun(time T)
{
return ;
}
现在发现,用一个数字就可以调用该函数。
sigfun(7); // 这里调用了time类中的单参数构造函数
上述说明一个现象是,系统自动完成了一个行为——从数字到对象的转换。即数字自动转换成了类对象。这种转换称为
隐式转换
或简称
隐式类型转换
。
此外,接着上述代码继续书写,如下代码也是调用了
time
类的单参数构造函数。
mytimesig2 = 7; // 生成一个临时对象,把临时对象的值复制到了mytimesig2中
time mytimesig3 = {7}; // 这种写法正常,明确告诉系统调用带一个参数的构造函数
time mytimesig4 = 7; // 代码比较含糊,存在临时对象或者隐式转换的问题
当然,对上述问题,可以强制系统不做隐式转换。这就需要在函数声明中加上
explict(显示)
,则这个构造函数只能用于初始化和显示类型转换。
具体如下:
public:
explicit time(int tmp_H);
此时,
time mytimesig3 = {7};
会报错。而
time mytimesig3{7};
能够成功创建对象。这说明了一个问题:
有了这个等号,就变成了一个隐式转换(其实是构造并初始化),省略这个等号,就变成了显示初始化(也叫直接初始化)
。
同样,下面的写法也会报错。
mytimesig2 = 7;
time mytimesig4 = 7;
sigfun(7);
那如何修改呢?
mytimesig2 = time(7);
time mytimesig4 = time(7); // 或者 time{7}
sigfun(time(7));
一般来说,单参数的构造函数都声明为
explicit
。当然,
explicit
也可以用于无参数或者多个参数的构造函数。
如:
public:
explicit time();
sigfun({}); // 不可以,隐式转换了
sigfun(time{}); // 可以,显示转换,生成临时对象,调用无单数构造函数