C++遠征離港篇-學習筆記

天涯明月笙發表於2018-07-24

C++遠征離港篇

離港總動員

C++遠征計劃的學習者肯定是衝著封裝,繼承,多型來的。

mark

知識點:

  • 指標 VS 引用
  • #define VS const(更強資料控制力)
  • 函式預設值 & 函式過載
  • 記憶體管理(頭疼): 堆中的記憶體管理幾乎完全由程式設計師操心[出來混總是要還的]
  • 封裝 繼承 多型

c++語言引用

引用型別:

  • 什麼是引用?

mark

引用就是變數的別名

  • 能不能只有別名?

只有別名,別名就變成了真實姓名.只有別名也是無法進行命名的。

基本資料型別的引用

#include <stdlib.h>
#include <iostream>
using namespace std;
int main(void)
{
    int a = 3;
    // 給a起了一個別名b
    int &b = a; //引用必須初始化

    b = 10; // 給b賦值10,a的值也就由3變為10
    cout << a << endl;

    system("pause");
    return 0;
}

為a起別名b: 對別名做的操作就是對a本身做了操作[叫小蘿蔔頭幹什麼,羅某某也幹了什麼]

結構體型別的引用

使用別名對於結構體做操作的例子:

typedef struct
{
    int x;
    int y;
}Coor;
#include <stdlib.h>
#include <iostream>
using namespace std;

typedef struct
{
    int x;
    int y;
}Coor;

int main(void)
{
    Coor c1;
    Coor &c = c1; // 給c1起了別名c
    c.x = 10; // 使用別名對真實值做操作
    c.y = 20;
    cout << c1.x << endl << c1.y << endl;

    system("pause");
    return 0;
}

指標型別的引用

型別 *&指標引用名 = 指標;
#include <iostream>
using namespace std;
int main(void)
{
    int a = 10;
    int *p = &a; // 定義指標p
    int *&q = p; // 指標p的別名q
    *q = 20;
    cout << a << endl;
    system("pause");
    return 0;
}
  • int a = 10; // 給a分配一個記憶體邏輯地址,如0x100001。這個地址存放了值10;
  • int *p = &a; //建立指標變數p指向a,給p分配地址0x100002,這個地址存放的值是”0x100001″(a的邏輯地址值);
    int *&q = p; // (給指標p起別名q)建立變數q,給q分配地址也是0x100002, 因此這個地址存放的值還是a的邏輯地址值;
  • *q = 20; // (對q做操作)訪問存放在q變數地址下的值,獲得了a的地址值, 再訪問一下a的地址值,修改裡面存放的內容為20;

引用作為函式引數

C語言中將兩個數的值進行交換:

void fun(int *a, int *b)
{
    int c =0;
    c =*a;
    *a =*b;
    *b =c;
}
int main()
{
    int x =10;
    int y =20;
    fun(&x,&y);
    return 0;
}

c++中引用實現:

void fun(int &a, int &b)
{
int c =0;
c =a;
a =b;
b =c;
}
int main()
{
    int x=10,y=20;
    fun(x,y)
    return 0;
}

a是x的別名。b是y的別名。 裡面操作的就是實際的引數了。

C++語言引用程式碼演示:

基本資料型別引用示例:

2-2-C++Two-ReferenceDemo/main.cpp

#include <iostream>
#include <stdlib.h>
using namespace std;

int main(void)
{
    int a = 10;
    int &b = a; // 定義一個引用(別名)
    // int &b = NULL; 計算機會報錯, 初始化 無法從 int 轉換為 int &

    b = 20;
    cout << a << endl;

    a = 30;
    cout << b << endl;
    system("pause");
    return 0;
}

mark

對於本體和別名的操作具有相同的效果。

結構體引用示例:

2-2-2-C++Two-ReferenceStructDemo/main.cpp

#include <stdlib.h>
#include <iostream>
using namespace std;

typedef struct
{
    int x;
    int y;
}Coord; //Coord 座標

int main(void)
{
    Coord c;
    Coord &c1 = c; // 起別名c1
    c1.x = 10;
    c1.y = 20;
    cout << c.x << endl << c.y << endl;

    system("pause");
    return 0;
}

mark

指標引用示例:

2-2-3-C++Two-ReferencePointerDemo/main.cpp

#include <stdlib.h>
#include <iostream>
using namespace std;

int main(void)
{
    int a = 3;
    int *p = &a;
    int *&q = p; // 指標p的別名q

    *q = 5;

    cout << a << endl;
    system("pause");
    return 0;
}

mark

函式引數引用示例:

2-2-4-C++Two-ReferenceFunctionParameter/main.cpp

#include <stdlib.h>
#include <iostream>
using namespace std;
void fun(int &a, int &b);
int main(void)
{
    int x = 10;
    int y = 20;

    cout << x << endl;
    cout << y << endl;
    fun(x, y);
    cout << "交換後:" << endl;
    cout << x << endl;
    cout << y << endl;
    system("pause");
    return 0;
}

void fun(int &a, int &b)
{
    int c = 0;
    c = a;
    a = b;
    b = c;
}

mark

看起來傳入的是實參x,y 實際上 a是x的引用,b是y的引用

int a; 
int &b = a;
int &c = a;

一個本體可以起多個別名

單元鞏固

定義一個引用y,y是x的引用,然後列印x和y的值。將y的值更改之後再次列印,x和y的值。

2-2-5-C++Two-ReferenceUnitDemo/main.cpp

#include <stdlib.h>
#include <iostream>
using namespace std;

int main(void)
{
    int x = 3;
    //定義引用,y是x的引用
    int &y = x;
    //列印x和y的值
    cout << x << endl;
    cout << y << endl;
    //修改y的值
    y = 5;
    //再次列印x和y的值
    cout << "After Change Y:" << endl;
    cout << x << "," << y << endl;
    system("pause");
    return 0;
}

mark

C++語言-const

const關鍵字是用來控制變數是否可以變化的。

const與基本資料型別

沒有const之前的情況:

int x =3; //變數

mark

const int x=3;  //此時的x為常量

mark

x變成了一個常量,無法進行更改。再賦值其他數字,編譯時就會報錯。

const與指標型別

  • const int *p=NULL; 完全等價於int const *p=NULL

mark

int *const p=NULL與前兩種有區別。

  • 除了常規上面加一處,也可以在前面後面都加

const int * const p = NULL; 完全等價於 int const * const p = NULL;

mark

int x =3;
const int *p = &x;
// p = &y;正確
// *p = 4;錯誤

const 修飾*p; 因此p可以指向其他的地址,但*P不可以被改變。

變數名 儲存地址 儲存內容
x &x 3
p &p &x
int x =3;
int *const p = &x;
// p = &y;錯誤
// *p = 4;正確

const寫在了*的後面。const 修飾p; const修飾的p只能指向一個地址。

變數名 儲存地址 儲存內容
x &x 3
p &p &x (不可改變)

const 修飾p;

const int x =3;
const int *const p = &x;
// p = &y; 錯誤
// *p = 4; 錯誤
變數名 儲存地址 儲存內容
x &x 3 (不可改變)
p &p &x (不可改變)

const 與引用

int x =3; 
const int &y =x;
// x=10; 正確
// y=20; 錯誤 y作為別名加了const
變數名 儲存地址 儲存內容
x &x 3

const 例項

//錯誤
const int x =3;
x =5;
// 常量x不能進行賦值了

//錯誤
int x =3;
const int y =x ;
y = 5;
// y 變成了常量,不能再賦值

//錯誤
int x =3;
const int *y =&x; // 修飾*y
*y = 5; // *y不可變化

//錯誤
int x =3,z=4;
int * const y = &x;
y = &z; // 修飾y 不允許重新指向

//錯誤
const int x =3;
const int &y =x;
y =5;

//錯誤:指標會存在改變常量的風險。
const int x =3;
int *y = &x; // x不可變,指標可變。 
// 使用一個可變的指標,指向一個不可變的變數。風險是可以通過*y的方式改變x的值。
// 編譯器會禁止

//正確。x擁有讀寫,y只可讀。
int x =3;
const int *y =&x; //許可權小的接收許可權大的

const程式碼示例

3-2-1-constIntChangeDemo/main.cpp

#include "stdafx.h"
#include <iostream>
using namespace std;
int main(void)
{
    const int x = 3;
    x = 5;

    system("pause");
    return 0;
}

1> error C3892: “x”: 不能給常量賦值

通過define和const修飾的都可以達成設定常量目的。

  • const的優點是,常量有型別,在編譯的時候要檢查語法錯誤。
  • 而#define定義的沒有資料型別,是巨集定義在編譯時不再檢查語法錯誤
  • 推薦用const來定義常量

3-2-2-constPointerChangeDemo/main.cpp

#include <iostream>
using namespace std;
int main(void)
{
    int x = 3;
    int y = 4;
    int const *p = &x; // const int *p = &x等價
    // 都是修飾*p的

    //*p = 5;
    x = 5;
    p = &y;
    cout << *p << endl;
    system("pause");
    return 0;
}

此時*p的值不能進行修改。但是可以修改p指標指向的地址

錯誤  C3892   “p”: 不能給常量賦值

mark

3-2-3-constPointerChangeDemo2/main.cpp

#include <iostream>
using namespace std;
int main(void)
{
    int x = 3;
    int y = 5;
    int *const p = &x; // const修飾p
    // p = &y;  // p不能給常量賦值
    *p = 10;
    cout << x << endl;
    system("pause");
    return 0;
}

此時對於p指向的地址不能修改。但是對於*p的值可以進行修改。

mark

3-2-4-constPointerMoveDemo/main.cpp

#include <iostream>
using namespace std;
int main(void)
{
    int x = 3;
    int y = 5;
    int const *p = &x;
    cout << *p << endl;
    p = &y;
    //*p = 10;
    cout << *p << endl;
    system("pause");
    return 0;
}

因為此時const修飾的*p,而p是可以移動到其他地址。

mark

const修飾一個引用:

3-2-5-constReferenceDemo/main.cpp

#include <iostream>
using namespace std;
int main(void)
{
    int x = 3;
    int y = 5;

    int const &z = x;
    // z = 10;  // z不能被改變
    x = 20;
    cout << x << endl;
    system("pause");
    return 0;
}

mark

別名被限制上了不能修改,但是原變數是可以修改的。

函式中的const

因為可以保證傳入函式內部的值不會因為誤操作而修改原有值

3-2-6-constFunctionDemo/main.cpp

#include <iostream>
using namespace std;

void fun( const int &a,  const int &b);

int main(void)
{
    int x = 3;
    int y = 5;
    fun(x, y);
    cout << x << "," << y << endl;
    system("pause");
    return 0;
}

void fun( const int & a, const int & b)
{
    // 錯誤因為傳入的值為const。不能進行修改。
    a = 10;
    b = 20;
}

mark

把const去掉,因為傳入的是引用,所以原始值可以被修改。

而當const修飾之後,傳入函式內部的值並不會修改原有值

關於const用法,以下錯誤的是:

A. int const a = 3; int *p = &a; // 
B. int a = 3; int const *p = &a;
C. int a = 3; int * const p = &a;
D. const int a = 3; int const &b = a;

B const 修飾*p C const 修飾 p D const修飾a的別名b
mtianyan:指標指向const修飾的變數時,應該是const int const *p = &a;

單元鞏固

使用const關鍵字定義整型變數count,並定義指標p引用變數count。利用for迴圈列印count次Hello C++

3-4-C++-UnitDemo/main.cpp

#include <stdlib.h>
#include <iostream>
using namespace std;

int main(void)
{
    // 定義常量count
    const int count = 3;
    int const *p = &count;
    // 列印count次字串Hello C++
    for (int i = 0; i < *p; i++)
    {
        cout << "Hello C++" << endl;
    }
    system("pause");
    return 0;
}

因為只對*p進行了const。因此可以讓p指向其他地址。

c++函式新特性

函式引數預設值

void fun(int i, int j=5, int k=10); // j,k有預設值
void fun(int i, int j=6, int k); // 錯誤寫法

有預設引數值的引數必須在參數列的最右端

宣告寫預設值,定義不建議寫。 定義時寫預設值有些編譯器無法通過。

void fun(int i, int j = 5, int k = 10);

void fun(int i, int j, int k)
{
    cout << i << j << k;
}

使用時:

int main()
{
    fun(20);
    fun(20,30);
    fun(20,30,40);
}

無實參則用預設值,否則實參覆蓋預設值

函式過載

前提: 在相同作用域下

兩個條件:

  • 用同一個函式名定義的多個函式
  • 引數個數引數型別不同

demo程式碼:

int getMax(int x, int y, int z)
{
    //TO DO
}
double getMax(double x ,double y)
{
    //TO DO
}

思考:編譯器如何識別過載的函式

實際的編譯之後,名稱+引數形成新的函式。來區分兩個所謂的同名函式。

  • int getMax(int x, int y, int z) ->getMax_int_int_int
  • double getMax(double x ,double y) ->getMax_double_double

呼叫時,則根據實參型別和個數自動識別。

過載的好處:

  • 求幾個數最大值,比如有時候求三個數有時候求5個數,有時候求整數,有時候求浮點數。不需要想名字,計算機幫我們決定。

行內函數

行內函數

主調函式呼叫普通函式有五個步驟:

  1. 呼叫fun(),2. 找到fun()的相關函式入口 3. 執行fun() 中的相關程式碼 4. 返回主調函式
  1. 主調函式向下執行其他程式碼直到結束。

行內函數會在編譯時將函式體程式碼和實參代替函式呼叫語句。

  • 省掉了2和4步驟,會節省時間,尤其是迴圈呼叫。

行內函數關鍵字inline

inline int max(int a,int b,int c);

int main()
{
    int i =10,j=20,k=30,m;
    m = max(i,j,k);
    cout<<"max="<<m<<endl;
    return 0;
}

使用時和普通函式一樣使用。程式碼展開後相當於程式碼貼上進來。

mark

mark

思考: 為什麼不所有地方都使用行內函數?

  • 內聯編譯是建議性的,由編譯器決定
  • 邏輯簡單(最好不要包含for迴圈等),呼叫頻繁的函式建議使用內聯
  • 遞迴函式無法使用內聯方式。

內容總結

  1. 函式引數預設值: 實參覆蓋預設值
  2. 函式過載: 名稱相同,引數可辯 (個數型別)
  3. 行內函數: inline 效率高 有條件(1.邏輯簡單,2.不能是遞迴)

C++函式特性程式碼演示

學習函式預設引數,過載,行內函數。

  • 函式引數預設值:

4-2-c++-functionDefaultParameter/main.cpp

#include <stdlib.h>
#include <iostream>
using namespace std;
void fun(int i=30, int j = 20, int k = 10);

int main(void)
{
    fun();
    fun(100);
    fun(100, 200);
    fun(100, 200, 300);
    system("pause");
    return 0;
}

void fun(int i, int j, int k)
{
    cout << i << "," << j << "," << k << endl;
}

mark

已傳入的實參覆蓋預設值,未傳入的使用預設值。 預設值從右側開始賦值,宣告時賦預設值,定義時不要預設值。

函式過載

前提條件,函式在同一個作用域下,預設多個函式在同一個名稱空間時。

當沒有定義名稱空間時,函式同名就預設是過載了。

4-2-C++-FunctionOverload/main.cpp

#include <stdlib.h>
#include <iostream>
using namespace std;

void fun(int i = 30, int j = 20, int k = 10);
void fun(double i = 30.0, double j = 40.0);
int main(void)
{
    // fun(); //“fun” : 對過載函式的呼叫不明確
    //  有多個 過載函式 "fun" 例項與引數列表匹配


    fun(1, 2);
    fun(1.1, 2.2);

    system("pause");
    return 0;
}

void fun(int i, int j, int k)
{
    cout << i << "," << j << "," << k << endl;
}

void fun(double i, double j)
{
    cout << i << "," << j << endl;
}

mark

fun() 兩個函式都可以,因此編譯器懵了。

inline函式實現只需要加上inline關鍵字

inline void fun(int i = 30, int j = 20, int k = 10);
inline void fun(double i = 30.0, double j = 40.0);

inline這種內聯,只是一種編譯方式,結果上沒有什麼不同。

C++的過載的兩個函式引數數量可以相同也可以不同, 當引數數量相同時,只需要對應引數型別不同即稱為過載。

單元鞏固: 程式碼練習

4-4-ReturnMaxDemo/main.cpp

#include <stdlib.h>
#include <iostream>
using namespace std;
/**
*函式功能:返回a和b的最大值
*a和b是兩個整數
*/
int getMax(int a, int b)
{
    return a > b ? a : b;
}

/**
* 函式功能:返回陣列中的最大值
* arr:整型陣列
* count:陣列長度
* 該函式是對上面函式的過載
*/
int getMax(int arr[], int count)
{
    //定義一個變數並獲取陣列的第一個元素
    int a = arr[0];
    for (int i = 1; i < count; i++)
    {
        //比較變數與下一個元素的大小
        if (a <arr[i])
        {
            //如果陣列中的元素比maxNum大,則獲取陣列中的值
            a = arr[i];
        }
    }
    return a;
}

int main(void)
{
    //定義int陣列並初始化
    int numArr[3] = { 3, 8, 6 };

    //自動呼叫int getMax(int a, int b)
    cout << getMax(6, 4) << endl;

    //自動呼叫返回陣列中最大值的函式返回陣列中的最大值
    cout << getMax(numArr, 3) << endl;
    system("pause");
    return 0;
}

mark

C++記憶體管理

什麼是記憶體管理?

思考:記憶體的本質是什麼?

記憶體的本質是一種資源,由作業系統掌控。

我們能做什麼?

我們可以對記憶體進行申請和歸還操作,申請/歸還記憶體資源稱為記憶體管理。

記憶體的申請與釋放

運算子: new delete

  • 記憶體的申請:int *p=new int;
  • 釋放:delete p;

這是申請和釋放某一個記憶體

申請和釋放塊記憶體

int *arr=new int[10]; // 申請塊記憶體
delete []arr; // 釋放快記憶體

記憶體操作注意事項

回憶: 申請和釋放記憶體的其他方式

  • c語言中:
void *malloc(size_t size); // 使用申請記憶體函式
void free(void *menblock); // 使用釋放記憶體函式
  • c++: new delete 運算子

配套使用不要混搭

  • 申請記憶體是否一定成功: 不一定會有那麼多記憶體.
int *p=new int [1000];

if(NULL==p)
{
    //記憶體分配失敗
}

釋放記憶體注意:

  • 在釋放記憶體後,要將指標值賦為空
int *p=new int [1000];

if(NULL==p)
{
    //記憶體分配失敗
}

delete []p;
p = NULL;
int *p=new int;

if(NULL==p)
{
    //記憶體分配失敗
}

delete p;
p = NULL;

不置為空,它就會指向剛才那塊記憶體。我們如果再次使用delete,就會造成同一塊記憶體回收兩次。
計算機會出現異常。

內容總結:

使用new申請記憶體,使用delete釋放記憶體,配套使用。

申請記憶體需要判斷是否失敗。釋放記憶體要記得指標置空。

new和delete配套使用

記憶體管理程式碼演示

5-2-NewDeleteMemoryManage/main.cpp

#include <stdlib.h>
#include <iostream>
using namespace std;

int main(void)
{
    //int *p = new int(20); //申請同時初始化
    
    int *p = new int;
    if (NULL == p)
    {
        system("pause");
        return 0;
    }

    *p = 20;
    cout << *p << endl;

    delete p;
    p = NULL;

    system("pause");
    return 0;
}

mark

申請塊記憶體:

5-2-2-BlockMemoryManage/main.cpp

#include <stdlib.h>
#include <iostream>
using namespace std;

int main(void)
{
    int *p = new int[1000];
    if (NULL == p)
    {
        system("pause");
        return 0;
    }

    p[0] = 10;
    p[1] = 20;
    cout << p[0] << "," << p[1] << endl;

    delete []p; // 注意這裡的[],否則只會釋放第一塊。
    p = NULL;
    system("pause");
    return 0;
}

mark

單元鞏固

在堆中申請100個char型別的記憶體,拷貝Hello C++字串到分配的堆中的記憶體中,列印字串,最後釋放記憶體。

5-4-StrcpyMemoryMange/main.cpp

#include <string.h>
#include <iostream>
using namespace std;
int main(void)
{
    //在堆中申請100個char型別的記憶體
    char *str = new char[100];
    if (NULL == str)
    {
        system("pause");
        return 0;
    }
    //拷貝Hello C++字串到分配的堆中的記憶體中
    strcpy_s(str,100,"Hello C++");
    //列印字串
    cout << str << endl;
    //釋放記憶體
    delete[]str;
    str = NULL;
    system("pause");
    return 0;
}

沒有與引數列表匹配的 過載函式 “strcpy_s”,新增char陣列的長度。

相關文章