无论哪一种程序语言,其基本数据类型都是有限的,C++的基本数据类型也远不能满足描述现实世界中各种对象的需要。于是C++的语法提供了对自定义类型的支持,这就是类。类实际上相当于一种用户自定义的类型,原则上我们可以自定义无限多种新类型。因此不仅可以用int类型的变量表示整数,也可以用自定义的变量表示“时钟”、“水果”、“几何图形”或者“人”等对象。正如基本数据类型隐含包括了数据和操作,在定义一个类时也要说明数据和操作。也就是通过对现实世界的对象进行数据抽象和行为(功能)抽象,得到类的数据成员和函数成员。
当定义了一个类之后,便可以定义该类的变量,这个变量就称为类的对象(或实例),这个定义的过程也称为类的实例化。
类
1.类的定义
类:将现实生活中的事物抽象出来,将它的不同类型的属性以及与这些属性相关的操作封装在一起,有的是私有的,不允许外界访问,有的是公有的,构成一个集合,形成一种新的数据类型—类(构造数据类型)。类是抽象出来的,本身不占内存空间。
比如说:
(1)钟表类
属性:时针,分针,秒针
操作:显示时间、操作时间
(2)苹果,梨,香蕉,橙子…—水果类
属性:大小,颜色,重量
操作:Eat,VC
(3)小学生,中学生,大学生,研究生----学生类
属性:学号,姓名,家庭住址,年龄,分数
操作:学习
(4)矩形,圆形,三角形—形状类
属性:点,线
操作:面积,周长
以钟表为例,定义一个钟表类:
class Clock
{
public: //操作(成员函数)
void setTime(int newH, int newM, int newS);
void showTime();
private://属性(数据成员)
int m_hour;
int m_minute;
int m_second;
};
这里封装了钟表的属性和操作,分别称为Clock类的数据成员(成员变量)和函数成员。
定义类的语法形式如下:
class 类名
{
public:
外部接口
protected:
保护型成员
private:
私有成员
};
其中public,protected,private分别表示对成员的不同访问权限控制。
2.类成员的访问控制
将所有钟表的共性抽象为钟表类,正常使用时,使用者只能通过面板查看时间,通过旋钮或按钮调整时间。这样,面板、旋钮或按钮就是我们接触和使用钟表的仅有途径,因此将它们设计为类的外部接口。而钟表记录的时间值便是类的私有成员,使用者只能通过外部接口去访问私有成员。
对类成员访问权限的控制,是通过设置成员的访问控制属性实现的,访问控制属性有三种:公有类型(public),私有类型(private),保护类型(protected)。
(1)公有类型(public):公有类型成员定义了类的外部接口。除了自己外界也可以使用------一些可以进行操作的成员函数一般都是公有类型。
(2)私有类型(private):只能被本类的成员函数访问,来自类外部的任何访问都是非法的,除过友元。-----类的属性一般都是私有类型。
(3)保护类型(protected):自己和子类可以访问,外界不能访问,除过友元。
【注意】:一般情况下,一个类的数据成员都应该声明为私有成员,这样内部数据结构就不会对该类以外的其余部分造成影响,程序模块之间的相互作用就被降低到最小。
3.类的大小
(1)类里面数据成员大小之和
(2)普通函数是不占类的空间大小
(3)static数据成员不在类中分配空间
(4)virtual函数占一个指针的大小
注意:类是抽象出来的,类本身没有大小,只有在定义类之后使用类的时候类才有大小。
①
class Clock
{
public:
void Set();
void Show();
private:
int m_hour;
int m_minute;
int m_second;
};
void main()
{
Clock c;
cout << sizeof(Clock) << endl;
}
运行结果:
②
class Clock
{
public:
void Set();
void Show();
private:
int m_hour;
int m_minute;
int m_second;
static int m_a;
};
void main()
{
Clock c;
cout << sizeof(Clock) << endl;
}
③
class Clock
{
public:
void Set();
void Show();
virtual void fn() {}
private:
int m_hour;
int m_minute;
int m_second;
static int m_a;
};
void main()
{
Clock c;
cout << sizeof(Clock) << endl;
}
运行结果:
对象
类实际上是一种抽象机制,它描述了一类事物的共同属性和行为。在C++中,类的对象就是该类的某一特定实体(也称实例)。例如将一个公司的雇员看作一个类,那么每一个雇员就是该类的一个特定实体,也就是一个对象。
实际上每一种数据类型都是对一类数据的抽象,在程序中声明的每一个变量都是其所属数据类型的一个实例。如果将类看作是自定义的类型,那么类的对象就可以看作是该类型的变量。因此,有时将普通变量和类类型的对象都称为对象。
对象:是类的一个实例化,是现实生活中实实在在存在的,占用内存单元.
声明一个对象和声明一个一般变量的方式相同,采用以下方式:
类名 对象名;
例如:Clock myClock;
声明了一个钟表类型的对象myClock。
注意:对象所占据的内存空间只是用于存放数据成员,函数成员不在每一个对象中存储副本每个函数的代码在内存中只占据一份空间。
1.访问对象的成员
定义了类及其对象,就可以访问对象的成员,例如设置和显示对象myClock的时间值。这种访问采用的是“.”操作符。
(1)访问数据成员的一般形式是:对象名.数据成员名
(2)调用成员函数的一般形式是:对象名.成员函数名(参数表)
例如:
访问类Clock中的对象myClock的函数成员showTime()的方式如下:
myClock.showTime();
在类的外部只能访问到类的公有成员,在类的成员函数中可以访问到类的全部成员。
2.成员函数的实现
函数的原型声明要写在类体中,原型说明了函数的参数表和返回值类型。而函数的具体实现是写在类定义之外的。与普通函数不同的是,实现成员函数时要指明类的名称,具体形式为:
返回值类型 类名::成员函数名(参数表)
{
函数体
}
【例如】:
class Clock
{
public:
void setTime(int newH, int newM, int newS);
void showTime();
private:
int hour, minute, second;
};
void Clock::setTime(int newH, int newM, int newS)
{
hour = newH;
minute = newM;
second = newS;
}
void Clock::showTime()
{
cout << hour << ":" << minute << ":" << second << ":" << endl;
}
可以看出,与普通函数不同,类的成员函数名需要用类名来限制,例如“Clock::showTime
”。
3.成员函数调用中的目的对象
调用一个成员函数与调用普通函数的差异在于,需要使用.
操作符指出所针对的对象,这一对象在本次调用中称为目的对象。例如使用myClock.showTime()调用showTime函数时,myClock就是这一调用过程中的目的对象。
在成员函数中可以不使用.
操作符而直接引用目的对象的数据成员,例如上面代码中的showTime函数中所引用的hour,minute,second都是目的对象的数据成员,以myClock.showTime()调用该函数时,被输出的是myClock对象的hour,minute,second属性。在成员函数中调用当前类的成员函数时,如果不使用.
操作符,那么这一次调用所针对的仍然是目标对象。
在成员函数中引用其他对象的属性和调用其他对象的方法时,都需要使用.
操作符。
【注意】在类的成员函数中,及可以访问目的对象的私有成员,又可以访问当前类的其他对象的私有成员。
4.成员函数的重载
class Clock
{
public:
void setTime()
{
m_hour = 12;
m_minute = 20;
m_second = 30;
}
void setTime(int hour)
{
m_hour = hour;
m_minute = 20;
m_second = 30;
}
void setTime(int hour, int m)
{
m_hour = hour;
m_minute = m;
m_second = 30;
}
void setTime(int hour, int m, int s)
{
m_hour = hour;
m_minute = m;
m_second = s;
}
void showTime()
{
cout << m_hour << ":" << m_minute << ":" << m_second << endl;
}
private:
int m_hour; //时
int m_minute; //分
int m_second; //秒
};
/*void Clock::Show()
{
cout << m_hour << ":" << m_minute << ":" << m_second << endl;
}*/ //如果把Show函数写到类外面,在类中只用声明函数即可
void main()
{
Clock c1;
Clock c2;
Clock c3;
Clock c4;
c1.setTime();
c1.showTime();
c2.setTime(19);
c2.showTime();
c3.setTime(21,10);
c3.showTime();
c4.setTime(20, 7, 20);
c4.showTime();
}
代码分析:
①4个Set同名(Clock::Set),不同参,这四个函数重载。
②非静态成员函数中有隐含的this指针,接收当前对象的地址。
运行结果:
5.带默认形参值的成员函数
(1)调用成员函数时可以不传参数
(2)如果传参了,就用所传递的值
(3)如果有一个参数带默认值,则后面的必须带默认
(4)类成员函数的默认值,一定要写在类的定义中,而不能写在类定义之外的函数实现中。(如果将函数的定义放在类的外部,则不需要带默认值)
class Clock
{
public:
void setTime(int hour = 0, int minute = 0, int second = 0)
{
m_hour = hour;
m_minute = minute;
m_second = second;
}
void showTime()
{
cout << m_hour << " :" << m_minute << ":" << m_second << endl;
}
private:
int m_hour;
int m_minute;
int m_second;
};
/*
void Clock::Set(int hour, int minute, int second)
{
m_hour = hour;
m_minute = minute;
m_second = second;
}
*/ //将函数的定义放在类的外部的情况,定义时不需要带参数默认值,而且这时在类中只需对函数声明,但是声明时要带参数默认值
void main()
{
Clock c1;
Clock c2;
Clock c3;
Clock c4;
c1.setTime();
c1.showTime();
c2.setTime(19);
c2.showTime();
c3.setTime(21, 20);
c3.showTime();
c4.setTime(20, 7, 20);
c4.showTime();
}
运行结果:
代码分析:
函数参数带默认值,如果在调用这个函数时没有给出实参,就会按照默认形参值将时钟设置到午夜零点。
6.内联函数(inline函数):
将功能比较简单,代码比较短小(一般不超过5行)的函数作为内联来编译,用空间的消耗换取了函数调用时间的消耗,inline只是程序员的建议,至于编译器是否按照inline来编译,不一定。比如说:sort函数的功能比较复杂,代码也相对比较多,即使程序员写了inline,编译器在编译的时候也不会按照inline来编译。
class Clock
{
public:
inline void setTime(int hour = 12, int minute = 30, int second = 30);
//默认按照inline函数来编译
void showTime()
{
cout << m_hour << " :" << m_minute << ":" << m_second << endl;
}
private:
int m_hour;
int m_minute;
int m_second;
};
inline void Clock::setTime(int hour , int minute , int second )
{
m_hour = hour;
m_minute = minute;
m_second = second;
}
void main()
{
Clock c1;
Clock c2;
Clock c3;
Clock c4;
c1.setTime();
c1.showTime();
c2.setTime(19);
c2.showTime();
c3.setTime(21, 20);
c3.showTime();
c4.setTime(20, 7, 20);
c4.showTime();
}
运行结果:
代码分析:
①上述代码中Clock类中的Set函数可以定义为内联函数,写为inline void Set
,Show函数则默认按照inline函数来编译。
②如果不是内联函数,那么在主函数中程序运行时,运行到调用函数的部分就会返回到相应的函数中运行完在回到主函数,如果是内联函数,则在程序运行到调用该函数的部分,则不返回到该函数运行相应的代码,而是把相应的代码拿到主函数来运行。
③宏定义和inline的区别
a.宏定义是在编译之前,inline是在编译时
b.宏定义会出现歧义,inline函数不会
c.宏定义是预处理命令,inline是函数