C++ 基础
-
const的用法
作用于自定义对象
-
作用于类成员函数
- const 修饰类成员函数,其目的是防止成员函数修改被调用对象的值,如果我们不想修改一个调用对象的值,所有的成员函数都应当声明为 const 成员函数。
- 如果想在const成员函数中修改值,那么在定义该值时用 mutable修饰
-
作用于返回值
- const 修饰内置类型的返回值,修饰与不修饰返回值作用一样。
- const 修饰自定义类型的作为返回值,此时返回的值不能作为左值使用,既不能被赋值,也不能被修改。
- const 修饰返回的指针或者引用,是否返回一个指向 const 的指针,取决于我们想让用户干什么
-
作用于函数参数
- 值传递的 const 修饰传递,一般这种情况不需要 const 修饰,因为函数会自动产生临时变量复制实参值。
- 当 const 参数为指针时,可以防止指针被意外篡改。
-
const作用于指针
const 保护的是它右侧的数据- const 修饰指针指向的内容,则内容为不可变量, const 保护*p。
const int *p = 8;
- const 修饰指针,则指针为不可变量, const 保护p。
int a = 8; int * const p = &a;
作用于引用
引用的这个值不能改变
-
虚函数表
- 同一个类的不同实例共用同一份虚函数表
- 对象拥有一个虚表指针
- 派生类有重写方法,则会更新虚函数表
- 派生类自己有虚函数,会加载基类虚表的后面
- 多继承会有多个虚表
- 多继承情况下派生类自己的虚函数会加在第一个虚表的后面;(基类都有虚函数的情况)
- 多继承下,有虚标的基类虚表会放在前面,没有的放后面
-
Volatile 关键字作用
- 在编译成汇编时,每次都从地址中去取值,而非因为该值在两次获取时都没有被修改,而沿用上一次读取出来的值
- 修饰指针时 和const 类似,修饰右侧的数据
-
volatile char * vpch
修饰值 -
char* volatile pchv
修饰指针
-
- 可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象。
- 不会被编译程序优化
- 汇编指令吧保证顺序性
- 只能保障当前线程时顺序的,但无法保障多线程间,是顺序的,既该关键词不能保证happens-before,要保障则要用锁
-
四种cast转换
- const_cast:去掉变量const属性或者volatile属性的转换符
- static_cast:多用于非多态类型的转换,比如说将int转化为double。但是不可以将两个无关的类型互相转化。(在编译时期进行转换)
- dynamic_cast:可以安全的将父类转化为子类,子类转化为父类都是安全的
- reinterpret_cast:重新解释(无理)转换。即要求编译器将两种无关联的类型作转换
-
static
- 不管全局静态变量还是局部静态变量都是在全局数据区开辟空间
- 局部静态变量作用域为当前作用域
- 类的静态成员函数中不能引用非静态成员
- 类的非静态成员函数可以调用用静态成员函数
- 类的静态成员变量必须先初始化再使用
- static修饰全局变量或函数时,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是extern外部声明也不可以。这个函数也只能在本文件中调用,不能被其他文件调用
-
请你来说一下fork函数:
- 创建一个和当前进程映像一样的进程可以通过fork
-
静态函数和虚函数的区别
- 静态函数在编译的时候就已经确定运行时机,虚函数在运行的时候动态绑定。虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销
strlen与strcpy区别 :strlen返回一个C风格字符串的长度,不包括\0,strcpy拷贝时包括\0
++i和i++的实现: i++ 会产生一个临时对象,++i则不会,运算符重载时 ++i 为 operator++(int); 而i++ 为 operator++()
-
main函数前执行的有哪些:
- 全局对象,
- 全局静态变量,
- attribute关键字指定函数(https://www.cnblogs.com/zpcoding/p/10805639.html)
-
常量
- #define
- const
-
C语言程序的内存一般分为(https://www.cnblogs.com/418ks/p/10802184.html)
- 内核区
- 栈区:由编译器自动分配释放 ,存放函数的参数值,局部变量的值等
- 缓冲区
- 堆区:一般由程序员分配释放, 若程序员不释放,在程序结束时,操作系统回收。
- BSS段:.bss段被用来存放那些没有初始化或者初始化为0的全局变量。bss段只占运行时的内存空间而不占文件空间。在程序运行的整个周期内,.bss段的数据一直存在
- 全局区:全局变量和静态变量的存储是放在一块的, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 在程序序结束后由系统释放
- 只读常量区:常量字符串就是放在这里的。 程序结束后由系统释放
- 代码段
-
隐式类型转换
- 算术转换:在混合类型的算术表达式中, 最宽的数据类型成为目标转换类型
- 一种类型表达式赋值给另一种类型的对象:目标类型是被赋值对象的类型(除void外,指针赋值给其他对象,则编译错误)
- 将一个表达式作为实参传递给函数调用,此时形参和实参类型不一致:目标转换类型为形参的类型
-
显式类型转换
- C 风格: (type-id)
- C++风格: static_cast、dynamic_cast、reinterpret_cast、和const_cast
-
extern "C" C++ 引用 C代码
- C++和C的函数签名不一样,所以在C++中用到C的代码 C的头文件要通过该关键字包裹,不然链接会报错
-
C 引用 C++代码
- 非类成员函数,在C++ 中 extern该函数,在C中 申明该方法,然后直接调用
- 类成员函数,在1的基础上 在C++中要extern的一个包装函数,且参数列表为struct C* + 类方法需要参数列表
C与C++代码如何互相调用
-
new/delete与malloc/free区别
new/delete与malloc/free的区别与联系详解- malloc/free 是C的标准库函数,new/delete 是C++的运算符,支持重载
- malloc开辟空间类型大小需手动计算,new是由编译器自己计算;
- malloc返回类型为void*,必须强制类型转换对应类型指针,new则直接返回对应类型指针;
- malloc开辟内存时返回内存地址要检查判空,因为若它可能开辟失败会返回NULL;new则不用判断,因为内存分配失败时,它会抛出异常bac_alloc,可以使用异常机制;
- 无论释放几个空间大小,free只传递指针,多个对象时delete需加[];
- malloc/free为函数只是开辟空间并释放,new/delete则不仅会开辟空间,并调用构造函数和析构函数进行初始化和清理
- malloc我们知道它是在堆上分配内存的,但new其实不能说是在堆上,C++中,对new申请内存位置有一个抽象概念,它为自由存储区,它可以在堆上,也可以在静态存储区上分配,这主要取决于operator new实现细节,取决与它在哪里为对象分配空间。
-
基类为什么需要虚析构函数
- 因为子类的析构函数需要被调用,构造时肯定用的是子类的构造函数,但析构的时候,不一定用的是子类的对象指针
C语言函数参数入栈顺序:从右往左
-
C++中拷贝赋值函数的形参能否进行值传递?
- 不能,会循环调用拷贝赋值函数
-
C++三种继承:
- public继承:不改变基类的访问权限
- protected继承:除了基类的private成员不变,其他都变成protected
- private继承:基类的所有成员都变成private
-
C++中的8个默认函数:
- 构造函数
- 析构函数
- 拷贝构造
- 赋值运算符
- 取值运算符
- 取值运算符const
- 移动构造函数
- 移动赋值函数
// 这两个类的效果相同
class Person
{}
class Person
{
public:
Person() {...} // deafault构造函数;
Person(const Person&) {...} // 默认拷贝构造函数
~Person() {...} // 析构函数
Person& operator = (const Person &) {...} // 赋值运算符
Person *operator &() {...} // 取值运算符
const Person *operator &() const {...} // 取值运算符const
Person(Person &&); // 移动构造函数
Person & operator =(Person &&); // 重载移动赋值操作符函数
}
-
继承关系中,构造和析构的顺序
- 构造:先基类再子类
- 析构: 先子类再基类
多态,虚函数,虚表
-
class与struct区别:
- 本质没有太大区别,只是默认访问控制权限不一样
-
C++类中数据成员初始化顺序?
- 成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关。
- 如果不使用初始化列表初始化,在构造函数内初始化时,此时与成员变量在构造函数中的位置有关。
- 类中const成员常量必须在构造函数初始化列表中初始化。
- 类中static成员变量,只能在类内外初始化(同一类的所有实例共享静态成员变量)。
C++ 11相关
-
智能指针
- auto_ptr:采用所有权模式
- unique_ptr:
- “唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。
- shared_ptr:
- 多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。
- 最大的陷阱是循环引用,循环,循环引用会导致堆内存无法正确释放,导致内存泄漏。
- weak_ptr :
- 和shared_ptr 配合使用,不会导致count增加,用于解决循环引用问题
- 内存泄漏问题
-
auto关键字:
- 自动类型推断发生在编译期
- 变量必须在定义时初始化,这类似于const关键字
- auto并不是一个真正的类型。 auto仅仅是一个占位符,它并不是一个真正的类型,不能使用一些以类型为操作数的操作符,如sizeof或者typeid
-
左值右值的区别
- 左值能取地址,右值不能取地址
-
std::move
- 将左值变成右值
- 就是一个类型转换 static_cast
-
std::forward
- 左值变为左值
- 右值变为右值
-
universal references(通用引用)
-
只有当发生自动类型推断时(如函数模板的类型自动推导,或auto关键字),
&&
才是一个universal references
template<typename T> void f( T&& param); //这里T的类型需要推导,所以&&是一个 universal references template<typename T> class Test { Test(Test&& rhs); //Test是一个特定的类型,不需要类型推导,所以&&表示右值引用 }; void f(Test&& param); //右值引用 //复杂一点 template<typename T> void f(std::vector<T>&& param); //在调用这个函数之前,这个vector<T>中的推断类型 //已经确定了,所以调用f函数的时候没有类型推断了,所以是 右值引用 template<typename T> void f(const T&& param); //右值引用 // universal references仅仅发生在 T&& 下面,任何一点附加条件都会使之失效
-
-
完美转发
- 通过一个函数将参数继续转交给另一个函数进行处理,原参数可能是右值,可能是左值,如果还能继续保持参数的原有特征,那么它就是完美的。
C++ 多线程,并发
- std::atomic
自旋锁的机制- 适用于被持有时间短的情况
- compare_exchange_strong 与 compare_exchange_weak
- 读写的内存值V, 期望值E,写入的值B, 当前仅当V值为E的时候才将B写入,如果不相等,则重新读取E值
- 线程不挂起
- 信号量
- C++中没有信号量
- condition_variable + mutex 实现
- 锁
- 互斥锁:mutex
- 条件变量:condition_variable
- wait:线程挂起,
- notify:通知wait
- 自旋锁:atomic
- 读写锁
- std::lock _ guard 与 std::unique_lock 区别
- lock _ guard 纯粹一个区域锁
- unique_lock 在lock _ guard 基础上,有一些额外的功能方法,因为维护了mutex的状态,则复杂度上有开销
工程
- 如何查找内存泄漏:
- windows:_CrtDumpMemoryLeaks
- mac/linux:valgrind
算法
播放器架构
-
base
- stream
- local-stream
- remote-stream
- clock
- codec
- encode
- decode
- render
- video
- audio
- common
- stream
-
Implement
- player
- state machine
- editor
- .........
- player
-
Platform
- mac
- qt
- window
- android
- ios
- mac
播放器流程
- 打开文件地址,创建AVFormatContext
- 获得关注的AVStream,创建codec_context
- 从AVFormatContext 读取AVPacket
- 用对应的codec_context(audio\video)解码对应AVPacket,获得AVFrame
- 视频帧渲染,音频帧渲染
声画同步
- 视频时钟作为基准时钟
- 音频时钟作为基准时钟
- 音频根据采样率\采样格式\声道和给音频驱动的数据来控制音频播放的时钟
- 视频根据当前音频frame的pts,大于阈值,则重复显示当前帧,否则则加快展示下一帧
- 因为音频在给音频数据回调的时候有个buffer,在算音视频的时钟差时,要把这个算上
- 外部时钟作为基准时钟
seek 流程
- 切换成seek 状态
- 输入流读取的位置seek,serial = pack+1,废弃小于pack
- 时钟seek,记录seek Position,用于精准seek
可动态扩容的frame buffer,packet buffer
播放器开发中的坑
-
线程死锁
- packet buffer frame buffer 有数量上线,没有消费,生产端死锁,没有生产,消费端死锁
-
颜色取值问题
- libyuv::I420ToABGR 有大小端问题,拿到的数据是 RGBA
-
sound-touch
- 包问题
设计模式
- 单例模式(多线程)
音视频基础
H264 多模式运动估计、帧内预测、多帧预测、基于内容的变长编码、4x4二维整数变换等新的编码方式
播放器框架
- 传递播放数据给ijk,(director_service, quality_service)
- 播放器层UI的管理 FunctionWidgetService
- 播放控制 SeekService,,playcore_service