C++遠征離港篇-學習筆記
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;
-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數組的長度。
C++遠征離港篇-學習筆記