一.类与对象的基本概念
1.结构体与类
C++提供了一种比结构体类型更安全有效的数据类型——类。并且用class取代struct。
在C++中将类的成员分为两类:私有成员(用private说明)和公有成员(用public说明)。私有成员(包括数据成员和成员函数)只能被类内的成员函数访问,而不能被类外的对象访问;公有成员(包括数据成员和成员函数)既可被类内的成员函数访问,也可被类外的对象访问。但在C++中(即没有指定属于私有或公有时),类内的成员是私有的。
代码解释如下:
#include<iostream>
#include<cmath>
using namespace std;
class Complex
{
private:
double real,imag;
public:
void init(double r,double i)
{
real=r;
imag=i;
}
double abs()
{
double t;
t=real*real+imag*imag;
return sqrt(t);
}
};
int main()
{
Complex A;
A.init(1.1,2.2);
cout<<A.abs()<<endl;
return 0;
}
在所示代码中,类外的对象A是无法对类内的私有成员进行直接访问。所以主函数中的代码语句
A.init(1.1,2.2);
cout<<A.abs()<<endl;
在编译时是错误的。
类类型的声明为:
class 类名
{
private:
私有成员和成员函数
public:
公有成员和成员函数
};
除了private和public之外,类中的成员还可以用另一个关键字protected来说明。被protected说明的数据成员和成员函数称为保护成员。
2.成员函数
成员函数可以访问本类中的任何成员。
成员函数在类外定义的一般形式为:
返回值类型 类名::成员函数名(参数表)
{
函数体
}
注:
(1)在类的声明中,成员函数的原型的参数表中可以不说明参数的名字,而只说明它们的类型。如:
void setpoint(int,int);
但是,在类外定义成员函数时,不但要说明参数表中的参数类型,还必须要指出其参数名。
(2)成员函数还可以直接定义在类的内部。如:
class Point
{
public:
void setpoint(int a,int b) //成员函数setpoint直接定义在类的内部
{
x=a;
y=b;
}
int getx() //成员函数getx直接定义在类的内部
{
return x;
}
int gety() //成员函数gety直接定义在类的内部
{
return y;
}
private:
int x,y;
};
当然使用内联函数会减少函数调用的开销。定义函数的代码如下:
class Point
{
public:
inline void setpoint(int,int); //声明成员函数setpoint为内联函数
private:
int x,y;
};
inline void Point::setpoint(int a,int b) //在类外定义此函数为内联函数
{
x=a;
y=b;
}
3.对象的定义及使用
C++中的类的对象是对该类实例化的结果。
(1)对象的定义
有两种定义方式:
a.在声明类的同时,直接定义对象,即在声明类的右花括号“}”后。例如:
class Point
{
public:
void setpoint(int,int);
int getx();
int gety();
private:
int x,y;
}op1,op2; //直接定义了对象op1和op2
b.声明了类之后,在使用时再定义对象。基本格式如下:
类名 对象名1,对象名2,······;
例如:
Point op1,op2;
(2)对象中成员的访问
(a)通过对象名和对象选择符访问对象中的成员
一般形式为:
对象名.数据成员名
或
对象名.成员函数名[(实参表)]
其中“.”叫做对象选择符,简称点运算符。
(b)通过指向对象的指针访问对象中的成员
例:
Date d,*ptr;
ptr=*d;
使用d.year或(*ptr).year或ptr->year三者等价。
(c)定义一个引用,通过引用来访问对象中的成员,其方法跟通过对象名来访问对象中的成员是相同的。
二.构造函数和析构函数
1.对象的初始化和构造函数
class Complex
{
double real=0.0; //在类声明中不能给数据成员赋初值
}
在定义对象时,对数据成员赋初值称为对象的初始化。
如果一个类的对象都是公有的,则可以在定义对象时对数据成员进行初始化。若类中包含私有的或者保护的成员,就不能用这种方法进行初始化。
所以一般用构造函数对数据进行初始化。构造函数的名字必须与类名相同。他可以有任意类型的参数,但不可有返回值类型。
构造函数的主要功能是对对象进行初始化,即对数据成员赋值。
在建立对象的同时,采用构造函数给数据成员赋值,通常采用以下两种形式:
(1)类名 对象名[(实参表)];
例:
complex(double r,double i)
{
real=r;
imag=i;
}
类型一:
complex A(1.1,2.2);
(2)类名*指针变量名=new类名[(实参表)];
这是一种用new运算符动态建立对象的方式。
说明:
(1)构造函数无返回值,在定义构造函数时,不能说明其类型。
(2)构造函数即可以定义在类内,也可以定义在类外。
定义在类外的情况如下:
Complex::Complex(double r,double i)
{
real=r;
imag=i;
}
(3)构造函数的作用主要是用来对对象进行初始化。
(4)构造函数一般声明为公有成员,但它不需要也不能像其他成员函数一样被显式调用,他是在定义对象时被自动调用的,且只执行一次。
(5)在实际应用中,通常要给每个类定义构造函数。如果没有定义构造函数,系统就会自动的生成一个默认的构造函数。
(6)构造函数还可以不带参数。如:
class Complex
{
public:
Complex()
{
real=0;
imag=0;
}
private:
double real,imag;
};
2.用成员初始化列表对数据成员进行初始化
在声明类中,对数据成员的初始化工作一般是在构造函数中用赋值语句进行。
如:
class Complex
{
private:
double real,imag;
public:
Complex(double r,double i);
};
Complex::Complex(double r,double i)
{
real=r;
imag=i;
}
还有另一种方式——用成员初始化列表来实现对数据成员的初始化。如:
class Complex
{
private:
double real,imag;
public:
Complex(double r,double i);
};
Complex::Complex(double r,double i):real(r),imag(i)
{
}
带有初始化成员列表的构造函数的一般形式如下:
类名::构造函数名([参数表])[:(成员初始化列表)]
{
//构造函数体
}
成员初始化列表一般形式为:
数据成员名1(初始值1),数据成员名2(初始值2),······
对于const修饰的数据成员,或者是引用类型的数据成员,是不允许使用赋值语句直接赋值的。只能用成员的初始化列表对其进行初始化。
如:
#include<iostream>
using namespace std;
class A
{
public:
A(int x1):x(x1),rx(x),pi(3.14)
{
}
void print()
{
cout<<"x="<<x<<"rx="<<rx<<"pi="<<pi<<endl;
}
private:
int x;
int& rx;
const double pi;
};
int main()
{
A a(10);
a.print();
return 0;
}
输出结果为:
x=10 rx=10 pi=3.14
数据成员是按照他们在类的声明中的顺序来进行初始化的,与他们在成员初始化列表中列出的顺序无关。
3.构造函数的重载
C++允许构造函数重载,以便适应不同的场合。例:
#include<iostream>
using namespace std;
class Date
{
public:
Date();
Date(int y,int m,int d);
void showDate();
private:
int year;
int month;
int day;
};
Date::Date()
{
year=2000;
month=4;
day=28;
}
Date::Date(int y,int m,int d):year(y),month(m),day(d)
{
}
inline void Date::showDate()
{
cout<<year<<"."<<month<<"."<<day<<endl;
}
int main()
{
Date date1;
cout<<"Date1 output:"<<endl;
date1.showDate();
Date date2(2002,11,14);
cout<<"Date2 output:"<<endl;
date2.showDate();
return 0;
}
运行结果如下:
注:
使用无参构造函数创建对象时,应该用语句“Date date1;”,而不能用语句“Date date1();”,因为语句“Date date1();”表示声明一个名为date1的普通函数,此函数的返回值为Date类型。
4.带默认参数的构造函数
例:
#include<iostream>
#include<cmath>
using namespace std;
class Complex
{
public:
Complex(double r=0.0,double i=0.0);
double abs();
private:
double real,imag;
};
Complex::Complex(double r,double i)
{
real=r;
imag=i;
}
double Complex::abs()
{
double t;
t=real*real+imag*imag;
return sqrt(t);
}
int main()
{
Complex s1; //没有传递实参,全用默认值
cout<<"s1"<<s1.abs()<<endl;
Complex s2(1.1); //传递了一个实参
cout<<"s2"<<s2.abs()<<endl;
Complex s3(1.1,2.2); //传递了两个实参
cout<<"s3"<<s3.abs()<<endl;
return 0;
}
说明:
(1)如果构造函数在类的声明外定义,那么默认参数应该在类的声明构造函数原型时指定,而不能在类外构造函数定义时指定。
(2)如果构造函数的全部参数都指定了默认值,那么这时的构造函数也属于默认构造函数。
(3)在一个类中定义了全部是默认参数的构造函数时,不能再定义重载构造函数。
4.析构函数
析构函数常用于执行一些清理任务,如释放内存的分配空间。
注:
(1)析构函数在他前面必须加一个波浪号(~)。
(2)析构函数不返回任何值。不能说明其类型。
(3)析构函数没有参数,不能被重载,一个类只能有一个析构函数。
说明:
(1)每个类必须有一个析构函数。
(2)除了在主函数结束之后,对象被撤销时,析构函数会被自动调用外,还有两种情况会被调用。
其一:如果一个对象被定义在函数体内,当这个函数调用结束之后,该对象会被释放,析构函数自动调用。
其二:若一个对象是使用new运算符动态创建的,则用delete来释放它时,delete会自动调用构造函数。