C++ 语法基础

C++ 基础
  • https://cloud.tencent.com/developer/article/1509367

  • 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++代码

    1. 非类成员函数,在C++ 中 extern该函数,在C中 申明该方法,然后直接调用
    2. 类成员函数,在1的基础上 在C++中要extern的一个包装函数,且参数列表为struct C* + 类方法需要参数列表
      C与C++代码如何互相调用
  • new/delete与malloc/free区别
    new/delete与malloc/free的区别与联系详解

    1. malloc/free 是C的标准库函数,new/delete 是C++的运算符,支持重载
    2. malloc开辟空间类型大小需手动计算,new是由编译器自己计算;
    3. malloc返回类型为void*,必须强制类型转换对应类型指针,new则直接返回对应类型指针;
    4. malloc开辟内存时返回内存地址要检查判空,因为若它可能开辟失败会返回NULL;new则不用判断,因为内存分配失败时,它会抛出异常bac_alloc,可以使用异常机制;
    5. 无论释放几个空间大小,free只传递指针,多个对象时delete需加[];
    6. malloc/free为函数只是开辟空间并释放,new/delete则不仅会开辟空间,并调用构造函数和析构函数进行初始化和清理
    7. 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++类中数据成员初始化顺序?

    1. 成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关。
    2. 如果不使用初始化列表初始化,在构造函数内初始化时,此时与成员变量在构造函数中的位置有关。
    3. 类中const成员常量必须在构造函数初始化列表中初始化。
    4. 类中static成员变量,只能在类内外初始化(同一类的所有实例共享静态成员变量)。
C++ 11相关
  • https://www.jianshu.com/p/ba965aedeca0

  • https://www.jianshu.com/p/d19fc8447eaa

  • 智能指针

    • 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
      void f( T&& param); //这里T的类型需要推导,所以&&是一个 universal references
      
      template
      class Test {
        Test(Test&& rhs); //Test是一个特定的类型,不需要类型推导,所以&&表示右值引用  
      };
      
      void f(Test&& param); //右值引用
      
      //复杂一点
      template
      void f(std::vector&& param); //在调用这个函数之前,这个vector中的推断类型
      //已经确定了,所以调用f函数的时候没有类型推断了,所以是 右值引用
      
      template
      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
  • Implement

    • player
      • state machine
    • editor
    • .........
  • Platform

    • mac
      • qt
    • window
    • android
    • ios
播放器流程
  1. 打开文件地址,创建AVFormatContext
  2. 获得关注的AVStream,创建codec_context
  3. 从AVFormatContext 读取AVPacket
  4. 用对应的codec_context(audio\video)解码对应AVPacket,获得AVFrame
  5. 视频帧渲染,音频帧渲染
声画同步
  • 视频时钟作为基准时钟
  • 音频时钟作为基准时钟
    • 音频根据采样率\采样格式\声道和给音频驱动的数据来控制音频播放的时钟
    • 视频根据当前音频frame的pts,大于阈值,则重复显示当前帧,否则则加快展示下一帧
    • 因为音频在给音频数据回调的时候有个buffer,在算音视频的时钟差时,要把这个算上
  • 外部时钟作为基准时钟
seek 流程
  1. 切换成seek 状态
  2. 输入流读取的位置seek,serial = pack+1,废弃小于pack
  3. 时钟seek,记录seek Position,用于精准seek

可动态扩容的frame buffer,packet buffer

播放器开发中的坑
  • 线程死锁

    • packet buffer frame buffer 有数量上线,没有消费,生产端死锁,没有生产,消费端死锁
  • 颜色取值问题

    • libyuv::I420ToABGR 有大小端问题,拿到的数据是 RGBA
  • sound-touch

    • 包问题
设计模式
  • 单例模式(多线程)

音视频基础

H264 多模式运动估计、帧内预测、多帧预测、基于内容的变长编码、4x4二维整数变换等新的编码方式

播放器框架

  1. 传递播放数据给ijk,(director_service, quality_service)
  2. 播放器层UI的管理 FunctionWidgetService
  3. 播放控制 SeekService,,playcore_service

refrence

C++面试常见题

你可能感兴趣的:(C++ 语法基础)