类和对象
类是面对对象程序设计的核心,是通过抽象数据类型方法实现的一种用户自定义数据类型,它同时包含了数据和对数据进行操作的函数。
利用类可以实现数据的封装和隐藏。
类与对象:
- 类:是对某一类对象的抽象。
- 对象:是某一类的实例。
tips:类和对象是密切相关的。没有脱离对象的类,也没有不依赖于类的对象。
类的定义
类是一种用户定义的复杂数据类型,它是将不同类型的数据和与这些数据相关的操作封装在一起的集合体。
类的声明与定义
类的定义可以分为两个部分:
- 说明部分:说明类中包含的数据成员和成员函数。
- 实现部分:是对成员函数的定义
class <类名>
{
public:
<成员函数或数据成员的说明> // 公有成员、外部接口:任何来自外部的访问都必须通过外部接口进行
protected:
<成员函数或数据成员的说明> // 保护成员:用于继承和派生时
private:
<成员函数或数据成员的说明> // 私有成员:只允许本类的成员函数访问,来自类外部的任何访问都是非法的
}; // ; 表示声明结束
// 类的实现部分
<各个成员函数的实现> // 成员函数的实现可以在类内定义,也可以在类外定义
类的数据成员
类中的数据成员描述类所表达问题的属性。
数据成员在类体中定义,其定义方式与一般变量相同,但对数据成员的访问要受到访问权限修饰符的控制。
TIPS:
- 类中的数据成员可以是任意类型的,包括整型、浮点型、字符型、数组、指针和引用,可以是对象。但只有其他类的对象才可以作为该类的成员。自身类的对象不可以作为自身类的成员存在,但自身类的指针可以。
class Date { private: Date dates; // 错误 Date * dates: // 正确 }
- 在类体中不允许对所定义的数据成员进行初始化。
class Date { private: int year(2000), month(10), day(1) // 错误,类体中不允许对所定义的数据成员进行初始化。 }
类的成员函数
类的成员函数描述类所表达的问题的行为。
类中所有的成员函数都必须在类体内进行说明,其定义可以在类体内也可以在类体外。
class Date
{
public:
int returnYear()
{
return 2000; // 成员函数在类体中说明并定义
}
int returnMonth(); // 类体中说明成员函数,类体外定义成员函数
int returnDay();
};
int Date::returnMonth() // :: 域运算符:标识该成员属于哪个类
{
return 1;
}
inline int Date::returnDay() // 通过关键字inline将类体外定义的成员函数设置为内联函数(==在类体类定义)
{
return 1;
}
成员函数定义位置的区别:
- 类体内:在类体内定义的成员函数都是内联函数。
- 类体外:一般函数。如果想想设置为内联函数,在成员函数的定义前加关键字:inline
成员函数的重载:
- 目的:对不同类型的参数做同一的功能处理。
- 要求:方法名相同,参数列表不同,与返回值类型无关。
对象的定义
对象是类的实例,一个对象必须属于一个已知的类。因此在定义对象之前,必须先定义该对象所属的类。
对象定义的格式:
<类型> <对象名>(<参数列表>);
常用的定义对象的方法:
Date date; // 一般对象
Date dates[32]; // 对象数组
Date *pDate; // 指向对象的指针
Date &date=date; // 指向对象的引用
对象的成员
一个对象的成员就是该对象的类所定义的成员,包括数据成员和成员函数。
对象成员的访问
- “.” :使用于一般对象和指向对象的引用(引用对象)。
- “->” :指向对象的指针(指针对象)。
class Date
{
public:
int month;
int returnYear()
{
return 2000;
}
}
int main
{
Date date;
Date &date2 = date;
date.month;
date.returnYear();
date2.month;
date2.returnYear();
Date *pDate;
pDate -> month;
pDate -> returnYear();
}
构造函数和析构函数
类是一种用户自定义的类型,定义一个类对象时,编译程序要为对象分配存储空间,进行必要的初始化。在C++中,这项工作由构造函数完成。与构造函数对于的是析构函数,当撤消类对象时,析构函数回收存储空间,并做一些善后工作。
TIPS:构造函数和析构函数都属于类,即可以由用户提供,也可以由系统自动生成。
构造函数和析构函数
- 构造函数
- 作用:对象创建时利用特定的值构造对象,将对象初始化为一种特定的状态,使该对象具有区别于其他对象的特征。
- 调用时机:在对象被创建时由系统自动调用。
- 特殊性质:构造函数也是成员函数的一种,除具有成员函数的性质外,还具有一些其他的特殊性质
- 构造函数的名字必须与类名相同。
- 构造函数不指定返回值类型,它隐含有返回值,由系统内部使用。
- 构造函数可以有一个或多个参数,所以可以重载。
- 创建对象时,由系统自动调用。
- 析构函数
- 作用:用来完成对象被删除前的一些清理工作,即专门做扫尾工作的。
- 调用时机:在对象的生存期即将结束的时候由系统自动调用,调用完成后,对象就消失了,相应的内存空间也被释放。
- 特殊性质:析构函数也是成员函数的一种,除具有成员函数的性质外,还具有一些其他的特殊性质
- 析构函数的名字为类名前加~(求补符号)。
- 析构函数不指定返回值,它不能有返回值。
- 析构函数没有参数,所以它不能重载,即一个类中只可有一个析构函数。
- 在撤消对象时,系统会自动调用析构函数。
class Date
{
public:
Date(int y, int m , int d);
Date(int y=200):year(y)
{
}
~Date(){}
};
Date::Date() // 编译器默认生成的构造函数格式,因为类中没有明确声明,所以在类体外定义
{
}
Date::~Date() // 编译器默认生成的析造函数格式,因为类中没有明确声明,所以在类体外定义
{
}
复制构造函数
类中有一个特殊的构造函数叫作复制构造函数,它用一个已知的对象初始化一个正在创建的同类对象。
复制构造函数具有以下特定:
- 也是一中构造函数,因此函数名与类名相同,并且不能指定函数返回类型。
- 只有一个参数,是对同类的某个对象的引用
- 每一个类中必须有一个复制构造函数。如果没有显式定义,编译器会自动生成一个共有的复制构造函数。
复制构造函数在以下三种情况下被调用:
- 用类的一个已知对象去初始化该类的另一个正在创建的对象。
- 采用传值调用方式时,对象作为函数实参传递给函数形参。
- 对象作为函数返回值。
复制构造函数一般格式如下:
<类名>::<类名>(const <类名>& <引用对象名>)
{
// 复制构造函数体
}
class Date
{
public:
Date(const Date& d);
private:
int year;
}
Date::Date(const Date& d)
{
year=d.year
}
自由存储对象
- 静态存储方式:编译时就确定了所占存储空间的大小
- 动态存储方式:程序运行过程中按照实际需要申请适量的内存,使用结束后再进行释放。这种在程序运行过程中根据需要可以随时创建和删除的对象称为自由存储对象。
自由存储对象的建立和删除
- new:通过new运算符完成创建对象,即通过new调用构造函数。
- delete:通过delete运算符完成删除对象,即通过delete调用析构函数。
this 指针
C++提供了一个特殊的对象指针:this指针,它是成员函数所属对象的指针,它指向类对象的地址。成员函数通过这个指针可以知道自己属于哪一个对象。
当一个对象调用成员函数时,编译程序先将对象的地址赋值给this指针,然后调用成员函数。每次成员函数读取数据成员时,都会隐含地使用this指针。
Date& Date::AddYear(int n)
{
if(day == 29){
day = 1;
}
return *this;
}
等价于
Date& Date::AddYear(int n)
{
if(this -> day == 29){
day = 1;
}
return *this;
}
静态成员
- 非静态数据成员:每个类对象拥有一个副本,即每个对象的同名数据成员可以有不同的值,这时保证每个对象拥有于区别其他对象的特征的需求。
- 静态成员:不管类创建多少个对象,都只有一个副本。这个副本由所有属于这个类的对象共享。
静态成员,用static关键字声明:
- 静态数据成员:不在类对象中占用内存空间,只在每个类中分配存储空间,共所有对象使用。
- 静态数据成员具有静态生存期,必须对它进行初始化。
- 静态成员初始化注意事项:
- 类体中仅仅对静态数据成员进行了引用性声明,因此必须在文件作用域的某个地方对静态数据成员进行定义并初始化,即应在类体外对静态成员进行初始化。
- 静态成员初始化时不加关键字static,以免与一般静态变量或对象混淆。
- 由于静态成员是类的成员,因此在初始化时必须使用域运算符(::)限定它所属的类。
<数据类型> <类名>::<静态数据成员名>=<初始值>; class Test { private: static int count; }; int Test::count=0;
- 静态成员函数:共有的静态数据成员可直接访问,但私有的静态数据成员却必须通过共有的接口进行访问,即共有的静态成员函数。使用static关键字修饰的成员函数就是静态成员函数,静态成员函数也属于整个类而不是属于类的某个对象,它是该类的所有对象共享的成员函数。
- 静态成员函数可在类体内定义,也可在类体外定义。类体外定义时不要使用关键字static作为前缀。
- 静态成员函数只可直接访问类中的静态数据成员,非静态数据成员,必须通过参数传递的方式得到对应的对象,再通过对象进行访问。
class Test { public: static void Display(Test& p); private: int x,y,z; static int count; }; void Test::Display(Test& p) { cout<<p.x<<endl; } int Test::count=0;
常成员(常量)
对于即需要共享又需要防止改变的数据应该定义为常量进行保护,保证它在整个程序运行期间是不可改变的。
关键字const
- 修饰类对象(常对象):
- 定义格式: <类名> const <对象名>,常对象在定义时必须被初始化,而且不能被更新。
class A { public: A(int i, int j):x(i), y(j){} private: int x, y; }; const A a(1,2); // or A const a(1,2)
- 修饰成员函数(常成员函数):
- 说明格式: <返回类型> <成员函数名>(<参数表>) const;
class Date { public: int GetYear() const { return year; }; int GetDay() const; }; inline int Date::GetDay() const // 类体外定义时,也需要const关键字 { return day; // 常成员函数不能更新对象的数据成员,否在会发生错误 }
- 常成员函数和一般成员函数:函数是常成员函数时,一般对象和常对象都可以调用。函数是一般函数时,只有一般对象可以调用,常对象调用一般函数会产生错误,因此,如果成员函数不修改对象时,最好将其声明为常成员函数。
- const还可参数函数的重载,原则是常对象调用常成员函数,一般对象调用一般成员函数。如果只有一个常成员函数时,一般对象也可调用常成员函数。
- 修饰数据成员(常数据成员):常数据成员必须进行初始化(只可通过构造函数的成员初始化列表显示进行),并且不能被更新。
class MyClass { public: MyClass(int i, int j); private: const int a; int b; }; MyClass::MyClass(int i, int j):a(i),b(j){}
友元
类具有数据封装和隐藏的特性,只有类的成员函数才能访问类的私有成员和保护成员,外部函数只能访问类的公有成员。但在某些情况下,需要在类的外部访问类的私有成员和保护成员。这时,如果通过公有成员函数进行访问,由于参数传递,类型检查和安全性检查等需要时间上的开销,将影响程序运行效率。为了解决这个问题,引入了友元。友元可在类外部直接访问类的私有成员和保护成员,提高了程序运行的效率。
友元提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。
对于一个类,可以利用friend关键字将一般函数、其他类的成员函数或者其他类声明为该类的友元,使得该类中本来隐藏的信息可以被友元所访问。如果友元是一般成员函数或者类的成员函数,称为友元函数;如果友元是一个类,则称为友元类,友元类的所有成员函数都是友元函数。
- 友元函数:友元函数不是当前类的成员函数,而是独立于当前类的外部函数,但它可以访问该类的所有成员。
- 友元函数在类定义时声明。该声明可放在public、protected、private中。友元函数的定义通常在类外部。
class TIME; class DATE { public: DATE(int y=2003,int m=1, int d=1):year(y),month(m),day(d){} friend void DateTime(const DATE &d, const TIME &t); friend void TIME::Time(const TIME &t); // 该类中定义另一个类的友元函数 private: itn year, month, day; }; class TIME { public: TIME(int h=0, int m=0, int s = 0):hour(h),minute(m),second(s) {} friend void DateTime(const DATE &d, const TIME &t); private: int hour,minute,second; }; void DateTime(const DATE &d, const TIME &t) { } int main() { DATE d(2003,10,10); TIME t(10,10,10); DateTime(d,t); // 访问类外部定义的友元函数时,不需要通过在函数名前加 “类名::”调用 return 0; }
- 友元类:友元类中的所有成员函数自动设置为友元函数
class TIME; class DATE { public: DATE(int y=2003,int m=1, int d=1):year(y),month(m),day(d){} void DateTime(const TIME &t); private: itn year, month, day; }; class TIME { friend DATE; //定义为友元类,DATE类的常用函数DateTime自动称为类TIME的友元函数,可以之间访问TIME类的所有数据成员 public: TIME(int h=0, int m=0, int s = 0):hour(h),minute(m),second(s) {} private: int hour,minute,second; }; void DATE::DateTime(const DATE &d, const TIME &t) { } int main() { DATE d(2003,10,10); TIME t(10,10,10); DateTime(d,t); // 访问类外部定义的友元函数时,不需要通过在函数名前加 “类名::”调用 return 0; }
成员对象
类的数据成员可以是简单类型或自定义数据类型的变量,也可以是类类型的对象。
建立一个类对象时,要调用它的构造函数对类对象进行初始化,此时应先执行初始化列表对各个成员进行初始化,在执行当前类的构造函数。
class Conuter
{
public:
Counter():val(0){}
private:
int val;
};
class Example
{
public:
Example():val(0){}
private:
Conuter c1,c2;
int val;
};