概念
对象指的是类的实例,将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性,灵活性和扩展性
C++不是纯面向对象的语言,而是基于面向对象的语言(c语言面向过程)
面向对象三大特性
封装、继承、多态
封装:
- 将数据和方法封装到一个类中
- 类中有访问限定符
三种访问限定符
- public成员可从类外部直接访问,private/protected成员不能从类外部直接访问。
- 每个限定符在类体中可使用多次,它的作用域是从该限定符出现开始到下一个限定符之前或类体结束前。
- 类体中如果没有定义限定符,则默认为私有的。
- 类的访问限定符体现了面向对象的封装性。
作用域
- 每个类都定义了自己的作用域,类的成员(成员函数/成员变量)都在这个类的作用域内,成员函数内可以任意访问成员变量和其他成员函数。
- 对象可以通过.直接访问共有成员,指向对象的指针通过->也可以直接访问对象的共有成员。
- 在类体外定义成员,需要使用::作用域解析符指明成员属于哪个类域。
#include <iostream>
using namespace std;
class Date
{
public:
void Display()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
public:
int _year; //年
int _month; //月
int _day; //日
};
int main()
{
Date d;
d._year = 2018;
d._month = 3;
d._day = 21;
d.Display();
Date* p = &d;
p->_year = 2018;
p->_month = 2;
p->_day = 20;
p->Display();
system("pause");
return 0;
}
内外定义成员函数时:
class Date
{
public:
void Display();
public:
int _year; //年
int _month; //月
int _day; //日
};
void Date::Display()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
类对象存储模型
- 计算类的大小或对象的大小可以认为是成员变量的大小之和,再加上内存对齐规则即可。(成员函数不占空间)
- 内存对齐作用:可以提高代码执行效率。在windows下CPU是4字节4字节访问的,就是所谓的空间换时间。
- 空类对象的大小:1字节。当实例化出对象后,会给出1个字节的空间来标识对象,但不是为了存储数据。
隐含的this指针
- 每个成员函数都有一个指针形参,它的名字是固定的,称为this指针(隐式的)。(构造函数没有这个this形参)
- 编译器会对成员函数进行处理,在对象调用函数时,对象地址做实参传递给成员函数的第一个形参this指针。
- this指针是成员函数隐含指针形参编译器自行处理,我们不能传这个参数,也不能写这个参数,但可以使用这个参数。
类的默认成员函数
1.【构造函数】
相当于初始化函数。
- 函数名与类名相同。
- 无返回值。
- 对象实例化时系统自动调出对应的构造函数。
- 构造函数可以重载。
- 构造函数可以在类中定义,也可以在类外定义。
- 如果类定义中没有给出构造函数,则C++编译器自动产生一个缺省的构造函数,但只要定义了系统就不会生成缺省的构造函数。
- 无参的构造函数和全缺省的构造函数都是缺省构造函数,并且缺省的构造函数只能有一个。
//1.无参构造函数
Date()
{}
//2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//3.带缺省参数参构造函数
Date(int year = 2018, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
//4.带半缺省参数参构造函数
Date(int year, int month = 3)
{
_year = year;
_month = month;
_day = 1;
}
注:若缺省参数声明和定义分离,则可以在声明或定义中给默认参数。
不能使用Date d();
来调用缺省构造函数(编译器不允许)。
成员函数中自定义类型时,只能调用缺省(无参)的构造函数
2.【拷贝构造函数】
- 拷贝构造函数是一个构造函数的重载。
- 拷贝构造函数的参数必须使用引用传参,使用传值方式时在语义上会成为无穷递归。
- 若未显示定义,编译器会自动生成缺省的拷贝构造函数。缺省的拷贝构造函数会依次拷贝类成员进行初始化。
//拷贝构造函数
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date d1;
Date d2(d1); //调用拷贝构造函数
Date d3 = d1; //调用拷贝构造函数
以上两种调用拷贝构造函数是等价的。
3.【析构函数】
析构函数是特殊的成员函数。
- 析构函数在类名前加~。
- 析构函数无参数无返回值。
- 一个类有且只有一个析构函数。若未显示定义,系统会自动生成缺省的析构函数。
- 对象生命周期结束时,C++编译系统自动调用析构函数。
- 注意析构函数体内并不是删除对象,而是做一些清理工作。
class Array
{
public:
Array(int size)
{
_ptr = (int*)malloc(sizeof(int)*size);
}
~Array()
{
if (_ptr)
{
free(_ptr);
_ptr = 0;
}
}
private:
int* _ptr;
};
4.【赋值运算符重载】
运算符重载特征:
- operator+合法的运算符,构成函数名(重载=运算符的函数名:operator=)
- 重载运算符后,不能改变运算符的优先级/结合性/操作数个数。
赋值运算符重载:
- 拷贝构造函数是创建的对象,使用一个已有对象来初始化这个准备创建的对象。
- 赋值运算符重载是对一个已存在的对象进行拷贝赋值。
#include <iostream>
using namespace std;
class Date
{
public:
void Display()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
Date(int year = 2018, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造函数
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//赋值运算符重载
Date& operator= (const Date& d)
{
if (this != &d) //当d1=d1赋值时可直接返回,减少代码执行量
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year; //年
int _month; //月
int _day; //日
};
int main()
{
Date d1;
Date d2 = d1; //调用拷贝构造函数
d2.Display();
Date d3(1900, 1, 1);
d3 = d1; //调用赋值运算符重载
d3.Display();
system("pause");
return 0;
}
注:
- 赋值返回引用作用:避免调用拷贝构造函数(出了作用域还在则用引用返回)
- 若用void返回,无法完成d3=d2=d1
- 赋值运算符重载若不写,系统会自动生成一个
- 5个C++不能重载的运算符:.* / :: / sizeof / ?: / .
深入探究构造函数
类成员变量有两种初始化方式:
- 初始化列表
- 构造函数体内赋值
初始化列表以一个冒号开始,接着一个逗号分隔数据列表,每个数据成员都放在一个括号中进行初始化。尽量使用初始化列表进行初始化,因为它更高效。
原因:
- 初始化列表省去临时对象的存在
- 若在构造函数内进行赋值操作,会产生临时对象,降低效率
Date(int year = 2018, int month = 3, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
Date(int year = 2018, int month = 3, int day = 21) //初始化列表
: _year(year)
, _month(month)
, _day(day)
{}
必须放在初始化列表中的成员变量:
- 常量成员变量。(常量创建时必须初始化)
- 引用类型成员变量。(引用创建时必须初始化)
- 没有缺省构造函数的类成员变量。
成员函数按声明顺序依次初始化,而非初始化列表出现的次序。