点进来的都是家人了,来,今天带你们一起速通江某英的面向对象。
首先,我们先看一下江某英的教学安排,所谓知己知彼,百战不殆。
一共是九个章节,但是最后一个总复习没讲,这不是为难我们吗,既然他不讲,那我们狠狠滴学!正如标题,我是传奇,那我们肯定就不能再跟着江某英的计划来学了,鄙人有一个更加完美的学习小路线,可以抄近路,速通,一共分为两部分:
一部分是基础,也就是一些基础的语法知识,这个我们讲得快一点
//以我对江某英的了解,只会基础在考试中别想拿分了。狗头.jpg
一部分是多态,也就是面向对象滴核心了,这个我们得学得细致一点
话不多说,我们直接开始!
目录:
目录
第一部分、面向对象基础语法
第二部分、多态
重载
函数重载
运算符重载
继承
继承方式
虚函数
纯虚函数
多继承
模板
函数模板
类模板
好啦,到此为止,速通成功!!!
第一部分、面向对象基础语法
什么是面向对象?鄙人认为,就是通过一些抽象的类来构造一些实例对象来达到提高代码复用率、可读性、可扩展性等等目的。
首先,第一个映入我们眼帘的,就是class关键字了,class 关键字就是用来定义一个类的,举个例子:
#include<bits/stdc++.h>
using namespace std;
class A{
public:
void set_number(int a){
number=a;
}
void get_number(){
cout<<number<<endl;
}
void hello(){
cout<<"hi,jiang mou ying~"<<endl;
}
private:
int number;
};
int main(){
A a;
a.hello();
a.set_number(9420);
a.get_number();
}
//输出:
hi,jiang mou ying~
9420
我们可以看到,这个A类就是我们定义的一个类,大括号里面的就是它的成员啦。
欸,这个public和private是什么呢?学过英语的朋友应该都知道,这是“公共的”和“私有的”的意思,与之类似的,还有protected,它们的区别在于别人对数据的访问权限不同,家人们从字面意思上也能看出,public就像公交车,大伙都能访问,private就像私家车,只有对象内部的成员可以调用~
好了,那public 和 private 下面的又是什么呢?
这个是成员辣,public 下面的叫公有成员,private下面的叫私有成员,成员又分为成员函数和数据成员,这个很好理解。(后面在继承我会详细介绍公有成员和私有成员的区别)
好了,我们现在有了一个类,那么如何获取一个对象呢?(当然不是npy那种,鼠鼠我还单身捏!
构造!
想要构造就得要有构造函数。什么?你不会写?服了你了,不过没关系,善良的c++会自动帮你写好,你只要这样就可以得到一个对象(什么都没有的对象):
A a;
是不是很简单鸭!后面我再教你高级的方法~
当然,有构造函数就会有析构函数辣,什么?你又不会写?不过还是没关系,c++在程序结束时会自动调用析构函数,目前你不用管,时机成熟你自然会知道。
有了对象之后,怎么办呢?当然是对她做点什么了(奸笑.jpg)
咳咳,是函数调用辣,你在定义类的时候设置了多少方法(函数)决定了你可以对它执行哪些操作。比如上面的代码,我们可以设置和获取并输出number的值。(通过这个'.'来调用)
好啦,以上就是基础辣,简单吧?接下来的多态比这还要简单!
第二部分、多态
多态是面向对象的核心,是实现提高代码复用率等等的关键步骤,鄙人认为,多态,就是用尽可能少的代码实现尽可能多的功能(当然,这里多态是广义的多态,我把能减少代码的方式都叫做多态,家人们不要过分纠结于名词)
对于多态呢,我想分成下面三部分来讲:重载和继承和模板
重载
什么是重载?重载就是将一些常用的运算符或者函数定义不同的处理逻辑,比如说‘+’号,正常来说是实现两个数的加法运算,但是,如果是矩阵相加呢?我们能不能用一个‘+’号就搞定呢?当然可以,我们只需要重新定义一下‘+’号的逻辑,就可以节省很多代码。这就是重载~
重载分为函数重载和运算符重载
函数重载
所谓函数重载,就是一个函数名,多种实现逻辑,举个例子:
#include<bits/stdc++.h>
using namespace std;
class A{
public:
A(){
number=0;
}
A(int a){
number=a;
}
void get_number(){
cout<<number<<endl;
}
private:
int number;
};
int main(){
A a;
A b(10);
a.get_number();
b.get_number();
}
//输出0和10
对于这个构造函数A,我们在类里面写了两种实现逻辑,一种是直接赋值为0,一种是将传入的值赋值,当我们重载了构造函数,默认构造函数便不存在了,如果我们想要使用默认构造函数,那就得再写一遍。
此时,假设你是编译器,当你面对两个名字一样的函数时,你该如何区分调用哪个呢?是不是很头大?所以,我们在重载函数时,需要秉承一个可区分原则,让编译器知道,你调用的是哪个函数的哪种实现方式,避免出现二义性。
如何避免二义性?我们可以让形参的数量和种类不同。注意辣,返回值不同并不能让编译器区分(如果能区分也是在运行完后区分,这样就晚辣~
我们的例子是重载构造函数,那么,所有的函数都能重载吗?
当然不是啦!析构函数不能被重载~(因为它没有参数,自然也就不满足可区分原则
运算符重载
就像前面说的,将‘+’赋予新的操作逻辑就叫运算符的重载,其本质和函数重载是类似的,只是重载的对象变了。下面是一个运算符重载的例子:
#include<bits/stdc++.h>
using namespace std;
class Complex{
public:
Complex(){
s=0;
v=0;
}
Complex(int _s,int _v){
s=_s;
v=_v;
}
Complex operator+(Complex b){
Complex c;
c.s=s+b.s;
c.v=v+b.v;
return c;
}
void get_number(){
cout<<s<<" "<<v<<endl;
}
friend Complex operator-(Complex a,Complex b);
private:
int s;
int v;
};
Complex operator-(Complex a,Complex b){
Complex c;
c.s=a.s-b.s;
c.v=a.v-b.v;
return c;
}
int main(){
Complex a(1,2);
Complex b(2,3);
Complex c,d;
c=a+b;
d=a-b;
c.get_number();
d.get_number();
}
那么有灵性的家人们就发现了,这里有两种重载运算符的方式,一种是使用成员函数重载,
一种是使用友元函数重载。先给家人们介绍一下什么是友元函数哈:友元函数的本质就是一个普通的函数,只不过它被别人当成了好朋友、好家人(通过friend关键字声明)。前面我们说到,私有变量不能被对象外的东西访问,那么我们这个友元函数虽然在对象外,但是因为它被当成了类好朋友,所以它可以直接访问对象的私有成员。
那么,哪些运算符的重载用友元函数、哪些用成员函数呢?这里将特殊的列了出来,其它的两种重载方式都行:
只能用成员函数重载的运算符:= , [] , -> , ()
只能用友元函数重载:>>, <<
至于为什么?这是因为成员函数的左操作数默认为对象,并且成员函数中有默认的=、[]、->、()重载函数。
对于重载的语法,我相信聪明的家人们不需要我多说什么啦~
需要注意的是输入输出运算符的重载:
friend ostream& operator<<(ostream& os,Complex& a){
cout<<a.s<<" "<<a.v<<endl;
return os;
}
friend istream& operator>>(istream& is,Complex& a){
cin>>a.s>>a.v;
return is;
}
好了,以上就是重载的全部内容辣,是不是很简单呀?下面我们讲继承辣!
继承
面向对象的继承与我们现实生活中的继承很像,比如你继承你爸爸的财产(这里没有别的意思~)就是你爸爸的东西全都给你了,当然也不是所有的东西都给你,比如说浏览器记录他会加密吧,他的身份证不能给你吧(~_~),那么这个面向对象的继承也是如此(构造函数和析构函数不能继承),先看一个例子:
#include<bits/stdc++.h>
using namespace std;
class Verhical{
public:
Verhical(int _price){
price=_price;;
}
void set_price(int _price){
price=_price;
}
void get_price(){
cout<<price<<endl;
}
private:
int price;
};
class Car:public Verhical{
public:
Car(int _price,int _speed):Verhical(_price){//这里注意语法
speed=_speed;
}
void showCar(){
get_price();
cout<<speed<<endl;
}
private:
int speed;
};
int main(){
Car a(100,50);
a.showCar();
}
可以看到,继承的语法是:
class Car:public Verhical{
};
继承方式
这个public是什么意思呢?哈哈,这是公有继承的意思,也可以简单理解为基类的数据成员继承过来之后它的访问权限是不变的。公有的成员在类的外部可以直接被访问......
有公有继承那么也就有私有继承了,所谓私有继承,也就是将基类的公有成员变为派生类
(哦,派生类我还没给你讲?现在讲一下吧~,派生类就是继承了基类的类,就好比你继承了你爹的钱,你不可能只花你爹的钱吧?你还得去额外赚钱,这个继承过来的类,也不仅仅只有基类的成员,它也可以有别的成员,这就是派生)的私有成员,其性质呢也就和私有成员一样了:派生类的外部不能直接访问(当然了,基类中原本的私有成员在派生类中也不能直接访问。
还有一个受保护的继承,和私有继承很像,就是将基类中的所有成员均变为派生类中的受保护的成员。受保护的成员虽不能在类的外部访问,但是它可以在本类的派生类中被直接访问,请注意与私有成员的区别。
下面这段代码可以帮助理解:
class base
{
public:
int x;
protected:
int y;
private:
int z;
};
class publicDerived: public base
{
// x is public
// y is protected
// z is not accessible from publicDerived
};
class protectedDerived: protected base
{
// x is protected
// y is protected
// z is not accessible from protectedDerived
};
class privateDerived: private base
{
// x is private
// y is private
// z is not accessible from privateDerived
}
好啦,继承过来的访问权限我们已经讲清楚了,派生类的对象的构造顺序和析构顺序请访问该文章:
https://blog.csdn.net/weixin_61133168/article/details/130611813
虚函数
假如现在你已经继承了你爹的银行卡,某天你去买东西付款的时候,如何来分辨该用哪张呢?这是一个问题,正常人会在卡上贴一个标签:“my father's credit card",那么在面向对象中,派生类中出现了一个于基类中函数名相同的函数怎么办呢?类似的,我们可以使用虚函数来帮助区分。下面是个例子:
#include<bits/stdc++.h>
using namespace std;
class Verhical{
public:
Verhical(int _price){
price=_price;;
}
int get_price(){
return price;
}
virtual void show(){
cout<<price<<endl;
}
private:
int price;
};
class Car:public Verhical{
public:
Car(int _price,int _speed):Verhical(_price){
speed=_speed;
}
void show(){
cout<<speed<<endl;
}
private:
int speed;
};
int main(){
Verhical verhical(1),*ptr;
ptr=&verhical;
ptr->show();
Car car(1,2);
ptr=&car;
ptr->show();
}
聪明的家人们已经看出来了,虚函数的语法如下:
virtual void show(){
cout<<price<<endl;
}
使用virtual关键字来告诉编译器对该函数的调用进行动态联编,动态联编是实现运行时多态的关键因素,(编译时多态通过重载和模板实现,也就是静态联编,模板咱们后面会讲)这样,就实现了通过一个基类指针,指向不同的对象来调用不同的方法,非常的便利。(在派生类中重新定义基类中的虚函数也叫做函数超越)当然了,虚函数只能是成员函数,因为友元函数是不能被继承的。
纯虚函数
谈到这个虚函数,我们就不能不说一下这个纯虚函数了,它和虚函数很像,唯一不同的是它在基类中是没有被定义的,它必须要在派生类中被重新定义,下面是例子:
#include<bits/stdc++.h>
using namespace std;
class Verhical{
public:
Verhical(int _price){
price=_price;;
}
virtual void show()=0;
private:
int price;
};
class Car:public Verhical{
public:
Car(int _price,int _speed):Verhical(_price){
speed=_speed;
}
void show(){
cout<<speed<<endl;
}
private:
int speed;
};
int main(){
Verhical *ptr;
Car car(1,2);
ptr=&car;
ptr->show();
}
我们可以看到,纯虚函数的语法如下:
virtual void show()=0;
包含纯虚函数的类叫做抽象类,既然是抽象类,那么它也就不能被实例化,也就是不能通过它来直接构造对象,我们只能通过它的派生类来构造对象。虽然我们不能通过抽象类来实例化,但是当我们通过基类指针删除派生类对象时,需要调用虚析构函数:
#include<bits/stdc++.h>
using namespace std;
class Verhical{
public:
Verhical(int _price){
price=_price;;
}
virtual void show()=0;
virtual ~Verhical(){
cout<<"Deconstruct"<<endl;
}
private:
int price;
};
class Car:public Verhical{
public:
Car(int _price,int _speed):Verhical(_price){
speed=_speed;
}
void show(){
cout<<speed<<endl;
}
~Car(){
cout<<"deconstruct"<<endl;
}
private:
int speed;
};
void fun(Verhical *a){
delete a;
}
int main(){
Car car(1,2),*ptr;
ptr=&car;
fun(ptr);
}
//基类中不定义虚虚构函数程序运行会报错
//输出
deconstruct
Deconstruct
多继承
好啦,不晓得大家有没有考虑过这么一个问题,就是说,你继承的话,不单单是只能继承你爹的,你还能继承你妈的,爷爷奶奶的等等,在面向对象里面这叫多继承,与单继承的区别就是继承的基类的数目不同,下面看个例子:
#include<bits/stdc++.h>
using namespace std;
class Animal{
public:
Animal(){
size=0;
}
Animal(int _size){
size=_size;
}
void eat(){
cout<<"bajibaji"<<endl;
}
private:
int size;
};
class Human:virtual public Animal{
public:
Human(string _name,int _age,int _size):Animal(_size){
name=_name;
age=_age;
}
void speak(){
cout<<"balabala"<<endl;
}
private:
string name;
int age;
};
class Dog:virtual public Animal{
public:
Dog(string _color,int _age,int _size):Animal(_size){
color=_color;
}
void speak(){
cout<<"Wang wang wang!"<<endl;
}
private:
string color;
};
class Dogman:public Human,public Dog{
public:
Dogman(string _name,string _color,int _age,int _size):Human(_name,_age,_size),Dog(_color,_age,_size){}
};
int main(){
Dogman a("zyq","white",20,10);
a.Human::speak();
a.Dog::speak();
}
我们可以看到,多继承的语法与单继承是十分类似的,需要注意的是,在多继承中可能会出现二义性问题,比如我们这的speak函数的调用,就存在二义性,我们可以通过类名限定来避免:
a.Human::speak();
a.Dog::speak();
不晓得家人有没有想过这么一个问题,假如你爸继承了你爷爷的房子,你妈也继承了你爷爷 的房子,那么,你继承你爸妈继承下来的房子,是不是说,你就有两套房子了?做梦~当然不可能辣!
面向对象也是一样的,没必要把两个父类共同的父类全部继承下来,只需要继承一遍就好啦~,这个可以通过虚基类实现:
class Human:virtual public Animal
class Dog:virtual public Animal
class Dogman:public Human,public Dog
这里将Animal类声明为虚基类,这样,就可以避免重复继承啦~
模板
c++的模板提供了重用源代码的方法,极大地提高了代码的复用率
模板分为函数模板和类模板
函数模板
函数模板定义了参数化的非成员函数,使程序能用不同类型的参数来调用相同的函数(注意辣,这里和函数重载可不一样嗷,这里只有一个函数,实现的逻辑是单一的,而函数重载的实现逻辑是多样的~)。下面是个例子:
#include<bits/stdc++.h>
using namespace std;
template<class T>
T mmax(T a,T b){
return a>b?a:b;
}
int main(){
cout<<mmax(1,2)<<endl;
cout<<mmax('a','b')<<endl;
}
相信聪明的家人们一眼就能看出来函数模板的语法:
template <class T>
T <函数名称>( T <参数名>, … )
{
<函数操作>
}
注意咯,这里函数的参数类型必须得一样嗷~
函数模板的重载
和函数一样,函数模板也是可以重载的,其重载的方法也是类似的,下面看一个例子:
#include<iostream>
using namespace std;
template<class T>
T fun(T t){
return t;
}
template<class T>
int fun(int i,T t){
return i*t;
}
int main(){
cout<<fun(1)<<endl;
cout<<fun(1,2)<<endl;
}
//输出:
1
2
可以看到,我们通过一个函数模板名实现了多种逻辑,这就是函数模板的重载~
注意也要满足可区分原则哦~(和上面的函数重载一样滴~)
类模板
类模板,顾名思义,就是用来描述类的模板,直接看例子:
#include<iostream>
using namespace std;
template<class T>
class A{
public:
A(T _a){
a=_a;
}
void show(){
cout<<a<<endl;
}
private:
T a;
};
int main(){
A<int> a(1);
A<double> b(2.0);
a.show();
b.show();
}
//输出
1
2
可以看到,类模板实例化产生了一个类,我们再通过类的实例化产生对象。注意,类模板中的函数也是可以重载的,其实,类模板和普通的类具有的功能是一样的,它只是个通用的类。
好啦,到此为止,速通成功!!!
最后送大家一些头文件(江某英的那个极品考试平台用不了万能头文件~)
#include<iostream>
#include<string>
#include<algorithm>
#include<iomanip>
祝家人们考试顺利~