创建新的类型
类的定义
class 类名 {
private:
私有数据成员和成员函数
public:
公有数据成员和成员函数
};
公有成员(public):类的用户可以调用的信息,是类对外的接口。
私有成员(private):只能由类的成员函数调用,被封装在一个类中,类的用户是看不见的。
数据成员一般是私有的;成员函数一般是公有的,成员函数实现时分解出的小函数是私有的。
private 和public的出现次序可以是任意的。也可以反复出现多次。
接口和实现分开
类的定义写在接口文件中,成员函数的实现写在实现文件中。某些简单的成员函数的定义可以直接写在类定义中,称为内联函数。
类定义实例
以有理数类为例,应用接口与实现分开。
//接口文件 Rational.h
#ifndef _rational_h
#define _rational_h
#include <iostream>
using namespace std;
class Rational {
private:
int num;
int den;
void ReductFraction(); //将有理数化简成最简形式
public:
void create(int n, int d){num = n; den = d; ReductFraction();}
void add(const Rational &r1, const Rational &r2);
void multi(const Rational &r1, const Rational &r2);
void display() {cout << num << '/' << den;}
};
#endif
.
//实现文件
#include "Rational.h"
//add函数将r1和r2相加,结果存于当前对象
void Rational::add(const Rational &r1, const Rational &r2)
{
num = r1.num * r2.den + r2.num * r1.den;
den = r1.den * r2.den;
ReductFraction();
}
void Rational::multi(const Rational &r1, const Rational &r2)
{
num = r1.num * r2.num;
den = r1.den * r2.den;
ReductFraction();
}
void Rational::ReductFraction()
{
int tmp = (num > den) ? den : num;
for (; tmp > 1; --tmp)
if (num % tmp == 0 && den % tmp ==0)
{num /= tmp; den /= tmp; break;}
}
对象的使用
类与对象的关系相当于类型与变量的关系
对象的定义
直接在程序中定义某个类的对象:
存储类别 类名 对象列表;
用动态内存申请的方法申请一个动态对象:
Rational *Rp,*rp;
Rp = new Rational;
rp = new Rational[20];
delete Rp;或 delete []rp;
对象的引用
对象名.数据成员名 或 对象指针->数据成员名
对象名.成员函数名(实际参数表)或 对象指针->成员函数名(实际参数表)
外部函数不能引用对象的私有成员
this指针
每个成员函数都有一个隐藏的指向本类型的指针形参this,它指向当前调用成员函数的对象。通常,在写成员函数时可以省略this,编译时会自动加上它们。如果在成员函数中要把对象作为整体来访问时,必须显式地使用this指针。这种情况常出现在函数中返回一个对调用函数的对象的引用。
对象的构造与析构
构造函数
在定义对象时为对象赋初值;
由系统在定义对象时自动调用;
如果没有给类定义构造函数,编译系统会自动生成一个缺省的构造函数。它只为对象开辟存储空间,空间中的内容为随机数。
构造函数的名字必须与类名相同;
不能说明它的类型;
构造函数可以重载;
初始化列表方法
构造函数初始化列表位于函数头和函数体之间。它以一个冒号开头,接着是一个以逗号分隔的数据成员构造列表。
如DoubleArray的构造函数可写为
DoubleArray::DoubleArray(int lh, int rh):low(lh), high(rh){
storage = new double [high - low + 1];
}
利用初始化列表可以提高构造函数的效率。在初始化数据成员的同时完成了赋初始的工作。
以下情况必须使用初始化列表:
- 数据成员不是普通的内置类型,而是某一个类的对象,可能无法直接用赋值语句在构造函数体中为它赋初值
- 类包含了一个常量的数据成员,常量只能在定义时对它初始化,而不能对它赋值。因此也必须放在初始化列表中。
拷贝构造函数
在创建一个对象时,可以用一个同类的对象对其初始化。这是需要调用一个特殊的构造函数,称为拷贝构造函数。
拷贝构造函数以一个同类对象引用作为参数,它的原型为:
类名 (const <类名> &ob);
如果用户没有定义拷贝构造函数,系统会定义一个缺省的拷贝构造函数。该函数将已存在的对象原式原样地复制给新成员。
一般情况下,默认的拷贝构造函数足以满足要求。但某些情况下可能需要设计自己的拷贝构造函数,比如数据成员涉及到指针,会导致两者指向同一个地,对一者的操作会导致对另一个同样的操作。
拷贝函数应用的场合:
1、对象定义时,将初始值放在圆括号中,直接调用与实参类型相匹配的构造函数。
class point{
int x, y;
public:
point(int a, int b){x=a; y=b;}
point(const point &p){x=2*p.x; y=2*p.y;}
void print() {cout<<x<<" "<<y<<endl;}
};
void main()
{
point p1(10, 20), p2(p1), p3 = p1, p4(1, 2);
p1.print(); p2.print(); p3.print(); p4.print();
p4 = p1; p4.print();
}
输出为
10 20
20 40
20 40
1 2
10 20
第三行和第五行不一样,虽然两者都是用等号赋值p1
,但是p3
是在定义中赋值,使用拷贝函数,p4
是单独的一个等号赋值语句。
2、把对象作为值传递参数,如果是引用传递就没有这个构造过程了
3、把对象作为返回值时,当执行到return语句时,会创建一个类的临时对象,并调用拷贝构造函数用返回对象初始化该临时对象,并将此临时对象的值作为返回值。
析构函数
执行与构造函数相反的操作,通常执行一些清理工作,如释放分配给对象的动态空间等。
析构函数与构造函数名字相同,但它前面必须加一个波浪号(~)。
析构函数没有参数,没有返回值,也不能重载。
一般在构造函数中有动态申请内存的,必须有析构函数。
析构顺序:1、局部变量先消失,然后是静态局部变量,最后是全局变量;2、后创建的先消失;
常量对象与const成员函数
const ClassName Obj (params);
const对象不能被赋值,只能初始化,而且一定要初始化,否则无法设置它的值。
数据成员一般都由成员函数修改。当定义了一个const对象后,只能调用const成员函数修改。格式为
type func(params) const {
...
}
type ClassName::func(params) const{
...
}
常量成员
const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。同一类的不同的对象其const数据成员的值可以不同。
const type variables
不能在类声明中初始化const数据成员。const数据成员的初始化只能在类构造函数的初始化表中进行,不能在构造函数中对他赋值。
const深入
const与引用
是被引用对象的别名;不管被引用对象是常量还是变量,用此名字是不能赋值。const引用的初值可以是常量或表达式。
出现在函数的形式参数前
void func(const type &variable);
表示函数中只能引用不能修改variable
的值。variable
的实际参数可以是变量、常量或表达式。
出现在函数返回值前,提高函数返回效率
const type &func(params);
函数func的返回值必须是一个离开函数func后依然存在的变量或常量;
函数func只能被引用,不能被赋值。
const与指针
const type *p;
: 不能通过p修改它指向的单元的内容,但p可以指向不同的变量。
type *const p = &x;
: 可以通过p修改指向的地址的值,但p指的地址不能变,即p永远只能指向x,但x的值可变。P定义时必须给出初值。
const type *const p = &x;
: p的值不能修改,*p的值也不能修改。
函数参数。如void f(const int *p)
表示采用指针传递,但在函数中不能修改p指向的对象。
函数的返回值。如const int *f()
表示函数的返回值是一个指针,该指针指向的对象在离开函数f后依然存在。可以使用const int *p = f();
但不能使用int *p = f()
。
const可以放在成员函数原型后面,表示该成员函数不会修改对象的值,const放在全局函数的后面是没有意义的!!!
静态数据成员及其函数
静态数据成员指整个类所有对象共享的数据,在变量类型前加static
。
静态数据成员的初始化不能放在类的构造函数中;
静态数据成员不属于对象的一部分,而是类的一部分;
由于静态数据成员属于类,因此定义对象时并不为静态成员分配空间;
但类定义并不分配空间,空间是在定义对象时分配。
上述三句话表明,静态数据成员既不是在定义类也不是在定义类的对象时分配空间。
为静态数据成员分配空间称为静态成员的定义,静态数据成员的定义可以出现在使用类的程序中,或在类的实现文件中。形式为
type ClassName::static_variable = value;
在为static_variable分配空间的同时赋予初值。
静态成员函数
静态的成员函数是为类的全体对象服务,而不是为某个类的特殊对象服务。
由于静态成员函数不需要借助任何对象就可以被调用,所以编译器不会为它暗加一个this指针。因此,静态成员函数无法处理类中的非静态成员变量。
静态成员函数的声明只需要在类定义中的函数原型前加上保留词static。
静态的常量数据成员的声明:
如果成员的类型是整型
static const int 数据成员名 = 常量表达式;
如果成员类型是非整型,在类中值是声明,在类外给初值
static const double st; //类声明
const double 类名::st = 1.5; //类外
友元
类的私有成员只能通过它的成员函数来访问。友元函数是一扇通往私有成员的后门。
友元的特点:
- 友元关系是授予的而不是索取的。也就是说,如果函数f要成为类A的友元,类A必须显式声明函数f是他的友元,而不是函数f自称是类A的友元。
- 友元关系不是对称关系,如果类A声明了类B是它的友元,并不意味着类A也是类B的友元。
- 友元关系不是传递关系。如果类A是类B的友元,类B是类C的友元,并不意味着类A是类C的友元。
友元声明
friend type func(params);
在类定义中用关键词friend
声明友元函数,可声明在public部分,也可声明在private部分。
但一个较好的程序设计的习惯是将所有的友元关系的声明放在最前面的位置,并且不要在它的前面添加任何访问控制说明。
友元函数定义
友元函数是一个全局函数,可以定义在类中,也可以定义在类外,不管定义在哪里,都不是类的一部分。
声明方法:
friend 返回类型 函数名(参数表);
实例
// 定义在类内
class girl{
char name[10];
int age;
public:
girl(char *n, int d){
strcpy(name,n);
age=d;
}
friend void disp(girl &x){
cout<<x.name<<""<<x.age<<endl;
}
};
void main(){
girl e("abc", 15);
disp(e);
}
// 定义在类外
class girl{
char name[10];
int age;
public:
girl(char *n, int d){
strcpy(name,n);
age=d;
}
friend void disp(girl &x);
};
void disp(girl &x){
cout<<x.name<<""<<x.age<<endl;
}
int main(){
girl e("abc", 15);
disp(e);
return 0;
}
友元成员
其他某个类的成员函数;可以访问friend声明语句所在类的私有成员和公有成员的成员函数。
类A的成员函数作为类B的友元函数时,必须先定义类A,再定义类B。在定义类A和B前必须先声明类B。
声明方法:
friend 函数返回类型 类名标识符::函数名(参数列表);
实例
//girl类的disp函数是boy类的友元
class boy;
class girl{
char name[10];
int age;
public:
girl(char *n, int d){strcpy(name,n);age=d;}
void disp(boy &x);
};
class boy{
char name[10];
int age;
public:
boy(char *n, int d){strcpy(name,n);age=d;}
friend void girl::disp(boy &);
};
void girl::disp(boy &x)
{
cout<<name<<" "<<age<<endl;
cout<<x.name<<" "<<x.age<<endl;
}
int main()
{
girl e("abc", 15);
boy b("cde",20);
e.disp(b);
return 0;
}
友元类
整个类作为另一个类的友元。当A类被说明为类B的友元时,类A的所有函数都是类B的友元函数。
声明方法:
friend Y;
//或friend class Y;
实例
class boy;
class girl {
char name[10];
int age;
public:
girl(char *n, int d) {strcpy(name,n);age=d;}
void disp(boy &x);
};
class boy {
char name[10];
int age;
public:
boy(char *n, int d) {strcpy(name,n);age=d;}
friend class girl;
};
void girl::disp(boy &x)
{
cout << name << " “ << age << endl;
cout << x.name << " “ << x.age << endl;
}
int main()
{
girl e("abc", 15);
boy b("cde",20);
e.disp(b);
return 0;
}
注:友元关系可以写在类定义中的任何地方。但一个较好的程序设计的习惯是将所有的友元关系的声明放在最前面的位置,并且不要在他的前面添加任何访问控制说明。