本文是我在学习C++过程当中的心得和学习笔记,在学习C++时已经有C语言的基础,因此入门知识省略了一部分。文章包含了C++的入门基础内容和核心进阶内容,并附上了学习的代码,仅供大家参考。如果有问题,有错误欢迎大家留言。剩余的内容可以通过 这篇文章找到。
类和对象
# include <iostream>
using namespace std;
const double PI = 3.14;
// 设计一个圆类,求圆的周长
// class 代表设计一个类,类后面紧跟着就是类的名称
class Circle
{
// 访问权限
public: // 公共权限
int r;
// 获取圆的周长(圆的行为)
double calcZC()
{
return 2 * PI * r;
}
};
int main()
{
Circle c1; // 创建一个名为c1的圆(实例化),通过一个类,创建一个对象的过程
c1.r = 10;
cout << "圆的周长为:" << c1.calcZC() << endl;
system("pause");
return 0;
}
一、 封装
// 访问权限
// 公共权限 public 类内可以访问 类外可以访问
// 保护权限 protected 类内可以访问 类外不可以访问 子类可以访问父类
// 私有权限 private 类内可以访问 类外不可以访问 子类不可以访问父类
# include <iostream>
using namespace std;
class Person
{
public: // 公共权限
string name; // 姓名
protected: // 保护权限
string car; // 汽车
private: // 私有权限
int password; // 银行卡密码
public:
void func()
{
name = "张三";
car = "拖拉机";
password = 123456;
}
};
int main()
{
// 实例化具体对象
Person p1;
p1.name = "李四";
//p1.car = "奔驰"; // 保护权限内容,在类外访问不到
//p1.password = 123; // 私有权限内容,在类外访问不到
//p1.func(); // 报错,访问不到
system("pause");
return 0;
}
1.1 struct和class区别
在C++中struct结构体和class类基本上没有不同。
# include <iostream>
using namespace std;
class C1
{
int A; // 默认权限是私有的 private
};
struct C2
{
int A; // 默认权限是公共的 public
};
int main()
{
C1 c1;
//c1.A = 100; // 报错,不能访问
C2 c2;
c2.A = 100; // 可以访问
system("pause");
return 0;
}
1.2 成员属性私有化
# include <iostream>
# include "string.h"
using namespace std;
class Person
{
public: // 通过设置接口访问类内部私有权限的属性
void set_name(string name) // 设置姓名
{
Myname = name;
}
string get_name() // 获取姓名
{
return Myname;
}
void set_age(int age) // 设置姓名
{
if (age < 0 || age>150)
{
Myage = 0;
return;
}
Myage = age;
}
int get_age() // 获取姓名
{
return Myage;
}
private:
string Myname; // 姓名 可读可写
int Myage; // 年龄 只读
string MyLover; // 情人 只写
};
int main()
{
Person p;
p.set_name("张三");
cout << "姓名为:" << p.get_name() << endl;
system("pause");
return 0;
}
1.3 对象的初始化和清理
1.3.1 构造函数和析构函数
# include <iostream>
using namespace std;
// 对象的初始化和清理
class Person
{
public:
// 1.构造函数进行初始化操作
// 按参数分类 无参构造(默认构造) 和 有参构造
// 按类型分类 普通构造 和 拷贝构造
Person() // 构造函数与类名相同,创建对象时会自动调用
{
cout << "Person的构造函数的调用" << endl;
}
Person(int a)
{
age = a;
cout << "Person(int a)的构造函数的调用" << endl;
}
// 拷贝构造
Person(const Person &p)
{ // 将传入的人身上的所有属性,拷贝到我身上。
age = p.age;
cout << "Person的拷贝构造函数的调用" << endl;
}
// 2.析构函数进行清理操作
~Person()
{
cout << "Person的析构函数的调用" << endl;
}
int age;
};
// 调用构造函数
void test1()
{
// 1.括号法
Person p1; // 默认构造函数调用,注意不需要加()
//Person p1(); // 这个语句编译器会认为这是一个函数的声明,p1视为一个返回值为Person的函数(在函数内部写另外一个函数的声明语法上是允许的)
Person p2(10); // 有参构造函数
Person p3(p2); // 拷贝构造函数
cout << "p2的年龄是:" <<p2.age << endl;
cout << "p3的年龄是:" << p3.age << endl;
// 2.显示法
Person p4;
Person p5 = Person(10); // 有参构造
/*Person(10)为匿名对象,特点:当前语句执行结束后就马上消失*/
Person p6 = Person(p2); // 拷贝构造
//Person(p3); //不要利用拷贝构造函数初始化匿名对象
//3.隐式转换法
Person p7 = 10; // 相当于 Person p4 = Person(10);
}
int main()
{
test1(); // 创建的对象在函数内部,离开函数就被释放,因此语句执行结束后才会调用析构函数,释放内存
system("pause");
return 0;
}
1.3.2 拷贝函数的用处
# include <iostream>
using namespace std;
// 对象的初始化和清理
class Person
{
public:
// 1.构造函数进行初始化操作
// 按参数分类 无参构造(默认构造) 和 有参构造
// 按类型分类 普通构造 和 拷贝构造
Person() // 构造函数与类名相同,创建对象时会自动调用
{
cout << "Person的构造函数的调用" << endl;
}
Person(int a)
{
age = a;
cout << "Person(int a)的构造函数的调用" << endl;
}
// 拷贝构造
Person(const Person &p)
{ // 将传入的人身上的所有属性,拷贝到我身上。
age = p.age;
cout << "Person的拷贝构造函数的调用" << endl;
}
// 2.析构函数进行清理操作
~Person()
{
cout << "Person的析构函数的调用" << endl;
}
int age;
};
// 拷贝构造函数用处
// 1.使用一个已经创建完毕的对象来初始化一个新对象
void test1()
{
Person p1(20);
Person p2(p1);
}
// 2.值传递的方式给函数参数传值
void dowork(Person p) {}
void test2()
{
Person p;
dowork(p);
}
// 3.值方式返回局部对象
Person dowork2()
{
Person p;
return p;
}
void test3()
{
Person p = dowork2();
}
int main()
{
test1(); // 创建的对象在函数内部,离开函数就被释放,因此语句执行结束后才会调用析构函数,释放内存
cout << "1111111" << endl;
test2();
cout << "2222222" << endl;
test3();
system("pause");
return 0;
}
1.3.3 深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
如果使用的是编译器默认提供的拷贝构造函数,会做浅拷贝。浅拷贝带来的问题是堆区的内存重复释放,浅拷贝出现的问题需要是深拷贝解决。
# include <iostream>
using namespace std;
class Person
{
public:
Person() // 构造函数与类名相同,创建对象时会自动调用
{
cout << "Person的构造函数的调用" << endl;
}
Person(int a, int h)
{
age = a;
height = new int(h);
cout << "Person(int a)的构造函数的调用" << endl;
}
// 拷贝构造
Person(const Person &p)
{ // 将传入的人身上的所有属性,拷贝到我身上。
age = p.age;
//height = p.height; // 编译器默认实现的就是这个语句 浅拷贝
height = new int(*p.height); // 深拷贝
cout << "Person的拷贝构造函数的调用" << endl;
}
// 2.析构函数进行清理操作
~Person()
{
// 析构代码,将堆区开辟的数据做释放操作
if (height != NULL)
{
delete height;
height = NULL;
}
cout << "Person的析构函数的调用" << endl;
}
int age;
int* height;
};
void test1()
{
Person p1(20,160);
cout << "p1的年龄为:" <<p1.age<<" 身高为:" <<*p1.height<< endl;
Person p2(p1);
cout << "p1的年龄为:" << p2.age << " 身高为:" << *p2.height << endl;
}
int main()
{
test1();
system("pause");
return 0;
}
1.4 对象初始化列表
# include <iostream>
using namespace std;
class Person
{
public:
int A;
int B;
int C;
/*
Person(int a, int b, int c) // 传统初始化操作
{
A = a;
B = b;
C = c;
}*/
// 初始化列表初始化属性
Person(int a, int b, int c) : A(a), B(b), C(c)
{
}
};
void test1()
{
//Person p(10, 20, 30); // 传统初始化操作
Person p(30, 20, 10); // 30先给int a,然后再给:后面的a,最后a再赋值给A。
cout << "A:" << p.A << " B:" << p.B << " C:" << p.C << endl;
}
int main()
{
test1();
system("pause");
return 0;
}
类对象作为类成员时构造函数和析构函数的运行顺序。
# include <iostream>
using namespace std;
class Phone
{
public:
Phone(string pname)
{
Pname = pname;
cout << "Phone的函数调用" << endl;
}
~Phone()
{
cout << "Phone的析构函数调用" << endl;
}
string Pname;
};
class Person
{
public:
Person(string name, string pname) : m_name(name), m_phone(pname)
{
cout << "Person的函数调用" << endl;
}
~Person()
{
cout << "Person的析构函数调用" << endl;
}
string m_name;
Phone m_phone;
/*构造时:当其他类对象作为本类成员时,先构造其他类对象,在构造自身;析构时:先释放自身,然后在释放其他类*/
};
void test1()
{
Person p("张三", "苹果");
cout << p.m_name << "拿着" << p.m_phone.Pname << "手机" << endl;
}
int main()
{
test1();
system("pause");
return 0;
}
1.5 类的静态成员变量和静态成员函数
# include <iostream>
using namespace std;
// 静态成员变量特点
/*
1.所有对象都共享同一份数据
2.编译阶段就分配内存
3.类内声明,类外初始化操作
静态成员变量不属于某个对象,所有对象都共享同一份数据,因此静态成员变量由两种访问方式:
1.通过对象进行访问
2.通过类名进行访问
*/
class Person
{
public:
static int m_A;
/*静态成员函数
所有成员共享一个函数,静态成员函数只能访问静态成员变量
*/
static void func()
{
m_A = 0;// 静态成员函数只能访问静态成员变量
//m_C = 0; // 不能访问非静态成员变量,因为编译器不能区分到底是哪个对象的m_C(每个对象都有一个m_C属性)
cout << "static void func()的调用" << endl;
}
int m_C;
private:
static int m_B;
static void func2()
{
cout << "static void func2()的调用" << endl;
}
};
int Person::m_A = 100;
int Person::m_B = 300;
void test1() // 1.通过对象来访问
{
Person p;
cout << p.m_A << endl; // 访问静态成员变量
p.func(); // 访问静态成员函数
Person p2;
p2.m_A = 200;
cout << p2.m_A << endl;// 改变p2.m_A所有m_A都改变,所有对象共享这个属性
// 2.通过类名来访问
cout << Person::m_A << endl;// 访问静态成员变量
//cout << Person::m_B << endl; // 类外可以对静态变量进行初始化,但是不能进行访问
Person::func(); // 访问静态成员函数
//Person::func2(); // 类外访问不到私有的静态成员函数
}
int main()
{
test1();
system("pause");
return 0;
}
1.6 this指针
类的成员变量和成员函数占用内存大小,以下代码也说明了所有类的对象是共用静态成员变量和成员函数的。那么也会产生一个问题,成员函数如何分辨是哪个对象调用了自己呢?this指针解决了这个问题。
# include <iostream>
using namespace std;
// 成员变量 和 成员函数是分开存储的
/*空对象占用内存空间为1byte,C++编译器会给每个空的对象也分配一个字节空间,是为了区分空对象占用内存的地址,每个空对象也应该有一个独一无二的内存地址*/
class Person
{
int m_A; // 非静态成员变量 属于类的对象上
static int m_B; // 静态成员变量 不属于类的对象上
void func() {}; // 非静态成员函数 不属于类的对象上
static void func1() {}; // 静态成员函数 不属于类的对象上
// 即只有非静态成员变量占用的内存属于类的对象占用的内存,其他不属于,是另外存储的。
};
void test1()
{
Person p;
cout << "size of p = " << sizeof(p) << endl;
}
int main()
{
test1();
system("pause");
return 0;
}
为了解决名称冲突的问题,使用this指针
# include <iostream>
using namespace std;
class Person
{
public:
Person& PersonAddage(Person& p)
{
this->age += p.age;
return *this;
// this 是指向p2的指针,而*this 指向的就是p2这个对象本体
}
Person(int age)
{
//m_age = age;
// 这里的m_age与age 做区分,也可以改成
this->age = age;
// this 指针指向被调用的成员函数所属的对象
}
//int m_age;
int age;
};
void test1()
{
Person p1(10); // 这里就是p1在调用Person()构造函数,那么里面的this指针就指向p1
cout << "p1 age = " << p1.age << endl;
Person p2(10);
p2.PersonAddage(p1).PersonAddage(p1);
cout << "p2 age = " << p2.age << endl;
}
int main()
{
test1();
system("pause");
return 0;
}
空指针访问成员函数
# include <iostream>
using namespace std;
// 空指针调用成员函数
class Person
{
public:
void ShowClassName()
{
cout << "this is a showclassname func" << endl;
}
void ShowPersonAge()
{
// 报错的原因是传入了空指针
if (this == NULL)
{
return;
}
cout << "age = " << this->age << endl;
}
int age;
};
void test1()
{
Person* p = NULL;
p->ShowClassName();
p->ShowPersonAge();
}
int main()
{
test1();
system("pause");
return 0;
}
1.7 const修饰成员函数
class Person
{
public:
// this 指针的本质是指针常量, 指针的指向是不可以修改的
void ShowPerson() const // 成员函数后面加const, 修饰的是this指向,让指针指向的值也不可以修改,相当于const Person* const this;
{
//this = NULL; // 报错,this指针不可以修改指向
//this->age = 100;
this->A = 100; // mutable修饰的成员变量在常函数中也可以修改
cout << "age = " << this->age << endl;
}
void func(){}
int age;
mutable int A; // 特殊变量,即使在常函数中也可以修改
};
void test2()
{
const Person p; // 在对象前面加const,对象变为常对象
//p.age = 100; // 报错
p.A = 100; // A 是特殊变量,在常对象下也能修改
// 常对象只能调用常函数
p.ShowPerson();
//p.func(); // 报错 常对象不可以调用普通成员函数,因为普通成员函数可以修改成员变量值,而常对象不可以修改
}
1.8 友元
在程序当中,有些私有属性(private)也想让类外特殊的一些函数或者类进行访问,就需要用到友元技术。友元的目的就是让一个函数或者类访问另一个类中私有成员。友元的关键字为friend。
// 全局函数作友元
# include <iostream>
using namespace std;
class Building
{
// 说明了GoodGay全局函数是Building的好朋友,可以访问Building的私有成员
friend void GoodGay(Building* building);
public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
void GoodGay(Building *building)
{
cout << "好基友全局函数 正在访问:" << building->m_SittingRoom << endl;
cout << "好基友全局函数 正在访问:" << building->m_BedRoom << endl;
}
void test1()
{
Building building;
GoodGay(&building);
}
int main()
{
test1();
system("pause");
return 0;
}
// 类作友元
# include <iostream>
# include <string>
using namespace std;
class Building
{
friend class GoodGay;
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
class GoodGay
{
public:
GoodGay();
void visit(); // 参观函数 访问Building中的属性
Building* building;
};
// 类外写成员函数
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay()
{
// 创建建筑物对象
building = new Building;
}
void GoodGay::visit()
{
cout << "好基友正在访问:" << building->m_SittingRoom << endl;
cout << "好基友正在访问:" << building->m_BedRoom << endl;
}
void test1()
{
GoodGay gg;
gg.visit();
}
int main()
{
test1();
system("pause");
return 0;
}
// 成员函数作友元
# include <iostream>
# include <string>
using namespace std;
class Building;
class GoodGay
{
public:
GoodGay();
void visit(); // 让visit()可以访问Building中的属性
void visit2(); // 让visit2()不可以访问Building中的属性
Building* building;
};
class Building
{ // 让visit()可以访问Building中的属性
friend void GoodGay::visit();
// 注意这里的GoodGay类要写在Building类前面
//friend class GoodGay;
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
// 类外写成员函数
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay()
{
// 创建建筑物对象
building = new Building;
}
void GoodGay::visit()
{
cout << "visit函数正在访问:" << building->m_SittingRoom << endl;
cout << "visit函数正在访问:" << building->m_BedRoom << endl;
}
void GoodGay::visit2()
{
cout << "visit2函数正在访问:" << building->m_SittingRoom << endl;
//cout << "visit2函数正在访问:" << building->m_BedRoom << endl;
}
void test1()
{
GoodGay gg;
gg.visit();
gg.visit2();
}
int main()
{
test1();
system("pause");
return 0;
}
1.9 运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
1.9.1 加法
/*加法运算符重载
作用:实现两个自定义数据类型相加的运算
例如,int a + int b 编译器知道如何计算,但是如果是两个类相加呢?*/
# include <iostream>
using namespace std;
class Person
{
public:
// 1、成员函数重载+号
/*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;
};
// 2.全局函数重载+号
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 num)
{
Person temp;
temp.m_A = p1.m_A + num;
temp.m_B = p1.m_B + num;
return temp;
}
void test1()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1 + p2;
/*
成员函数重载+号本质上是Person p3 = p1.operator+(p2);
全局函数重载+号本质上是Person p3 = operator+(p1, p3);
*/
cout << "p3.m_A: " << p3.m_A << endl << "p3.m_B: " << p3.m_B << endl;
Person p4 = p1 + 100; // 同理,也可以通过+号重载实现本质上是调用了这个Person operator+(Person& p1, int num)函数。
cout << "p4.m_A: " << p4.m_A << endl << "p4.m_B: " << p4.m_B << endl;
}
int main()
{
test1();
system("pause");
return 0;
}
/*总结:1.对于内置的数据类型表达式的运算符是不可能改变的
2.不要滥用运算符重载*/
1.9.2 左移运算符
左移运算符只能用全局函数重载。
class Person
{
public:
int m_A;
int m_B;
};
ostream& operator<<(ostream& cout, Person& p)
{
cout << "m_A: " << p.m_A << endl << "m_B: " << p.m_B << endl;
return cout;
}
void test1()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
cout << p1 << endl;
}
1.9.3 递增运算符
class MyInterger
{
friend ostream& operator<<(ostream& cout, MyInterger myint);
public:
MyInterger()
{
m_Num = 0;
}
MyInterger& operator++() // 重载前置++运算符
{
m_Num++; // 先进性++运算
return *this; // 再将自身做返回
}
MyInterger operator++(int) // 重载后置++运算符
{
MyInterger temp = *this; // 先记录自增前结果,后自增
m_Num++;
return temp;
}
private:
int m_Num;
};
ostream& operator<<(ostream& cout, MyInterger myint)
{
cout << myint.m_Num;
return cout;
}
void test1()
{
MyInterger myint;
cout << myint << endl;
cout << ++myint << endl; // 先自增 后输出
cout << myint << endl;
cout << myint++ << endl; // 先输出 后自增
cout << myint << endl;
}
1.9.4 赋值运算符重载
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 test1()
{
Person p1(18);
Person p2(10);
Person p3(30);
p3 = p2 = p1;
cout << "p1的年龄为:" << *p1.m_age << endl;
cout << "p2的年龄为:" << *p2.m_age << endl;
cout << "p3的年龄为:" << *p3.m_age << endl;
}
1.9.5 关系运算符重载
class Person
{
public:
Person(string name, int age)
{
m_age = age;
m_name = name;
}
bool operator==(Person& p) // =运算符重载
{
if (this->m_name == p.m_name && this->m_age == p.m_age)
{
return true;
}
else return false;
}
int m_age;
string m_name;
};
void test1()
{
Person p1("Tom",18);
Person p2("Tom",18);
if (p1 == p2)
{
cout << "p1和p2是相等的" << endl;
}
else
{
cout << "p1和p2不相等" << endl;
}
}
1.9.6 函数调用运算符重载
// 函数调用运算符重载
class Myprint // 打印输出类
{
public:
void operator()(string test)
{
cout << test << endl;
}
int m_age;
string m_name;
};
// 正常的打印输出函数
void Myprint02(string test)
{
cout << test << endl;
}
/*仿函数非常灵活,没有固定的写法*/
class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test1()
{
Myprint myprint;
myprint("helloworld");
// 由于使用起来非常类似于函数,因此被称为仿函数
Myprint02("helloworld");
MyAdd myadd;
int ret = myadd(100, 100);
cout << "ret = " << ret << endl;
// MyAdd()创建了一个匿名的函数对象,使用完就被释放了
cout << MyAdd()(100, 100) << endl;
}
二、继承
2.1 继承规则
语法: class 子类:继承方式 父类
因此我们把子类也称为派生类,父类也称为基类。
继承的方式一共有三种:公共继承,保护继承,私有继承。
1、父类中私有成员不可以访问
2、子类公共继承父类,那么父类的公共成员和保护成员权限都不变。
3、子类保护继承父类,那么父类的公共成员和保护成员权限都变成保护权限。
4、子类私有继承父类,那么父类的公共成员和保护成员权限都变成私有权限。
父类中所有非静态成员变量属性都会被字子类继承下去,父类中的私有属性,是被编译器给隐藏了,只是访问不到,是确实继承下去了,并且占有内存空间。
class base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class son1 :public base1 // 公共继承
{
public:
void func()
{
m_A = 10; // 父类中公共权限成员,子类中任然是公共权限
m_B = 10; // 父类中保护权限成员,子类中任然是保护权限
//m_C = 10; // 报错,父类中私有权限成员,子类不可以访问
}
};
class son2 :protected base1 // 保护继承
{
public:
void func()
{
m_A = 10; // 保护继承,子类中是保护权限
m_B = 10; // 保护继承,子类中是保护权限
//m_C = 10; // 报错,父类中私有权限成员,子类不可以访问
}
};
class son3 :private base1 // 私有继承
{
public:
void func()
{
m_A = 10; // 私有继承,子类中是私有权限
m_B = 10; // 私有继承,子类中是私有权限
//m_C = 10; // 报错,父类中私有权限成员,子类不可以访问
}
};
void test1()
{
son1 s1;
s1.m_A = 100; // 公共权限
//s1.m_B = 100; // 保护权限类外不可以访问
son2 s2;
//s2.m_A = 100;
//s2.m_B = 100; // 都报错,保护权限
son3 s3;
//s3.m_A = 100;
//s3.m_B = 100; // 都报错,私有权限
}
class Grandson2 :public son2
{
public:
void func1()
{
m_A = 10;
m_B = 10; // 都不报错,证明父类son2是从base1继承而来的两个成员变量的权限变为了保护而非私有
}
};
class Grandson3 :public son3
{
public :
void func1()
{
//m_A = 10;
//m_B = 10; // 都报错,证明父类son3是从base1继承而来的两个成员变量的权限变为了私有
}
};
2.2 父类子类构造和析构顺序
继承中构造和析构的顺序如下:先构造父类,然后再有子类,最后先释放子类,然后释放父类。(可以记忆成:先有父亲再有儿子,白发人送黑发人。)
class base1
{
public:
base1()
{
cout << "base1的构造函数" << endl;
}
~base1()
{
cout << "base1的析构函数" << endl;
}
};
class son1 :public base1
{
public:
son1()
{
cout << "son1的构造函数" << endl;
}
~son1()
{
cout << "son1的析构函数" << endl;
}
};
void test1()
{
son1 s1;
}
2.3 父类子类用同名成员的处理办法
/*当子类父类出现同名的成员,如何通过子类对象,访问到子类父类的成员呢?(继承中同名成员的处理方式)
解决: 访问子类同名成员 直接访问
访问父类同名成员 需要加作用域*/
class base1
{
public:
base1()
{
m_A = 100;
}
void func()
{
cout << "base1 func()的调用" << endl;
}
int m_A;
};
class son1 :public base1
{
public:
son1()
{
m_A = 200;
}
void func()
{
cout << "son1 func()的调用" << endl;
}
int m_A;
};
void test1()
{
son1 s1;
cout << "m_A: " << s1.m_A << endl; // 访问的是子类的成员
cout << "m_A: " << s1.base1::m_A << endl; // 加作用域访问的是父类成员
s1.func();
//s1.base1::func(); // 等价上一句
s1.base1::func();
/*总结:当父类和子类有同名成员时,默认访问的是子类,父类的同名成员会被隐藏, 若想访问需要加上作用域。*/
}
2.4 多继承
多继承(了解语法):
1、羊继承了动物的数据,骆驼同样继承了动物的数据,当草泥马使用数据时就会产生二义性。
2、草泥马继承自动物的数据继承了两份,但实际上我们仅仅需要其中一份。
class animal
{
public:
int m_age;
};
/*加virtual变为虚继承,这时animal类称为虚基类*/
class sheep: virtual public animal {};
class tuo : virtual public animal {};
class sheeptuo : public sheep, public tuo {};
void test1()
{
animal a1;
sheep s1;
tuo t1;
sheeptuo st;
a1.m_age = 10;
st.sheep::m_age = 18;
st.tuo::m_age = 28;
// 多继承访问父类同一份数据时,需要加以作用域区分
cout << "st.sheep::m_age = " << st.sheep::m_age << endl;
cout << "st.tuo::m_age = " << st.tuo::m_age << endl;
// 菱形继承的出现导致数据的浪费,利用虚继承可以解决
cout << "st.m_age = " << st.m_age << endl;
cout << "a1.m_age = " << a1.m_age << endl;
/*添加虚继承后m_age这个成员就变成父类子类共享的一个数据,因此改变三个结果输出都为28,无论改变sheep或是tuo作用域下的m_age,子孙类的m_age都会发生改变,父类不受影响*/
}
三、多态
3.1 多态的基本概念
/*地址早绑定,在编译阶段就已经确定.
若想让猫说话,函数地址就不能早绑定,需要晚绑定,即实现动态多态,父类说话函数用virtual修饰,即虚函数
动态多态满足条件:
1.有继承关系
2.子类重写父类的虚函数
动态多态使用:父类的指针或者引用执行子类对象
*/
class Animal
{
public:
/*不使用virtual修饰,子类继承父类就是简单的复制;
使用virtual时,父类中的函数就变成了指向子类函数的指针,dospeak(cat);运行时先产生父类然后生成子类,然后animal.speak();指向子类的函数并调用。*/
virtual void speak() // 虚函数
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
// 子类重写父类函数,函数返回值 函数名 参数列表 完全相同
void speak()
{
cout << "喵喵喵" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "汪汪汪" << endl;
}
};
void dospeak(Animal& animal)
{
animal.speak();
}
void test1()
{
cout << "size of animal: " << sizeof(Animal) << endl; // 通过animal类的大小推断出此时virtual修饰的说话函数占8byte(64位),指针类型的数据
Cat cat;
Dog dog;
dospeak(cat); // animal = cat
dospeak(dog);
}
3.2 多态案例设计一计算器类
多态的优点:代码组织结构清晰;可读性强;利于前期和后期的扩展以及维护。利用多态设计计算器:
// 普通写法
/*如果想扩展新的功能,需要修改源码,在真正的开发中,提倡开闭原则,即对扩展进行开发,对修改进行关闭*/
//class Calculator
//{
//public:
// int getResult(string oper)
// {
// if (oper == "+")
// {
// return m_num1 + m_num2;
// }
// else if (oper == "-")
// {
// return m_num1 - m_num2;
// }
// else if (oper == "*")
// {
// return m_num1 * m_num2;
// }
// else
// return m_num1 / m_num2;
// }
// int m_num1;
// int m_num2;
//};
//void test1()
//{
// Calculator c;
// c.m_num1 = 10;
// c.m_num2 = 10;
// cout << c.m_num1 << " + " << c.m_num2 << " = " << c.getResult("+") << endl;
// cout << c.m_num1 << " - " << c.m_num2 << " = " << c.getResult("-") << endl;
// cout << c.m_num1 << " * " << c.m_num2 << " = " << c.getResult("*") << endl;
// cout << c.m_num1 << " / " << c.m_num2 << " = " << c.getResult("/") << endl;
//}
// 利用多态实现计算器
class AbstractCalculator // 实现计算器抽象类
{
public:
virtual int getResult()
{
return 0;
}
int m_num1;
int m_num2;
};
class AddCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_num1 + m_num2;
}
};
class SubCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_num1 - m_num2;
}
};
void test2()
{
AbstractCalculator* add = new AddCalculator; // 父类指针指向子类对象
add->m_num1 = 10;
add->m_num2 = 10;
cout << add->m_num1 << " + " << add->m_num2 << " = " << add->getResult() << endl;
delete add; // 用完记得销毁
AbstractCalculator* sub = new SubCalculator;
sub->m_num1 = 10;
sub->m_num2 = 10;
cout << sub->m_num1 << " - " << sub->m_num2 << " = " << sub->getResult() << endl;
delete sub; // 用完记得销毁
}
3.3 纯虚函数和抽象类
// 纯虚函数和抽象类
class base
{
public:
virtual void func() = 0;
/*纯虚函数,只有有一个纯虚函数,这个类称为抽象类
抽象类特点:
1.无法实例化对象
2.抽象类的子类必须要重写父类中的纯虚函数,否则也属于抽象类*/
};
class son :public base
{
public:
virtual void func()
{
cout << "func函数调用" << endl;
};
};
void test1()
{
//base b;
//new base // 都报错,抽象类无法实例化
//son s; // 子类必须重写父类中的纯虚函数,否则也时抽象类,无法实例化对象
base* base = new son;
base->func();
/*总结:多态的目的就是让函数的接口通用化,通过一个父类指针,根据创建的对象不同从而调用不同的子类函数。*/
}
3.3.1 多态案例二-制作饮品
// 多态案例2 制作饮品
class AbstractDrinking
{
public:
virtual void boil() = 0; // 烧开水
virtual void brew() = 0; // 冲泡
virtual void pour_in_cup() = 0; // 倒入杯中
virtual void put_something() = 0; // 加作料
void makeDrink() // 制作饮品
{
boil();
brew();
pour_in_cup();
put_something();
}
};
class Coffee : public AbstractDrinking
{
public:
virtual void boil() // 子类的virtual加不加都可以,这里没有孙子类,没关系
{
cout << "煮农夫山泉" << endl;
}
virtual void brew()
{
cout << "冲泡咖啡" << endl;
}
virtual void pour_in_cup()
{
cout << "倒入杯中" << endl;
}
virtual void put_something()
{
cout << "加入糖和牛奶" << endl;
}
};
class Tea : public AbstractDrinking
{
public:
virtual void boil()
{
cout << "矿泉水" << endl;
}
virtual void brew()
{
cout << "冲泡茶叶" << endl;
}
virtual void pour_in_cup()
{
cout << "倒入杯中" << endl;
}
virtual void put_something()
{
cout << "加入枸杞" << endl;
}
};
void doWork(AbstractDrinking *abs)
{
abs->makeDrink();
delete abs; // 释放堆区数据
}
void test1()
{
doWork(new Coffee);
cout << "--------------" << endl;
doWork(new Tea);
}
3.3.2 虚析构和纯虚析构
多态使用时,如果子类中属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。解决:将父类析构函数改为虚析构或者纯虚析构。
class Animal
{
public:
virtual void speak() = 0;
Animal()
{
cout << "animal构造函数调用" << endl;
}
// 利用虚析构可以解决父类指针释放子类对象时不干净的问题
/*virtual ~Animal()
{
cout << "animal虚析构函数调用" << endl;
}*/
virtual ~Animal() = 0; /*纯虚析构函数需要有函数的声明,也需要有具体的函数实现,而纯虚构造函数不需要有具体函数实现,有纯虚析构函数的类也属于抽象类,无法实例化*/
};
Animal:: ~Animal() // 纯虚析构函数实现
{
cout << "animal析构函数调用" << endl;
}
class Cat : public Animal
{
public:
Cat(string name)
{
cout << "cat构造函数调用" << endl;
m_name = new string(name);
}
virtual void speak()
{
cout << *m_name <<" 喵喵喵" << endl;
}
~Cat()
{
cout << "cat析构函数调用" << endl;
if (m_name != NULL)
{
delete m_name;
m_name = NULL;
}
}
string* m_name;
};
void test1()
{
Animal* animal = new Cat("Tom"); // 在堆区创建了一个Animal类型的animal指针指向子类函数
animal->speak();
// 父类指针在析构的时候不会调用子类中析构函数,导致子类中如果有堆区数据,则无法释放,出现内存泄露
delete animal;
}
/*总结:
1.虚析构或者纯虚析构就是用来解决通过父类指针释放子类对象
2.如果子类中没有堆区数据,可以不写虚析构和纯虚析构函数
3.拥有春虚析构函数的类也属于抽象类
*/