面向对象
是一种程序设计范型,同时也是一种程序开发的方法。对象指的是类的实例,将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性,灵活性和扩展性。把一组数据结构和处理他们的方法组成对象,把相同行为的对象归纳为类,通过对类的封装隐藏内部细节,通过继承实现类的特化/泛化,通过多态实现基于对象类型的动态分派。
C++不是纯面向对象语言,而是基于面向对象的语言。因为它包含C的部分,而C是面向过程的。
面向对象的三大特征:封装、继承、多态。
类的大小
- 为类的非静态成员数据的类型大小之和。
- 有编译器额外加入的成员变量的大小,用来支持语言的某些特性(如:指向虚函数的指针)
- 为了优化存取效率,进行的边缘调整。
- 与类中的构造函数,析构函数以及其他成员函数无关。
类的大小与它当中的构造函数,析构函数,以及其他的成员函数无关,只与它当中的成员变量有关。需考虑内存对齐。
为什么要内存对齐
内存对齐主要存在于struct和union等复合结构在内存中的分布情况,许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们要求这些数据的首地址的值是某个数(通常是4或8);对于内存对齐,主要是为了提高程序的性能,数据结构,特别是栈,应尽可能在自然边界上 对齐,经过对齐后,cpu的内存访问速度大大提升。
简单来说,就是以空间换时间
内存对齐的计算
1.第一个成员在与结构体变量偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数=编译器默认的一个对齐数与该成员大小的较小值
VS中默认的值是8
gcc中默认的值是4
3.结构体总大小为最大对齐数(每个成员变量出了第一个成员都有一个对齐数)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
空类的计算
空类的大小为1。表示这个类型的对象存在过(用来占位)。
隐含的this指针
- 每个成员函数都有一个指针形参。它存在栈里,它的名字是固定的,称为this指针,this指针是隐式的。(构造函数比较特殊,没有这个隐含的this指针)。this指针存在ecx寄存器中,方便更快的调用。
- 编译器会对成员函数进行处理,在对象调用成员函数时,对象地址作实参传递给成员函数的第一个形参this指针。
- this指针是成员函数隐含指针形参,是编译器自己处理的,我们不能在成员函数的形参中添加this指针的参数定义,也不能在调用时显示传递对象的地址给this指针。
类的4个默认成员函数
1.构造函数
类的构造函数是一种特殊的成员函数。主要用来在创建对象时初始化对象,即为成员变量赋初始值。
成员变量为私有的,要对他们进行初始化,必须用一个共有成员函数来进行。同时这个函数应该有且仅在定义对象时自动执行一次,这时调用的函数称为构造函数。
特征:
a.函数名与类名相同。
b.无返回值
c.对象构造(对象实例化)时系统自动调用对应的构造函数。
d.构造函数可以在类中定义,也可以在类外定义。
e.如果类定义中没有给出构造函数,则C++编译器自动产生一个缺省的构造函数,但只要我们定义了一个构造函数,系统就不会生成缺省的构造函数。
f.无参的构造函数和全缺省值的构造函数都认为是缺省构造函数,并且缺省的构造函数只能有一个。
- 无参构造函数和带参构造函数
class Date
{
public:
//1.无参的构造函数
Date()
{
_year = 1900;
_month = 1;
_day = 1;
}
//Date();
void Display()
{
cout << this->_year << "-" << this->_month << "-" << this->_day<<endl;
}
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
//Date::Date()
//{
// _year = 1900;
// _month = 1;
// _day = 1;
//}
void TestDate()
{
Date d1; //调用无参构造函数
d1.Display();
d1.SetDate(2017, 1, 1);
d1.Display();
}
Date::Date() 缺省构造参数声明与定义分离,可以在声明或定义中给默认参数。在类外调用构造函数,与在类内调用构造函数结果是一致的。
- 带参的构造函数
class Date
{
public:
//2.带参的构造函数
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
void Display()
{
cout << this->_year << "-" << this->_month << "-" << this->_day<<endl;
}
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void TestDate()
{
Date d2(2018, 1, 1);//调用带参的构造函数
Date d3();//注意这里没有调用d3的构造函数定义出d3
d2.Display();
//d3.Display();
d2.SetDate(2017, 2, 1);
d2.Display();
}
- 带缺省参数的构造函数
class Date
{
public:
//3.缺省的构造函数
Date(int year = 2000,int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Display()
{
cout << this->_year << "-" << this->_month << "-" << this->_day<<endl;
}
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void TestDate()
{
Date d4;
Date d7(2013, 4, 1);
d4.Display();
d7.Display();
}
无参构造函数不用传参,带缺省参数的构造函数也不用传参,因此无参相当于带缺省参数,调用时只需用一个即可。
- 半缺省构造函数
class Date
{
public:
//4.半缺省的构造函数
Date(int year, int month = 2, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Display()
{
cout << this->_year << "-" << this->_month << "-" << this->_day<<endl;
}
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void TestDate()
{
Date d5(2011);
Date d6(2011,3,6);
d5.Display();
d6.Display();
}
2.拷贝构造函数
创建对象时使用同类对象来进行初始化,这时所用的构造函数被称为拷贝构造函数,拷贝构造函数是特殊的构造函数。
特征:
- 拷贝构造函数其实是一个构造函数的重载。
- 拷贝构造函数的参数必须使用引用传参,使用传值方式会引发无穷递归。传值参数,把形参复制到实参会调用拷贝构造函数。如果允许拷贝构造函数传值,就会在拷贝构造函数内调用拷贝构造函数,就会形成无休止的递归周期从而导致栈溢出。
- 若未显示定义,系统会默认缺省的拷贝构造函数。缺省的拷贝构造函数会依次拷贝成员进行初始化。
class Date
{
public:
Date()
{
_year = 1900;
_month = 1;
_day = 1;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
//1.这里的对象可以直接访问私有的成员变量,在类的成员变量函数中可以直接访问同类对象的私有/保护成员。
//2.C++访问限定符是以类为单位的,也就是说在这个单位内的成员可以互相访问
}
void Display()
{
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
private:
int _year;
int _month;
int _day;
};
void TestDate2()
{
Date d1;
d1.Display();
Date d2(d1);
d2.Display();
Date d3 = d1;
d3.Display();
}
3.析构函数
当一个对象的生命周期结束时,C++编译系统会自动调用一个成员函数,这个特殊的成员函数即析构函数。析构函数是特殊的成员函数,其特征如下:
a.析构函数在类名前加上字符~。
b.析构函数无参数无返回值。
c.一个类有且只有一个析构函数。若未显示定义,系统会自动生成缺省的析构函数。
d.对象生命周期结束时,C++编译系统自动调用析构函数。
e.注意析构函数体内并不是删除对象,而是做一些清理工作。
f.如果先构造的V1,后构造V2。那么析构的顺序应该是:先析构V2,再析构V1。(在栈上)
class Date()
{
public:
Date(int year = 2000,int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//析构函数:做清理工作
~Date()
{
cout << "~Data()" << endl;
}
private:
int _year;
int _month;
int _day;
}
这里的析构函数需要完成清理工作
class MyVector
{
public:
MyVector(size_t capacity = 0)
{
if (capacity == 0)
{
_a = NULL;
_size = 0;
_capacity = 0;
}
else
{
_a = (int*)malloc(capacity*sizeof(int));
_size = 0;
_capacity = capacity;
}
}
~MyVector()
{
if (_a)
{
free(_a);
_a = NULL;
}
_size = _capacity = 0;
}
private:
int* _a;
size_t _size;
size_t _capacity;
};
4.赋值运算符重载
拷贝构造函数是创建的对象,使用一个已有对象来初始化这个准备创建的对象。赋值运算符的重载是对一个已存在的对象进行拷贝赋值。让自定义类型像内置类型一样使用。
特征:
a.operator+合法的运算符构成函数名
b.重载运算符以后,不能改变运算符的优先级/结合性/操作数个数。
class Date
{
public:
Date()
{}
//拷贝构造函数
Date(const Date&d)
:_year(d._year)
,_month(d._month)
, _day(d._day)
{}
Date& operator=(const Date& d)
{
if (this != &d)
{
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
return *this;
}
void Display()
{
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
private:
int _year = 1900;
int _month = 1;
int _day = 1;
};