多态:是指有多种形态
多态有两大类:
1.静态多态:函数重载,运算符重载,复用函数名(编译阶段确定地址)
2.动态多态:子类和虚函数实现运行时的多态(运行阶段确定地址)
细分以下多态:
- 参数多态(函数模板和类模板)
- 包含多态 (virtual)
- 重载多态(重载多态是指函数名相同,但函数的参数个数或者类型不同的函数构成多态)
- 强制多态(强制类型转换)
其中:
- 类型参数化多态和包含多态统称为一般多态性
- 重载多态和强制多态统称为特殊多态性
静态多态(编译阶段提前标定,当父类指针或引用接收之类对象时)会在编译阶段提前绑定父类中的函数
class A
{
public:
void show(){cout << "父类show()" << endl;}
};
class B :public A
{
public:
void show(){cout << "子类show()" << endl;}
};
//父类引用可以接受子类对象
void show1(A &a)
{
a.show();
}
//父类指针接收子类对象
void show2(A*p)
{
p->show();
}
int main()
{
B b;
//结果输出 父类的show 因为编译阶段 show1函数提早绑定了父类地址
show1(b);
B *b1 = new B;
//结果输出 父类的show 因为编译阶段 show1函数提早绑定了父类地址
show2(b1);
return 0;
}
在这里我们发现,我声明的时一个子类对象,但有父类指针或引用去接收时,调用的函数却是父类的,为了解决这个问题,c++提供了虚函数的概念。
虚函数:
虚函数 是在基类中使用关键字 virtual 声明的函数,在派生类中重写这个函数,当父类指针(引用)接收子类对象时,会采用动态多态,在运行阶段确定地址,会使用子类的函数子类重写父类虚函数时:子类的virtual可加可不加
动态多态:
class A
{
public:
virtual void show(){cout << "父类show()" << endl;}//声明为虚函数
};
class B :public A
{
public:
void show(){cout << "子类show()" << endl;}
};
//父类引用接受子类对象
void show1(A &a)
{
a.show();
}
//父类指针接收子类对象
void show2(A*p)
{
p->show();
}
int main()
{
B b;
//结果输出 子类的show 因为运行阶段 show1函数最后绑定了子类地址
show1(b);
B *b1 = new B;
//结果输出 子类的show 因为运行阶段 show1函数最后绑定了子类地址
show2(b1);
return 0;
}
C++提供多态的目的是:可以通过基类指针对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数。如果没有多态,我们只能访问成员变量。
纯虚函数:是一个在基类中声明的虚函数,在类中没有定义具体的操作内容。
格式:
virtual 函数类型 函数名(参数)=0;
声明为纯虚函数,则该函数可以不用实现 (也可以实现),拥有纯虚函数的类也称为 抽象类
1.抽象类不能实例化,但可以定义一个抽象类 指针和引用
2.子类一般需要重写抽象类的纯虚函数,不然也成抽象类
class person
{
public:
//纯虚函数
virtual void show() = 0;
int age;
};
class son:public person
{
public:
//重写父类的纯虚函数 子类未重写的话也为抽象类
void show()
{
age = 20;
cout << "son中age的值" << this->age << endl;
}
};
int main()
{
//纯虚函数不能实例化
//person p;//报错
//但可以使用指针和引用
//vector 为容器,类似数组
vector<person*>p;
vector<person&>p1;
//非纯虚函数可以实例化
son s;
return 0;
}
什么样的函数不能声明为虚函数?
- 不能被继承的函数。
- 不能被重写的函数。
- 普通函数,友元函数,构造函数,内联成员函数,静态成员函数
1)普通函数
普通函数不属于成员函数,是不能被继承的。普通函数只能被重载,不能被重写,因此声明为虚函数没有意义。因为编译器会在编译时绑定函数。
而多态体现在运行时绑定。通常通过基类指针指向子类对象实现多态。
2)友元函数
友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法。
3)构造函数
首先说下什么是构造函数,构造函数是用来初始化对象的。假如子类可以继承基类构造函数,那么子类对象的构造将使用基类的构造函数,而基类构造函数并不知道子类的有什么成员,显然是不符合语义的。从另外一个角度来讲,多态是通过基类指针指向子类对象来实现多态的,在对象构造之前并没有对象产生,因此无法使用多态特性,这是矛盾的。因此构造函数不允许继承。
4)内联成员函数
我们需要知道内联函数就是为了在代码中直接展开,减少函数调用花费的代价。也就是说内联函数是在编译时展开的。而虚函数是为了实现多态,是在运行时绑定的。因此显然内联函数和多态的特性相违背。
5)静态成员函数
首先静态成员函数理论是可继承的。但是静态成员函数是编译时确定的,无法动态绑定,不支持多态,因此不能被重写,也就不能被声明为虚函数。
虚析构和纯虚析构:
- 虚析构是把析构函数设置为虚函数,纯虚析构的话,是把析构函数弄成纯虚函数
- 虚析构和纯虚析构都需要实现
- 纯虚析构在类内声明,类外实现
当子类开辟到堆区时,父类指针释放时无法调用子类析构代码虚析构的作用:当父类指针(或引用)接收子类在堆区生成的对象时,父类可以调用子类的析构函数
当父类析构不是虚析构时:(delete 父类指针时不会调用子类的析构函数)
class person
{
public:
person()
{
cout << "person的构造函数" << endl;
}
//纯虚函数
virtual void show() = 0;
//析构
~person()
{
cout << "person的析构函数" << endl;
}
};
class son :public person
{
public:
son(int a)
{
age = new int(a);
cout << "son的构造函数" << endl;
}
//重写父类的纯虚函数 子类未重写的话也为抽象类
void show()
{
cout << "son中age的值" << this->age << endl;
}
~son()
{
if (age != NULL)
{
delete age;
age = NULL;
}
cout << "son的析构函数" << endl;
}
int* age;
};
把父类析构函数设置为虚析构时:
//虚析构
virtual ~person()
{
cout << "person的析构函数" << endl;
}
把父类析构函数设置为纯虚析构时:
//纯虚析构 类内声明
virtual ~person() = 0;
//类外实现
person::~person()
{
cout << "person的纯虚析构函数" << endl;
}
正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消正在上传…重新上传取消
虚函数和纯虚函数的权限:
- 虚函数可以为private,并且可以被子类覆盖(因为虚函数表的传递),但子类不能调用父类的private虚函数。虚函数的重载性和它声明的权限无关。
- virtual修饰符则强调父类的成员函数可以在子类中被重写,因为重写之时并没有与父类发生任何的调用关系,故而重写是被允许的。
- 纯虚函数可以设计成私有的,不过这样不允许在本类之外的非友元函数中直接调用它,子类中只有覆盖这种纯虚函数的义务,却没有调用它的权利。
class person
{
public:
private:
virtual void show()
{
cout << "父类的show()函数" << endl;
}
};
class son :public person
{
public:
void show()
{
cout << "子类的show()函数" << endl;
}
};
int main()
{
son s;
s.person::show();//子类无法访问父类的私有成员
return 0;
}
运算符重载:
c++规定: = [ ] () -> 这四个运算符只能被重载为类的非静态成员函数重载
其他的可以被友元重载
解释:
其他运算符重载函数都会更具参数类型或数目进行精确匹配,
这4个不具有这总检查功能,使用友元定义就会出错
不能被重载的运算符:
sizeof : ? . ::
长度运算符 三目运算符 成员选择符 域解析符
运算符重载的关键词:operator
运算符重载规则:
- 只能重载C++已有的运算符
- 重载的功能应该与原有功能相似(一般不要 重载 + 号用 - 号的作用)
- 重载子后,优先级和结合性不变
以下是重载运算符的示例:
+号的重载(-号和+号类似)
person operator+(const person &p)
{
this->age += p.age;
this->height += p.height;
return *this;
}
int age;
int height;
全局函数:
person operator+(const person &p,const person &p1)
{
person p2;
p2.age = p.age + p1.age;
p2.height = p.height + p1.height;
return p2;
}
想要实现连续运算:可以返回类的引用即可实现
全局函数:
person& operator+(const person &p,const person &p1)
{
person p2;
p2.age = p.age + p1.age;
p2.height = p.height + p1.height;
return p2;
}
++重载:(--和++类似)
前置++:++x
person& operator++()
{
age++;
height++;
return *this;
}
int age;
int height;
后置++:x++
//int为占位参数用来区分 前置++和后置++
person& operator++(int)
{
person p = *this;
age++;
height++;
return p;
}
重载<<运算符:
一般用全局函数实现,成员函数难以实现
cout 的类为 ostream 输出流类
cin 的类为 istream 输出流类
class person
{
public:
//有参构造
person(int a,string b):age(a),name(b){}
int age;
string name;
};
//全局函数重载<<
ostream& operator<<(ostream& o,const person &p1)
{
cout << "年龄为:" << p1.age << "姓名为:" << p1.name << endl;
return cout;
}
int main()
{
person p(10, "a");
//直接输出 age和name
cout << p<<endl;
return 0;
}
重载new和delete
重载的格式:
- 返回值为 void*
- 参数为size_t size,表示开辟空间的大小
重载new:(成员函数)
void * operator new(size_t size)
{
void* p = malloc(size);
}
重载delete:(成员函数)
void operator delete(void *)
{
free(p);
}
重载()
- 一种为仿函数
- 直接重载
class person
{
public:
//仿函数:用法与函数类似
void operator()()
{
cout << "仿函数" << endl;
}
int operator()(int a, int b)
{
return a + b;
}
};
int main()
{
person p;
//输出一段话
p();
//输出两个数的和
p(10, 20);
return 0;
}