1. 程式人生 > >C++遠征離港篇-學習筆記

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

思考 空間 c語言 man 內存回收 運算 賦值 https 分享圖片

C++遠征離港篇

離港總動員

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

技術分享圖片

知識點:

  • 指針 VS 引用
  • #define VS const(更強數據控制力)
  • 函數默認值 & 函數重載
  • 內存管理(頭疼): 堆中的內存管理幾乎完全由程序員操心[出來混總是要還的]
  • 封裝 繼承 多態

c++語言引用

引用類型:

  • 什麽是引用?

技術分享圖片

引用就是變量的別名

  • 能不能只有別名?

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

基本數據類型的引用

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

技術分享圖片

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

結構體引用示例:

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

技術分享圖片

指針引用示例:

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

技術分享圖片

函數參數引用示例:

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

技術分享圖片

看起來傳入的是實參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;
}

技術分享圖片

C++語言-const

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

const與基本數據類型

沒有const之前的情況:

int x =3; //變量

技術分享圖片

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

技術分享圖片

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

const與指針類型

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

技術分享圖片

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

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

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

技術分享圖片

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”: 不能給常量賦值

技術分享圖片

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的值可以進行修改。

技術分享圖片

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是可以移動到其他地址。

技術分享圖片

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

技術分享圖片

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

函數中的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;
}

技術分享圖片

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

使用時和普通函數一樣使用。代碼展開後相當於代碼粘貼進來。

技術分享圖片

技術分享圖片

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

  • 內聯編譯是建議性的,由編譯器決定
  • 邏輯簡單(最好不要包含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;
}

技術分享圖片

已傳入的實參覆蓋默認值,未傳入的使用默認值。 默認值從右側開始賦值,聲明時賦默認值,定義時不要默認值。

函數重載

前提條件,函數在同一個作用域下,默認多個函數在同一個命名空間時。

當沒有定義命名空間時,函數同名就默認是重載了。

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

技術分享圖片

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

技術分享圖片

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

技術分享圖片

申請塊內存:

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

技術分享圖片

單元鞏固

在堆中申請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數組的長度。

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