本博文是学习黑马程序员C++视频时做的笔记,记录一下只是方便温故知新,不做其他用途。
C++面向对象三大特性:封装、继承、多态。
一、封装
封装:隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互,将数据和操作数据的方法进行有机结合。
封装的意义:
(1)将属性和行为作为整体,表现生活中的事物
(2)将属性和行为加以权限控制
1.1 类的定义
语法:class 类名 {访问权限:属性和行为}。
#include<iostream>
using namespace std;
//创建类
class Student{
public:
// 属性
string name;
int s_id;
// 行为
void ShowStudent()
{
cout<<"name:"<<name<<" student_id="<<s_id<<endl;
}
};
int main()
{
// 实例化对象(通过类创建对象)
Student s1;
s1.name = "张三";
s1.s_id = 001;
s1.ShowStudent();
return 0;
}
1.2 访问权限
把属性和行为放在不同的权限下,加以控制。
(1)public:公共权限——成员 类内可以访问,类外可以访问。
(2)protected:保护权限——成员 类内可以访问,类外不可访问,儿子可以访问父亲中的保护内容。
(3)private:私有权限——成员 类内可以访问,类外不可访问。
#include<iostream>
#include<string>
using namespace std;
//三种访问权限
//(1)public:公共权限——成员 类内可以访问,类外可以访问。
//(2)protected:保护权限——成员 类内可以访问,类外不可访问,儿子可以访问父亲中的保护内容。
//(3)private:私有权限——成员 类内可以访问,类外不可访问。
class Person
{
// 公共权限
public:
string m_name; //名字
// 保护权限
protected:
string m_car;//车
// 私有权限
private:
int m_password;//支付密码
public:
void func()
{
m_name="张三";
m_car = "小车";
m_password=123456;
cout<<m_name<<m_car<<m_password<<endl;
}
};
int main()
{
Person p1;
p1.m_name="王二麻子";//公共权限,类外也可以访问
// p1.m_car="电动车";//保护权限,类外不可访问,其子类可以访问。
// p1.m_password =987654;//私有属性,类外不可访问
p1.func();
return 0;
}
1.3 class和struct区别
类内都可以访问;类外,class 默认权限为私有,struct默认权限为公有
#include<iostream>
#include<string>
using namespace std;
class C1{
int m_A;//class 默认权限为私有
};
struct S1{
int m_A;//struct默认权限为公有
};
int main()
{
C1 c1;
// c1.m_A = 100;//报错
S1 s1;
s1.m_A = 100;//正常
return 0;
}
1.4 成员属性私有化
优点:
(1)可以控制读写权限
(2)对于写可以检测数据的有效性
例子中,将所有变量都在设置为私有,然后在public里面分别添加各个对应的函数就可以实现各种权限。
#include<iostream>
#include<string>
using namespace std;
//实现目标:
//1、姓名 可读可写
//2、年龄 只读
//3、爱人 只写
class Person
{
//步骤一、将所有变量设置为私有
private:
string m_name;
int m_age;
string m_lover;
//步骤二、添加各个对应的函数就可以实现各种权限
public:
// 1、姓名 可读可写
// 设置姓名(写入)
void setname(string name)
{
m_name=name;
}
// 显示姓名(读取)
string showname()
{
return m_name;
}
// 2、年龄 只读
int readAge()
{
m_age=23;
return m_age;
}
// 3、设置爱人 只写
void setlover(string lover)
{
m_lover = lover;
}
};
int main()
{
Person p1;
// 1 姓名可读可写
p1.setname("王五");
cout<<p1.showname()<<endl;
// 2 年龄 只读
cout<<p1.readAge()<<endl;
// 3 爱人 只写
p1.setlover("ccc");
}
二、对象特性
2.1构造函数和析构函数
构造函数:用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用。
语法:类名(){}
(1)没有返回值 也不写void;
(2)函数名称与类名称相同;
(3)构造函数可以有参数,因此可以发生重载;
(4)创建对象时,会自动调用构造函数,无需手动调用,而且只会调用一次。
析构函数:用于对象销毁前系统自动调用,执行一些清理工作。
语法:~类名(){}
(1)没有返回值 也不写void;
(2)函数名称与类名称相同,需要加上符号~;
(3)构造函数不可以有参数,因此不可以发生重载;
(4)对象销毁前,会自动调用构造函数,无需手动调用,而且只会调用一次。
#include<iostream>
#include<string>
using namespace std;
//1、构造函数分为:
// 按参数:无参构造、有参构造
// 按类型:普通构造、拷贝构造
class Person
{
//构造函数
private:
string m_name;
public:
Person()//无参构造
{
cout<<"无参构造函数!"<<endl;
}
Person(int a)//有参构造
{
m_name=a;
cout<<"有参构造函数!"<<endl;
}
Person(const Person &p)//拷贝构造函数
{
cout<<"拷贝构造函数!"<<endl;
}
//2 析构函数
~Person()
{
cout<<"析构函数!"<<endl;
}
};
//调用函数
void test()
{
//1、括号法
Person p1;//默认调用
Person p2(10);//有参构造函数
Person p3(p2);//拷贝构造函数
// //2、显示法
// Person p1;
// Person p2=Person(10);//有参构造函数
// Person p3=Person(p2);//拷贝构造函数
// //3、隐式转换法
// Person p4=10;//相当于:Person p4=Person(10),有参构造函数
// Person p5=p4;//拷贝构造函数
}
int main()
{
test();
return 0;
}
深拷贝和浅拷贝区别
深拷贝:在堆区重新申请空间,进行拷贝操作;
浅拷贝:简单的赋值拷贝操作。
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
//无参构造函数
Person()
{
cout << "无参构造函数" << endl;
}
//有参构造函数
Person(int age ,int height)
{
cout << "有参构造函数" << endl;
m_age = age;
m_height = new int(height);
}
//拷贝构造函数
Person(const Person& p)
{
cout << "拷贝构造函数" << endl;
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
m_age = p.m_age;
//m_height = p.m_height; 编译器默认创造的拷贝构造
m_height = new int(*p.m_height);//深拷贝解决方法
}
//析构函数
~Person() {
if (m_height != NULL)
{
delete m_height;
m_height = NULL;
}
cout<<"析构函数调用"<<endl;
}
public:
int m_age;
int* m_height;
};
void test()
{
Person p1(26, 172);
Person p2(p1);
cout << "p1的年龄= " << p1.m_age << " 身高= " << *p1.m_height << endl;
cout << "p2的年龄=" << p2.m_age << " 身高= " << *p2.m_height << endl;
}
int main() {
test();
return 0;
}
2.2 初始化列表
语法:构造函数():属性1,属性2…{}
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
Person(int a,int b,int c):m_A(a),m_B(b),m_C(c) {}
void PrintPerson()
{
cout << "m_A= " <<m_A <<endl;
cout << "m_B= " <<m_B <<endl;
cout << "m_C= " <<m_C <<endl;
}
private:
int m_A;
int m_B;
int m_C;
};
int main() {
Person p(1,2,3);
p.PrintPerson();
return 0;
}
2.3 类对象作为类成员
类中的成员可以是另一个类的对象。
#include<iostream>
#include<string>
using namespace std;
//手机类
class Phone
{
public:
Phone(string Pname)
{
m_Pame = Pname;
cout<<"Phone 类"<<endl;
}
~Phone(){
cout<<"Phone 析构"<<endl;
}
string m_Pame;
};
//人类
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 test()
{
// 会先构造类类,再构造自身;
// 先析构自身,再析构其他类
Person p("张三","xiaomi");
cout<<p.m_Name<<"has a "<<p.m_Phone.m_Pame<<endl;
}
int main()
{
test();
return 0;
}
2.4 静态成员函数
所有对象共享一个函数
静态成员函数只能访问静态成员变量
#include<iostream>
using namespace std;
//创建类
class Student{
public:
//静态成员函数
static void func()
{
m_A = 100;//静态成员变量是共享的
//静态成员函数不能访问非静态成员,这是因为静态函数属于类而不是属于整个对象。
// m_B = 200;//报错,静态函数不可以访问非静态成员变量。一个类可以实例化出多个对象,函数体无法区分属于m_B属于哪个对象
cout<<"static void func:"<<endl;
}
static int m_A;//静态成员变量
int m_B;//静态成员变量
};
int Student::m_A;//静态成员变量需要在外部声明
//两种访问方式
void test01(){
// 1. 通过对象访问
Student s1;
s1.func();
// 2. 通过类名访问
Student::func();
}
int main()
{
test01();
return 0;
}
2.5 C++对象模型和this指针
2.5.1 成员变量成员函数分开存储
#include<iostream>
#include<string>
using namespace std;
class Person
{
int m_name;//非静态成员属于类对象上,占用内存
static int m_b; //静态成员变量不属于类对象上,访问内存是发现不占内存
void func(){}//非静态成员函数也不占内存,不属于类的对象上
};
void test01()
{
Person p;
// 空对象占用存储空间为:1字节
cout<<"the size of p is:"<<sizeof(p)<<endl;
}
int main()
{
test01();
return 0;
}
2.5.2 this指针概念
1 概念:this指针指向被调用的成员函数所属的对象。
2 特点:
(1)this指针隐含在每一个非静态成员函数内,
(2)this指针不需要定义直接使用
3 用途:
(1)当形参和成员变量同名时,可用this指针来区分;
(2)返回对象本身return *this
#include<iostream>
#include<string>
using namespace std;
//this指针隐含在每一个非静态成员函数内,不需要定义直接使用
//作用:1、解决名称冲突;2、返回对象本身(*this)
class Person
{
public:
Person(int age)
{
//出现age=age等号两侧同名时,使用this指针来解决。这样,等号“="左侧this指针指向被调用的成员函数所属的对象,等号"=”右侧为形参。
this->age = age;
}
Person& PersonAddAge(Person &p)
{
this->age += p.age;
return *this;
}
int age;
};
void test01()
{
Person p1(18);
cout<<"p1的年龄"<<p1.age<<endl;
}
void test02()
{
Person p1(10);
Person p2(10);
// 链式编程思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout<<"p2的年龄"<<p2.age<<endl;
}
int main()
{
test01();
test02();
return 0;
}
2.5.3 空指针访问成员函数(判定this指针是否为空来解决)
空指针调用普通成员函数,如果使用到了this指针(访问非静态成员变量),程序会崩溃;如果没有使用到this指针,程序不会崩溃。
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
void showClassName(){
cout<<"this is Person class"<<endl;
}
void showClassage(){
/*+++++++++++++++++解决办法,空指针判定+++++++++++*/
if(this == NULL){
return;
}
/*+++++++++++++++++++++++++++++++++*/
cout<<"this is class age"<< this->m_age<<endl;
}
int m_age;
};
void test01()
{
Person *p1 = NULL;
p1->showClassName();
// 如果没有判定this指针是否为空,下面这行代码报错
p1->showClassage();//报错原因,传入的指针为空
}
int main()
{
test01();
return 0;
}
2.5.4 const修饰成员函数(常函数和常对象)
1、常函数( void func() const {…} )
(1)成员函数后加const变成常函数;
(2)常函数内不可以修改成员属性;
(3)成员属性声明的时候加关键字mutable后,在常函数中就可以修改。
2、常对象
(1)声明对象的时候在前面加上const即可变成常对象;
(2)常对象只能访问常函数,普通函数不能被访问。
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
Person(){
m_a = 0;
m_B = 0;
}
// this指针的本质是指针常量 指针的指向是不可以修改的
// 1 常函数:成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
void showPerson() const
{
// this->m_a = 100;//加了const后,指针指向的值不能修改,此处会报错!
this->m_B = 100;
cout<<"常函数"<<endl;
}
void func1()
{
cout<<"普通函数"<<endl;
}
public:
int m_a;
mutable int m_B;//特殊变量,即使在常函数中也可以修改这个值
};
void test01()
{
Person p1;
// 类对象可以调用常函数、普通函数
p1.showPerson();
p1.func1();
// 也可修饰成员变量,带mutabe关键字的成员变量
p1.m_a = 100;
p1.m_B = 100;
}
void test02()
{
// 2 常对象:对象前加了const后,变为常对象,其值不能被修改
const Person p2;
p2.showPerson();
// p1.func1();//报错,常对象只能调用常函数
// p1.m_a = 100;//会报错
p2.m_B = 100;//加了mutabe关键字的可以修改
}
int main()
{
test01();
test02();
return 0;
}
三、 C++友元
友元的作用就是令全局函数或者类,可以访问另一个类。
3.1 全局函数做友元
例子中的void goodGay(Buiding *buiding)为全局函数,friend void goodGay(Buiding *buiding)为友元,可以访问Buiding中的私有成员。
#include<iostream>
#include<string>
using namespace std;
class Buiding
{
//goodGay全局函数是Buiding的好朋友,可以访问Buiding中的私有成员,友元
friend void goodGay(Buiding *buiding);
public:
Buiding()
{
m_sintingroom="客厅";
m_bedroom = "卧室";
}
public:
string m_sintingroom;//客厅
private:
string m_bedroom;//卧室
};
//全局函数
void goodGay(Buiding *buiding)
{
cout<<"goodGay正在访问:"<<buiding->m_sintingroom<<endl;
cout<<"goodGay正在访问:"<<buiding->m_bedroom<<endl;
}
void test01()
{
Buiding Buiding;
goodGay(&Buiding);
}
int main()
{
test01();
return 0;
}
3.2 类做友元
例子中在Building类中添加friend class GoodGay;这一句,实现类做友元,GoodGay类可以访问Building类中的私有内容。
#include<iostream>
using namespace std;
#include<string>
//类做友元
class Building;
class GoodGay
{
public:
GoodGay();
public:
void visit();//参观函数 访问Building中的属性
Building* building;
};
class Building
{
//goodGay类是Building好朋友,可以访问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 << "好基友类正在访问:" << building->m_SittingRoom << endl;
cout << "好基类友正在访问:" << building->m_BedRoom << endl;
}
void test01()
{
GoodGay g;
g.visit();
}
int main()
{
test01();
return 0;
}
3.3 成员函数做友元
例子中friend void goodGay::visit1()这句,实现了让基友visit1可以访问卧室和客厅,基友visit2()只能访问客厅。
//成员函数做友元函数:
#include<iostream>
#include<string>
using namespace std;
class Building;
class goodGay
{
public:
goodGay();
void visit1();//让visit1函数可以访问Building中私有成员
void visit2();//让visit2函数不可以访问Building中私有成员
private:
Building * building;
};
class Building
{
//告诉编译器 goodGay类下的visit1函数是Building类的好朋友,可以访问到Building类中私有内容
friend void goodGay::visit1();
public:
Building();
public:
string m_sittingroom; //客厅
private:
string m_bedroom; //卧室
};
//类外实现成员函数
Building::Building()//Building的构造函数
{
this->m_bedroom = "卧室";
this->m_sittingroom = "客厅";
}
goodGay::goodGay()//goodGay的构造函数
{
building = new Building;
}
void goodGay::visit1()
{
cout << "基友visit1正在" << this->building->m_sittingroom << endl;
cout << "基友visit1正在" << this->building->m_bedroom << endl;
}
void goodGay::visit2()
{
cout << "基友visit2正在" << this->building->m_sittingroom << endl;
//cout << "基友visit2正在" << this->building->m_bedroom << endl;
}
void test01()
{
goodGay gg;
gg.visit1();
gg.visit2();
}
int main()
{
test01();
return 0;
}
四、运算符重载
概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
4.1 加号运算符重载
作用:实现两个自定义数据类型相加的运算。
主要包括:
(1)成员函数重载+号:Person p3 = p1.operator+(p2);
(2)全局函数重载+号:Person p3 = operator+(p1,p2);
#include<iostream>
#include<string>
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 = p2.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 test01(){
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
// (1)简单版本的调用
// Person p3 = p1 + p2;
// (2)成员函数的本质调用
// Person p3 = p1.operator+(p2);
// (3)全局函数的本质调用
Person p3 = operator+(p1,p2);
// (4) 函数重载版本的调用
Person p4 = p1 + 100;
cout<<"p3.m_A = "<<p3.m_A<<endl;//20
cout<<"p3.m_B = "<<p3.m_B<<endl;//20
cout<<"p4.m_A = "<<p4.m_A<<endl;//110
cout<<"p4.m_B = "<<p4.m_B<<endl;//110
}
int main()
{
test01();
return 0;
}
4.2 左移运算符重载
作用:输出自定义数据类型。
#include"iostream"
using namespace std;
class Person//自己创建一个类
{
// 声明为友元
friend ostream & operator<<(ostream &out,Person &p);
public:
Person(int a,int b)
{
this->m_A = a;
this->m_B = b;
}
private:
int m_A;
int m_B;
};
//利用全局函数重载左移运算符
ostream & operator<<(ostream &out,Person &p)
{
out<<"m_A="<<p.m_A<<" m_B="<<p.m_B;
return out;
}
void test01()
{
Person p(10,10);
cout<<p<<" Hello world!"<<endl;
}
int main()
{
test01();
return 0;
}
4.3 递增运算符重载
#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;
}
//重载后置++运算符
//这个int代表的是占位参数,可以用于区分前置和后置递增
MyInteger operator++(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 test01()
{
MyInteger myint;
cout<<++myint<<endl;
}
void test02()
{
MyInteger myint;
cout<<myint++<<endl;
}
int main()
{
test01();
test02();
return 0;
}
4.4 赋值运算符重载
#include <iostream>
using namespace std;
//赋值运算符重载
class Person
{
public:
// 年龄开辟到堆区
Person(int age)
{
m_Age = new int(age); // new 来的对象都是一个内存地址
}
// 把年龄这个属性写成指针
int *m_Age;
// 析构函数,程序员自己控制内存
~Person()
{
if (m_Age != NULL)
{
delete m_Age; // 删除所占内存,并置空
m_Age = NULL;
}
}
Person& operator=(Person &p)
{
//首先,应该先判断是否有属性在堆区,如果有应该先释放干净
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//再进行深拷贝 解决浅拷贝问题
m_Age = new int(*p.m_Age);
//最后返回自身
return *this;
}
};
void test01()
{
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()
{
test01();
return 0;
}
4.5 关系运算符重载
#include<iostream>
#include <stdio.h>
#include <string>
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;
}
else
{
return false;
}
}
string m_Name;
int m_Age;
};
void test01(){
Person p1("王二",18);
Person p2("王二",19);
if(p1==p2)
{
cout<<"相等";
}
else
{
cout <<"不相等";
}
}
int main(){
test01();
return 0;
}
4.6 函数调用运算符重载
函数调用运算符()可以重载,由于重载后使用的方式非常像函数的调用,称为仿函数。
#include <iostream>
#include <stdio.h>
using namespace std;
//函数调用运算符重载
class MyPrint
{
public:
void operator()(string test)
{
cout <<test <<endl;
}
};
void test01(){
MyPrint myprint ;
myprint("Hello World!");//由于使用起来非常像函数的调用,因此叫仿函数
}
class MyAdd
{
public:
int operator()(int num1 ,int num2)
{
return num1+num2;
}
};
void test02()
{
MyAdd myadd;
int sum = myadd(100,200);
cout << "sum="<<sum<<endl;
//MyAdd()匿名函数对象
cout <<MyAdd()(100,100);
}
int main(){
test01();
test02();
return 0;
}
五、继承
继承是面向对象的三大特性之一。利用继承的方法可以有效减少重复代码。
5.1 继承的基本语法
语法:class 子类:继承方式 父类
class jpage : public page
#include <iostream>
using namespace std;
class basepage
{
public:
void show()
{
cout << "基础界面" << endl;
}
};
class javapage : public basepage
{
public:
void javashow()
{
cout << "java界面" << endl;
}
};
class cpppage : public basepage
{
public :
void cppshow()
{
cout << "cpp界面" << endl;
}
};
class pythonpage : public basepage
{
public:
void pythonshow()
{
cout << "python界面" << endl;
}
};
void test01(){
javapage jav;
jav.show();
jav.javashow();
cout << "**************" << endl;
cpppage cp;
cp.show();
cp.cppshow();
cout << "**************" << endl;
pythonpage py;
py.show();
py.pythonshow();
cout << "**************" << endl;
}
int main()
{
test01();
return 0;
}
5.2 继承方式
语法: class 子类 : 继承方式 父类
方式: 公共继承、保护继承、私有继承
#include <iostream>
using namespace std;
//父类1
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//1 公共继承
class Son1 :public Base1
{
public:
void func()
{
m_A; //父类中的public权限成员,继承到子类中仍然是public权限
m_B; //父类中的protected权限成员,继承到子类中仍然是protected权限
//m_C; //父类中的private权限成员,子类不可访问
}
};
void test01()
{
Son1 s1;
s1.m_A = 100; //父类继承到Son1中,公共权限可访问
// s1.m_B;//报错,到Son1中m_B是保护权限,类外访问不到
}
//父类2
class Base2
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//2 保护继承
class Son2:protected Base2
{
public:
void func()
{
m_A; //父类中的public权限成员,继承到子类中是protected权限
m_B; //父类中的protected权限成员,继承到子类中仍然是protected权限
//m_C; //父类中的private权限成员,子类不可访问
}
};
void test02()
{
Son2 s;
//s.m_A; //子类中的protected权限,类外不可访问
}
//3 私有继承
class Base3
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son3:private Base3
{
public:
void func()
{
m_A; //父类中的public权限成员,继承到子类中是private权限
m_B; //父类中的protected权限成员,继承到子类中仍然是private权限
//m_C; //父类中的private权限成员,子类不可访问
}
};
void test03()
{
Son3 s;
// s.m_A = 1000; //子类中的private权限成员,类外不可访问
}
class GrandSon3 :public Son3
{
public:
void func()
{
//Son3是私有继承,所以继承Son3的属性在GrandSon3中都无法访问到
//m_A;
//m_B;
//m_C;
}
};
int main(){
return 0;
}
5.3 继承中的对象模型
#include <iostream>
using namespace std;
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//公共继承
class Son :public Base
{
public:
int m_D;
};
void test01()
{
// 父类中所有的非静态成员都会被子类继承下去
// 父类中私有成员属性是被编译器隐藏了,因此访问不到
cout << "sizeof Son = " << sizeof(Son) << endl;//16
}
int main() {
test01();
return 0;
}
5.4 继承中的构造和析构顺序
#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 test01()
{
// Base b;
//继承中的构造和析构顺序如下:
// 先构造父类,再构造子类,析构顺序与构造相反
Son s;
}
int main() {
test01();
return 0;
}
5.5 继承中同名成员处理方式
#include <iostream>
using namespace std;
//继承中同名成员处理方式
class Base {
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "Base - func()调用" << endl;
}
void func(int a)
{
cout << "Base - func(int a)调用" << endl;
}
public:
int m_A;
};
class Son : public Base {
public:
Son()
{
m_A = 200;
}
void func()
{
cout << "Son - func()调用" << endl;
}
public:
int m_A;
};
void test01()
{
Son s;
cout << "Son下的m_A = " << s.m_A << endl;//200
cout << "Base下的m_A = " << s.Base::m_A << endl;//100
s.func();//子类对象可以直接访问子类中的同名成员
// 如果子类中出现父类中被隐藏的同名成员函数,子类的同名成员会隐藏父类中的所有同名成员函数
s.Base::func();//子类对象加父类作用域,可以访问父类下的同名成员
s.Base::func(10);
}
int main() {
test01();
return 0;
}
5.6 继承中同名静态成员处理方式
#include <iostream>
using namespace std;
//继承中同名静态成员处理方式
class Base {
public:
static void func()
{
cout << "Base - static void func()" << endl;
}
static void func(int a)
{
cout << "Base - static void func(int a)" << endl;
}
static int m_A;
};
int Base::m_A = 100;
class Son : public Base {
public:
static void func()
{
cout << "Son - static void func()" << endl;
}
static int m_A;
};
int Son::m_A = 200;
//同名静态成员属性
void test01()
{
//1 通过对象访问
cout << "通过对象访问: " << endl;
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
cout << "Base 下 m_A = " << s.Base::m_A << endl;
//2 通过类名访问
cout << "通过类名访问: " << endl;
cout << "Son 下 m_A = " << Son::m_A << endl;
cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}
//同名静态成员函数
void test02()
{
//通过对象访问
cout << "通过对象访问: " << endl;
Son s;
s.func();
s.Base::func();
cout << "通过类名访问: " << endl;
Son::func();
Son::Base::func();
//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
Son::Base::func(100);
}
int main() {
test01();
test02();
return 0;
}
5.7 多继承语法
C++中允许一个类继承多个类
语法:class 子类:继承方式 父类1,继承方式 父类2…
多继承中可能会引发父类中有同名成员出现,需要加作用域区分
#include <iostream>
using namespace std;
//多继承语法
//创建父类1
class Base1 {
public:
Base1()
{
m_A = 100;
}
public:
int m_A;
};
//创建父类2
class Base2 {
public:
Base2()
{
m_A = 200; //开始是m_B 不会出问题,但是改为mA就会出现不明确
}
public:
int m_A;
};
//子类继承父类1和父类2
//语法:class 子类:继承方式 父类1 ,继承方式 父类2
class Son : public Base2, public Base1
{
public:
Son()
{
m_C = 300;
m_D = 400;
}
public:
int m_C;
int m_D;
};
//多继承容易产生成员同名的情况
//通过使用类名作用域可以区分调用哪一个基类的成员
void test01()
{
Son s;
cout << "sizeof Son = " << sizeof(s) << endl;//16,
cout << s.Base1::m_A << endl;//100
cout << s.Base2::m_A << endl;//200
}
int main() {
test01();
return 0;
}
5.8 菱形继承
两个派生类继承同一个基类;
又有两个类同时继承这两个派生类;
#include <iostream>
using namespace std;
//菱形继承
//动物类
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 test01()
{
SheepTuo st;
st.Sheep::m_Age = 10;
st.Tuo::m_Age = 20;
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;
}
int main() {
test01();
return 0;
}
六、多态
6.1 多态的基本概念
多态分为两类:
(1)静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名;
(2)动态多态: 派生类和虚函数实现运行时多态。
静态多态和动态多态的区别:
(1)静态多态的函数地址早绑定 - 编译阶段确定函数地址
(2)动态多态的函数地址晚绑定 - 运行阶段确定函数地址
#include <iostream>
using namespace std;
//多态的基本概念
//动物类
class Animal
{
public:
//Speak函数就是虚函数
//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
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 & animal = cat;
{
animal.speak();
}
//
//多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象
void test01()
{
Cat cat;
DoSpeak(cat);
Dog dog;
DoSpeak(dog);
}
int main()
{
test01();
return 0;
}
6.2 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数。
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也称为抽象类。
抽象类特点:
(1)无法实例化对象;
(2)子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
#include <iostream>
using namespace std;
//纯虚函数和抽象类
class Base
{
public:
// 一、纯虚函数:类中只要有一个纯虚函数就称为抽象类
// 二、抽象类特点
//1 抽象类无法实例化对象
//2 子类必须重写父类中的纯虚函数,否则也属于抽象类
// 下面这是一个纯虚函数:virtual 返回值类型 函数名 (参数列表)= 0 ;
virtual void func() = 0;
};
class Son :public Base
{
public:
//下面重写父类中的纯虚函数
virtual void func()
{
cout << "func函数调用" << endl;
};
};
void test01()
{
// Base b;// 错误,抽象类无法实例化对象
// new Base;错误,抽象类无法实例化对象
Base *base = new Son;
base->func();
}
int main()
{
test01();
return 0;
}
6.3 虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。
解决方式:将父类中的析构函数改为虚析构或者纯虚析构。
虚析构和纯虚析构共性:
(1)可以解决父类指针释放子类对象;
(2)都需要有具体的函数实现。
虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象。
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0;
类名::~类名(){}
#include <iostream>
using namespace std;
#include <string>
//虚析构和纯虚析构
class Animal
{
public:
Animal()
{
cout << "Animal 构造函数调用!" << endl;
}
// 纯虚函数
virtual void Speak() = 0;
//虚析构函数就是用来解决通过父类指针去释放,会导致子类对象可能清理不干净的问题
//析构函数加上virtual关键字,变成虚析构函数
//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 (this->m_Name != NULL) {
delete m_Name;
m_Name = NULL;
}
}
public:
string *m_Name;
};
void test01()
{
Animal *animal = new Cat("Tom");
animal->Speak();
delete animal;
}
int main()
{
test01();
return 0;
}