目录
-
类和对象的重要知识点
-
运算符重载
-
继承
-
多态
-
模版
一、类和对象的重要知识点
1.1 深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作,拷贝构造
深拷贝:在堆区重新申请空间,进行拷贝操作
1.2 this指针
this指针指向被调用的成员函数所属的对象。
本质是指针常量(即指针的指向是不可以修改的,但指向的内容的值是可以修改的)
用途
-
当形参和成员变量同名时,可用this指针来区分
-
在类的非静态成员函数中返回对象本身,可使用
return * this 返回该对象的引用,达到链式编程
1.3 const修饰成员函数
常函数:
-
成员函数后加const,称这个函数为常函数,本质上是 修饰this指针指向,让指针指向的内容值也不可以修改. eg: 返回值 函数名() const
-
一般常函数内不可以修改成员属性
-
成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
-
声明对象前加const,称该对象为常对象 eg: const Object obj;
-
常对象只能调用常函数。
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
实践
#include <iostream>
using namespace std;
class temp{
public:
temp(){
this->k = 10;
}
// void setValue(int k) const{ //函数后面加上const表示该函数中不可以修改成员变量的值
// this->k = k;
// }
void setValue(int k) const{ //但是变量申明伟mutable 可以改变
this->k = k;
}
int getValue() const{
return this->k;
}
private:
mutable int k;
};
int main(void) {
temp t;
temp *p = &t;
cout<< "t.getValue()="<<t.getValue()<<endl;//“.”是成员运算符,用于调取成员变量和成员函数
cout<< "p->getValue()="<<p->getValue()<<endl;//“->”是地址运算符,用于引用成员变量和成员函数;
return 0;
}
1.4 友元
目的是让一个函数或者类,访问另一个类中私有成员
友元用关键字 friend表示
友元的三种实现
-
全局函数做友元
-
类做友元
-
成员函数做友元
实践
#include <iostream>
using namespace std;
class temp{
friend int main(void) ;//声明该函数为友元函数
public:
temp(){
this->k = 10;
}
int getValue() const{
return this->k;
}
private:
void setValue(int k) {
this->k = k;
}
int k;
};
int main(void) {
temp t;
cout<< "t.getValue()="<<t.getValue()<<endl;
t.setValue(100);// 由于setValue是私有函数,如果正常情况类外部是无法访问的,通过在类中声明该函数为友元函数访问
cout<< "t.getValue()="<<t.getValue()<<endl;
return 0;
}
二、运算符重载
operator@
运算符重载(operator overloading)只是一种“语法上的方便”,它只是另一种函数调用的方式,本质上是函数(成员函数或者全局函数),简化省略operator后就是运算符重载的语法糖效果。
运算符重载有两种方式:
-
成员函数
-
全局函数
运算符重载 也可以发生函数重载
对于内置的数据的类型的运算符无法改变,只可用于用户自定义类型。
不要滥用运算符重载
常用的运算符重载 “+”、“<<”、“++”、“=” “< > != ==”
下面以重载”+”运算符,实现两个对象相加。
#include <iostream>
using namespace std;
class temp{
public:
temp(){
this->k = 10;
this->t = 100;
}
int k;
int t;
};
temp operator+(temp param1,temp param2){
temp a;
a.k = param1.k+param2.k;
a.t = param1.t+param2.t;
return a;
}
int main(void) {
temp t1;
temp t2;
cout<<(t1+t2).k<<endl;
cout<<(t1+t2).t<<endl;
return 0;
}
三、继承
单继承时派生类的定义
语法
class 派生类名:继承方式 基类名
{
成员声明;
}
多继承时派生类的定义
语法
class 派生类名:继承方式1 基类名1,继承方式2 基类名2,...
{
成员声明;
}
有三种继承方式:共有继承;私有继承;保护继承
CPP允许多继承,但是实际开发中不建议使用多继承,因为父类中可能出现重名的属性或方法,需要加作用域区分。
菱形继承 可以采用虚函数虚继承的方式解决,即晚捆绑,这个我们在下一小节多态中再具体看。
通过sizeof查看父类和子类,父类的私有成员在子类中仍然占用存储空间,只不过不可以直接访问。
四、多态
多态性(Polymorphism)提供了接口与具体实现之间额一层隔离,改善了代码的组织结构和可读性,利于前后期的维护及扩展
动态多态满足条件
-
有继承关系
-
子类重写父类的虚函数
动态多态的场景:当父类的指针或者引用指向子类对象时,就发生了动态多态。
地址早绑定,在编译阶段确定了函数地址。
使用visual虚函数关键字告诉编译器,这个函数地址不能提前绑定,需要在运行阶段进行绑定,
加入virtual后编译器会分配一个指针 vfptr:virtual function pointer
vftable:virtual function table 记录虚函数地址。
当子类重写父类的虚函数
子类的虚函数表内部会替换成子类的虚函数地址
函数调用捆绑
把函数体与函数调用相联系称为捆绑(binding)。当捆绑在程序运行之前(有编译器和连接器)完成时,成为早捆绑(early biding)。在运行时才能确定捆绑类型成为晚捆绑(late dbinding)。
对于特定的函数,为了引起晚捆绑,CPP要求在基类中声明这个函数时使用virtual关键字。为了创建一个virtual成员函数,可以简单地在这个函数声明的前面加上关键字virtual,仅仅在声明的使用需要使用关键值virtual,定义时不需要。如果一个函数在基类中被声明为virtual,那么所有的派生类中它都是virtual。
纯虚函数与抽象类
在设计时,比较已扩展性的设计时基类仅仅作为其派生类的一个接口,音视频开发知识点路线图:https://docs.qq.com/doc/DQm1VTHBlQmdmTlN2,即不希望用户创建一个基类对象。要做到这一点,可以在基类中加入至少一个纯虚函数(pure virtual function),使基类变成抽象(abstract)类。纯虚函数使用关键字virtual,并且在函数后面加上 =0. 相当于java的 abstract前缀
纯虚函数的语法
virtual void f() = 0;
-
抽象类无法实例化对象 和java一致。
-
抽象类的子类必须重写父类的纯虚函数 —》和java一致
构造函数不能为虚函数,但析构函数能够且常常必须使虚函数
虚析构和纯虚析构
问题:父类指针在析构时,不会调用子类的析构函数
利用虚析构可以解决这个问题。
纯虚析构 既要声明,也要实现。
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
实践
#include<stdio.h>
#include<iostream>
using namespace std;
class BaseClass
{
public:
BaseClass(){
cout<< " BaseClass construct"<<endl;
}
//添加virtual关键字,告诉编译器,采用晚捆绑的方式,编译后会创建一个vfprt,指向vftable,存放print的地址
virtual void print(){
cout << " BaseClass print"<<endl;
}
~BaseClass(){
cout<< " BaseClass destruct"<<endl;
}
private:
void innerMethod(){
cout<< " BaseClass inner"<<endl;
}
};
class BaseClass2
{
public:
BaseClass2(){
cout<< " BaseClass2 construct"<<endl;
}
virtual void print(){
cout << " BaseClass2 print"<<endl;
}
~BaseClass2(){
cout<< " BaseClass2 destruct"<<endl;
}
private:
void innerMethod(){
cout<< " BaseClass2 inner"<<endl;
}
};
class ChildClass : public BaseClass, private BaseClass2
{
public:
ChildClass(){
cout<< " ChildClass construct"<<endl;
}
void print()
{
BaseClass::print();
// BaseClass::innerMethod(); //不能访问父类的私有函数或变量
BaseClass2::print(); //私有继承,可以访问父类的public函数
cout<< "ChildClass print"<<endl;
}
~ChildClass(){
cout<< " ChildClass destruct"<<endl;
}
};
int main()
{
BaseClass tmp;
ChildClass childClass;
//如果BaseClas中print设置virtual后,编译器分配一个指针vfptr,派生类也会继承该vfptr
cout<<"size of BaseClass"<<sizeof(tmp)<<endl;
cout<<"size of childClass"<<sizeof(childClass)<<endl;
BaseClass *baseclass = &childClass; //创建BaseClass类 指向 childClass
baseclass->print();//如果print不加virtual,此时打印出的只有BaseClass的print
return 0;
}
五、模版
继承和组合提供了重用对象代码的方法,而CPP的模版特征提供了重用源代码的方法,建立通用性的模版,大大提高复用性。
有点像工具类中的通用方法,但是有不仅限于此,通过泛型提供了更好的通用性,另外不仅有函数模版,还有类模版,下面我们一起来学习具体知识点:
特点:
-
模版只是一个框架,不能直接使用
-
模版不是万能的
模版的声明和定义通常放在头文件中,这看似违背了“头文件不要放置分配内存存储空间的任何东西”,但模版的定义比较特殊, 在template<…>之后的代码意味着编译器在当时不分配存储空间,等到被使用时才分配。
5.1 函数模版
使用场景:泛型编程
语法
template<typename T>
函数声明或者定义
template --》声明创建模版
typename -->表示其后面的符号是一种数据类型,也可以用class替代
T --》通用的数据类型,名称可以替换,通常为大写字母
使用函数模版的方式
自动类型推导 —〉必须要推导
显示指定类型
函数注意事项
自动类型推导,必须推导出一致的数据类型T才可以使用。
模版必须要确定T的数据类型,才可以使用
普通函数与函数模版的区别
是否可以发生自动类型转换(隐式类型转换)
普遍函数可以,函数模版自动推导不可以,显示指定可以。
建议使用显示指定类型的方式调用函数模版
普通函数与函数模版的调用规则
-
如果函数模版和普通函数都可以实现,优先调用普通函数
-
可以通过空模版参数列表来强制调用函数模版
-
函数模版也可以发生重载
-
如果函数模版可以产生更好的匹配,优先调用函数模版
即然提供了函数模版,最好就不要提供普通函数,否则容易出现二义性。
实践
#include<stdio.h>
#include<iostream>
#include<string>
using namespace std;
template<typename T>
int add(T a,T b){
cout <<"template T method"<<endl;
return a + b;
}
template<typename T, typename S>
int add(T a,S b){
cout <<"template S T method"<<endl;
return a + b;
}
int add(int a,int b){
cout <<"normal method"<<endl;
return a + b;
}
int main()
{
cout <<"add "<< add(1,1)<< endl; //如果函数模版和普通函数都可以实现,优先调用普通函数
cout <<"add "<< add<>(1,1)<< endl;//可以通过空模版参数,强制的调用模版函数
cout <<"add "<< add(1.0,2.0)<< endl;
cout <<"add "<< add(1.0,3)<< endl; //模版支持重载
cout <<"add "<< add<int ,int>(1.0,3)<< endl;
// cout <<"add "<< add("ab","ca")<< endl; // 错误,模版不是万能的,复合对类型才行
return 0;
}
5.2 类模版
template<class T>
class
template --》声明创建模版
class -->表示其后面的符号是一种数据类型,当然也可以写为typename
T --》通用的数据类型,名称可以替换,通常为大写字母
类模版和函数模版的区别
-
类模版没有自动类型推导,只能使用显示指定类型
-
类模版在模版参数列表中可以有默认参数
类模版中成员函数的创建时机
-
普通类中的成员函数一开始就可以创建
-
类模版中的成员函数在调用时才可以创建
类模版对象做函数参数,三种方式
-
指定传入的类型 — 直接显示对象的数据类型
-
参数模版化 — 将对象中的参数变为模版进行传递
-
整个类模版化 — 将这个对象类型 模版化进行传递
实践
#include <iostream>
using namespace std;
template <class T>
class templateclass {
public:
//构造函数
templateclass(T k = 10)
{
this->k = k;
cout<< "templateclass construt k = "<<this->k <<endl;
}
void setValue(T k){
this->k = k;
}
T getVaule() const { return k; }
private:
T k;
};
// 使用类模板,函数参数必须显示指定类型
void print(templateclass<int>& params) {
cout << params.getVaule() << endl;
}
int main(void) {
// templateclass<> p(100);//错误,类模版没有自动类型推导,只能使用显示指定类型
templateclass<int> k(100);
cout << k.getVaule() << endl;
k.setValue(1000.5);//会被强制转为模版类声明的类型
// k.setValue(“a”);//错误
print(k);
return 0;
}
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓