C++面向对象的三大特性为:封装、继承、多态。C++认为万事万物都皆为对象,对象上有其属性和行为。如人可以作为对象,属性有姓名、年龄、身高、体重等,行为有吃、喝、走、跑等。
1.封装
1.1 封装的意义
封装是C++面向对象的三大特性之一。其意义为:
1)将属性和行为作为一个整体:在设计类的时候,属性和行为写在一起,表现事务语法class 类名{ 访问权限 : 属性 / 行为 };
#include<iostream>
using namespace std;
const double PI = 3.14;
//设计一个圆类,求圆的周长
class circle {
public:
circle(int r) {
this->r = r;
}
double GetZC() {
return 2 * r * PI;
}
private:
int r;
};
int main() {
//创建一个具体的圆(对象),实例化
circle c(10);
cout << c.GetZC() << endl;
return 0;
}
类中的属性和行为,统一称为成员;属性:成员属性、成员变量;行为:成员函数、成员方法。
2)将属性和行为加以权限控制,访问权限有三种:
i:public 公共权限:成员 类内可以访问,类外可以访问
ii:protected 保护权限:成员 类内可以访问,类外不可访问,子类可以访问父类中的保护内容
iii:private 私有权限:成员 类内可以访问,类外不可访问,子类不可以访问父类中的私有内容
#include<iostream>
#include<string>
using namespace std;
class person{
public:
//公共权限
string name;//姓名
protected:
//保护权限
string car;//汽车
private:
//私有权限
int password;//银行卡密码
public:
//类内可以访问
void func() {
name = "张三";
car = "拖拉机";
password = 123456;
}
};
int main() {
//实例化具体对象
person p;
p.name = "李四";//可以访问
//p.car = "奔驰";//保护权限内容,在类外访问不到
//p.password = 456;//私有权限内容,类外访问不到
return 0;
}
1.2 struct和class区别
在C++中struct和class唯一的区别就在于默认的访问权限不同,struct默认权限为公共,class默认权限为私有。
#include<iostream>
#include<string>
using namespace std;
class person{
int ID;//默认为私有的。
};
struct MyStruct{
int ID;//默认权限为公共
};
int main() {
person p;
//p.ID = 100;//默认为私有
MyStruct m;
m.ID = 100;//默认公共
return 0;
}
1.3 成员属性设置为私有
优点:
1)将所有成员属性设置为私有,可以自己控制读写权限。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
//设置姓名
void setname(string name) {
this->name = name;
}
//获取姓名
string getname() {
return name;
}
private:
string name;
};
int main() {
person p;
p.setname("zhinen");
cout << p.getname() << endl;
return 0;
}
2)对于写权限,可以检测数据的有效性。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
//设置姓名
void setname(string name) {
this->name = name;
}
//获取姓名
string getname() {
return name;
}
//设置年龄
void setage(int age) {
if (age >= 0 && age <= 100) {
this->age = age;
}
else {
cout << "年龄设置错误!已默认为0。" << endl;
this->age = 0;
}
}
//获取年龄
int getage() {
return age;
}
private:
string name;
int age;
};
int main() {
person p;
p.setname("zhinen");
cout << p.getname() << endl;
p.setage(200);
cout << p.getage() << endl;
p.setage(60);
cout << p.getage() << endl;
return 0;
}
2.对象的初始化和清理
2.1 构造函数和析构函数
C++中的构造函数和析构函数会被编译器自动调用,完成对象的初始化和清理工作。如果我们不提供构造和析构函数,编译器会提供编译器提供的构造函数和析构函数是空实现。
构造函数:在创建对象时为对象的成员属性赋值,构造函数自动调用,无需手动调用
析构函数:在对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名 (){}
1)没有返回值也不写void;
2)函数名称和类名相同;
3)构造函数可以有参数,因此可以发生重载;
4)程序在调用对象时会自动调用构造函数,无需手动调用而且只会调用一次。
析构函数语法:~类名 (){}
1)没有返回值也不写void;
2)函数名称和类名相同,在名称前面加上~;
3)构造函数不可以有参数,因此不可以发生重载;
4)程序在对象销毁前时会自动调用析构函数,无需手动调用而且只会调用一次。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
//构造函数
person() {
cout << "person" << endl;
}
//析构函数
~person() {
cout << "~person" << endl;
}
};
int main() {
person p;//在栈上的数据,程序执行完,会释放这个对象
return 0;
}
2.2 构造函数的分类及调用
两种分类方式:
1)按参数分为:有参构造和无参构造(默认构造)
2)按类型分为:普通构造和拷贝构造
三种调用方式:
1)括号法
2)显示法
3)隐式转换法
#include<iostream>
#include<string>
using namespace std;
class person{
public:
//构造函数
//有参和无参
person() {
cout << "person()" << endl;
}
person(int a) {
age = a;
cout << "person(int a)" << endl;
}
//拷贝构造函数
person(const person &p) {
age = p.age;
cout << "person(person p)" << endl;
}
private:
int age;
};
int main() {
//括号法
person p;//调用无参
//如果person p();会被认为是一个函数声明,不会认为在创建对象
person h(10);//调用有参
person q(p);//调用拷贝构造函数
//显示法
person p1;
person p2 = person(10);//person(10)为匿名对象,特点:当前行执行结束后,系统立即会受掉匿名对象
person p3 = person(p2);
//person(p2);
//不要利用拷贝构造函数,初始化匿名对象,编译器会认为person(p2)===person p2
//隐式转换法
person p4;
person p5 = 10;//==person p5 = person(10);
person p6 = p5;
return 0;
}
2.3 拷贝构造函数调用时机
C++中拷贝函数调用时机通常有三种情况:
1)使用一个已经创建完毕的对象来初始化一个新对象
2)值传递的方式给函数参数传值
3)以值返回局部对象
#include<iostream>
#include<string>
using namespace std;
class person{
public:
//构造函数
//有参和无参
person() {
age = 0;
cout << "person()" << endl;
}
person(int a) {
age = a;
cout << "person(int a)" << endl;
}
//拷贝构造函数
person(const person &p) {
age = p.age;
cout << "person(const person & p)" << endl;
}
int getage() {
return age;
}
private:
int age;
};
void fun(person) {
}
person fun2() {
person p;
return p;
}
int main() {
//1)使用一个已经创建完毕的对象来初始化一个新对象
person p1(10);
person p2(p1);
cout << p2.getage() << endl;
//2)值传递的方式给函数参数传值
person p3;
fun(p3);//调用了拷贝构造函数
//3)以值返回局部对象
person p4 = fun2();
return 0;
}
2.4 构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
1)默认构造函数(无参,函数体为空)
2)默认析构函数(无参,函数体为空)
3)默认拷贝函数,对属性进行值拷贝
构造函数调用规则如下:
1)如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝函数
#include<iostream>
#include<string>
using namespace std;
class person{
public:
//构造函数
//有参和无参
/*person() {
age = 0;
cout << "person()" << endl;
}*/
person(int a) {
age = a;
cout << "person(int a)" << endl;
}
//拷贝构造函数
/*person(const person &p) {
age = p.age;
cout << "person(const person & p)" << endl;
}*/
int getage() {
return age;
}
~person() {
cout << "~person()" << endl;
}
private:
int age;
};
int main() {
//1)如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝函数
//person p;// 错误 C2512 “person” : 没有合适的默认构造函数可用
person p1(20);
person p2(p1);
cout << p2.getage() << endl;
return 0;
}
2)如果用户定义拷贝构造函数,C++不会再提供其他构造函数
#include<iostream>
#include<string>
using namespace std;
class person{
public:
//构造函数
//有参和无参
/*person() {
age = 0;
cout << "person()" << endl;
}*/
/*person(int a) {
age = a;
cout << "person(int a)" << endl;
}*/
//拷贝构造函数
person(const person &p) {
age = p.age;
cout << "person(const person & p)" << endl;
}
int getage() {
return age;
}
~person() {
cout << "~person()" << endl;
}
private:
int age;
};
int main() {
//2)如果用户定义拷贝构造函数,C++不会再提供其他构造函数
//person p;// 错误 C2512 “person” : 没有合适的默认构造函数可用
//person p1(20);//错误(活动) E0289 没有与参数列表匹配的构造函数
return 0;
}
2.5 深拷贝和浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person() {
age = 0;
cout << "person()" << endl;
}
person(int a,int h) {
age = a;
height = new int(h);
cout << "person(int a,int h)" << endl;
}
person(const person &p) {
age = p.age;
//height = p.height;//编译器默认实现的代码
height = new int(*p.height);
cout << "person(const person & p)" << endl;
}
int getage() {
return age;
}
int* getheight() {
return height;
}
~person() {
//析构函数,将堆区开辟的数据做释放操作
//出错,浅拷贝带来的问题:堆区的内存重复释放,
//要利用深拷贝进行解决,需重新写拷贝构造函数,解决浅拷贝带来的问题
if (height != NULL) {
delete height;
height = NULL;
}
cout << "~person()" << endl;
}
private:
int age;
int* height;
};
int main() {
person p(18,160);
cout << p.getage() << " " << *p.getheight() << endl;
person p1(p);
cout << p1.getage() << " " << *p1.getheight() << endl;
return 0;
}
2.6 初始化列表
C++提供了初始化列表语法,用来初始化属性
语法:构造函数(): 属性1(值1), 属性2(值2)…{}
#include<iostream>
#include<string>
using namespace std;
class person{
public:
//传统初始化的方式
/*person(int a,int h) {
age = a;
height = h;
cout << "person(int a,int h)" << endl;
}*/
//初始化列表初始化属性
person(int a,int h) :age(a),height(h) {}
int getage() {
return age;
}
int getheight() {
return height;
}
~person() {
cout << "~person()" << endl;
}
private:
int age;
int height;
};
int main() {
person p(18,160);
cout << p.getage() << " " << p.getheight() << endl;
return 0;
}
2.7 类对象作为类成员
C++类中的成员可以是另一个类的对象,称该成员为对象成员。
#include<iostream>
#include<string>
using namespace std;
class Phone{
public:
Phone(string name) {
this->name = name;
cout << "phone" << endl;
}
string name;
~Phone() {
cout << "~phone" << endl;
}
};
class person{
public:
person(string myname, string pname) : name(myname), myphone(pname) {
cout << "person" << endl;
}
~person() {
cout << "~person" << endl;
}
string name;
Phone myphone;
};
int main() {
//当其他对象作为本类成员,构造先构造类对象,在构造自身,析构相反
person p("zhinen","huawei");
cout << p.name << " " << p.myphone.name << endl;
return 0;
}
2.8 静态成员
静态成员就是在成员变量和成员函数前加上关键词static。静态成员分为:
1)静态成员变量:所i有对象共享同一份数据;在编译阶段分配内存;类内声明,类外初始化。
2)静态成员函数:所有对象共享同一个函数;静态成员函数只能访问静态成员变量。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
static void fun() {
//静态成员函数只能访问静态成员变量。无法区分到底是哪个对象调用的。
//grade = 100;//错误
id = 1;//
cout << "static function" << endl;
}
//类内声明,类外初始化。
static int id;
int grade;
private:
//静态成员函数也是有访问权限的,私有类外访问不到
static void func() {
cout << "func" << endl;
}
};
//类内声明,类外初始化。
int person::id = 0;
int main() {
//通过对象访问
person p;
p.fun();
//通过类名访问
person::fun();
return 0;
}
3. C++对象模型和this指针
3.1 成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储,只有非静态成员变量菜属于类的对象上。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
private:
};
class animal {
public:
int a;
};
class cat {
public:
int a;
static int b;
};
class dog {
public:
int a;
void fun() {}
};
class tiger {
public:
int a;
static void fun() {}
};
int main() {
person p;
//空对象占用的内存空间为1.
//C++比那一起会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
//每个空对象也应该有一个独一无二的内存地址
cout << sizeof(p) << endl;
//非静态成员,属于类的对象上
animal a1;
cout << sizeof(a1) << endl;
//静态成员不属于类对象上
cat c1;
cout << sizeof(c1) << endl;
//非静态成员函数,不属于类对象上
dog d1;
cout << sizeof(d1) << endl;
//静态成员函数,不属于类的对象上
tiger t1;
cout << sizeof(t1) << endl;
return 0;
}
3.2 this指针的概念
this指针指向被调用的成员函数所属的对象,this指针是隐含每一个非静态成员函数内的一种指针。this指针不需要定义,直接使用即可。this指针的用途:
1)当形参和成员变量同名时,可用this指针来区分
2)在类的非静态成员函数中返回本身对象,可使用return *this;
#include<iostream>
#include<string>
using namespace std;
class person{
public:
//解决名称冲突
person(int age) {
//this指针指向的是 被调用的成员函数 所属的对象
this->age = age;
}
//
person& add(person &p) {
this->age += p.age;
return *this;
}
int age;
private:
};
int main() {
person p1(18);
cout << p1.age << endl;
person p2(20);
//链式编程思想
p2.add(p1).add(p1);
cout << p2.age << endl;
return 0;
}
3.3 空指针访问成员函数
C++中空指针也可以调用成员函数的,但是也要注意有没有用到this指针。如果用到,需要加以判断保证代码的健壮性。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person(string name = "zhinen", int age = 18) {
this->name = name;
this->age = age;
}
void getname() {
cout << name << endl;//this->name
}
void getage() {
cout << age << endl;//this->age
}
void fun() {
cout << "person" << endl;
}
private:
string name;
int age;
};
class cat {
public:
cat(string name = "zhinen", int age = 18) {
this->name = name;
this->age = age;
}
void getname() {
if (this == NULL)
return;
cout << name << endl;//this->name
}
void getage() {
if (this == NULL)
return;
cout << age << endl;//this->age
}
void fun() {
cout << "person" << endl;
}
private:
string name;
int age;
};
int main() {
person* p=NULL;
p->fun();//可
//错误,引发了异常: 读取访问权限冲突。this 是 nullptr。
/*p->getage();
p->getname();*/
cat* c = NULL;
//可
c->getage();
c->getname();
return 0;
}
3.4 const修饰成员函数
常函数:
1)成员函数后加const称之为常函数;
2)常函数内不可以修改成员属性
3)成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
1)声明对象前加const称该对象为常对象
2)常对象只能调用常函数
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person(string name = "zhinen", int age = 18) {
this->name = name;
this->age = age;
}
//this指针的本质是指针常量,指针的指向是不可修改的
void getname() const{//相当于const person *const this;让指针指向的值也不可以改变
//this->name = "zjw";//报错
mininame = "awen";//关键字mutable可修改
cout << name << " " << mininame << endl;//this->name
}
void getage() {
cout << age << endl;//this->age
}
void fun() {
cout << "person" << endl;
}
string name;
int age;
mutable string mininame;
};
int main() {
const person p;//常对象
//p.name = "123";//错误
p.mininame = "zhi";//mininame是特殊值
//p.getage();//错误,常对象只能调用常函数
p.getname();
return 0;
}
4.友元
友元的目的就是让一个函数或者类访问另一个类中私有成员。友元的关键字为friend。友元函数的三种实现:
4.1全局函数做友元
#include<iostream>
#include<string>
using namespace std;
class person{
friend void fun(person& p);//好朋友可以看手机
public:
person() {
name = "zhinen";
phone = "private";
}
string name;
private:
string phone;
};
//全局函数
void fun(person& p) {
cout << p.name << endl;
cout << p.phone << endl;
}
int main() {
person p;
fun(p);
return 0;
}
4.2 类做友元
#include<iostream>
#include<string>
using namespace std;
class person{
friend class person1;
public:
person() {
name = "zhinen";
phone = "private";
}
string name;
private:
string phone;
};
class person1 {
public:
void visit();
person* p;
person1() {
p = new person;
}
};
void person1::visit() {
cout << p->name << endl;
cout << p->phone << endl;
}
int main() {
person1 p;
p.visit();
return 0;
}
4.3 成员函数做友元
#include<iostream>
#include<string>
using namespace std;
class person;
class person1 {
public:
void visit();
person * p;
person1();
};
class person{
friend void person1::visit();
public:
person() {
name = "zhinen";
phone = "private";
}
string name;
private:
string phone;
};
void person1::visit() {
cout << p->name << endl;
cout << p->phone << endl;
}
person1::person1() {
p = new person;
}
int main() {
person1 p;
p.visit();
return 0;
}
5.运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
5.1 加号运算符重载
定义两个自定义数据类型相加的运算。
全局函数实现运算符重载:
本质:person p3=operator+(p1,p2);
#include<iostream>
#include<string>
using namespace std;
class person{
public:
int a;
int b;
};
person operator+(person& p1, person& p2) {
person temp;
temp.a = p1.a + p2.a;
temp.b = p1.b + p2.b;
return temp;
}
int main() {
person p1;
p1.a = 10;
p1.b = 10;
person p2;
p2.a = 10;
p2.b = 10;
person p3 = p1 + p2;
cout << p3.a << " " << p3.b << endl;
return 0;
}
或者,成员函数实现函数重载:
本质person p3=p1.operator+(p2);
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person operator+(person& p) {
person temp;
temp.a = this->a + p.a;
temp.b = this->b + p.b;
return temp;
}
int a;
int b;
};
int main() {
person p1;
p1.a = 10;
p1.b = 10;
person p2;
p2.a = 10;
p2.b = 10;
person p3 = p1 + p2;
cout << p3.a << " " << p3.b << endl;
return 0;
}
总结:对于内置的数据类型的表达式的运算符是不可能改变的;不要滥用运算符重载。
5.2 左移运算符重载
作用:可以输出自定义数据类型。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
//不会利用成员函数重载<<运算符,因为无法实现cout在左侧
//void operator<<(cout) {}//p<<cout;
int a;
int b;
};
void operator<<(ostream &cout, person& p) {
cout << p.a << " " << p.b << endl;
}
int main() {
person p1;
p1.a = 10;
p1.b = 10;
cout << p1;
return 0;
}
链式思想实现:
#include<iostream>
#include<string>
using namespace std;
class person{
public:
//不会利用成员函数重载<<运算符,因为无法实现cout在左侧
//void operator<<(cout) {}//p<<cout;
int a;
int b;
};
ostream &operator<<(ostream &cout, person& p) {
cout << p.a << " " << p.b;
return cout;
}
int main() {
person p1;
p1.a = 10;
p1.b = 10;
cout << p1 << endl;
return 0;
}
5.3 递增运算符重载
作用:通过重载递增运算符,实现自己的整型数据。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
//前置++运算符
//返回引用是为了对一个数一直进行++
person& operator++() {
a++;
return *this;
}
//后置++运算符
//int代表占位参数,可以用于区分前置和后置递增
person operator++(int) {
person p = *this;
a++;
return p;
}
int a;
};
ostream &operator<<(ostream &cout, person& p) {
cout << ++p.a << endl;
cout << p.a++ << endl;
cout << p.a << endl;
return cout;
}
int main() {
person p1;
p1.a = 10;
cout << p1 << endl;
return 0;
}
5.4 递减运算符重载
#include<iostream>
#include<string>
using namespace std;
class person{
public:
//前置--运算符
//返回引用是为了对一个数一直进行--
person& operator--() {
a--;
return *this;
}
//后置--运算符
//int代表占位参数,可以用于区分前置和后置递减
person operator--(int) {
person p = *this;
a--;
return p;
}
int a;
};
ostream &operator<<(ostream &cout, person& p) {
cout << --p.a << endl;
cout << p.a-- << endl;
cout << p.a << endl;
return cout;
}
int main() {
person p1;
p1.a = 10;
cout << p1 << endl;
return 0;
}
5.5 赋值运算符重载
C++编译器至少给一个类添加4个函数:
1)默认构造函数:无参,函数体为空
2)默认析构函数:无参,函数体为空
3)默认拷贝函数,对属性进行值拷
4)赋值运算符operator=,对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person(int a){
this->a = new int(a);
}
person& operator=(person &p) {
//应该判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
if (a != NULL) {
delete a;
a = NULL;
}
a = new int(*p.a);
return *this;
}
~person() {
if (a != NULL) {
delete a;
a = NULL;
}
}
int *a;
};
int main() {
//堆区内存重复释放,程序崩溃,使用编译器提供的拷贝函数
/*person p1(20);
person p2(22);
p2 = p1;
cout << *p1.a << endl;
cout << *p2.a << endl;*/
person p1(20);
person p2(22);
p2 = p1;
cout << *p1.a << endl;
cout << *p2.a << endl;
return 0;
}
5.6 关系运算符重载
作用:重载关系运算符,可以让两个自定义类型的对象进行对比操作。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
person(int a,string b){
this->a = a;
this->b = b;
}
bool operator==(person& p) {
if (this->a == p.a && this->b == p.b)
return true;
return false;
}
bool operator!= (person & p) {
if (this->a == p.a && this->b == p.b)
return false;
return true;
}
int a;
string b;
};
//也可以用这种方法,全局函数
//bool operator==(person& p1, person& p2) {
// if (p1.a == p2.a && p1.b == p2.b)
// return true;
// return false;
//}
int main() {
person p1(18, "zhinen");
person p2(18, "zhinen");
if (p1 == p2) {
cout << "==" << endl;
}
else {
cout << "!=" << endl;
}
if (p1 != p2) {
cout << "!=" << endl;
}
else {
cout << "==" << endl;
}
return 0;
}
5.7 函数调用运算符重载
函数调用运算符()也可以重载,由于重载后使用的方式非常像函数的调用,因此称为仿函数。仿函数没有固定的写法,非常灵活。
#include<iostream>
#include<string>
using namespace std;
class person{
public:
void operator()(string str) {
cout << str << endl;
}
};
class person1 {
public:
int operator()(int a,int b) {
return a + b;
}
};
int main() {
person p1;
p1("zhinen");
person1 p2;
cout << p2(10, 20) << endl;
return 0;
}
6. 继承
继承是面向对象的三大特性之一。有些类与类之间存在特殊的关系,继承的好处:减少重复的代码。
6.1 继承的基本语法
语法:class 子类 : 继承方式 父类
子类也成为派生类;父类也成为基类。
派生类中的成员,包含两大部分:一类是从基类继承过来的,一类是自己增加的成员,从基类继承过来的表现其共性,而新增的成员体现了其个性。
6.2 继承方式
继承的方式一共有三种:
1)公共继承
2)保护继承
3)私有继承
#include<iostream>
#include<string>
using namespace std;
class A {
public:
int a;
protected:
int b;
private:
int c;
};
class B:public A {
public:
void fun() {
a = 10;//父类中的公共权限成员,到子类依然是公共权限。
b = 10;//父类中的保护权限成员,到子类依然是保护权限。
//c = 20;//父类中的私有权限成员,子类访问不到。
}
};
class C :protected A {
public:
void fun() {
a = 10;//父类中的公共权限成员,到子类变为是保护权限。
b = 10;//父类中的保护权限成员,到子类依然是保护权限。
//c = 20;//父类中的私有权限成员,子类访问不到。
}
};
class D :private A {
public:
void fun() {
a = 10;//父类中的公共权限成员,到子类变为是私有权限。
b = 10;//父类中的保护权限成员,到子类变为是私有权限。
//c = 20;//父类中的私有权限成员,子类访问不到。
}
};
class F :public D {
public:
void fun() {
//a = 100;//父类中的私有权限成员,子类访问不到。
//b = 100;//父类中的私有权限成员,子类访问不到。
//c = 100;//父类中的私有权限成员,子类访问不到。
}
};
int main() {
B p;
p.a = 100;
//p.b = 100;//到子类中是保护权限,访问不到
C p2;
//p2.a = 100;//到子类中是保护权限,访问不到
//p2.b = 100;//到子类中是保护权限,访问不到
D p3;
//p3.a = 100;//到子类中是私有权限,访问不到
//p3.b = 100;//到子类中是私有权限,访问不到
return 0;
}
6.3 继承中的对象模型
#include<iostream>
#include<string>
using namespace std;
class A {
public:
int a;
protected:
int b;
private:
int c;
};
class B:public A {
public:
int d;
};
int main() {
//在父类中所有的非静态成员都会被子类继承下去,父类中私有成员属性,被编译器隐藏了,访问不到。
cout << sizeof(B) << endl;
return 0;
}
6.4 继承中构造和析构顺序
子类继承父类后,当子类创建子类对象,也会调用父类的构造函数。
#include<iostream>
#include<string>
using namespace std;
class A {
public:
A() {
cout << "A的构造函数" << endl;
}
~A() {
cout << "A的析构函数" << endl;
}
};
class B:public A {
public:
B() {
cout << "B的构造函数" << endl;
}
~B() {
cout << "B的析构函数" << endl;
}
};
class C :public B {
public:
C() {
cout << "C的构造函数" << endl;
}
~C() {
cout << "C的析构函数" << endl;
}
};
int main() {
//继承中的构造函数:先构造父类再构造子类
//析构函数:先析构子类,再析构父类(恰好相反)
C p;
return 0;
}
6.5 继承同名成员处理方式
当子类和父类出现同名的成员:
1)访问子类同名成员,直接访问即可
2)访问父类同名成员,需要加作用域
#include<iostream>
#include<string>
using namespace std;
class A {
public:
A() {
a = 100;
}
void fun() {
cout << "A" << endl;
}
int a;
};
class B:public A {
public:
B() {
a = 200;
}
void fun() {
cout << "B" << endl;
}
int a;
};
int main() {
B p;
cout << p.a << endl << p.A::a << endl;
p.fun();//直接调用的是子类
p.A::fun();
//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
//如果像访问,必须要加作用域
return 0;
}
6.6 继承同名静态成员处理方式
静态成员和非静态成员出现同名,处理方式一致:
1)访问子类同名成员,直接访问即可
2)访问父类同名成员,需要加作用域
#include<iostream>
#include<string>
using namespace std;
class A {
public:
static void fun() {
cout << "A" << endl;
}
static int a;
};
int A::a = 100;
class B:public A {
public:
static void fun() {
cout << "B" << endl;
}
static int a;
};
int B::a = 200;
int main() {
//对象方式访问
B p;
cout << p.a << endl << p.A::a << endl;
//类访问B::A::a:第一个::表示类名,第二个::访问父类作用域下
cout << B::a << endl << B::A::a << endl;
//对象访问
p.fun();//直接调用的是子类
p.A::fun();
//类名访问
B::fun();
B::A::fun();
//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
//如果想访问,必须要加作用域
return 0;
}
6.7 多继承语法
C++允许一个类继承多个类,但实际开发中不建议用多继承。
语法:class 子类 : 继承方式 父类1 , 继承方式 父类2,…
多继承可能会引发父类中有同名成员出现,需要加作用域区分。
#include<iostream>
#include<string>
using namespace std;
class A {
public:
A() {
a = 100;
}
int a;
};
class B {
public:
B() {
a = 200;
}
int a;
};
class C :public A,public B{
public:
C() {
c = 300;
d = 400;
}
int c;
int d;
};
int main() {
cout << sizeof(C) << endl;
C p;
cout << p.A::a << endl << p.B::a << endl;
return 0;
}
6.8 菱形继承
概念:两个派生类继承同一个基类,又有某个类同时继承这两个派生类,这种继承称为菱形继承,或者钻石继承。
#include<iostream>
#include<string>
using namespace std;
class A {
public:
int a;
};
class B:public A {
public:
};
class C :public A{
public:
};
class D :public B, public C {
};
int main() {
D p;
//p.a = 200;//错
//当菱形继承时,两个父类拥有相同的数据,需要加以作用域区分
p.B::a = 100;
p.C::a = 200;
//a这份数据只要有一份就可以了,菱形继承导致数据有两份,资源浪费
return 0;
}
利用虚继承解决菱形继承问题。
#include<iostream>
#include<string>
using namespace std;
class A {
public:
int a;
};
//利用虚继承解决菱形继承问题(继承指针)
//继承之前加上关键字virtual,变为虚继承
//A类称为虚基类
class B:virtual public A {
public:
};
class C :virtual public A{
public:
};
class D :public B, public C {
};
int main() {
C p;
p.a = 12;
cout << p.a << endl;
return 0;
}
7.多态
7.1 多态的基本概念
多态时C++面向对象三大特性之一。多态分为两类:
1)静态多态:函数重载和运算符重载属于静态多态,复用函数名
2)动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态的区别:
1)静态多态的函数地址早绑定-编译阶段确定函数的地址
#include<iostream>
#include<string>
using namespace std;
class animal {
public:
void speak() {
cout << "动物在说话" << endl;
}
};
class cat:public animal {
public:
void speak() {
cout << "喵喵喵~~~~" << endl;
}
};
void fun(animal &anim) {
anim.speak();
}
int main() {
cat ca;
fun(ca);
return 0;
}
2)动态多态的函数地址晚绑定-运行阶段确定函数的地址
#include<iostream>
#include<string>
using namespace std;
class animal {
public:
virtual void speak() {
cout << "动物在说话" << endl;
}
};
class cat:public animal {
public:
void speak() {
cout << "喵喵喵~~~~" << endl;
}
};
void fun(animal &anim) {
anim.speak();
}
int main() {
cat ca;
fun(ca);
return 0;
}
7.2 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容。因此,可以将虚函数改为纯虚函数,纯虚函数语法:virtual 返回值类型 函数名 (参数列表)=0;当类中含有纯虚函数,这个类也称为抽象类。抽象类的特点:
1)无法实例化对象
2)子类必须重写类中的纯虚函数,否则也属于抽象类。
#include<iostream>
#include<string>
using namespace std;
class animal {
public:
//纯虚函数
virtual void speak() = 0;
};
class cat:public animal {
public:
//子类必须重写类中的纯虚函数,否则也属于抽象类。
void speak() {
cout << "喵喵喵~~~~" << endl;
}
};
class dog :public animal {
public:
//子类必须重写类中的纯虚函数,否则也属于抽象类。
void speak() {
cout << "汪汪汪~~~~" << endl;
}
};
int main() {
//animal a;//抽象类无法实例化对象
cat c;
c.speak();
animal* a = new dog;
a->speak();
return 0;
}
7.3 虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。
#include<iostream>
#include<string>
using namespace std;
class animal {
public:
animal() {
cout << "animal构造" << endl;
}
virtual void speak() = 0;
~animal() {
cout << "animal析构" << endl;
}
};
class cat:public animal {
public:
cat(string str) {
cout << "cat构造" << endl;
name = new string(str);
}
void speak() {
cout << *name << "喵喵喵~~~~" << endl;
}
~cat() {
if (name != NULL) {
cout << "cat析构" << endl;
delete name;
name = NULL;
}
}
string* name;
};
class dog :public animal {
public:
void speak() {
cout << "汪汪汪~~~~" << endl;
}
};
int main() {
animal* a = new cat("Tom");
a->speak();
//父类的指针在析构时候,不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄露情况
delete a;
return 0;
}
解决方式:将父类中的析构函数改为虚析构或纯虚析构。
#include<iostream>
#include<string>
using namespace std;
class animal {
public:
animal() {
cout << "animal构造" << endl;
}
virtual void speak() = 0;
//利用虚析构可以解决,父类指针释放子类对象时不干净问题。
virtual ~animal() {
cout << "animal析构" << endl;
}
};
class cat:public animal {
public:
cat(string str) {
cout << "cat构造" << endl;
name = new string(str);
}
void speak() {
cout << *name << "喵喵喵~~~~" << endl;
}
~cat() {
if (name != NULL) {
cout << "cat析构" << endl;
delete name;
name = NULL;
}
}
string* name;
};
class dog :public animal {
public:
void speak() {
cout << "汪汪汪~~~~" << endl;
}
};
int main() {
animal* a = new cat("Tom");
a->speak();
//父类的指针在析构时候,不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄露情况
delete a;
return 0;
}
虚析构和纯虚构共性:
1)可以解决父类指针释放子类对象
2)都需要具体的函数实现
虚析构和纯虚构区别:如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:virtual ~类名(){}
纯虚析构语法:virtual ~类名()=0;类外实现:类名:: ~类名(){}
总结:
1)虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2)如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3)拥有纯虚析构函数的类也属于抽象类。