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個數,有時候求整數,有時候求浮點數。不需要想名字,計算機幫我們決定。
行內函數
主調函式呼叫普通函式有五個步驟:
- 呼叫fun(),2. 找到fun()的相關函式入口 3. 執行fun() 中的相關程式碼 4. 返回主調函式
- 主調函式向下執行其他程式碼直到結束。
行內函數會在編譯時將函式體程式碼和實參代替函式呼叫語句。
- 省掉了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迴圈等),呼叫頻繁的函式建議使用內聯
- 遞迴函式無法使用內聯方式。
內容總結
- 函式引數預設值: 實參覆蓋預設值
- 函式過載: 名稱相同,引數可辯 (個數型別)
- 行內函數: 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陣列的長度。