4.3 this指针
成员变量和成员函数分开储存
只有非静态成员变量才属于类的对象上,静态成员变量和成员函数不属于类的对象上,即sizeof(对象),其字节大小只包括非静态成员变量
空对象占用的内存空间为:1个字节;C++编译器会给每个空对象分配一个字节的内存空间,是为区分空对象占内存的位置。
4.3.1 this指针
this指针的本质是指针常量,其指向不可以修改,但是指向的值可以修改。
-
指向被调用的成员函数所属的对象
-
隐含在每一个非静态成员函数内,不用程序员手动去写
-
不需要定义,直接使用即可
使用场景:
class Point
{
public:
int x,y;
//场景一:当形参和成员变量同名是,可用this指针区分
//this是指向当前对象的指针
Point(int x, int y)
{
this->x = x;
this->y = y;
}
//场景二:在类的用非静态成员函数中用于返回对象本身,可用return *this
//链式编程思想
Point& swap()
{
x += 2;
y += 2;
return *this;
}
};
调用:
Point p2 = p1.swap().swap().swap();
注意:
C++中空指针可以调用成员函数,需注意如果用到this指针(即访问到了非静态成员变量),需要加以判断是否是空指针来保证代码的健壮性。
4.3.2 const修饰成员函数
常函数:
- 成员函数后加const关键字,这个函数即称作成员函数
- 常函数不可修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
示例:
int x;
mutable int y;
void showPoint() const
{
this = NULL;//错误,this指针本质是常量指针,指向不可以修改;
this->x = 0;//错误,常函数类似与const Point *const this;this指针指向的值也不可以修改
this->y = 0;//正确
}
常对象:
- 声明对象前加关键字const,该对象称为常对象
- 常对象只能调用常函数
示例:
const Point p;
p.x = 0;//出错,常对象的成员变量不可以修改
p.y = 0;//mutable声明的成员变量可以修改
p.swap();//出错,不允许调用成员函数
p.showPoint();//正确
4.4 友元
友元的目的:让一个函数或者一个类访问另一个类中的私有成员
使用friend关键字
友元的三种场景:
- 全局函数做友元
- 类做友元
- 成员函数做友元
4.4.1 全局函数做友元
在类中进行全局函数的友元声明:(不需要用public等权限说明,直接放到类中即可),使得该全局函数可以访问该类(Point)的私有成员
friend void goodGay(Point &p);
4.4.2 类做友元
class goodGay
{
public:
Point *p; //声明一个Point对象,以访问该类的成员
};
在Point中声明友元类,表名goodGay是本类的友元,该类的中的成员可以访问本类的私有成员
friend class goodGay;
4.4.3成员函数做友元
class goodGay
{
public:
Point *p; //声明一个Point对象,以访问该类的成员
void visit(); //让visit函数可以访问Point中的私有成员
};
在Point中声明友元函数,表名visit函数是本类的友元,该成员函数可以访问本类的私有成员
friend void goodGay::visit();
4.5运算符重载
4.5.1 四则运算运算符重载
运算符重载的含义是对于非内置的类让其也可以通过运算符进行运算。但内置的数据类型的表达式的运算符是不可以改变的。
可以成员函数,全局函数进行重载,下面是一个复数相加的"+"成员函数重载的示例。
#include <iostream>
using namespace std;
class Complex{
friend ostream& operator<<(ostream &cout,Complex c);
private:
int m_real, m_imag;
public:
Complex(int real, int imag):m_real(real), m_imag(imag)
{
}
// 重载"+"运算符
Complex& operator+(Complex& c)
{
m_real += c.m_real;
m_imag += c.m_imag;
return *this;
}
//重载前置递增运算符 ,返回引用是为了对一个数据进行操作
Complex& operator++()
{
m_real++;
return *this;
}
//重载后置递增运算符 ,后置递增要返回值,因为不能返回局部变量的引用
Complex operator++(int)
{
Complex temp = *this;
m_real++;
return temp;
}
};
//重载左移运算符
ostream& operator<<(ostream &cout,Complex c)
{
cout<<c.m_real<<" + "<<c.m_imag<<"i"<<endl;
}
int main()
{
Complex c1(2,2), c2(3,3), c3(4,4);
Complex c4 = c1 + c2 +c3;
cout<<++(++c4)<<endl;
cout<<c4++<<endl;
cout<<c4<<endl;
system("pause");
return 0;
}
运算符重载也可以发生函数重载,即可以不和同类对象相加,重载函数参数可以是其他类型对象
4.5.2 左移/右移运算符重载
作用:可用于输出自定义的数据类型
一般不会在自定义的类中利用成员函数来重载左移运算符,因为无法实现cout在左侧
ostream& operator<<(ostream &cout,Complex &c)
{//cout属于ostream类对象,输出流对象
cout<<c.m_real<<" + "<<c.m_imag<<"i"<<endl;
}
调用:
cout<<c4<<endl;
4.5.3 递增递减运算符重载
4.5.4 赋值运算符重载
C++编译器默认给一个类添加赋值运算符,对属性进行值拷贝;
与复制构造函数一样,值拷贝也有出现浅拷贝问题,这时我们需要自己定义赋值运算符重载进行深拷贝,来解决这个问题。
以下成员函数接上面复制构造函数的例子:
Point& operator=(Point &p)
{
if(p_z)
{
delete p_z;
p_z = NULL;
}
p_x = p.p_x; p_y = p.p_y;
p_z = new int(*(p.p_z));
return *this;
}
4.5.5 比较运算符重载
bool operator==(Complex &c)
{
if(m_real==c.m_real && m_imag==c.m_imag) return true;
else return false;
}
4.5.6 函数调用运算符重载
- 函数调用运算符()可以重载
- 由于重载后使用方式很像函数调用,因为也称为仿函数
- 仿函数没有固定写法,非常灵活
示例:
在类中定义成员函数:
void operator()()
{
cout<<"函数调用运算符重载"<<endl;
}
调用:
c(); //c为一个类对象
Complex()();//匿名函数对象,Complex()为一个匿名对象
4.6 继承
继承是面向对象的三大特性之一
用继承的技术,子类继承父类中的成员,可减少重复代码
子类中的成员,包含两部分:从父类继承过来的成员,以及自己增加的成员
语法:
class 子类B(派生类):继承方式 父类A(基类)
{
仅包含B-A的部分
}
4.6.1 继承方式
三种继承方式:
- 公共继承:父类中公共属性在子类的依旧是公共属性,保护属性依旧是保护属性,私有属性依旧是私有属性。子类可访问父类公共属性和保护属性。类外可访问公共属性。
- 保护继承:依旧是私有属性。父类中公共属性和保护属性在子类中都是保护属性,私有属性依旧是私有属性。子类可访问父类公共属性和保护属性。
- 私有继承:父类中公共属性,保护属性,私有属性在子类的均为私有属性。子类可访问父类公共属性和保护属性。
父类中私有成员,子类不论用什么继承方式均不能访问
4.6.2继承中的对象模型
sizeof(子类) = sizeof(父类) + 自己新增的
父类所有权限非静态成员都会在子类中包含一份,只是因为权限问题可能访问不到
4.6.3 继承中构造和析构
创建一个子类对象,也需要创建一个父类对象
顺序:
- 调用父类的构造函数
- 调用子类的构造函数
- 调用子类的析构函数
- 调用父类的构造函数
4.6.4 继承中同名成员的处理方式
-
子类访问子类中同名成员,直接访问即可
-
子类访问父类中的同名成员,需要添加作用域
如果子类中有和父类同名的成员函数,子类的同名成员函数会隐藏掉父类中的所有同名成员函数,包括不同的重载函数
Son s;
s.m_A = 10;//访问子类的成员变量
s.Father::m_A = 10;//访问父类的成员变量
s.func();//访问子类的成员函数
s.Father::func();//访问子类的成员函数
继承中同名静态成员的处理方法:
- 通过对象访问:与非静态成员处理方法一致
- 通过类名访问:直接访问,也可写Son::Father::m_A
4.6.5 多继承
class 子类(派生类):继承方式 父类1,继承方式 父类2....
实际开发中不建议使用
4.6.5 虚继承
概念:
- 两个派生类(B,C)继承同一个基类(A)
- 又有某个类(D)同时继承这两个派生类
- 这种继承被称为菱形继承,或者称为钻石继承
问题:
-
D类同时继承了B类和C类,同名成员存在二义性的问题
-
D类中包含了2份A类中的数据,怎么处理
加上visual关键字,利用虚继承,可以解决菱形继承的问题,同名成员就只有一份,调用时不用加作用域;
A称为虚基类
class B:visual public A{};
class C:visual public A{};
class D:public B, public C{};
vbptr —虚基类指针,指向vbtable,vbtable中记录一个偏移量,通过该偏移量找到成员变量