一、关系运算符重载
以重载等于号运算符为例:
#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];//让用户输入的内容保存到strcin >> 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; //20cout << Base::m_A; //10Son::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; //20cout << d.C2::m_A << endl; //20//不加作用域不会有二义性cout << d.m_A << endl;
}int main()
{test();system("pause");return 0;
}