阅读 73

音视频学习之路--C++

前言

C和C++作为学习音视频技术首要具备的语言基础,所以十分必要学习和复习一下之前学习C++语言基础。

这里IDE和环境配置在前面C语言复习的文章里已经说过了,还是使用CLion这个软件,话不多说,直接开始学习。

正文

C++作为一门用途更广、功能更齐全的语言,其知识深度很深,所以这里也就复习、学习一些基本知识点,等后续在实际项目中有遇到难点再进行补充。

hello world

创建完一个C++项目,还是打印hello world,代码如下:

#include <iostream>
//命名空间,告诉编译器使用std命名空间
using namespace std;

int main() {
    printf("Hello, World! \n");
    //这里的cout就是std里面定义的函数
    //<<是操作符重载,后面细说
    cout << "Hello, World!" << endl;
    return 0;
}复制代码

打印是:

image.png

从这个简单的程序我们可以看出入口函数和返回值和C语言是一样的,但是这里有个命名空间的概念,啥是命名空间呢:

namespace.png

说白了就是C++不像Java那样有包限制,所以对于同名的需要进行区分,这里就是使用命名空间。

还有就是这里的cout函数,cout函数其实就是std::cout的简写了,在std这个命名空间下的函数,用来标准化输出到屏幕,和printf一样,这里的 << 叫做流插入运算符,其中endl是换行,要是使用 \n 也可以,这里就没啥说的了,相当于简化了printf。

关键字

C++关键字有很多,我们不能和学习C语言一样,全都罗列一遍给看一下,这里就看一些常用的,后面有用到其他的再进行补充。

C++关键字.png

不得不说C++是真的复杂,这仅仅是一部分关键字,就让人看的头晕,不过不怕,先慢慢来,后面有补充再补上。

变量作用域

关于基本数据类型这些知识点在之前C中都介绍过,有点类似,就不说了,这里说一下变量作用域。

  • 在函数或者一个代码块内部声明的变量,叫做局部变量。

  • 在函数参数的定义中声明的变量,叫做形式参数。

  • 在所有函数外部声明的变量,叫做全局变量。

定义常量

关于啥是常量这类的概念就先不说了,这里说一下定义常量的2种常见方式:

  • 使用#define预处理器。

  • 使用const关键字。

直接看下面代码:

#include <iostream>
//这个必须得有
#include <string>

using namespace std;

#define NAME "zyh"

const int AGE = 20;

const string COMPANY = "WY";

int main() {
    cout << NAME << endl;
    cout << "age : " << AGE << endl;
    cout << "company : " << COMPANY << endl;
    return 0;
}复制代码

打印结果:

image.png

注意这里cout输出时,如果输出的string类型,则必须要include 才可以正常输出,否则只能使用char数组来表示字符串输出。

字符串

在前面学习C时,我们使用字符串都是使用字符数组来完成,和Java中的String类简直不能比,太不方便了,所以在C++中引入了string类,但是依旧可以适应C风格的字符串,直接看一下代码:

using namespace std;

int main() {
    //C风d格定义字符串
    char greet[6] = "Hello";
    char *greet1 = "Hello";
    char greet2[] = "Hello";
    //C++使用string类型
    string greet3 = "Hello";

    cout << greet << endl;
    cout << greet1 << endl;
    cout << greet2 << endl;
    cout << greet3 << endl;

    return 0;
}复制代码

这里不论是C风格还是C++的string类型都可以正常使用字符串,当然在C中的那些字符串操作函数比如strcpy、strcat等等都可以正常使用,除此之外,还还有一些string类的函数,这就很像Java的一些方法了,比如append、length方法等。

指针

关于指针我们在前面文章学习C中已经了解过了,C++的指针差不多,指针的作用主要就俩点:

  • 简化一些任务,使用指针

  • 动态内存分配,当动态内存分配时,必须要使用指针

指针的定义和使用和C语言的一模一样,包括定义和取地址符号 & 以及获取指针指向的值 * 这里就不再说了,不清楚可以回顾一下前面的C语言中的指针。

引用

关于引用这个概念比较特殊,它类似于指针,但是又不是指针,相当于变量名的别名,我们来看一下:

C++引用概念.png

这里这个概念比较特殊,引用不能为空,必须初始化赋值,这个概念感觉有点多余,毕竟C++中有指针的概念,引用在Java中倒是基本概念,以为有引用类型。所以这里还是要区分一下,尤其是使用 & 来定义,下面代码简单看一下:

using namespace std;

int main() {
    //声明变量
    int a = 10,b = 20;
    //声明引用变量
    int &i = a;
    int &j = b;

    cout << "a ==" << a << "\t &i ==" << i <<endl;
    cout << "b ==" << b << "\t &j ==" << j <<endl;
    //改变变量的值,引用也会变化
    a = 5,b = 6;

    cout << "a ==" << a << "\t &i ==" << i <<endl;
    cout << "b ==" << b << "\t &j ==" << j <<endl;

    return 0;
}复制代码

这里声明了引用,也就相当于a和i指向同一块内存区域,但是它可以和a一样使用,也就相当于变量,其地址是一样的,打印如下:

image.png

函数参数为引用类型

在前面学习C语言时,我们说道C的函数参数可以是值传递也可以是指针传递,当使用值传递时会复制值,函数执行不影响传递进来的值,指针由于是地址,函数执行肯定会改变,那这个引用类型呢 又是如何。

所以C++这个引用类型设计的感觉有问题,它和指针是一样的,前面也说了,引用只是变量的一个别名,所以参数类型是引用,它的效果和是指针是一样的,看下面代码:

using namespace std;

void swap(int& x,int& y);

void swap1(int x,int y);

int main() {
    int a = 100,b = 200;
    cout << "交换前 a = " << a << "  b = " << b << endl;
    swap(a,b);
    //发生了变化,函数执行影响了传递的参数值
    cout << "第一次交换后 a = " << a << " b = " << b << endl;
    swap1(a,b);
    //没有发生变化,值传递会复制参数,不会影响
    cout << "第二次交换后 a = " << a << " b = " << b << endl;

    return 0;
}
//参数是引用类型
void swap(int& x,int& y){
    int temp;
    temp = x;
    x = y;
    y = temp;
}
//参数是值
void swap1(int x,int y){
    int temp;
    temp = x;
    x = y;
    y = temp;
}复制代码

这里说了2种交换,一种是传值一种是引用,打印如下:

image.png

会发现使用引用也会导致原来值发生变化。

类和对象

终于到了C++的重头戏了,也就是面向对象,熟悉Java语言对于面向对象肯定是手到擒来,这里还是来看看C++是如何面向对象的。

还是直接看代码:

#ifndef CPLUSTEST_PERSON_H
#define CPLUSTEST_PERSON_H

class Person{
    //公共属性
public:
    Person();    //空参数构造函数
    ~Person();      //析构函数
    Person(char *name,int age); //有参构造函数

    //成员变量
    char *name;
    int age;

    //函数
    void setName(char *name);
    char *getName();

    void setAge(int age);
    int getAge();
};

#endif //CPLUSTEST_PERSON_H复制代码

这是person.h文件,前面在学习C语言种说过,头文件一般是定义函数、类啥的,这里也一样,不过这里仅仅只是定义,和Java还是有着非常大的区别,真正实现的地方在下面:

#include <iostream>
#include "person.h"

using namespace std;

Person::Person(char *name, int age) {
    this -> name = name;
    this -> age = age;
}

Person::~Person() {
    cout << "Person销毁" << endl;
}

Person::Person() {
    cout << "执行 Person 空构造函数" << endl;
}

void Person::setAge(int age) {
     this -> age = age;
}

void Person::setName(char *name) {
    this -> name = name;
}

int Person::getAge() {
    return this -> age;
}

char *Person::getName() {
    return this -> name;
}复制代码

这个是person.cpp文件,导入前面头文件,这里是进行实现,这里和Java的类定义区别很大,在Java或者kotlin中一般定义后就直接都实现了,不会单独搞2个地方,然后就是调用的地方:

int main() {

    //栈里面定义的 在该方法执行完就会回收掉Person对象
    Person personTemp;
    personTemp.setName("张三");
    personTemp.setAge(10);
    cout << personTemp.getName() << "\t" << personTemp.getAge() << endl;

    //如果使用new初始化构造函数,
    Person *person = new Person("zyh",18);
    cout << person -> name << "\t" << person->getAge() << endl;
    //释放person内存
    delete person;

    return 0;
}复制代码

同样这里也有着很大的区别,首先是对象声明,在Java中一个对象如果没有调用构造函数它就是null,无法调用其方法,但是在C++中确不一样,同时使用new操作符新建的对象,需要手动delete释放内存,Java是有GC自动回收,也比较麻烦。

这里先简单总结一下,下面再细说:

C++类简单总结.png

C++类成员函数

啥是类成员函数就不用说了,学过面向对象语言的都知道,这里主要说一下类成员函数有2种定义的方式。

一种是直接在类中定义,这也是Java语言的方式,还有一种就是在类中声明,定义在别的地方,这是C++推荐的方式,比如下面类Box在头文件中声明:

#ifndef CPLUSTEST_BOX_H
#define CPLUSTEST_BOX_H

//第一种是类成员函数直接在类中就定义

class Box{
public:
    double length;  //长度
    double width;   //宽度
    double height;  //高度

    double getVolume(){
        return length * width * height;
    }

    double getVolume2();    //体积

};

#endif //CPLUSTEST_BOX_H复制代码

其中有2个成员函数,getVolume2我们放在.cpp文件中进行定义:

#include "box.h"

double Box::getVolume2() {
    return length * width * height * 2;
}复制代码

上面2种方式都可以。其中重点说明一下在类中定义的成员函数把函数声明为内联的,啥是内联,后面再说;其中 :: 叫做范围解析运算符。

C++的构造和析构函数

关于构造函数我们自然都很熟悉了,这里只说一点就是使用初始化列表来初始化字段的情况,其实就是名字意思,使用初始化列表来初始化字段。

这个我们知道在C++中构造函数的参数是参数,和字段没关系,那这时如何在构造函数中初始化字段呢,如果是kotlin的数据类是自动帮我做的,如果参数有val/var修饰符,其他类也就在init代码块中进行赋值,C++有个不一样的写法,就是下面这样:

#ifndef CPLUSTEST_BOX_H
#define CPLUSTEST_BOX_H

//第一种是类成员函数直接在类中就定义

class Box{
public:
    double length;  //长度
    double width;   //宽度
    double height;  //高度

    double getVolume(){
        return length * width * height;
    }

    double getVolume2();    //体积
    //自定义的构造函数
    Box(double len);

};

#endif //CPLUSTEST_BOX_H复制代码

这里的Box定义加了一个有参构造函数,然后在定义的地方:

Box::Box(double len) : length(len) {
    std::cout << "构造函数输入len" << endl;
}复制代码

就是这个在函数后直接 : 对字段进行赋值,这种写法还是蛮新奇的。

还有就是析构函数,和无参构造函数一样,不写的话编译器会自己加上。那析构函数是啥呢,就是和默认构造函数一样只不过前面多个~,可以使用析构函数跳出程序、关闭资源等。

还是前面的Person类,里面有定义析构函数,然后我们分别使用2个对象来看一下打印:

int main() {

    //栈里面定义的 在该方法执行完就会回收掉Person对象
    Person personTemp;
    personTemp.setName("张三");
    personTemp.setAge(10);
    cout << personTemp.getName() << "\t" << personTemp.getAge() << endl;

    //如果使用new初始化构造函数,
    Person *person = new Person("zyh",18);
    cout << person -> name << "\t" << person->getAge() << endl;
    //释放person内存
    delete person;

    return 0;
}复制代码

打印如下:

image.png

会发现这里不论是自动回收掉的对象还是手动delete的对象都会在对象释放时调用析构函数。当然使用new关键字创造的对象,在不调用delete时是不会释放的。

C++拷贝构造函数

什么是拷贝构造函数呢,这个其实非常简单,就是使用一个类之前创建的对象来创建新的对象,比如我有Box A,现在想要一个Box B,让B和A的内容一样,这时就要考虑了,如果是Java代码的话B、A2个引用指向同一对象,这不太符合要求,所以会调用拷贝函数,肯定会创建出一个新对象。

在C++中,直接就有了拷贝构造函数这个概念,让复制更方便。但是和Java的复制一样,Java复制需要考虑浅拷贝和深拷贝的问题,在C++中如果类的成员是指针变量,仔细想一下,直接把A的指针复制到B的指针变量中,这样A、B2个对象中该变量都是一个地址,则会相互影响,肯定不行。

所以即使默认会有拷贝构造函数,当类成员变量是指针的时候,也要进行重写拷贝构造函数。

#ifndef CPLUSTEST_LINE_H
#define CPLUSTEST_LINE_H

class Line{
public:
    int getLength();
    Line(int len);
    //拷贝构造函数,必须要定义
    Line(const Line &obj);
    ~Line();

private:
    //这个是指针变量
    int *ptr;
};

#endif //CPLUSTEST_LINE_H复制代码

比如上面代码中的成员变量有指针变量时,

#include "line.h"
#include <iostream>
using namespace std;

Line::Line(int len) {
    cout << "调用构造函数" << endl;
    //由于传递进来的是个int值,所以要先给指针分配内存
    ptr = new int ;
    //指针指向的内存值是len
    *ptr = len;
}

Line::Line(const Line &obj) {
    cout << "调用拷贝构造函数" << endl;
    //这里拷贝的时候就需要深拷贝了,新的对象的指针要重新分配内存
    ptr = new int ;
    //指针指向的值进行赋值
    *ptr = *obj.ptr;
}

Line::~Line() {
    cout << "释放内存" << endl;
    delete prt;
}复制代码

从上面代码我可以看出一个问题,就是C++的浅拷贝和深拷贝问题,和Java一样,如果是这种指针变量,需要重新分配内存。

上面例子说明了当对象需要另外一个对象进行初始化时会调用拷贝构造函数。

C++友元函数

这个概念还真没有在Java中存在过,不过也非常容易理解其含义,友元友元就是好朋友friend的意思,哈哈,也就是友元函数是声明在类中,但定义在类外,并且可以通过这个友元函数访问类的私有和保护成员。

这里也就相当于给一个类开了一个后门一样,这个友元函数不是类的成员函数,但是可以通过它访问类的私有变量。

还是直接看个代码例子:

#ifndef CPLUSTEST_BOX_H
#define CPLUSTEST_BOX_H

class Box{
    double width;   //默认是私有变量
public:
    friend void printWidth(Box box);        //声明为友元函数
    void setWidth(double width);

};

#endif //CPLUSTEST_BOX_H复制代码

这里声明了一个友元函数,用来获取私有变量的,

#include <iostream>
#include "box.h"

using namespace std;

void Box::setWidth(double width) {
    this->width = width;
}

void printWidth(Box box){
    cout << "width = " << box.width << endl;
}复制代码

友元函数因为不是Box中的,所以不能使用Box::来调用,完成定义后,便可以使用了:

using namespace std;

int main() {

    Box *box = new Box();
    box->setWidth(10);
    printWidth(*box);
    delete box;
}复制代码

感觉有点神奇,这个width属性没有任何get方法对外,居然可以通过友元访问到,不得不说C++的设计很到位。

C++内联函数

之前看内联函数的概念第一次是在kotlin中,由于在Java中没有这个概念,现在再来看C++,说明kotlin也是不断借鉴其他语言的优势来完善自己。

内联内联就是其字面意思,如果一个函数定义为内联时,在编译时,编译器会把该函数的代码副本放置到每个调用该函数的地方,其实就是为了效率。

对于一般函数都是在运行时才被替代加入栈中,但是对于内联函数在编译时便进行复制,这也就是用空间代价换时间的一种方式,所以内联函数一般不超过10行。

C++继承

面向对象来说,继承这个就不用考虑了,熟悉Java的都很了解,这里就不说了。

区别就是C++可以多继承,就是可以继承多个父类,这个在Java中只允许继承一个父类,实现多个接口。

动态内存

其实这个也非常简单,和Java的内存分配类似,在C++中函数执行也是出入栈,所以函数中定义变量将占用栈内存,在函数执行完会释放。

当使用new关键字可以动态分配内存,这个内存是在堆中的,使用完需要使用delete来进行释放。

这里的new对象就没啥说的了,其中对于指针变量,可以new内置类型的指针,这个在前面说拷贝函数时已经说过如何使用了。

命名空间

这个其实没啥说的,就是把一堆变量和函数给划分到一块,这一块给命个名子即可。

总结

其实C++部分就是在C上面加了面向对象的概念,面向对于熟悉Java的理解起来也非常容易,所以本篇文章就简单复习一下,等后面实际问题时再进行补充。


作者:元浩875
链接:https://juejin.cn/post/7021434763244208165


文章分类
后端
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐