音視頻學(xué)習(xí)之路--C++

前言

C和C++作為學(xué)習(xí)音視頻技術(shù)首要具備的語言基礎(chǔ),所以十分必要學(xué)習(xí)和復(fù)習(xí)一下之前學(xué)習(xí)C++語言基礎(chǔ)。

這里IDE和環(huán)境配置在前面C語言復(fù)習(xí)的文章里已經(jīng)說過了,還是使用CLion這個軟件,話不多說,直接開始學(xué)習(xí)。

正文

C++作為一門用途更廣、功能更齊全的語言,其知識深度很深,所以這里也就復(fù)習(xí)、學(xué)習(xí)一些基本知識點(diǎn),等后續(xù)在實(shí)際項(xiàng)目中有遇到難點(diǎn)再進(jìn)行補(bǔ)充。

hello world

創(chuàng)建完一個C++項(xiàng)目,還是打印hello world,代碼如下:

#include <iostream>
//命名空間,告訴編譯器使用std命名空間
using namespace std;

int main() {
    printf("Hello, World! \n");
    //這里的cout就是std里面定義的函數(shù)
    //<<是操作符重載,后面細(xì)說
    cout << "Hello, World!" << endl;
    return 0;
}

打印是:

從這個簡單的程序我們可以看出入口函數(shù)和返回值和C語言是一樣的,但是這里有個命名空間的概念,啥是命名空間呢:

說白了就是C++不像Java那樣有包限制,所以對于同名的需要進(jìn)行區(qū)分,這里就是使用命名空間。

還有就是這里的cout函數(shù),cout函數(shù)其實(shí)就是std::cout的簡寫了,在std這個命名空間下的函數(shù),用來標(biāo)準(zhǔn)化輸出到屏幕,和printf一樣,這里的 << 叫做流插入運(yùn)算符,其中endl是換行,要是使用 \n 也可以,這里就沒啥說的了,相當(dāng)于簡化了printf。

關(guān)鍵字

C++關(guān)鍵字有很多,我們不能和學(xué)習(xí)C語言一樣,全都羅列一遍給看一下,這里就看一些常用的,后面有用到其他的再進(jìn)行補(bǔ)充。

不得不說C++是真的復(fù)雜,這僅僅是一部分關(guān)鍵字,就讓人看的頭暈,不過不怕,先慢慢來,后面有補(bǔ)充再補(bǔ)上。

變量作用域

關(guān)于基本數(shù)據(jù)類型這些知識點(diǎn)在之前C中都介紹過,有點(diǎn)類似,就不說了,這里說一下變量作用域。

  • 在函數(shù)或者一個代碼塊內(nèi)部聲明的變量,叫做局部變量。

  • 在函數(shù)參數(shù)的定義中聲明的變量,叫做形式參數(shù)。

  • 在所有函數(shù)外部聲明的變量,叫做全局變量。

定義常量

關(guān)于啥是常量這類的概念就先不說了,這里說一下定義常量的2種常見方式:

  • 使用#define預(yù)處理器。

  • 使用const關(guān)鍵字。

直接看下面代碼:

#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;
}

打印結(jié)果:

注意這里cout輸出時,如果輸出的string類型,則必須要include 才可以正常輸出,否則只能使用char數(shù)組來表示字符串輸出。

字符串

在前面學(xué)習(xí)C時,我們使用字符串都是使用字符數(shù)組來完成,和Java中的String類簡直不能比,太不方便了,所以在C++中引入了string類,但是依舊可以適應(yīng)C風(fēng)格的字符串,直接看一下代碼:

using namespace std;

int main() {
    //C風(fēng)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風(fēng)格還是C++的string類型都可以正常使用字符串,當(dāng)然在C中的那些字符串操作函數(shù)比如strcpy、strcat等等都可以正常使用,除此之外,還還有一些string類的函數(shù),這就很像Java的一些方法了,比如append、length方法等。

指針

關(guān)于指針我們在前面文章學(xué)習(xí)C中已經(jīng)了解過了,C++的指針差不多,指針的作用主要就倆點(diǎn):

  • 簡化一些任務(wù),使用指針
  • 動態(tài)內(nèi)存分配,當(dāng)動態(tài)內(nèi)存分配時,必須要使用指針

指針的定義和使用和C語言的一模一樣,包括定義和取地址符號 & 以及獲取指針指向的值 * 這里就不再說了,不清楚可以回顧一下前面的C語言中的指針。

引用

關(guān)于引用這個概念比較特殊,它類似于指針,但是又不是指針,相當(dāng)于變量名的別名,我們來看一下:

這里這個概念比較特殊,引用不能為空,必須初始化賦值,這個概念感覺有點(diǎn)多余,畢竟C++中有指針的概念,引用在Java中倒是基本概念,以為有引用類型。所以這里還是要區(qū)分一下,尤其是使用 & 來定義,下面代碼簡單看一下:

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;
}

這里聲明了引用,也就相當(dāng)于a和i指向同一塊內(nèi)存區(qū)域,但是它可以和a一樣使用,也就相當(dāng)于變量,其地址是一樣的,打印如下:

函數(shù)參數(shù)為引用類型

在前面學(xué)習(xí)C語言時,我們說道C的函數(shù)參數(shù)可以是值傳遞也可以是指針傳遞,當(dāng)使用值傳遞時會復(fù)制值,函數(shù)執(zhí)行不影響傳遞進(jìn)來的值,指針由于是地址,函數(shù)執(zhí)行肯定會改變,那這個引用類型呢 又是如何。

所以C++這個引用類型設(shè)計(jì)的感覺有問題,它和指針是一樣的,前面也說了,引用只是變量的一個別名,所以參數(shù)類型是引用,它的效果和是指針是一樣的,看下面代碼:

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);
    //發(fā)生了變化,函數(shù)執(zhí)行影響了傳遞的參數(shù)值
    cout << "第一次交換后 a = " << a << " b = " << b << endl;
    swap1(a,b);
    //沒有發(fā)生變化,值傳遞會復(fù)制參數(shù),不會影響
    cout << "第二次交換后 a = " << a << " b = " << b << endl;

    return 0;
}
//參數(shù)是引用類型
void swap(int& x,int& y){
    int temp;
    temp = x;
    x = y;
    y = temp;
}
//參數(shù)是值
void swap1(int x,int y){
    int temp;
    temp = x;
    x = y;
    y = temp;
}

這里說了2種交換,一種是傳值一種是引用,打印如下:

會發(fā)現(xiàn)使用引用也會導(dǎo)致原來值發(fā)生變化。

類和對象

終于到了C++的重頭戲了,也就是面向?qū)ο?,熟悉Java語言對于面向?qū)ο罂隙ㄊ鞘值角軄恚@里還是來看看C++是如何面向?qū)ο蟮摹?/p>

還是直接看代碼:

#ifndef CPLUSTEST_PERSON_H
#define CPLUSTEST_PERSON_H

class Person{
    //公共屬性
public:
    Person();    //空參數(shù)構(gòu)造函數(shù)
    ~Person();      //析構(gòu)函數(shù)
    Person(char *name,int age); //有參構(gòu)造函數(shù)

    //成員變量
    char *name;
    int age;

    //函數(shù)
    void setName(char *name);
    char *getName();

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

#endif //CPLUSTEST_PERSON_H

這是person.h文件,前面在學(xué)習(xí)C語言種說過,頭文件一般是定義函數(shù)、類啥的,這里也一樣,不過這里僅僅只是定義,和Java還是有著非常大的區(qū)別,真正實(shí)現(xiàn)的地方在下面:

#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 << "執(zhí)行 Person 空構(gòu)造函數(shù)" << 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文件,導(dǎo)入前面頭文件,這里是進(jìn)行實(shí)現(xiàn),這里和Java的類定義區(qū)別很大,在Java或者kotlin中一般定義后就直接都實(shí)現(xiàn)了,不會單獨(dú)搞2個地方,然后就是調(diào)用的地方:

int main() {

    //棧里面定義的 在該方法執(zhí)行完就會回收掉Person對象
    Person personTemp;
    personTemp.setName("張三");
    personTemp.setAge(10);
    cout << personTemp.getName() << "\t" << personTemp.getAge() << endl;

    //如果使用new初始化構(gòu)造函數(shù),
    Person *person = new Person("zyh",18);
    cout << person -> name << "\t" << person->getAge() << endl;
    //釋放person內(nèi)存
    delete person;

    return 0;
}

同樣這里也有著很大的區(qū)別,首先是對象聲明,在Java中一個對象如果沒有調(diào)用構(gòu)造函數(shù)它就是null,無法調(diào)用其方法,但是在C++中確不一樣,同時使用new操作符新建的對象,需要手動delete釋放內(nèi)存,Java是有GC自動回收,也比較麻煩。

這里先簡單總結(jié)一下,下面再細(xì)說:

C++類成員函數(shù)

啥是類成員函數(shù)就不用說了,學(xué)過面向?qū)ο笳Z言的都知道,這里主要說一下類成員函數(shù)有2種定義的方式。

一種是直接在類中定義,這也是Java語言的方式,還有一種就是在類中聲明,定義在別的地方,這是C++推薦的方式,比如下面類Box在頭文件中聲明:

#ifndef CPLUSTEST_BOX_H
#define CPLUSTEST_BOX_H

//第一種是類成員函數(shù)直接在類中就定義

class Box{
public:
    double length;  //長度
    double width;   //寬度
    double height;  //高度

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

    double getVolume2();    //體積

};

#endif //CPLUSTEST_BOX_H

其中有2個成員函數(shù),getVolume2我們放在.cpp文件中進(jìn)行定義:

#include "box.h"

double Box::getVolume2() {
    return length * width * height * 2;
}

上面2種方式都可以。其中重點(diǎn)說明一下在類中定義的成員函數(shù)把函數(shù)聲明為內(nèi)聯(lián)的,啥是內(nèi)聯(lián),后面再說;其中 :: 叫做范圍解析運(yùn)算符。

C++的構(gòu)造和析構(gòu)函數(shù)

關(guān)于構(gòu)造函數(shù)我們自然都很熟悉了,這里只說一點(diǎn)就是使用初始化列表來初始化字段的情況,其實(shí)就是名字意思,使用初始化列表來初始化字段。

這個我們知道在C++中構(gòu)造函數(shù)的參數(shù)是參數(shù),和字段沒關(guān)系,那這時如何在構(gòu)造函數(shù)中初始化字段呢,如果是kotlin的數(shù)據(jù)類是自動幫我做的,如果參數(shù)有val/var修飾符,其他類也就在init代碼塊中進(jìn)行賦值,C++有個不一樣的寫法,就是下面這樣:

#ifndef CPLUSTEST_BOX_H
#define CPLUSTEST_BOX_H

//第一種是類成員函數(shù)直接在類中就定義

class Box{
public:
    double length;  //長度
    double width;   //寬度
    double height;  //高度

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

    double getVolume2();    //體積
    //自定義的構(gòu)造函數(shù)
    Box(double len);

};

#endif //CPLUSTEST_BOX_H

這里的Box定義加了一個有參構(gòu)造函數(shù),然后在定義的地方:

Box::Box(double len) : length(len) {
    std::cout << "構(gòu)造函數(shù)輸入len" << endl;
}

就是這個在函數(shù)后直接 : 對字段進(jìn)行賦值,這種寫法還是蠻新奇的。

還有就是析構(gòu)函數(shù),和無參構(gòu)造函數(shù)一樣,不寫的話編譯器會自己加上。那析構(gòu)函數(shù)是啥呢,就是和默認(rèn)構(gòu)造函數(shù)一樣只不過前面多個~,可以使用析構(gòu)函數(shù)跳出程序、關(guān)閉資源等。

還是前面的Person類,里面有定義析構(gòu)函數(shù),然后我們分別使用2個對象來看一下打?。?/p>

int main() {

    //棧里面定義的 在該方法執(zhí)行完就會回收掉Person對象
    Person personTemp;
    personTemp.setName("張三");
    personTemp.setAge(10);
    cout << personTemp.getName() << "\t" << personTemp.getAge() << endl;

    //如果使用new初始化構(gòu)造函數(shù),
    Person *person = new Person("zyh",18);
    cout << person -> name << "\t" << person->getAge() << endl;
    //釋放person內(nèi)存
    delete person;

    return 0;
}

打印如下:

會發(fā)現(xiàn)這里不論是自動回收掉的對象還是手動delete的對象都會在對象釋放時調(diào)用析構(gòu)函數(shù)。當(dāng)然使用new關(guān)鍵字創(chuàng)造的對象,在不調(diào)用delete時是不會釋放的。

C++拷貝構(gòu)造函數(shù)

什么是拷貝構(gòu)造函數(shù)呢,這個其實(shí)非常簡單,就是使用一個類之前創(chuàng)建的對象來創(chuàng)建新的對象,比如我有Box A,現(xiàn)在想要一個Box B,讓B和A的內(nèi)容一樣,這時就要考慮了,如果是Java代碼的話B、A2個引用指向同一對象,這不太符合要求,所以會調(diào)用拷貝函數(shù),肯定會創(chuàng)建出一個新對象。

在C++中,直接就有了拷貝構(gòu)造函數(shù)這個概念,讓復(fù)制更方便。但是和Java的復(fù)制一樣,Java復(fù)制需要考慮淺拷貝和深拷貝的問題,在C++中如果類的成員是指針變量,仔細(xì)想一下,直接把A的指針復(fù)制到B的指針變量中,這樣A、B2個對象中該變量都是一個地址,則會相互影響,肯定不行。

所以即使默認(rèn)會有拷貝構(gòu)造函數(shù),當(dāng)類成員變量是指針的時候,也要進(jìn)行重寫拷貝構(gòu)造函數(shù)。

#ifndef CPLUSTEST_LINE_H
#define CPLUSTEST_LINE_H

class Line{
public:
    int getLength();
    Line(int len);
    //拷貝構(gòu)造函數(shù),必須要定義
    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 << "調(diào)用構(gòu)造函數(shù)" << endl;
    //由于傳遞進(jìn)來的是個int值,所以要先給指針分配內(nèi)存
    ptr = new int ;
    //指針指向的內(nèi)存值是len
    *ptr = len;
}

Line::Line(const Line &obj) {
    cout << "調(diào)用拷貝構(gòu)造函數(shù)" << endl;
    //這里拷貝的時候就需要深拷貝了,新的對象的指針要重新分配內(nèi)存
    ptr = new int ;
    //指針指向的值進(jìn)行賦值
    *ptr = *obj.ptr;
}

Line::~Line() {
    cout << "釋放內(nèi)存" << endl;
    delete prt;
}

從上面代碼我可以看出一個問題,就是C++的淺拷貝和深拷貝問題,和Java一樣,如果是這種指針變量,需要重新分配內(nèi)存。

上面例子說明了當(dāng)對象需要另外一個對象進(jìn)行初始化時會調(diào)用拷貝構(gòu)造函數(shù)。

C++友元函數(shù)

這個概念還真沒有在Java中存在過,不過也非常容易理解其含義,友元友元就是好朋友friend的意思,哈哈,也就是友元函數(shù)是聲明在類中,但定義在類外,并且可以通過這個友元函數(shù)訪問類的私有和保護(hù)成員。

這里也就相當(dāng)于給一個類開了一個后門一樣,這個友元函數(shù)不是類的成員函數(shù),但是可以通過它訪問類的私有變量。

還是直接看個代碼例子:

#ifndef CPLUSTEST_BOX_H
#define CPLUSTEST_BOX_H

class Box{
    double width;   //默認(rèn)是私有變量
public:
    friend void printWidth(Box box);        //聲明為友元函數(shù)
    void setWidth(double width);

};

#endif //CPLUSTEST_BOX_H

這里聲明了一個友元函數(shù),用來獲取私有變量的,

#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;
}

友元函數(shù)因?yàn)椴皇荁ox中的,所以不能使用Box::來調(diào)用,完成定義后,便可以使用了:

using namespace std;

int main() {

    Box *box = new Box();
    box->setWidth(10);
    printWidth(*box);
    delete box;
}

感覺有點(diǎn)神奇,這個width屬性沒有任何get方法對外,居然可以通過友元訪問到,不得不說C++的設(shè)計(jì)很到位。

C++內(nèi)聯(lián)函數(shù)

之前看內(nèi)聯(lián)函數(shù)的概念第一次是在kotlin中,由于在Java中沒有這個概念,現(xiàn)在再來看C++,說明kotlin也是不斷借鑒其他語言的優(yōu)勢來完善自己。

內(nèi)聯(lián)內(nèi)聯(lián)就是其字面意思,如果一個函數(shù)定義為內(nèi)聯(lián)時,在編譯時,編譯器會把該函數(shù)的代碼副本放置到每個調(diào)用該函數(shù)的地方,其實(shí)就是為了效率。

對于一般函數(shù)都是在運(yùn)行時才被替代加入棧中,但是對于內(nèi)聯(lián)函數(shù)在編譯時便進(jìn)行復(fù)制,這也就是用空間代價換時間的一種方式,所以內(nèi)聯(lián)函數(shù)一般不超過10行。

C++繼承

面向?qū)ο髞碚f,繼承這個就不用考慮了,熟悉Java的都很了解,這里就不說了。

區(qū)別就是C++可以多繼承,就是可以繼承多個父類,這個在Java中只允許繼承一個父類,實(shí)現(xiàn)多個接口。

動態(tài)內(nèi)存

其實(shí)這個也非常簡單,和Java的內(nèi)存分配類似,在C++中函數(shù)執(zhí)行也是出入棧,所以函數(shù)中定義變量將占用棧內(nèi)存,在函數(shù)執(zhí)行完會釋放。

當(dāng)使用new關(guān)鍵字可以動態(tài)分配內(nèi)存,這個內(nèi)存是在堆中的,使用完需要使用delete來進(jìn)行釋放。

這里的new對象就沒啥說的了,其中對于指針變量,可以new內(nèi)置類型的指針,這個在前面說拷貝函數(shù)時已經(jīng)說過如何使用了。

命名空間

這個其實(shí)沒啥說的,就是把一堆變量和函數(shù)給劃分到一塊,這一塊給命個名子即可。

總結(jié)

其實(shí)C++部分就是在C上面加了面向?qū)ο蟮母拍睿嫦驅(qū)τ谑煜ava的理解起來也非常容易,所以本篇文章就簡單復(fù)習(xí)一下,等后面實(shí)際問題時再進(jìn)行補(bǔ)充。

本文轉(zhuǎn)自 https://juejin.cn/post/7021434763244208165,如有侵權(quán),請聯(lián)系刪除。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容