将c++的类的相关知识做些总结
面向对象:
1.数据抽象
2.数据封装
3.模块化
4.软件复用
5.软件维护
类中的数据成员应在构造函数内初始化。
通常将类定义存放到.h文件中,在cpp中实现成员函数的实现。
成员函数可以在类内实现,也可以在类外实现,在类外实现:
void A::display(){}
在类中实现的成员函数是建议编译器将成员函数作为内联函数编译,内联函数是指编译器将该函数展开到调用点,以减少函数调用的时间,所以内联函数是一些较为短小的函数。
若数据成员是类类型,而且这个类没有定义或者没有定义完,那么该数据成员只能是指针或引用类型。
访问控制:
public:程序中任何位置均可访问
private:本类和友元中访问,默认访问控制
protected:本类,友元,派生类中可访问
union和struct的默认访问控制是public
new 和 malloc的区别:
new自动分配足够大小的空间,malloc需指定
new自动调用类的默认构造函数,malloc不会,free也不会自动调用析构函数
new返回指定类型的指针,malloc返回void指针
new可以重载
对于每一个类的对象,对于非静态数据成员,有各自的内存空间,每一个类内实现的成员函数,第一个参数默认为this,指向调用该函数的对象,this的类型为:
<A> *const this;
void f(A *p);
class A{
public:
void func(){ f(this); }
};
构造函数和析构函数
构造函数用于对象的初始化,析构函数用于对象消亡前的处理。
默认构造函数,重载的构造函数,拷贝构造函数
对于常量数据成员和引用数据成员,可以在定义构造函数时使用成员初始化表初始化他们。
class A{
private:
int x;
const int y ;
int &z ;
public:
A():z(x),y(1){
x = 1;
}
};
初始化顺序由数据成员定义时顺序决定。
若不使用成员初始化表初始化常量和引用数据,那么编译器不会提供默认的构造函数,因为提供的默认构造函数无法对常量和引用数据初始化,所以该类无法创建对象。
若类中存在其他类的对象,那么,构造函数调用顺序是:
先调用自身的构造函数,但在执行之前,将先调用成员初始化表中的指明的成员对象类的构造函数,若没有成员初始化表那么调用默认构造函数,然后再执行自身的构造函数。
析构函数则是先调用自身的析构函数,执行完后在调用成员对象类的析构函数。
如果有多个成员对象,那么构造函数的调用依据成员对象在类中的定义顺序来。析构函数则相反。
拷贝构造函数:
A(const <A>&);
1. A a1;
A a2(a1);
2. void f(A x);
A a;
f(a);
3.A f(){
A a;
...
return a;
}
编译器提供一个隐式的拷贝构造函数,该函数对数据成员采用常规的初始化操作,对于成员对象,则调用成员对象类的拷贝构造函数。
隐式的拷贝构造函数通过=操作初始化一般的数据成员,所以会导致两指针指向同一块区域:一个指针的操作会影响到另一个指针,调用析构函数消亡时,重复释放同一块内存,产生异常。
所以要显式定义一个拷贝构造函数,但是显式的拷贝构造函数不会自动调用成员对象类的拷贝构造函数,可以使用成员初始化表中显式调用。
对于常成员函数:
类的常量对象只能调用常成员函数。
常成员函数在函数体内不能修改数据成员的值
void f()const { } 无论定义还是声明,都需要加const
为了防止函数修改形参对象,通常将形参定义为常量对象指针或常量对象指针:
void f ( const A *pa);
或
void f ( const A &pa);
静态数据成员:
所有对象共享的数据成员
static ...
静态数据成员只能在类外定义,静态数据成员也要遵循访问控制规则。
class A {
static int s;
.....
}
int A::s = 0;
静态成员函数:
static void f(){ }
静态成员函数只能访问静态成员(数据成员和静态函数).
可以通过对象访问静态成员也可以通过类名受限访问: A::f();
友元:
友元可以提高对象对private和protected成员的访问效率.但也和数据封装的原则相冲突,友元是两者的折中方案。
友元:友元函数,友元类,友元类成员函数
friend f();
friend class B;
friend void C::func();
操作符重载:
<return type> operator <操作符>( ){ }
.(成员选择符) .*(间接成员选择符) ::(域解析符) ?: sizeof() 这五个操作符无法重载.
派生类:
定义派生类前,需要先定义基类。
派生类中可以使用基类的public和protected成员。
派生类可以定义与基类中同名的函数,派生类的函数将隐藏基类函数,可以使用基类名受限访问基类的函数。
继承方式:
public
private
protected
继承方式的调整:
class A{
public:
void f();
protected:
void g();
};
class B:private A{
public:
A::f;
protected:
A::g;
};
派生类对象的初始化和赋值操作
派生类对象的初始化调用自身的构造函数,在执行前,先根据成员初始化表调用基类的相应的构造函数,若没有显式指出,则默认调用基类的默认构造函数。然后根据成员初始化表调用成员对象类的构造函数,若没有显式指出,则调用默认构造函数,最后调用派生类的构造函数。析构函数则相反。
对于拷贝构造函数,派生类的隐式拷贝函数将会调用基类的拷贝构造函数,而派生类的自定义构造函数默认调用基类的默认构造函数,可以在自定义拷贝函数的成员初始化表中显示的指出。
成员函数调用的动态绑定
c++默认采用静态绑定,在编译时刻根据对象的类型调用函数。
但是可以将函数声明为虚函数,在其派生类中定义一个同名,参数相同,返回类型相同的函数覆盖基类函数。若类对象是引用或指针类型则对该函数采用动态绑定。
通过类名受限访问虚函数不采用动态绑定
虚函数:
只有类的成员函数才是虚函数
静态成员函数不能使虚函数
构造函数不能是虚函数
析构函数可以是虚函数
纯虚函数和抽象类
纯虚函数是指给出声明而没有定义的徐成员函数。
virtual int f()=0;
包含纯虚函数的类称为抽象类。抽象类不能用于创建对象。其主要作用在于为派生类提供一个基本框架和一个公共对外接口,派生类需要对抽象基类的所有纯虚函数进行实现。
多继承
对基类的声明次序决定了对基类的构造函数和析构函数的调用顺序,决定了数据成员的存储顺序。
名冲突:使用基类名受限访问
重复继承--虚基类
class A;
class B:public A{};
class C:public A{};
class D:public B,public C{};
解决方案是将A定义为B,C的虚基类。
对于虚基类
虚基类的构造函数由最新派生出来的类的构造函数调用
虚基类的构造函数有限非虚基类的构造函数执行
一个例子:关于对象的构造和析构的调用次序:
#include<iostream>
using namespace std;
class A{
int x,y;
const int ci;
char *c;
static int count;
public:
A():ci(1){
cout<<"A:init1"<<endl;
x=y=0;
c = new char[5];
strcpy(c,"zcx");
count++;
}
A(int x1,int y1,char *c1):ci(1){
cout<<"A:init2"<<endl;
x=x1;
y=y1;
c = new char[strlen(c1)+1];
strcpy(c,c1);
count++;
}
A(A const &a):ci(1){
cout<<"A:init3"<<endl;
x=a.x;
y=a.y;
c = new char[strlen(a.c)+1];
strcpy(c,a.c);
count++;
}
void show()const;
virtual void display(){cout<<"A:display"<<endl;}
~A(){
cout<<"A:delete"<<endl;
delete c;
c=NULL;
}
};
int A::count = 0;
void A::show()const{
cout<<x<<' '<<y<<' '<<c<<' '<<count<<endl;
}
class B:public A{
int z;
A a;
public:
B():a(1,1,"a"){
z = 0;
cout<<"B:init"<<endl;
}
void display(){
cout<<"B:display"<<endl;
}
~B(){
cout<<"B:delete"<<endl;
}
};
int main(){
A a(2,2,"a"),*p;
a.display();
B b,*q;
p = &b;
q = &b;
p->display();
b.display();
q->display();
}
输出:
A:init2
A:display
A:init1
A:init2
B:init
B:display
B:display
B:display
B:delete
A:delete
A:delete
A:delete