面型对象就是根据需要,将后续用到的各种变量、函数按类别打包到一起,这些不同的类别就是类,代入数据之后(实例化)得到的就是对象。
类是对数据(属性)与函数(行为)的封装,定义格式:
class 类名
{
private:
成员数据;
成员函数;
protect:
成员数据;
成员函数;
public:
成员数据;
成员函数;
}**;**
注:对三个关键字(又叫限定词)的解释
private
:私有的。只能在类的内部使用,无法被派生类继承,无法在类外被调用。protect
:保护的。只能在类的内部和类的子类之内被调用,不能在类外被调用。public
:公有的。整个文件都有访问权限,是公开的。限定词一旦使用,一直有效,直到下一个限定词出现。默认限定词为private
。
成员函数可以只在类内做函数原型声明,在类外定义使用作用域运算符::
定义函数体
函数类型 类名 ::函数名(参数表)
{
函数体
}
在类定义时并未分配空间,不能直接对成员数据初始化。
2. 成员数据
1.访问私有数据
可以在public
中定义函数,实现对私有数据的访问。可以使用一般变量,指针,引用作为参数。
对象即类的实例,定义了对象之后会为对象分配空间,存储成员数据,但成员函数的代码是共享的。
定义对象的方式与一般变量相同:类名 对象名;
通过.
运算符调用对象的成员数据与函数。
同类型的变量可以整体赋值
用法与一般指针相同,用来存储类的对象的地址
类名 *指针名
可以用类指针访问成员:p -> setx()
成员函数重载与普通函数重载相同。
不同对象占据内存中的不同区域,它们的成员数据保存的位置各不相同,但对成员数据进行操作的成员函数的程序代码是同一段代码。当对一个对象调用成员函数时,编译程序先将对象的地址赋给this指针,然后调用成员函数,每次成员函数存取数据成员时,也隐含使用this指针。
this指针用来存放类对象的地址,在类内部访问其他成员函数,由于存在this
指针,可以不使用对象名.成员函数
的方式,在类外调用必须这样
在类体内定义:
类名(参数表)
{
函数体
}
在类体外定义:
类名::类名(参数表)
{
函数体
}
1. 构造函数的特点:
2. 当显式地定义了构造函数之后,系统不会自动生成缺省的构造函数。默认的(无参或缺省)构造函数只能有一个,即只能有一个构造函数可以在不提供参数而被调用。
3. 使用new运算符动态建立对象时可以直接初始化
A *pa1,*pa2;
pa1=new A(3.0, 5.0);//用new动态开辟对象空间,初始化
4. 局部对象,静态对象,全局对象的构造函数调用:
在对象生命期结束时,析构函数回收系统为对象分配的空间
~类名(参数表)
1. 析构函数的特征:
2. 对象中使用new运算符创建空间需要使用显式定义析构函数,在函数体中使用delete
运算符回收空间。
3. 当使用运算符delete删除一个由new动态产生的对象时,它首先调用该对象的析构函数,然后再释放这个对象占用的内存空间
4. 可以使用new
建立对象数组
pa1=new A[3];
使用delete []pa1
回收空间
全局对象:
局部对象
static局部对象:
new创建的对象:
定义一个对象时,用另一个对象为其初始化
类名 :: 类名(类名 &对象名)
{
函数体
}
调用格式:
类名 对象1(对象2)
类名 对象1=对象2
没有定义显示定义拷贝构造函数时,系统会自动生成一个拷贝构造函数,将实参对象的所有成员数据一一复制到新对象:
类名(类名 &对象名)
{
成员数据1=对象名.成员数据1;
……
}
有时,自动生成的拷贝构造函数会出问题。
但是,当类中的数据成员中使用new运算符,动态地申请存储空间进行赋初值时,必须在类中显式地定义一个完成拷贝功能的构造函数,以便正确实现数据成员的复制。否则会出现两次回收同一段内存的错误,因为new
运算符所开空间通过指针使用。
在b类中使用a类的对象,构造函数的格式
class a
{
public:
int a;char b
...
}
clas b
{
public:
int a;
a a1;
b(int a,char b, intc):a1(a,b){a=c}
}
通常,每当说明一个对象时,把该类中的有关成员数据拷贝到该对象中,即同一类的不同对象,其成员数据之间是互相独立的
如果将类中的某一成员数据用static
修饰,则这个成员数据会在编译时分配空间,该类所有的对象共享这块空间,即对于所有对象,该成员数据都是相同的,共享一块内存,所有对象都可以引用,公用的。
类的静态数据成员是静态分配存储空间的,而其它成员是动态分配存储空间的(全局变量除外)。当类中没有定义静态数据成员时,在程序执行期间遇到说明类的对象时,才为对象的所有成员依次分配存储空间,这种存储空间的分配是动态的;而当类中定义了静态数据成员时,在编译时,就要为类的静态数据成员分配存储空间。
public
权限的静态成员数据才能在类外被访问。数据类型 类名::静态成员数据名 = 初值
,不定义初值则默认为0::
调用,类名::静态成员数据名
或对象名.静态成员数据名
用static
修饰成员函数
2. 与静态数据成员一样,在类外的程序代码中,通过类名加上作用域操作符,可直接调用静态成员函数。
5. 不能把静态成员函数定义为虚函数。
1. 普通函数作为友元函数
友元函数是一种定义在类外部的普通函数,其特点是能够访问类中私有成员和保护成员,即类的访问权限的限制对其不起作用。
friend FuncName();
friend float Volume(A &a);
说明:
this
指针。2. 其他类的成员函数作为类的友元函数
A类中的某个成员函数是B类中的友元函数,这个成员函数可以直接访问B类中的私有数据。
注意:
class A;
class B
{
};
class A
{
...
friend 函数类型 B::成员函数名(参数)
...
};
例:
class A;
class B
{
double h;
public:
B(double h){h=high;}
void cal(A &c) //B中的成员函数z需要使用A中的数据成员
};
class A
{
double r;
public:
A(double a){r=a;}
friend void B::cal(A &c);//A的友元,B的成员函数,在A中只能给出声明
};
void B::cal(A &c) // A的友元,B的成员,在A、B之外给出完整定义
{
double v = 3.14*c.r*c.r*h
}
一个类的友元可以自由地用该类中的所有成员。
B是A的友元类,B可以使用A中的所有数据成员
class A
{
.....
friend class B;
}
一般先定义A,再定义B,但是B会在A之前先声明。
为了重载运算符,必须定义一个函数,并告诉编译器,遇到这个重载运算符就调用该函数,由这个函数来完成该运算符应该完成的操作。这种函数称为运算符重载函数,它通常是类的成员函数或者是友元函数。运算符的操作数通常也应该是类的对象。
规定:
不能重载:?:(三目运算符) .(成员运算符) .*(成员指针) ::(作用域运算符) sizeof(字节个数运算符)
格式:
类名 operator<运算符>(<参数表>)
{ 函数体 }
A operator + (A &);
重新定义运算符,由左操作符调用右操作符。最后将函数返回值赋给运算结果的对象,没有返回值就为void
类型。
2. 自增(自减)运算符重载:
前置:
operator ++( )
{ ......;}
后置:
operator ++(int)
{ ......;}
class A
{ float x, y;
public:
A(float a=0, float b=0){ x=a; y=b; }
A operator ++( ){A t; t.x=++ x; t.y=++y; return t;} //前置
A operator ++(int) { A t; t.x=x++; t.y=y++; return t;} //后置
};
void main(void)
{ A a(2,3), b;
b=++a;
b=a++;
}
调用格式:
b=a.operator++( );
b=++a;
注:成员函数实现运算符的重载时
所有参与运算的对象都作为参数,放入参数表
friend <类型说明> operator<运算符>(<参数表>)
{......}
friend A operator + (A &a, A &b)
{.....}
调用:
c=a+b;
// c = operat0r+(a,b)
重载自增自减运算符时,对于后置需要在参数表中加一个int
参数,用以区分前置。当++
或--
后置时,则调用含有int
的函数
注:=
(赋值运算符),[]
(下标运算符),->
(指针运算符)不可以用友元函数重载。
不同类型的数据运算时的相互转换规则,只能用成员函数实现。
声明格式:
operator < type >( )
定义格式(不需要返回值)
ClassName :: operator ( )
A :: operator float ( )
转换函数可以实现cout << 对象
。
转换之后的数据如果支持某些运算符,则可以直接运算。
运算符调用顺序:
注:
复合赋值运算符(+= -= *= /=
)可以用成员函数或友元函数重载,赋值运算符(=
)只能用成员函数重载。
1. 复合赋值运算符
声明格式:
返回值类型 operator+=(参数)
2. 赋值运算符
类名 &operator=(类名 &对象名)
{
成员数据1=对象名.成员数据1;
成员数据2=对象名.成员数据2;
...
成员对象n=对象名.成员数据n;
return *this;
}
简单来说就是operator 返回的是这个值,而&operator返回的是这个的地址(引用)。
主要的区别于用处就在于这个运算符的连用性,如果需要连用的话必须使用引用。
返回引用是为了能够连续赋值 如(a=b)=c,如果不返回引用的话像楼上说的那样,*this是当做临时变内量返回的容,C++为了保证临时变量从产生到返回不被修改,从而把临时变量定义为const,因而(a=b)=c的话,a=b为一个const,c是不能赋值给他的。
1. 输入运算符(>>
)
声明格式:
friend istream &operater>>(istream &, ClassName &);
返回类istream的引用,cin中可以连续使用运算符“>>”。
例:
istream &operator>>(istream &is, incount &cc)
{
is>>cc.c1>>cc.c2;
return is;
}
2. 输出运算符(<<
)
声明格式:
friend ostream &operater<<(ostream &, ClassName &);
定义格式:
ostream &operator<<(ostream &os,incount &cc) //重载cout<<
{os<<"c1="<< cc.c1<<'\t'<<"c2="<< cc.c2<< endl; return os;}
返回值为is或os对象
在派生类中不可以定义基类的对象
三种访问权限:public
,protected
,private
class className: 访问权限 基类名
{
...
}
1. 公有派生(public
)
访问权限变化:
成员属性 | 派生类中 | 派生类外 |
---|---|---|
公有 | 可以访问 | 可以访问 |
protected | 可以访问 | 不可以访问 |
private | 不可以访问 | 不可以访问 |
class A { int x;
protected: int y;
public: int z;
A(int a,int b,int c){x=a;y=b;z=c;}//基类初始化
int Getx(){return x;}//返回x
int Gety(){return y;}//返回y
void ShowA(){cout<< "x="<
2. 保护派生(protected
)
成员访问权限降一级,public->protected->private
成员属性 | 派生类中 | 派生类外 |
---|---|---|
public | 可以访问 | 不可以访问 |
protected | 可以访问 | 不可以访问 |
private | 不可以访问 | 不可以访问 |
class A
{
int x, y;
protected:
A(int a,int b){x=a;y=b;}
public:
void ShowA(){cout<< "x="<
3. 私有派生(private
)
访问权限全部变为private
访问权限:
成员属性 | 派生类内 | 派生类外 |
---|---|---|
public | 可以访问 | 不可以访问 |
protected | 可以访问 | 不可以访问 |
private | 不可以访问 | 不可以访问 |
class A
{
int x;
protected:
int y;
public:
int z;
A(int a,int b,int c){x=a;y=b;z=c;}
int Getx(){return x;}//返回x
int Gety(){return y;}//返回y
void ShowA(){cout<< "x="<
4. 抽象基类
这个类只能用作基类来派生出新的类,而不能用这种类来定义对象时,称这种类为抽象类。
将类的构造函数或者析构函数设为protected
时,该类为抽象类。
注:将类的构造函数或者析构函数设为private
没有意义
5. 构造函数
派生类构造函数名(总参数列表) 基类构造函数(参数列表)
{派生类新增数据的初始化语句}
6. 析构函数
先执行派生类的析构函数,再执行基类的析构函数
定义格式
class 类名:<访问权限>类名1,..., <访问权限>类名n
{
private: ...... ; //私有成员说明;
public: ...... ; //公有成员说明;
protected: ...... ; //保护的成员说明;
};
例:
class A{ int x1,y1;
public: A(int a,int b) { x1=a; y1=b; }
void ShowA(void){ cout<<"A.x="<
注:构造函数不能被继承,派生类的构造函数必须调用基类的构造函数来初始化基类成员基类子对象。
当派生类中有基类的对象时,在派生类的构造函数中需要单独调用对象的构造函数。
派生类构造函数的调用顺序如下:
例:
class Derived:public Base2, public Base1
{
int z;
Base1 b1,b2; //基类的对象
public:
Derived(int a,int b):Base1(a),Base2(20), b1(200),b2(a+b) //构造函数单独初始化基类的对象
{z=b; cout<<"调用派生类的构造函数!\n";}
~Derived( ){cout<<"调用派生类的析构函数!\n";}
}
当撤销派生类对象时,析构函数的调用正好相反
1. 冲突
多继承时,可能出现派生类继承多个同名的数据成员或成员函数,需要使用类限定符区分:类::成员名;
例:
class A{
public: int x;
void Show(){cout <<"x="<
2. 支配
派生类中新增的数据成员或者成员函数可能会与基类的数据成员或者成员函数重名。
当派生类中新增加的数据或函数与基类中原有的同名时,若不加限制,则优先调用派生类中的成员。
class A{
public: int x;
void Show(){cout <<"x="<
3. 赋值
派生类对象可以赋值给基类对象,反之则不行。
赋值类型
派生类对象赋值给基类对象
派生类对象初始化基类引用
派生类对象的地址赋给基类的指针
class A
{
int x;
int y;
…
};
classB:public A
{
int a;
int b;
…
}
B b1;
A &a1 = b1;
基类对象只会使用派生类对象中基类有定义的内容
4. 补充
任一基类在派生类中只能继承一次,否则,会造成成员名的冲突。若在派生类中,确实要有二个以上基类的成员,则将基类的两个对象作为派生类的成员。
实际的继承、派生关系很复杂,可能会出现一个派生类D中会有多个基类A的拷贝,造成数据成员的使用模糊。
虚基类可以使简介继承公共基类时,只保留一份公共基类的拷贝。
虚基类在派生时进行声明,只需要在每个直接继承的派生类中声明即可
class B:public virtual A //
{
类体
};
如果在虚基类中定义了带参数的构造函数或者没有定义带参数的构造函数,则在所有派生类中(直接、间接)都需要显示调用虚基类的构造函数。
多态性
虚函数可以实现,基类对象访问派生类中的同名成员函数。
可以在程序运行时通过调用相同的函数名而实现不同功能的函数称为虚函数
定义格式:
virtual 函数类型 函数名(参数列表)
一旦把基类的成员函数定义为虚函数,由基类所派生出来的所有派生类中,该函数均保持虚函数的特性。
在派生类中重新定义基类中的虚函数时,可以不用关键字virtual来修饰这个成员函数。
class A
{
protected:
int x;
public: A(){x =1000;}
virtual void print(){ cout <<“x=”<print();//调用类A的虚函数
pa=&b; pa->print();//调用类B的虚函数
pa=&c; pa->print();}//调用类C的虚函数
说明:
虚函数比一般的成员函数执行得慢
虚函数用基类指针调用才能体现多态性,用对象调用与一般成员函数无区别
在基类中不对虚函数给出有意义的实现,它只是在派生类中有具体的意义。这时基类中的虚函数只是一个入口,具体的目的地由不同的派生类中的对象决定。
定义:
virtual 函数类型 函数名(参数表)= 0;
说明:
=0
表示没有具体实现,在派生类中可以随便改写。例:弦截法求解方程的根
# include
# include
using namespace std;
class root
{
double
}
至少包含一个纯虚函数的类。这种类只能作为派生类的基类,不能用来说明这种类的对象。