一、关系运算符重载
以重载等于号运算符为例:
#include<string>
#include <iostream>
using namespace std;
class Person {
public:
Person(string Name, int age) {
this->m_Name = Name;
this->m_Age = age;
}
public:
string m_Name;
int m_Age;
};
bool operator== (Person& p1, Person& p2) {
if ((p1.m_Name == p2.m_Name) && (p1.m_Age == p2.m_Age)) {
return true;
}
return false;
}
void test() {
Person p1("小明", 10);
Person p2("小强", 15);
Person p3("小强", 15);
if (p1 == p2) {
cout << "p1和p2相等" << endl;
}
else {
cout << "p1和p2不相等" << endl;
}
if (p2 == p3) {
cout << "p2和p3相等" << endl;
}
else {
cout << "p2和p3不相等" << endl;
}
}
int main()
{
test();
system("pause");
return 0;
}
二、函数调用运算符重载
函数调用运算符就是()。
#include <iostream>
#include <string>
using namespace std;
class MyPrint{
public:
void operator() (string text) {
cout << text << endl;
}
};
void test() {
MyPrint myPrint;
myPrint("你好"); //仿函数
}
int main(int argc, char** argv)
{
test();
system("pause");
return 0;
}
不要重载&&和||运算符,重载了&&和||符号后短路特性就会失效,按照符号的先后顺序进行运算。
符号重载的总结
- =,[],()和->操作符只能通过成员函数进行重载
- <<和>>只能通过全局函数配合友元函数进行重载
- 不要重载&&和||操作符,因为重载后无法实现短路规则
三、强化训练-字符串封装
1.输出自定义的字符串;2.让用户能任意输入一个字符串。
//MyString.h
#pragma once //防止头文件重复编译
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class MyString {
friend ostream& operator<< (ostream& cout, MyString& str);
friend istream& operator>> (istream& cin, MyString& str);
public:
//有参构造
MyString(const char* str);
//拷贝构造
MyString(const MyString& str);
//析构
~MyString();
private:
char* pString; //指向堆区的指针
int m_Size; //字符串的大小
};
//MyString.cpp
#include "MyString.h"
//输出私有属性需要将其作为友元
ostream& operator<< (ostream& cout, MyString& str) {
cout << str.pString;
return cout;
}
//右移运算符的重载
istream& operator>> (istream& cin, MyString& str) {
char buf[1024];
//让用户输入的内容保存到str
cin >> buf;
if (str.pString != NULL) {
delete[] str.pString;
str.pString = NULL;
}
str.pString = new char[strlen(buf) + 1];
strcpy(str.pString, buf);
str.m_Size = strlen(buf);
return cin;
}
MyString::MyString(const char* str)
{
cout << "有参构造调用" << endl;
this->pString = new char[strlen(str) + 1];
strcpy(this->pString, str);
this->m_Size = strlen(str);
}
MyString::MyString(const MyString& str)
{
this->pString = new char[str.m_Size + 1];
strcpy(this->pString, str.pString);
this->m_Size = str.m_Size;
}
MyString::~MyString()
{
cout << "析构函数调用" << endl;
if (this->pString != NULL) {
delete[] this->pString;
this->pString = NULL;
}
}
//字符串的封装.cpp
#include <string>
#include <iostream>
#include "MyString.h"
using namespace std;
//测试MyStrring
void test() {
MyString str = "abc";
cout << str << endl;
cout << "请输入str新的内容:" << endl;
cin >> str;
cout << "您输入的str的内容为:" << str << endl;
}
int main()
{
test();
system("pause");
return 0;
}
后续实现cin, cout, =, +, [], == 的重载功能。
四、继承的引出
两个类的成员函数及成员变量高度重复时,如果分别定义会出现很多重复代码,因此,引出继承,一个成员可直接继承另一个成员的属性。
继承的目的是为了减少代码冗余,被继承的类叫做基类(父类),继承的类叫做派生类(子类)。
例如一个新闻类和一个娱乐类的网页,除了中间部分,网页的头部底部侧栏都是共同属性,使用继承的写法如下:
将公共部分提出来写一个单独的BasePage类,新闻页和娱乐页都可以继承BasePage,类中只需要再单独写content的部分。
#include <string>
#include <iostream>
using namespace std;
//使用继承写法--重复代码都写到基类网页
class BasePage {
public:
void header() {
cout << "网页头部" << endl;
}
void footer() {
cout << "网页底部" << endl;
}
void left() {
cout << "网页侧栏" << endl;
}
};
class News : public BasePage { //继承的写法
public:
void content() {
cout << "新闻页" << endl;
}
};
class YULE : public BasePage {
public:
void content() {
cout << "娱乐页" << endl;
}
};
void test() {//测试
News news;
news.footer();
YULE yl;
yl.content();
}
int main()
{
test();
system("pause");
return 0;
}
五、继承方式
- 继承语法:
class 子类名 : 继承方式 父类名 {
//子类新增的数据成员和成员函数
};
- 三种继承方式:1.public:公有继承 2.private:私有继承 3.protected:保护继承。
保护属性和私有属性只能类内访问,不能类外访问。
六、继承中的对象模型
子类中会继承父类的私有成员,只是被编译器隐藏了起来,所以访问不到。
例如,下面运行sizeof(Son)时会显示16,而不是12。
class Base{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son : public Base{
public:
int m_D;
};
七、继承中的构造与析构
继承类进行对象声明,构造函数和析构函数的调用顺序如下:
- 父类构造函数调用
- 子类构造函数调用
- 子类析构函数调用
- 父类析构函数调用
子类会继承父类的成员函数和成员属性,但是子类不会继承父类的构造函数和析构函数。
#include <iostream>
using namespace std;
class Base{
public:
Base() {
cout <<"Base的默认构造函数调用" << endl;
}
~Base() {
cout <<"Base的析构函数调用" << endl;
}
};
class Son : public Base {
public:
Son() {
cout <<"Son的默认构造函数调用" << endl;
}
~Son() {
cout <<"Son的析构函数调用" << endl;
}
};
void test() {
Son s1;
}
int main() {
test();
system("pause");
return 0;
}
如果父类没有默认构造函数,子类可以通过初始化列表的方式显式的调用父类的其他构造。
class Base2{
public:
Base2(int a) {
this->m_A = a;
cout << "有参构造函数的调用" << endl;
}
int m_A;
}
class Son2 : public Base2{
public:
Son2(int a) : Base2(a) {
}
}
八、继承中的同名成员处理
如果子类和父类拥有同名的函数和属性,如果用子类对象调用就会默认调用子类的函数和属性,调用父类时需要通过作用域进行同名成员的区分:
Son s1;
s1.Base::fun();
如果子类与父类的成员函数名称相同,子类会把父类的所有同名版本(不论参数情况)都隐藏, 想调用父类方法必须加作用域。
注:
- 构造函数和析构函数不能被继承;
- operator=不能被继承。
九、继承中的静态成员处理
静态对象在类内声明,类外初始化;非静态成员只能用对象调用。
静态成员属性可以被子类继承,如果子类中有同名成员,会把父类的隐藏,静态成员函数同理。
#include <string>
#include <iostream>
using namespace std;
class Base {
public:
static void func() {
cout << "Base的func()函数" << endl;
}
static void func(int a) {
cout << "Base的func(int)函数" << endl;
}
static int m_A;
};
//静态成员在类内定义,类外初始化
int Base::m_A = 10;
class Son : public Base {
public:
static void func() {
cout << "Son的func()函数" << endl;
}
static int m_A;
};
int Son::m_A = 20;
void test() {
cout << Son::m_A; //20
cout << Base::m_A; //10
Son::func(); //访问子类的静态成员函数
Son::Base::func(10); //通过子类访问父类的静态成员函数
}
int main()
{
test();
system("pause");
return 0;
}
十、多继承的概念以及问题
可以从一个类继承,也可以同时从多个类继承,继承多个基类就称为多继承,但是从多个类中继承时可能会由于函数、变量等同名问题导致过多的歧义,出现二义性需要使用作用域进行访问。
多继承使用逗号做拼接,语法如下:
class A : public B1, public B2;
十一、菱形继承和虚继承
菱形继承:两个派生类继承同一个基类而又有某个类同时继承这两个派生类,这种继承被称为菱形继承或者钻石继承。
菱形继承带来的问题:
- C1和C2都继承了Base的数据,D继承C1和C2,当D调用函数或数据时,就会产生二义性。
- D继承的Base函数和数据继承了两份,但是这些数据只需要一份就可以。
菱形问题的解决方案:利用虚继承
- 在继承时加virtual关键字,此时为虚基类
- D的内部结构:vbptr虚基类指针指向虚基类表;通过虚基类表能找到共有的数据的偏移量
#include <string>
#include <iostream>
using namespace std;
class Base {
public:
int m_A;
};
//虚基类
class C1 :virtual public Base{
};
//虚基类
class C2 :virtual public Base {
};
class D :public C1, public C2 {
};
//菱形继承的解决办法--虚继承
void test() {
D d;
d.C1::m_A = 10;
d.C2::m_A = 20;
//使用虚继承之后,只有一份m_A,操作的都是同一份数据
cout << d.C1::m_A << endl; //20
cout << d.C2::m_A << endl; //20
//不加作用域不会有二义性
cout << d.m_A << endl;
}
int main()
{
test();
system("pause");
return 0;
}