C++面向对象模型初探
C++对象模型可以概括为以下2部分:
- 语言中直接支持面向对象程序设计的部分,主要涉及如构造函数、析构函数、虚函数、继承(单继承、多继承、虚继承)、多态等等。
- 对于各种支持的底层实现机制。
- 在c语言中,“数据”和“处理数据的操作(函数)”是分开来声明的,也就是说,语言本身并没有支持“数据和函数”之间的关联性。
- 在c++中,通过抽象数据类型(abstract data type,ADT),在类中定义数据和函数,来实现数据和函数直接的绑定。
在C++类中有两种成员数据:static、nonstatic;三种成员函数:static、nonstatic、virtual。
类、对象、成员变量、成员函数
类,是一个抽象数据类型
对象,我们用类去定义对象
成员变量,C++中用于表示类属性的变量
成员函数,C++中用于表示类行为的函数
封装、多态、继承
封装
- 把属性和方法进行封装,对属性和方法进行访问控制
- 对类的访问控制:
- Public修饰成员变量和成员函数可以在类的内部和类的外部被访问
- Private修饰成员变量和成员函数只能在类的内部被访问
struct 与 class的区别
- 在用struct定义类时,所有成员的默认属性为public
- 在用class定义类时,所有成员的默认属性为private
构造函数与析构函数
构造函数:
- constructor来处理对象的初始化
- 构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行。
constructor的调用
- 自动调用:一般情况下C++编译器会自动调用构造函数
- 手动调用:在一些情况下则需要手工调用构造函数
- 没有任何返回类型的声明
析构函数:
- C++中的类可以定义一个特殊的成员函数清理对象,这个特殊的成员函数叫做析构函数
- 析构函数需要照顾对象的属性的内存生命周期
- 析构函数没有参数也没有任何返回类型的声明
- 析构函数在对象销毁时自动被调用
- C++编译器自动调用
copy构造函数
Test4() //无参数构造函数
{
m_a = 0;
m_b = 0;
cout<<"无参数构造函数"<<endl;
}
Test4(int a, int b) //有参数构造函数 //3种方法
{
m_a = a;
m_b = b;
cout<<"有参数构造函数"<<endl;
}
//赋值构造函数 (copy构造函数) //
Test4(const Test4& obj )
{
cout<<"我也是构造函数 " <<endl;
m_b = obj.m_b + 100;
m_a = obj.m_a + 100;
}
默认构造函数
2个特殊的构造函数
- 默认无参构造函数
当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空 - 默认拷贝构造函数
当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制
构造函数调用规则
- 当类中没有定义任何一个构造函数时,c++编译器会提供默认无参构造函数和默认拷贝构造函数
- 当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数
- 当类中定义了任意的非拷贝构造函数(即:当类中提供了有参构造函数或无参构造函数),c++编译器不会提供默认无参构造函数
- 默认拷贝构造函数成员变量简单赋值
总结:只要你写了构造函数,那么你必须用。
构造析构阶段性总结
- 构造函数是C++中用于初始化对象状态的特殊函数
- 构造函数在对象创建时自动被调用
- 构造函数和普通成员函数都遵循重载规则
- 拷贝构造函数是对象正确初始化的重要保证
- 必要的时候,必须手工编写拷贝构造函数
深copy与浅copy
浅拷贝
- 指针变量被赋值,但是指针变量所指向的内存空间未被赋值。
深拷贝
- 把对象的所有属性值和内存空间都拷贝
浅拷贝原因
- 因为obj2只是copy对象obj1的属性值,和指针值,所以为指针在堆区里面再分配内存空间,即obj1与obj2的指针指向的内存空间是同一个,故在调用析构函数时,先析构掉obj2指针所指向的内存空间,这时obj1的指针就变为了野指针。而再调用obj1的析构函数时,会发现指针指向的内存空间已经被析构掉了,这样就会发生core dump。
等号操作
解决浅拷贝的办法就是深拷贝,C++默认的拷贝构造函数与等号操作都是浅拷贝
对象初始化列表
- 如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,没有默认构造函数。这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,如果没有初始化列表,那么他将无法完成第一步,就会报错。
错误原因与浅拷贝原因一致
解决办法是重载等号操作符或者调用对象初始化列表 - 当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。
语法规则
Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
{
// some other assignment operation
}
- 注意概念
初始化:被初始化的对象正在创建
赋值:被赋值的对象已经存在
注意
- 成员变量的初始化顺序与声明的顺序相关,与在初始化列表中的顺序无关
- 初始化列表先于构造函数的函数体执行
class ABC{
public:
ABC(int a, int b, int c)
{
this->a = a;
this->b = b;
this->c = c;
printf("a:%d,b:%d,c:%d \n", a, b, c);
printf("ABC construct ..\n");
}
~ABC()
{
printf("a:%d,b:%d,c:%d \n", a, b, c);
printf("~ABC() ..\n");
}
protected:
private:
int a;
int b;
int c;
};
class MyD
{
public:
//初始化成员列表
MyD():abc1(1,2,3),abc2(4,5,6),m(100)
//MyD()
{
cout<<"MyD()"<<endl;
}
~MyD()
{
cout<<"~MyD()"<<endl;
}
protected:
private:
ABC abc1; //c++编译器不知道如何构造abc1
ABC abc2;
const int m;
};
int main()
{
MyD myD;
return 0;
}
对象的动态建立与释放
new 与 delete
- 在软件开发过程中,常常需要动态地分配和撤销内存空间,例如对动态链表中结点的插入与删除。
- 在C语言中是利用库函数malloc和free来分配和撤销内存空间的。
- C++提供了较简便而功能较强的运算符new和delete来取代malloc和free函数
注意new和delete是运算符,不是函数,因此执行效率高
new运算符的例子
new int; //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针)
new int(100); //开辟一个存放整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址new char[10]; //开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址
new int[5][4]; //开辟一个存放二维整型数组(大小为5-4)的空间,返回首元素的地址;
float *p=new float (3.14159); //开辟一个存放单精度数的空间,并指定该实数的初值为//3.14159,将返回的该空间的地址赋给指针变量p
new 与 delete 使用格式
类对象的动态建立与释放
使用类名定义的对象都是静态的,在程序运行过程中,对象所占的空间是不能随时释放的。但有时人们希望在需要用到对象时才建立对象,在不需要用该对象时就撤销它,释放它所占的内存空间以供别的数据使用
C++中,可以用new运算符动态建立对象,用delete运算符撤销对象
比如:
Box *pt; //定义一个指向Box类对象的指针变量pt
pt=new Box; //在pt中存放了新建对象的起始地址在程序中就可以通过pt访问这个新建的对象。
cout<<pt->height; //输出该对象的height成员
cout<<pt->volume( ); //调用该对象的volume函数,计算并输出体积
C++还允许在执行new时,对新建立的对象进行初始化.
Box *pt=new Box(12,15,18);
这种写法是把上面两个语句(定义指针变量和用new建立新对象)合并为一个语句,并指定初值,这样更精炼。
新对象中的height,width和length分别获得初值12,15,18。调用对象既可以通过对象名,也可以通过指针。
在执行new运算时,如果内存量不足,无法开辟所需的内存空间,目前大多数C++编译系统都使new返回一个0指针值。只要检测返回值是否为0,就可判断分配内存是否成功。
ANSI C++标准提出,在执行new出现故障时,就“抛出”一个“异常”,用户可根据异常进行有关处理。但C++标准仍然允许在出现new故障时返回0指针值。当前,不同的编译系统对new故障的处理方法是不同的。
在不再需要使用由new建立的对象时,可以用delete运算符予以释放.
delete pt; //释放pt指向的内存空间
这就撤销了pt指向的对象。此后程序不能再使用该对象。
如果用一个指针变量pt先后指向不同的动态对象,应注意指针变量的当前指向,以免删错了对象。在执行delete运算符时,在释放内存空间之前,自动调用析构函数,完成有关善后清理工作。
静态成员变量成员函数
静态成员变量
- 关键字 static 可以用于说明一个类的成员,静态成员提供了一个同类对象的共享机制
- 把一个类的成员说明为 static 时,这个类无论有多少个对象被创建,这些对象共享这个 static 成员
- 静态成员局部于类,它不是对象成员
#include<iostream>
using namespace std;
class counter
{
static int num ; //声明与定义静态数据成员
public :
void setnum ( int i ) { num = i ; } //成员函数访问静态数据成员
void shownum() { cout << num << '\t' ; }
} ;
int counter :: num = 0 ;//声明与定义静态数据成员
void main ()
{ counter a , b ;
a.shownum() ; //调用成员函数访问私有静态数据成员
b.shownum() ;
a.setnum(10) ;
a.shownum() ;
b.shownum() ;
}
静态成员函数
- 静态成员函数数冠以关键字static
- 静态成员函数提供不依赖于类数据结构的共同操作,它没有this指针
- 在类外调用静态成员函数用 “类名 :: ”作限定词,或通过对象调用
疑难问题:静态成员函数中,不能使用普通变量
静态成员变量属于整个类的,分不清楚,是那个具体对象的属性。