面向对象大三大特征:封装、继承、多态
面向对象:把数据和数据操作方法组合到一起,作为一个相互依存的整体
封装:把属性和行为抽象出来形成一个事和物。类可以把自己的数据和行为让信任的类和对象访问,对不可信的进行隐藏
继承:子类继承父类的所有属性和行为,不用重新编写,提高代码的复用
多态:同名函数的多种实现方式(一个接口,多种实现)对象不同,调用同一个接口,会形成多态
运算符重载
加号运算符重载
#include <iostream>
using namespace std;
class Person
{
public:
/*//成员函数重载+号
Person operator+(Person &p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}*/
int m_A;
int m_B;
};
//全局函数重载+号
Person operator+(Person &p1, Person &p2)
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
Person operator+(Person &p1, int p2)
{
Person temp;
temp.m_A = p1.m_A + p2;
temp.m_B = p1.m_B + p2;
return temp;
}
void test()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
//成员函数重载本质调用
//Person p3 = p1.operator+(p2);
//全局函数重载本质调用
//Person p3 = operator+(p1,p2);
Person p3 = p1 + p2;
//重载operator+重载
Person p4 = p1 + 100;
cout << "p3.m_A = " << p3.m_A << endl;
cout << "p3.m_B = " << p3.m_B << endl;
cout << "p4.m_A = " << p4.m_A << endl;
cout << "p4.m_B = " << p4.m_B<< endl;
}
int main()
{
test();
system("pause");
return 0;
}
左移运算符重载
输出一个对象就可以把里面的属性全都打印出来
#include <iostream>
using namespace std;
class Person
{
friend ostream & operator<<(ostream &cout, Person &p);
public:
Person(int a, int b)
{
m_A = a;
m_B = b;
}
private:
int m_A;
int m_B;
};
//只能利用全局函数重载左移运算符
ostream & operator<<(ostream &cout,Person &p)//本质 operator<<(cout,p) 简化cout << p
{
cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
return cout;
}
void test()
{
Person p(10,10);
cout << p<<endl;
}
int main()
{
test();
system("pause");
return 0;
}
递增运算符重载
#include <iostream>
using namespace std;
//重载递增运算符
//自定义整型
class MyInteger
{
friend ostream & operator<<(ostream &cout, MyInteger &myint);
public:
MyInteger()
{
m_Num = 0;
}
//重载前置++运算符 返回引用是为了一直对一个数据进行递增操作
MyInteger & operator++()
{
//先进行++运算
m_Num++;
//再将自身返回
return *this;
}
//重载后置++运算符
MyInteger operator++(int) //int 代表占位参数,用于区分前置和后置递增
{
//先 记录当时结果
MyInteger temp = *this;
//后 递增
m_Num++;
//最后将记录结果做返回
return temp;
}
private:
int m_Num;
};
//重载左移运算符
ostream & operator<<(ostream &cout, MyInteger &myint)
{
cout << myint.m_Num;
return cout;
}
void test()
{
MyInteger myint;
cout << myint << endl;
cout << ++myint << endl;//前置递增
cout << myint++ << endl;//后置递增
cout << myint << endl;
}
int main()
{
test();
system("pause");
return 0;
}
a+b operator+(a,b) a.operator+(b)
-a operator-(a) a.operator-()
a++ operator++(a,0) a.operator++(0)
–a operator–(a) a.operator–();
赋值运算符重载
#include <iostream>
using namespace std;
//赋值运算符重载
class Person
{
public:
Person(int age)
{
m_Age = new int(age);
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
//重载 赋值运算符
Person & operator=(Person &p)
{
//编译器提供浅拷贝 光拷贝指针
//m_Age = p.m_Age;
//先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//深拷贝 指针指向内存空间进行拷贝
m_Age = new int(*p.m_Age);
return *this;
}
int *m_Age;
};
void test()
{
Person p1(18);
Person p2(20);
Person p3(30);
p3 = p2 = p1; //赋值操作
cout << "p1的年龄为:" << *p1.m_Age << endl;
cout << "p2的年龄为:" << *p2.m_Age << endl;
cout << "p3的年龄为:" << *p3.m_Age << endl;
}
int main()
{
test();
system("pause");
return 0;
}
关系运算符重载
#include <iostream>
using namespace std;
//重载关系运算符
class Person
{
public:
Person(string name, int age)
{
m_name = name;
m_age = age;
}
//重载 == 号
bool operator ==(Person &p)
{
if (this->m_name == p.m_name && this->m_age == p.m_age)
{
return true;
}
return false;
}
bool operator !=(Person &p)
{
if (this->m_name == p.m_name && this->m_age == p.m_age)
{
return false;
}
return true;
}
string m_name;
int m_age;
};
void test()
{
Person p1("Tom",18);
Person p2("Toms",18);
if (p1 == p2)
{
cout << "p1和p2是相等的!" << endl;
}
else
{
cout << "p1和p2是不相等的!" << endl;
}
if (p1 != p2)
{
cout << "p1和p2是不相等的!" << endl;
}
else
{
cout << "p1和p2是相等的!" << endl;
}
}
int main()
{
test();
system("pause");
return 0;
}
a+b operator+(a,b) a.operator+(b)
-a operator-(a) a.operator-()
a++ operator++(a,0) a.operator++(0)
–a operator–(a) a.operator–();
函数调用运算符重载
#include <iostream>
#include <string>
using namespace std;
//函数调用运算符重载
//打印输出类
class MyPrint
{
public:
//重载函数调用运算符
void operator()(string test)
{
cout << test << endl;
}
};
void MyPrint02(string test)
{
cout << test <<endl;
}
void test()
{
MyPrint myPrint;
myPrint("Hello world");//由于使用起来非常类似于函数调用,因此成为仿函数
MyPrint02("hello world");
}
//仿函数非常灵活,没有固定的写法
class MyAdd
{
public:
int operator()(int a, int b)
{
return a + b;
}
};
void test02()
{
MyAdd myAdd;
cout<<myAdd(2,5)<<endl;
cout << MyAdd()(100, 100) << endl;
}
int main()
{
test();
test02();
system("pause");
return 0;
}
继承
继承的基本语法
-
继承的好处:减少重复代码
-
语法:
class 子类:继承方式 父类
-
子类 也称 派生类
-
父类 也称 基类
派生类的成员中,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过 过来的表现其共性,而新增的成员表现其个性
继承方式
继承一共有三种方式
- 公共继承(public)
- 保护继承(protected)
- 私有继承(private)
父类中的公共权限成员 到子类中依然是公共权限
父类中的保护权限成员 到子类中依然是保护权限
父类中的私有权限成员 子类访问不到
继承中的对象模型
父类中所有非静态成员属性都会被子类继承下去
父类中私有成员属性 是被编译器给隐藏了,因此是访问不到,但确实被继承了
继承中构造和析构顺序
继承中的构造和析构顺序如下:
先构造父类,再构造子类,析构的顺序与构造的顺序相反
说明:构造函数的工作是在创建对象时自动执行的。
继承中同名成员处理
- 访问子类同名成员 直接访问
- 访问父类同名成员 需要加作用域
s.Base::func() s.Base::m_A
如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名的成员函数
继承同名静态成员处理方式
静态成员变量特点:
- 所有对象都共享同一份数据
- 编译阶段分配内存
- 类内声明,类外初始化
静态成员函数特点:
- 只能访问静态成员变量
- 所有函数都共享同一份对象实例
多继承语法
语法:class 子类:继承方式 父类1,继承方式 父类2 ...
多继承可能会引发父类中同名成员出现,需要加作用域区分
多态
多态的基本概念
多态性是指同名函数对应多种不同的实现
多态分为两类:
-
静态多态:函数重载 和 运算符重载属于静态多态,复用函数名
-
动态动态:派生类和虚函数实现运行时的动态多态
静态多态和动态多态的区别:
-
静态多态的函数地址早绑定 - 编译阶段确定函数地址
-
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
动态多态满足条件
- 有继承关系
- 子类重写父类中的虚函数
重写:函数返回值的类型、函数名、参数列表完全相同
动态多态使用
- 父类的指针或引用指向子类对象,发生多态
vftpr - 虚函数指针
v - virtual
f - function
ptr - pointer
vftable - 虚函数表
v - virtual
f - function
table - table
Animal类:vftptr->vtfable 表内记录虚函数的地址(&Animal::speak)
Cat类:(&Cat::speak)
当子类重写父类的虚函数
子类中的虚函数表 内部 会替换成 子类的虚函数地址
纯虚函数和抽象类
纯虚函数语法:virtual 返回值类型 函数名(参数列表) = 0
类中虚函数占4个字节
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
基类被虚继承才是虚基类
虚析构和纯虚析构
虚析构和纯虚析构
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构区别:
- 如果湿纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0
类名::~类名(){}
总结:
- 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
- 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
- 拥有纯虚析构函数的类也属于抽象类
错题
char p1[15]= "abcd",*p2= "ABCD", str[50]= "xyz";
strcpy(str+2,strcat(p1+2,p2+1));
printf("%s",str);
strcat()字符串连接函数
strcat(p1+2,p2+1)是p2+1连接到p1+2后面,也就是把BDC连接到cd后 <==> cdBDC
**strcpy()字符串复制函数 **
strcpy(“z”,“cdDBC”)
- 单精度浮点数的有效位数是7位。
- 双精度浮点数的有效位数是16位
1、int(*p)[4];------ptr为指向含4个元素的一维整形数组的指针变量(是指针)
2、int *p[4];-------定义指针数组p,它由4个指向整型数据的指针元素组成(是数组)
3、int(*)[4];--------实际上可以看作是一种数据类型。也就是第一个(int(*p)[4];)
sizeof计算的是变量整体占用的内存大小,包括了字符串末尾的结束符“\0”,再加上转义字符
注意不要和strlen记混了, strlen是遇到结束符则停止,所以 strlen出来的结果是不包含\0的。
对于const和reference类型成员变量,它们只能够被初始化而不能做赋值操作,因此只能用初始化列表
变量的作用范围就是从其定义或者声明的位置处开始到文件末尾结束。
C提供的三种预处理功能:宏定义 文件包含 条件编译
宏定义: #define 标识符 字符串
文件包含:#include “ xxx.h ” 或#include< xxx.h >
除了类属关系运算符"."、成员指针运算符".*"、作用域运算符"::"、sizeof运算符和**三目运算符"?:"**以外,C++中的所有运算符都可以重载
但是=、()、[]、->这四个不能重载为类的友元函数。
友元函数不属于类的成员变量,不能使用this指针
&a + 1,其中 + 1 偏移的大小为 a 整个数组的大小
a + 1,其中 + 1 偏移的大小为 a 中一个元素的大小
ptr - 1,由于强制类型转换,此时 - 1 偏移的大小为 1 个 int 型;
ptr数值本来是a数组的结尾的后一位地址值,减一则为a数组最后一位的地址。
关于浅复制和深复制:
浅层复制:只复制指向对象的指针,而不复制引用对象本身。
如果是浅复制,修改一个对象可能会影响另外一个对象。
深层复制:复制引用对象本身
如果是深拷贝,修改一个对象可能不会影响另外一个对象。
浅拷贝会造成内存的重复释放
利用深拷贝解决浅拷贝带来的问题
先构造父类,再构造子类;先析构子类,再析构父类
this指针:
>this指针只能在类的成员函数中使用,它指向该成员函数被调用的对象。
>静态成员函数没有this指针。
>友元函数是非成员函数,没有this指针。
友元函数重载时,参数列表为1,说明是1元,为2说明是2元
成员函数重载时,参数列表为空,是一元,参数列表是1,为2元
内联函数:
1.内联函数的函数体内不能含有复杂的结构控制语句
2.递归函数不能被用来作为内联函数
3.内联函数一般适合于只有1-5行语句的小函数
4.内联函数的定义必须出现在内联函数第一次被调用之前。
5.对内联函数不能声明可能抛出的异常
字符串输出 总结如下:
1:字符串长度比设定长度大就按字符串实际长度输出;
2:字符串长度比设定长度小就按右对齐输出该字串,左补空格。
3:如果数字之前有-,即%-5s表示左对齐输出
指针常量与数组指针
int const *p:p为指向int型常量的指针,p指针指向的地址可以修改,指针指向的值不可修改
int * const p:p为一个int型指针的常量,p指针指向的地址不可修改,指针指向的值可以修改
指针函数与函数指针
int (*p)fun():函数指针,本质是指针,指向函数
int *pfun():指针函数,本质是函数,函数的返回值是一个指针
指针数组与数组指针
int *arr[4]:指针数组,数组中的所有元素都是指针类型
int (*p)arr[4]:数组指针,指针存着数组的首地址,指针指向数组的一个首地址
|,^,&(按位或,按位异或,按位与)
|按位或:
有一个为1,结果为1
例如:1|2 = 0001 | 0010 = 0011 ∴1|2 =3
9|5 = 1001 | 0101 = 1101 ∴9|5 = 13
&按位与:
有两个为1,结果为1
例如:1&2 = 0001 & 0010 = 0000 ∴1&2 =0
9&5 = 1001 & 0101 = 0001 ∴9&5 = 1
^安慰异或:
10为1,00、11为0
例如: 1^2 = 0001 ^ 0010 = 0011 ∴1^2 = 3
9^5 = 1001 ^ 0101 = 1100 ∴9^5=12