C++ 的面向对象有三大特性:封装、继承、多态
万物皆有对象,对象上有其属性和行为
一、封装
1、封装意义
-
将属性和行为作为一个整体,表现生活中的事务
-
将属性和行为加以权限作为控制
在设计类时,将属性和行为写在一起,表现事物
语法:class 类名 { 访问权限: 属性 / 行为 }
// 设计一个圆类,求圆周长
class circle{
// 类中的属性和行为统一称为 成员
// 属性 :成员属性、成员变量
// 行为 :成员方法、成员函数
//访问权限-公共权限
public:
//属性
int r;
//行为 获取圆的周长
double zhouchang(){
return 2 * PI * r;
}
};
int main(){
// 通过圆类创建具体的圆
circle c1;
//给圆对象的属性进行赋值
c1.r = 10;
cout << "圆的周长为: " << c1.zhouchang() << endl;
return 0;
}
类在设计时,可以把属性和行为放在不同的权限下,加以控制
权限有三种: 1、public 公共权限 2、protected 保护权限 3、private 私有权限
访问权限
-
公共权限 public 成员在类内可以访问,类外也可访问
-
保护权限 protected 成员在类内可以访问,类外不可以访问,儿子可以访问父中保护内容
-
私有权限 private 成员在类内可以访问,类外不可以访问,儿子不可以访问父中隐私内容
// 访问权限
//1 公共权限 public 成员在类内可以访问,类外也可访问
//2 保护权限 protected 成员在类内可以访问,类外不可以访问 儿子可以访问父中保护内容
//3 私有权限 private 成员在类内可以访问,类外不可以访问 儿子不可以访问父中隐私内容
class Persion{
public: // 公共权限
string pname;
protected: // 保护权限
string pcar;
private: // 私有权限
string pmoney;
public:
void showdata(){
pname = "张三";
pcar = "宝马"; //在类内可以访问,没有问题
pmoney = "1000";
}
};
int main(){
// 访问权限
Persion p1;
p1.pname = "张三";
//p1.pcar = "宝马"; //报错,保护成员在类外不可以访问
//p1.pmoney = "1000"; // 报错,私有成员在类外不可以访问
return 0;
}
2、struct和class 区别
在C++中struct和class 唯一的区别就在于:默认访问的权限不一样
-
struct: 默认权限为公共
-
class:默认权限为私有
//struct 和 class 使用的区别
class c1{
int a; // 默认权限是私有
};
struct c2{
int a; // 默认权限是公共
};
int main(){
// struct 和 class 使用的区别
c1 c1;
c2 c2;
//c1.a; // 访问报错,class成员变量默认的权限是私有
c2.a = 1; // 访问正确,没有问题
return 0;
}
3、成员属性设置为私有
-
优点1:将所有成员变量设置为私有,可以自己控制读写权限
-
优点2:对于写权限,我们可以检测数据的有效性
//成员属性设置为私有
class per{
public:
// 设置姓名
void setName(string Sname){
name = Sname;
}
// 读姓名
void showName(){
cout << "人的姓名是 :" << name << endl;
}
void showAge(){
cout << "人的年龄是 :" << age << endl;
}
void setIcar(string car){
icar = car;
}
private:
string name; // 可读可写
int age = 18; // 只读
string icar; //只写
};
int main(){
//成员属性设置为私有
per p;
p.setName("娜娜");
p.showName();
p.setIcar("pamei");
p.showAge();
return 0;
}
二、对象的初始化和清理
1、构造函数和析构函数
构造函数:进行初始化
析构函数:进行清理
以上两个函数是由编译器自动调用
语法:
构造函数:类名(){}
析构函数:~类名(){}
// 构造函数和析构函数,对象化的初始化和清理
class Pers{
public:
//1、构造函数:没有返回值不用写void
// 函数名和类名相同
//构造函数可以有参数,可以发生重载
//构造对象的时候,构造函数会自动调用,而且只调用一次
Pers(){
cout << "构造函数的调用" << endl;
}
//2、析构函数
//没有返回值,不谢void
//函数名和类名也是相同,需要加 ~
//不可以有参数,不可以发生重载
// 对象在销毁前,会自动调用析构函数,并且只发生一次
~Pers(){
cout << "析构函数的调用 " << endl;
}
};
int main(){
// 构造函数和析构函数,对象化的初始化和清理
// 构造和析构都是必须有的,如果自己不提供,编译器会提供一个空实现的构造和析构函数
Pers per; // 构造对象后,会自动调用构造函数,并且只调用一次
return 0;
}
2、构造函数的分类和调用
按参数分类:有参构造和无参构造
按类型分类:普通构造和拷贝构造
三种调用方式:括号法、显示法、隐式转换法
class Pers{
public:
//1、构造函数:没有返回值不用写void
// 函数名和类名相同
//构造函数可以有参数,可以发生重载
//构造对象的时候,构造函数会自动调用,而且只调用一次
Pers(){ //无参构造、默认构造
cout << "无参构造函数的调用" << endl;
}
//2、析构函数
//没有返回值,不谢void
//函数名和类名也是相同,需要加 ~
//不可以有参数,不可以发生重载
// 对象在销毁前,会自动调用析构函数,并且只发生一次
~Pers(){
cout << "析构函数的调用 " << endl;
}
// 构造函数的分类及调用
// 分为无参构造和有参构造
Pers(int a ){
age = a;
cout << "有参构造函数的调用" << endl;
}
// 分为普通构造函数和拷贝构造函数,以上均属于普通
// 拷贝构造函数
Pers(const Pers &p){ //将传入人 p 身上的所有属性拷贝到我自己的身上
age = p.age;
cout << "拷贝构造函数的调用" << endl;
}
int age;
};
// 构造函数的调用
void test01(){
// 1、括号法
Pers p1; // 调用默认构造函数、无参构造函数
Pers p2(10); // 调用有参构造函数
Pers p3(p2); // 调用拷贝构造函数
cout << "p2 的年龄: " << p2.age << endl;
cout << "p3 的年龄: " << p3.age << endl;
/*
注意事项:
1、调用默认参数时,不要加括号。 Pers p1()会被当做是函数的声明
2、不要利用拷贝构造函数初始化匿名对象。 Pers(p5) == Pers p5; 编译器会当做是对象的声明
*/
// 2、显示法
Pers p4; // 调用默认构造
Pers p5 = Pers(9) ; // 显示调用有参构造
Pers p6 = Pers(p5); // 显示调用拷贝构造函数
cout << "p5 的年龄: " << p5.age << endl;
cout << "p6 的年龄: " << p6.age << endl;
Pers(9); //当前属于匿名对象,此行执行结束会立马回收匿名对象
// 3、隐式转换法
Pers p7 = 11; //等价于 Pers p7 = Pers(11); 和显示调用有参一致
cout << "p7 的年龄: " << p7.age << endl;
}
int main(){
test01();
return 0;
}
3、拷贝构造函数调用时机
1、使用一个创建完毕的对象来初始化一个新的对象
2、值传递方式给函数参数传值
3、以值方式传回局部对象
// 拷贝构造函数的调用时机
class girl{
public:
girl(){
cout << "girl默认构造" << endl;
}
girl(int a){
age = a;
cout << "girl有参构造" << endl;
}
girl(const girl &g){
age = g.age;
cout << "girl拷贝构造" << endl;
}
~girl(){
cout << "girl析构函数" << endl;
}
int age;
};
void test03(girl a);
girl test04();
void test02(){
girl g1(23);
//1、使用一个创建完毕的对象来初始化一个新的对象
girl g2(g1);
// 2、值传递方式给函数参数传值
test03(g1);
// 3、以值方式传回局部对象
girl g3 = test04();
cout << "g3 的年龄是: " << g3.age << endl;
cout << (long long)&g3 << endl;
}
void test03(girl a){ //值传递,修改参数不会影响数据本身
cout << "值传递方式给函数参数传值" << endl;
}
girl test04(){
girl g(90);
cout << "g 的年龄是: " <<g.age << endl;
cout << (long long)&g << endl;
return g; //返回的 g 和 接受的不是同一个,返回的是副本
}
4、构造函数调用规则
在默认情况下,编译器会给每个类添加三个函数:默认构造、析构函数、拷贝函数
构造函数调用规则如下
1、如果用户定义有参构造函数,C++ 不再提供默认无参构造函数,但是会提供默认拷贝构造
2、如果用户定义拷贝构造函数,C++ 不会在提供其他构造函数
// 构造函数的调用规则
class rule{
public:
// rule(){
// cout << "rule的默认构造函数调用" << endl;
// }
rule(int a){
age = a;
}
// rule(const rule &r){
// age = r.age; // 编译器会默认写这行代码,
// }
~rule(){
cout << "rule的析构函数调用" << endl;
}
int age;
};
void test05(){
rule r(23);
rule r1(r);
cout << "r1 的年龄为:" << r1.age << endl;
}
void test06(){
rule r(10);
rule r1(r);
cout << r1.age << endl;
}
5、深拷贝和浅拷贝
深拷贝:在堆区重新申请空间,进行拷贝操作 (浅拷贝带来的问题就是,会两次释放同一块内存)
浅拷贝:简单的赋值拷贝操作
attention:如果属性有在堆区开辟的,必须自己提供拷贝构造函数,否则会出现浅拷贝带来的问题
// 构造函数的调用规则
class rule{
public:
rule(){
cout << "rule的默认构造函数调用" << endl;
}
rule(int a,int height){
age = a;
hei = new int(height); //在堆区申请内存空间
}
// 编译器生成的浅拷贝构造函数有问题,重新写深拷贝构造函数
rule(const rule &r){
age = r.age;
hei = new int(*r.hei);
}
// rule(const rule &r){ 浅拷贝
// age = r.age; // 编译器会默认写这行代码,
// hei = r.hei;
// }
~rule(){
//析构代码:将堆区开辟的数据做释放操作
if(hei != NULL){
delete hei;
hei = NULL;
}
cout << "rule的析构函数调用" << endl;
}
int age;
int * hei;
};
void test07(){
// 浅拷贝带来的问题就是,会两次释放同一块内存
rule r1(23,173);
rule r2(r1); //在栈区,r2先进行析构,r1后进行析构
cout << r1.age <<" 身高:" << *r1.hei << endl;
cout << r2.age <<" 身高:" << *r2.hei << endl;
}
6、初始化列表
作用:初始化属性的操作
// 初始化参数列表
class can{
public:
// 初始化列表 初始值
// can():a(2),b(3),c(4){ // 默认值写死,无法改变
//
// }
can(int a, int b ,int c):a(a),b(b),c(c){
}
int a;
int b;
int c;
};
7、类对象作为类成员
C++ 类中的成员可以是另一个类的对象,称为对象成员
当其他类对象作为本类成员,构造时候先构造其他类对象,在构造自身
析构函数顺序:先释放自身,再去释放其他对象,与构造相反
// 类对象作为类成员
class B{
public:
string phone;
B(string bname){
phone = bname;
}
};
class Aii{
public:
Aii(string name,string bn):aname(name),b(bn){ // B b = bn 隐式转换法
}
string aname;
B b;
};
8、静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为
静态成员变量:
-
所有对象共享同一份数据
-
在编译阶段分配内存
-
类内声明,类外初始化
静态成员函数:
-
所有对象共享同一个函数
-
静态成员函数只能访问静态成员变量
//静态成员变量
class ppersion{
public:
/*
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
*/
static int name; // 类内声明
// 静态访问对象也是有访问权限
private:
static int pp_b;
};
int ppersion::name = 100; // 类外初始化
int ppersion::pp_b = 200;
void test08(){
ppersion p;
cout << p.name << endl;
ppersion p1;
p1.name = 90;
cout << p.name << endl; // p1 修改值后,原来p 的值也发生改变
}
void test09(){
/*
静态成员变量不属于某一个对象上,所有对象都共享同一份数据
因此静态成员变量有两种访问方式
1、通过对象进行访问
2、通过类名进行访问
*/
//1、通过对象进行访问
ppersion pp;
cout << pp.name << endl;
//2、通过类名进行访问
cout << ppersion::name << endl;
//cout << ppersion::pp_b << endl; // 访问不了,因为是私有权限的
}
// 静态成员函数
class pererson{
/*
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
- 也是有访问权限的
*/
public:
static void fun(){
name = 90;
//a = 10; // 报错,不能修改非静态成员变量,因为静态成员变量是公用的,不知道修改谁的值
cout << "静态成员函数的 调用" << endl;
}
static int name;
int a = 1;
};
int pererson::name = 100;
void te01(){
pererson p;
p.fun();
p.a = 2;
cout << p.name << endl;
// a不是静态成员变量,每个对象只能修改自己的a值
cout << "p 的a: " << p.a << endl;
pererson p1; // 通过对象访问
p1.fun();
cout << "p1 的a: " << p1.a << endl;
pererson::fun(); //通过类名访问
}
三、C++的对象模型和this指针
1、成员变量和成员函数分开存储
在C++ 中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上
/* 成员函数和成员变量是分开存储的
空对象占用的内存空间为1
给空对象分配一个字节空间,是为了区分空对象占内存的位置
每个空对象也应该有个独一无二的内存地址
*/
2、this 指针概念
this指针指向被调用的成员函数所属的对象
-
this 指针是隐含每一个非静态成员函数内的一种指针
-
this 指针不需要定义,直接使用即可
用途:
-
当形参和成员变量同名时,可以用this指针区分
-
在类的静态成员函数中返回对象本身,可使用 return * this
//this 指针
class perp{
/*
- 当形参和成员变量同名时,可以用this指针区分
- 在类的静态成员函数中返回对象本身,可使用 return * this
*/
public:
perp(int age){
this->age = age; // this 指向的是被调用的成员函数 所属的对象
}
perp& add(perp &p){
this->age += p.age;
return *this; //this 指向p1的指针,解引用就是指向对象
}
int age;
};
void te02(){
perp p(19);
cout << p.age << endl;
perp p1(23);
p1.add(p).add(p);
cout << p1.age << endl;
}
3、空指针访问成员函数
空指针可以调用成员函数,用this指针保证代码的健壮性
// 空指针访问成员函数
class unllPoint{
public:
void showclass(){
cout << "空指针访问成员函数" << endl;
}
void showage(){
if(this == NULL)
return;
cout << "年龄是:" << age << endl;// 此时 age 默认就是 = this.age
}
int age;
};
void te03(){
unllPoint *p = NULL;
//p->showage(); 因为传入的指针是空,不能访问空里面的属性
p->showclass();
}
4、const 修饰成员函数
常函数(const 修饰的函数称为 常函数):
-
常函数内不可以修改成员属性
-
成员属性声明时加关键字 mutable ,在常函数中依然可以改变
常对象:
-
声明对象前加const 称对象称为常对象
-
常对象只能调用常函数
// const 修饰成员函数
class pp1{
public:
pp1(){
}
// this 指针是指针常量 * const this,指针的指向是不可以修改的
// 常函数修饰的是 this 指针,让指针指向的值也不可修改
void show() const{ // 等价于 const pp1 * const this
//this->age = 100;
this->grade = 100; //
}
void ii(){
}
int age;
mutable int grade; // 加上mutable可以修改此变量的值
};
void te04(){
const pp1 p; //此时 p 是常对象
//p.age = 100; 报错
p.grade = 90;
cout << p.grade << endl;
// 常对象只能调用常函数
//p.ii(); 不能调用普通函数
p.show();
}
四、友元
1、全局函数作友元
目的:就是让一个函数或者类,访问另一个类中私有成员
关键字:friend
三种实现:
-
全局函数做友元
-
类做友元
-
成员函数做友元
// 友元
class building{
// goodFreind 是builging 好朋友。可以访问building中的私有成员
friend void goodFreind(building *b);
public:
building(){
keitng = "客厅";
room = "卧室";
}
public:
string keitng;
private:
string room;
};
// 全局函数
void goodFreind(building *b){
cout << "好朋友正在访问:" << b->keitng << endl;
cout << "好朋友正在访问:" << b->room << endl; // 可以访问私有成员
}
void te05(){
building b;
goodFreind(&b);
}
2、类做友元
// 友元
class building{
// 类做友元
friend class goodF;
public:
building(){
keitng = "客厅";
room = "卧室";
}
public:
string keitng;
private:
string room;
};
// 类做友元
class goodF{
public:
goodF(); // 类外写构造函数
void visit(){ // 访问 builing 中的私有属性
cout << "类做友元,好朋友正在访问:" << b->keitng << endl;
cout << "类做友元,好朋友正在访问:" << b->room << endl; // 可以访问私有成员
}
building *b;
};
// 类外写构造函数
goodF::goodF(){
b = new building; //new 返回的是一个指针
}
void te05(){
goodF d;
d.visit();
}
3、成员函数做友元(对结构有严格的强调,在此并未指出)
// 友元
// 成员函数做友元
class building;
class goodd{
public:
goodd();
void visit(); // 可以访问私有成员
void visit2(); // 不可以访问私有成员
building *b;
};
class building{
// 成员函数做友元
friend void goodd::visit();
// goodFreind 是builging 好朋友。可以访问building中的私有成员
friend void goodFreind(building *b);
// 类做友元
friend class goodF;
public:
building(){
keitng = "客厅";
room = "卧室";
}
public:
string keitng;
private:
string room;
};
goodd::goodd(){
b = new building;
}
void goodd::visit(){// 可以访问私有成员
cout << "成员函数做友元,visit()正在访问:" << b->keitng << endl;
cout << "成员函数做友元,visit()正在访问:" << b->room << endl;
}
void goodd::visit2(){ // 不可以访问私有成员
cout << "成员函数做友元,visit2()正在访问:" << b->keitng << endl;
//cout << "成员函数做友元,visit2()正在访问:" << b->room << endl;
}
void te06(){
goodd g;
g.visit();
g.visit2();
}
五、运算符重载
运算符重载:对已有的运算符重新进行定义,赋予另一种功能,以适应不同的数据类型
1、加号运算符重载
作用:实现两个自定义数据类型相加的运算
成员函数实现:
// 1、加号运算符重载 : 成员函数实现 全局函数实现
class persion{
public:
//成员函数实现
persion operator+(persion &p){
persion 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;
};
void te01(){
persion p1;
p1.m_a = 9;
p1.m_b = 3;
persion p2;
p2.m_a = 5;
p2.m_b = 7;
persion p3 = p1 + p2;
cout << "p3 的a :" << p3.m_a << endl;
cout << "p3 的b :" << p3.m_b << endl;
}
全局函数实现:
// 1、加号运算符重载 : 成员函数实现 全局函数实现
class persion{
public:
int m_a;
int m_b;
};
//全局函数实现
persion operator+ (persion &p1,persion &p2){
persion temp;
temp.m_a = p1.m_a + p2.m_a;
temp.m_b = p1.m_b + p2.m_b;
return temp;
}
void te01(){
persion p1;
p1.m_a = 9;
p1.m_b = 3;
persion p2;
p2.m_a = 5;
p2.m_b = 7;
persion p3 = p1 + p2; // 简化的写法
cout << "p3 的a :" << p3.m_a << endl;
cout << "p3 的b :" << p3.m_b << endl;
}
2、左移运算符重载
作用:可以输出自定义数据类型
// 1、加号运算符重载 : 成员函数实现 全局函数实现
class persion{
public:
//成员函数实现左移运算符,
// 不会利用成员函数实现左移运算符。无法实现 cout 在左侧
// void operator<<(cout){
//
// }
int m_a;
int m_b;
};
//全局函数实现左移运算符
ostream &operator<< (ostream &cout , persion &p){
cout << "m_a = " << p.m_a << " m_b = " << p.m_b << endl;
return cout;
}
// 2、左移运算符重载
void te02(){
persion p1;
p1.m_a = 90;
p1.m_b = 13;
cout << p1 << endl ;
}
3、递增运算符重载
作用:通过重载递增运算符,实现自己的整型数据
// 3、递增运算符重载
class per{
friend ostream & operator<< (ostream &cout,per &p);
public:
per(){
m_num = 1;
}
// 前置运算符重载
per & operator++(){
// 先加1,在放回返回自身
m_num ++;
return *this;
}
// 后置运算符重载
int operator++(int){ // int 代表是一个占位参数,区分前置和后置
// 先返回自身,后+1
per pp = *this; // 先记录自身结果,在+1,然后返回记录
m_num++;
return pp.m_num;
}
private:
int m_num;
};
ostream & operator<< (ostream &cout,per &p){
cout << p.m_num << endl;
return cout;
}
// 前置递增运算符重载
void te03(){
per p;
cout << ++p << endl;
cout << p << endl;
}
// 后置运算符重载
void te04(){
per p1;
cout << p1++ << endl;
cout << p1 << endl;
}
// 递减运算符重载
class dele{
friend ostream &operator << (ostream &cout,dele &de);
public:
dele(){
m_num = 9;
}
// 前置递增
dele& operator--(){
m_num--;
return *this;
}
// 后置递增
int operator--(int){
dele d = *this;
m_num--;
return d.m_num;
}
private:
int m_num;
};
ostream &operator << (ostream &cout,dele &de){
cout << de.m_num << endl;
return cout;
}
void te05(){
dele de;
cout << --de << endl;
cout << de << endl;
dele ded;
cout << ded-- << endl;
cout << ded << endl;
}
4、赋值运算符重载
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝的问题
C++ 编译器至少给一个类添加4个函数
1、默认构造函数(无参,函数体为空
2、默认析构函数(无参,函数体为空
3、默认拷贝构造函数,对属性进行值拷贝
4、赋值运算符 ,对属性进行值拷贝
// 赋值运算符重载
class pers{
public:
pers(int age){
m_age = new int(age); // 存放在堆区
}
~pers(){
if(m_age != NULL){
delete m_age;
m_age = NULL;
}
}
// 重载赋值运算符
pers & operator=(pers &p){
// 先判断是否有属性在堆区,如果有先释放干净
if(m_age != NULL){
delete m_age;
m_age = NULL;
}
// 深拷贝操作
m_age = new int(*p.m_age);
// 返回对象本身
return *this;
}
int *m_age;
};
void te06(){
pers p1(10);
cout << "p1 的年龄是:" << *p1.m_age << endl;
pers p2(9);
pers p3(8);
p2 = p1 = p3; // 赋值操作,造成堆区的内容重复释放
// 利用深拷贝解决浅拷贝的问题
cout << "p2 的年龄是:" << *p2.m_age << endl;
cout << "p1 的年龄是:" << *p1.m_age << endl;
cout << "p3 的年龄是:" << *p3.m_age << endl;
}
5、关系运算符重载
作用:可以让两个自定义类型对象进行对比操作
//关系运算符重载
class compire{
public:
compire(string name,int age){
this->name = name;
this->age = age;
}
//关系运算符重载
bool operator==(compire &c2){
if(this->name == c2.name && this->age == c2.age){
return true;
}else return false;
}
string name;
int age;
};
void te07(){
compire c1("哈哈",23);
compire c2("娜娜",23);
compire c3("娜娜",23);
if(c1 == c2){
cout << " c1 和 c2 是相等的" << endl;
}else {
cout << " c1 和 c2 是不等的" << endl;
}
if(c3 == c2){
cout << " c3 和 c2 是相等的" << endl;
}else {
cout << " c3 和 c2 是不等的" << endl;
}
}
6、函数调用运算符重载
函数调用运算符()也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定写法,非常灵活
// 函数调用运算符重载
class myPrint{
public:
// 函数调用运算符重载
void operator()(string test){
cout << test << endl;
}
};
void te08(){
myPrint m;
m("daihan"); // 仿函数
// 匿名函数对象, 匿名对象,使用的是调用运算符重载
myPrint()("hhhhh");
}
六、继承
优点:减少重复代码
1、继承的基本语法
基本语法:class 子类 :继承方式 父类
子类也称为派生类
父类也称为基类
//使用继承
class baseP{
public:
void head(){
cout << "首页、公开课、登录、注册" << endl;
}
void footer(){
cout << "帮助中心、交流合作、站内地图" << endl;
}
void left(){
cout << "java、python、C++......" << endl;
}
};
//java 页面
class Javaa:public baseP{
public:
void content(){
cout << "java 学习内容" << endl;
}
};
//python 页面
class Pythonn:public baseP{
public:
void content(){
cout << "python 学习内容" << endl;
}
};
//C++ 页面
class Cprogram:public baseP{
public:
void content(){
cout << "C++ 学习内容" << endl;
}
};
void test02(){
cout << "java学习页面如下:" << endl;
Javaa jaa;
jaa.head();
jaa.footer();
jaa.left();
jaa.content();
cout << "--------------------" << endl;
cout << "python学习页面如下:" << endl;
Pythonn pyy;
pyy.head();
pyy.footer();
pyy.left();
pyy.content();
cout << "--------------------" << endl;
cout << "C++学习页面如下:" << endl;
Cprogram p1;
p1.head();
p1.footer();
p1.left();
p1.content();
}
2、继承方式
继承方式一共有三种:
-
公共继承
-
保护继承
-
私有继承
// 继承方式
class father{
public:
int a;
protected:
int b;
private:
int c;
};
class sonPublic :public father{
public:
void fun(){
a = 10;
b = 9;
//c = 8; // b报错
}
};
class sonProtected :protected father{
public:
void fun(){
a = 10;
b = 9;
//c = 8; // b报错
}
};
class sonPrivate : private father{
public:
void fun(){
a = 10;
b = 9;
//c = 8; // b报错
}
};
void test03(){
sonProtected s2;
//cout << s2.a << endl; // a 是受保护的,报错
sonPrivate s3;
//cout << s3.a << endl; // a是私有成员,报错
}
3、继承中的对象模型
问题:从父类继承过来的成员,哪些属于子类对象中?
//继承中的对象模型
class base{
public:
int a;
protected:
int b;
private:
int c;
};
class daughter :public base{
public:
int m_d;
};
void test04(){
// 父类中所有静态成员属性都会被子类继承下去
// 父类中私有成员属性,是被编译器隐藏,访问不断,但是确实被继承下去
daughter a;
base b;
cout << sizeof(a) << endl; // 16
cout << sizeof(b) << endl; //12
}
4、继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
顺序如下:
1父类的构造函数
2子类的构造函数
3子类的析构函数
4父类的析构函数
总结:继承中先调用父类构造函数,子啊调用子类构造函数,析构顺序与构造相反
5、继承同名成员处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据?
-
访问子类同名成员 直接访问即可
-
访问父类同名成员 需加作用域
//继承中同名成员处理方式
class ba{
public:
ba(){
m_a = 100;
}
void fun(){
cout << "父类中的成员函数" << endl;
}
void fun(int a){
cout << "父类daiyou int a 中的成员函数" << endl;
}
int m_a;
};
class bason:public ba{
public:
bason(){
m_a = 200;
}
void fun(){
cout << "子类中的成员函数" << endl;
}
int m_a;
};
void test05(){
bason b;
cout << b.m_a << endl; // 直接访问的是子类 bason 下面的 m_a
cout << b.ba::m_a << endl; // 访问的是父类 ba 下面的 m_a
b.fun(); // 子类中的成员函数的调用
b.ba::fun(); // 父类中的成员函数的调用
// 如果子类中出现和父类同名的成员函数,子类中的同名成员会隐藏掉父类中的所有同名成员函数
b.ba::fun(100) ;
}
如果子类中出现和父类同名的成员函数,子类中的同名成员会隐藏掉父类中的所有同名成员函数
6、继承同名静态成员处理方式
问题:继承中同名的静态成员在子类对象上如何进行访问
静态成员和非静态成员出现同名,处理方式一致
-
访问子类同名成员,直接访问即可
-
访问父类同名成员,需要加作用域
//继承中静态同名成员处理方式
class dd{
public:
static void fun(){
cout << "静态父类下成员函数的调用" << endl;
}
static int a;
};
int dd::a = 100; // 静态成员类外初始化(必须)
class ddson :public dd{
public:
static void fun(){
cout << "静态子类下成员函数的调用" << endl;
}
static int a;
};
int ddson::a = 90;
void test06(){
// 静态成员变量
//利用对象访问
ddson s;
cout << s.a << endl; // 90 直接访问时子类的成员变量
cout << s.dd::a << endl; // 100 加上作用域访问的是 父类中的成员变量
//利用类名访问
cout << ddson::a << endl ;
cout << ddson::dd::a << endl; // 通过类名,访问作用域下的a
// 静态成员函数
// 利用对象访问
ddson ss;
ss.fun(); // 直接调用子类下的静态函数
ss.dd::fun(); // 作用域调用父类下的静态函数
// 利用类名访问
ddson::fun();
ddson::dd::fun(); // 通过类名,访问作用域下的fun
// 如果子类中出现和父类同名的静态成员函数,子类中的同名成员会隐藏掉父类中的所有同名静态成员函数
}
// 如果子类中出现和父类同名的静态成员函数,子类中的同名成员会隐藏掉父类中的所有同名静态成员函数
7、多继承语法
C++ 中允许一个类继承多个类
语法:class 子类:继承方式 父类1,继承方式 父类2……..
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++ 实际开发中不建议使用多继承
// 多继承类
class dpdpd:public dd ,public ba{
public:
dpdpd(){
m_c = 90;
}
int m_c;
};
void test07(){
dpdpd dd;
cout << dd.dd::m_a << endl; // 当出现同名的成员时,需要加上作用域
cout << dd.ba::m_a << endl;
cout << dd.m_c << endl;
cout << dd.a << endl;
}
8、菱形继承 (虚继承解决此问题)
概念:两个派生类继承同一个基类,又有某个类同时继承两个派生类。成为菱形继承,或者钻石继承
// 菱形继承
class baseFather{
public:
baseFather(){
m_age = 25;
}
int m_age;
};
// 利用虚继承,解决菱形继承的问题
// class son1:virtual public baseFather 就是虚继承
// baseFather 称为虚基类
class son1:virtual public baseFather{
public:
// son1(){
// m_age = 11;
// }
// int m_age;
};
class son2:virtual public baseFather{
public:
// son2(){
// m_age = 23;
// }
// int m_age;
};
class sun:public son1,public son2{
public:
};
void test08(){
sun ss;
ss. son1:: m_age = 11;
ss. son2:: m_age = 23;
// 当两个父类拥有相同数据,需要加以作用域区分
cout << ss.son1::m_age << endl; // 11
cout << ss.son2::m_age << endl; // 23
cout << ss.m_age << endl;
// 加上虚继承都为23
// 相当于只拥有静态变量,共享同一份数据
cout << ss.son1::m_age << endl; // 23
cout << ss.son2::m_age << endl; // 23
cout << ss.m_age << endl; // 23
}
七、多态
1、多态的基本概念
多态是C++ 面向对象三大特性之一
分类:
-
静态多态 :函数重载和运算符重载属于静态多态,复用函数名
-
动态多态 :派生类和虚函数实现运行时多态
两者区别:
-
静态多态的函数地址早绑定:编译阶段确定函数地址
-
动态多态的函数地址晚绑定:运行阶段确定函数地址
动态多态满足条件
1、有继承关系
2、子类要重写父类的虚函数
动态多态的使用:
父类的指针或者引用 执行子类对象
/*
动态多态满足条件
1、有继承关系
2、子类要重写父类的虚函数
动态多态的使用:
父类的指针或者引用 执行子类对象
*/
class animal{
public:
// 虚函数
virtual void speak(){
cout << "动物在说话" << endl;
}
};
class Cat:public animal{
public:
void speak(){
cout << "xi猫在说话" << endl;
}
};
class dog :public animal{
public:
void speak(){
cout << "狗狗狗在说话 " << endl;
}
};
// 动作函数
// 地址早绑定,在编译阶段就确定函数地址
// 如果要猫先说话,那么这个函数地址就不可以早绑定,需要在运行阶段进行绑定,地址晚绑定
void doSpeak(animal &ani){
ani.speak();
}
void test01(){
Cat cat;
doSpeak(cat);
dog dd;
doSpeak(dd);
}
2、多态案例-计算器类
多态优点:
-
代码组织结构清晰
-
可读性强
-
利于前期和后期的扩展以及维护
案例:实现两个操作数进行运算的计算器类
// 案例-------计算器类
class computer{ // 普通写法
public:
int getReuslt(string oper){
if(oper == "+"){
return num1 + num2;
}else if(oper == "-"){
return num1 - num2;
}else if(oper == "*"){
return num1 * num2;
}else {
return num1 / num2;
}
}
int num1;
int num2;
};
void test02(){
computer c;
c.num1 = 9;
c.num2 = 8;
cout << c.getReuslt("*") << endl;
}
// 利用多态实现计算器
class com{
public:
virtual int getResult(){
return 0;
}
int num1;
int num2;
};
class addCom:public com{ // 加法
public:
int getResult(){
return num1 + num2;
}
};
class deleCom:public com{ // 减法
public:
int getResult(){
return num1 - num2;
}
};
class chengCom:public com{ // 乘法
public:
int getResult(){
return num1 * num2;
}
};
int jisuan(com &cc){ // 父类的引用指向子类对象
return cc.getResult();
}
void test03(){
addCom d;
d.num1 = 90;
d.num2 = 10;
cout << jisuan(d) << endl;
}
void test04(){
com *abc = new chengCom; // 父类指针指向子类对象
abc->num1 = 9;
abc->num2 = 7;
cout << abc->getResult() << endl;
// 用完需要销毁
delete abc;
}
3、纯虚函数和抽象类
多态中,父类中的虚函数通常没有意义,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
基本语法: virtual 返回值类型 函数名 (参数列表)= 0;
当类中有了纯虚函数,这个类也称为 抽象类
抽象类特点:
-
无法实例化对象
-
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
// virtual int getResult(){
// return 0;
// }
// 改为纯虚函数为:
virtual int getResult() = 0;
4、多态案例二-制作饮品
// 案例-----制作饮品
class dringk{
public:
void firstStep(){
cout << "1、煮水" << endl;
}
virtual void secondStep() = 0;
void thirdStep(){
cout << "3、倒入杯中" << endl;
}
virtual void lastStep() = 0;
void make(){
firstStep();
secondStep();
thirdStep();
lastStep();
}
};
class coffa:public dringk{
public:
void secondStep(){
cout << "2、冲泡咖啡" << endl;
}
void lastStep(){
cout << "4、加糖和牛奶" << endl;
}
};
class tea:public dringk{
public:
void secondStep(){
cout << "2、冲泡茶叶" << endl;
}
void lastStep(){
cout << "4、加柠檬" << endl;
}
};
void test05(){
dringk *dd = new coffa;
dd->make();
delete dd;
cout << "-----------" << endl;
dd = new tea;
dd->make();
delete dd;
}
5、虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构泰马
解决方式:将父类中的析构函数改为虚析构或者纯虚构
虚析构和纯虚析构共性:
-
可以解决父类指针释放子类对象
-
都需要有具体的函数实现
虚析构和纯虚构区别:
如果是纯虚析构,给类属于抽象类,无法实例化对象
虚析构语法:virtual ~类名(){}
纯虚析构语法:virtual ~类名 () = 0 ;
//虚析构和纯虚析构
class annnn{
public:
annnn(){
cout << "父类构造函数调用" << endl;
}
// 虚析构
// 利用虚析构可以解决父类指针释放子列对象时不干净的问题
// virtual ~annnn(){
// cout << "父类析构函数调用" << endl;
// }
// 纯虚构
// 有了纯虚析构,此类也属于抽象类
virtual ~annnn() = 0;
virtual void speak() = 0;
};
// 纯虚析构必要要有声明和实现。
// 父类也会有数据开辟到堆区
annnn::~annnn(){
cout << "父类纯虚析构函数调用" << endl;
}
class caa :public annnn{
public:
caa(string name){
cout << "子类构造函数调用" << endl;
m_name = new string(name); // 堆区属性
}
virtual void speak(){
cout << *m_name << " 小猫 在说话" << endl;
}
~caa(){
if (m_name != NULL) {
cout << "子类析构函数调用" << endl;
delete m_name;
m_name = NULL;
}
}
string *m_name;
};
void test06(){
annnn * ad = new caa("haha");
ad->speak();
// 父类的指针在析构时不会调用子类中析构函数,导致子类如果有堆区的数据会出现内存泄漏
delete ad;
}
总结:
-
虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
-
如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
-
拥有纯虚析构函数的类也称为抽象类
6、多态案例三-电脑组装
// 案例三 --电脑组装
class Cpu{
public:
virtual void prided() = 0;
};
class Xianka{
public:
virtual void display() = 0;
};
class Cache{
public:
virtual void cash() = 0;
};
class buildComp{
public:
buildComp(Cpu *cpuu,Xianka *xiankaa,Cache *cachee){
cpu = cpuu;
xianka = xiankaa;
cache = cachee;
}
// 提供工作的函数
void work(){
cpu->prided();
xianka->display();
cache->cash();
}
// 提供析构函数 释放零件
~buildComp(){
cout << "释放零件" << endl;
if (cpu != NULL) {
delete cpu;
cpu = NULL;
}
if (xianka != NULL) {
delete xianka;
xianka = NULL;
}
if (cache != NULL) {
delete cache;
cache = NULL;
}
}
private:
Cpu *cpu;
Xianka *xianka;
Cache *cache;
};
class InterCpu:public Cpu{
public:
void prided(){
cout << "inter的CPU提供计算" << endl;
}
};
class InterXianka:public Xianka{
public:
void display(){
cout << "inter的显卡提供显示" << endl;
}
};
class InterCache:public Cache{
public:
void cash(){
cout << "inter的cache提供存储" << endl;
}
};
void test07(){
// 创建第一台电脑
Cpu *intercpu = new InterCpu;
Xianka *interXianka = new InterXianka;
Cache *interCache = new InterCache;
buildComp *c1 = new buildComp(intercpu,interXianka,interCache);
c1->work();
delete c1;
}