音影片學習之路--C++

語言: CN / TW / HK

前言

C和C++作為學習音影片技術首要具備的語言基礎,所以十分必要學習和複習一下之前學習C++語言基礎。

這裡IDE和環境配置在前面C語言複習的文章裡已經說過了,還是使用CLion這個軟體,話不多說,直接開始學習。

正文

C++作為一門用途更廣、功能更齊全的語言,其知識深度很深,所以這裡也就複習、學習一些基本知識點,等後續在實際專案中有遇到難點再進行補充。

hello world

建立完一個C++專案,還是列印hello world,程式碼如下: ```

include

//名稱空間,告訴編譯器使用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

//這個必須得有

include

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

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

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

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的理解起來也非常容易,所以本篇文章就簡單複習一下,等後面實際問題時再進行補充。