一、引用的概念和應用
1.引用的概念
下面寫法定義了一個引用,並將其初始化為引用某個變量。 類型名 & 引用名 = 某變量名; int n = 4; int & r = n; // r引用了n,r的類型是 int & 某個變量的引用,等價於這個變量,相當於該變量的一個別名。
代碼示例:
// 課堂練習 # include <iostream> using namespace std; int main(void) { int n = 7; int & r = n; r = 4; cout<<r<<" "<<n<<endl; // 輸出 4 4 n = 5; cout<<r<<" "<<n<<endl; // 輸出 5 5 return 0; }
注意點: 1. 定義引用時一定要將其初始化為引用某個變量。
2. 初始化以后,從一而終,不會再引用別的變量。
3. 引用只能引用變量,不能引用常量和表達式。
代碼示例:
// 課堂練習 # include <iostream> using namespace std; int main(void) { double a = 4, b = 5; double & r = a; // r引用 a double & p = r; // 這時候 p 也引用了 a p = 10; cout<<a<<" "<<r<<" "<<p; // 輸出 10 10 10 r = b; // 這句話並不代表r重新引用b,而是把 b值賦值給r cout<<a<<" "<<r<<" "<<p; // 輸出 5 5 5 return 0; }
課堂習題:
下面程序片段輸出結果是什么? int a = 1,b = 2; int & r = a; r = b; r = 7; cout << a << endl;
2. 引用應用的簡單示例
2.1 引用作為函數的參數
C語言中如何交換兩個整型變量的值? void swap(int * a, int * b) { int temp; temp = *a; *a=*b; *b=temp; } int n1, n2; swap(&n1, &n2); // n1 n2 的值被交換 有了C++的引用: void swap(int & a, int & b) { int temp; temp = a; a=b; b=temp; } int n1, n2; swap(n1, n2); // n1 n2 的值被交換
2.2 引用作為函數的返回值
// 課堂練習 # include <iostream> using namespace std; int n = 4; int & SetValue() { return n; } int main(void) { SetValue() = 40; // 將40的值賦值給SetValue()函數的返回n cout<<n; // 輸出40 return 0; }
2.3 常引用
定義引用時,前面加const關鍵字,即為“常引用” int n; const int & r = n; // r 的類型是const int & 不能通過常引用去修改其引用內容。 int n = 100; const int & r = n; r = 200; // 編譯錯 n = 300; // 沒問題
2.4 常引用和非常引用的轉換
const T & 和 T & 是不同的類型! T &類型的引用或者T類型的變量可以用來初始化const T &類型的引用。 而const T類型的常變量和const T &類型的引用則 不能用來初始化T &類型的引用,除非強制類型轉化。
二、const關鍵字
0) 定義常引用 (在引用處已經講過,不再贅述)
1) 定義常量
const int MAX_VAL = 23; const double Pi = 3.14; const char * SCHOOL_NAME = "PKU";
2) 定義常量指針
A) 不能通過常量指針修改其指向內容
int n, m; const int * p = &n; * p = 5; // 試圖通過指針修改指向內容, 編譯出錯 n = 4; // ok 指向的內容本身是可以修改的 p = &m; // ok 常量指針的指向可以發生變化
B) 不能把常量指針賦值給非常量指針,反過來可以
const int * p; // 常量指針 int * q; // 非常量指針 p = q; // ok q = p; // error 企圖把常量指針賦值給非常量指針 q = (int *)p; //ok 強制類型轉化
C) 函數參數為常量指針時,可避免函數內部不小心改變參數指針所指地方的內容
// 課堂練習 # include <iostream> # include <string.h> using namespace std; void MyPrintf(const char * p) { strcpy(p,"123"); // 編譯出錯 // 因為strcpy()函數的第一個參數是 char * 非常量指針 // 而定義的 P 是一個常量指針,所以,賦值過程會報錯 printf("%s",p); // ok } int main(void) { char ch[5] = {"0123"}; const char * q = ch; MyPrintf(q); return 0; }
課堂習題:
下面說法哪種是對的? A) 常引用所引用的變量,其值不能被修改 B) 不能通過常量指針,去修改其指向的變量 C) 常量指針一旦指向某個變量,就不能再指向其他變量 D) 以上都不對
三、動態內存分配
1. 用new運算符實現動態內存分配
1.1) 用new 分配一個變量
P = new T; T 是任意的類型名,P是類型為T * 的指針。 動態分配出一片大小為sizeof(T)字節的內存空間,並且將該內存空間的起始地址賦值給P。
代碼示例:
// 課堂練習 # include <iostream> using namespace std; int main(void) { int * p; p = new int; // 給P指針分配內存 *p = 5; cout<<*p; return 0; }
課堂習題:
表達式 “new int”的返回值類型是: A) int B) int * C) int & D) void
1.2) 分配一個數組
P = new T[N]; T: 任意類型名 P: 類型為T * 的指針 N:要分配的數組元素的個數 動態分配出一片大小為N*sizeof(T)字節的內存空間,並且將該內存空間的起始地址賦值給P。
代碼示例:
// 課堂練習 # include <iostream> using namespace std; int main(void) { int * p; p = new int[20]; p[10] = 5; cout<<p[10]; // 輸出5 return 0; }
1.3) new 運算符的返回值類型
new T; new T[N]; 均返回T*類型
2. 用delete運算符釋放動態分配的內存
2.1) 用new 動態分配的內存空間,一定要用delete運算符進行釋放
delete 指針; // 該指針必須指向new出來的空間 int * p = new int; * p = 5; delete p; delete p; // error,不能delete兩次
代碼示例:
// 課堂練習 # include <iostream> using namespace std; int main(void) { int * p = new int; *p = 5; cout<<*p<<endl; // 輸出5 delete p; cout<<*p<<endl; // 輸出結果不為 5,空間已經被釋放 return 0; }
2.2) 用delete釋放動態分配的數組,要加 []
delete [] 指針; // 該指針必須指向new出來的數組 int * p = new int[20]; p[0] = 1; delete [] p;
代碼示例:
// 課堂練習 # include <iostream> using namespace std; int main(void) { int * p = new int[20]; p[10] = 5; cout<<p[10]<<endl; // 輸出5 printf("%p\n",p[10]); // 輸出p[10]的地址 delete [] p; cout<<p[10]<<endl; // 輸出5,空間已經被釋放 printf("%p\n",p[10]); // 輸出p[10]的地址 delete [] p; // 再次delete編譯不會報錯,但是程序無法繼續執行 cout<<p[10]<<endl; // 無輸出 printf("%p\n",p[10]); // 無輸出 return 0; }
課堂習題:
下面小段程序,哪個是正確的: A) char * p = new int; p = 'a'; delete p; B) int *p = new int[25]; p[10] = 100; delete p; C) char * p = new char[10]; p[0] = 'K'; delete [] p; D) 都不對?
四、動態內存分配
1. 內聯函數
函數調用是有時間開銷的。
如果函數本身只有幾條語句,執行非常快,而且函數被反復執行很多次,相比之下調用函數所產生的這個開銷就會顯得比較大。
為了減少函數調用的開銷,引入了內聯函數機制。
編譯器處理對內聯函數的調用語句時,是將整個函數的代碼插入到調用語句處,而不會產生調用函數的語句。
在函數定義前加“inline”關鍵字,即可定義內聯函數
inline int Max(int a,int b) { if( a > b) return a; return b; }
2. 函數重載
一個或多個函數,名字相同,然而參數個數或參數類型不相同,這叫做函數的重載。
以下三個函數是重載關系: int Max(double f1,double f2) { } int Max(int n1,int n2) { } int Max(int n1,int n2,int n3) { }
函數重載使得函數命名變得簡單。編譯器根據調用語句的中的實參的個數和類型判斷應該調用哪個函數。
具體使用:
(1) int Max(double f1,double f2) { } (2) int Max(int n1,int n2) { } (3) int Max(int n1,int n2,int n3) { } Max(3.4,2.5); //調用 (1) Max(2,4); //調用 (2) Max(1,2,3); //調用 (3) Max(3,2.4); //error,二義性
3. 函數的缺省參數
C++中,定義函數的時候可以讓最右邊的連續若干個參數有缺省值,那么調用函數的時候,若相應位置不寫參數,參數就是缺省值。
void func( int x1, int x2 = 2, int x3 = 3){ } func(10 ) ; //等效於 func(10,2,3) func(10,8) ; //等效於 func(10,8,3) func(10, , 8) ; //不行,只能最右邊的連續若干個參數缺省
函數參數可缺省的目的在於提高程序的可擴充性。
即如果某個寫好的函數要添加新的參數,而原先那些調用該函數的語句,未必需要使用新增的參數,那么為了避免對原先那些函數調用語句的修改,就可以使用缺省參數。
課堂例題:
下面說法正確的是: A) 多個重載函數的參數個數必須不同。 B) 兩個函數,參數表相同,返回值類型不同,它們是重載關系。 C) 調用一個第二個和第三個參數都有有缺省值的函數時,可以不寫第二個實參而寫第三個實參。 D) 使用內聯函數的目的是提高程序的運行速度。
RRR