07.指標與引用
07.指標與引用
指標
資料物件的地址與值
地址:資料物件的儲存位置在計算機中的編號
值:在該位置處儲存的內容
地址與值是辯證統一的關係
指標變數的定義與使用
指標的定義格式
格式:目標資料物件型別 *指標變數名稱
例一:定義p為指向整數的指標:int *p;
例二:定義p為指向結構體型別的指標:struct POINT{int x, y;}; POINT *p;
多個指標變數的定義
例三:int *p, *q;
指標變數的儲存佈局
實際上定義一個指標涉及到兩個變數,指標資料物件(變數)與目標資料物件(變數)
僅定義指標變數,未初始化
例一:int *p;
定義指標變數,並使其指向某個目標變數
例二:int n = 10; int *p = &n;
計算機有間接訪問機制,即通過p儲存的變數n的地址間接訪問變數n的內容,通過指標p來訪問p所指向的目標資料物件。
定義指標變數,並使其指向陣列首元素
例三:int a[8] = {1,2,3,4,5,6,7,8}; int *p = a;
指標變數的賦值
指標變數可以像普通變數一樣賦值
注意:下例q指標的初始化操作不能用*q = *p;
示例:int n = 10; int *p = &n, *q; q = p;
兩個指標指向同一個目標資料物件
取址運算子“&”
獲取資料物件的地址,可將結果賦給指標變數
示例:int n = 10; int *p; p = &n; int *q; q = p;
引領操作符“*”
獲取指標所指向的目標資料物件
例一:int m,n = 10; int *q = &n; m = *q; //使得m為10
例二(接上例):*q = 1; //使得n為1
指標的意義與作用
作為函式通訊的一種手段
使用指標作為函式引數,不僅可以提高參數傳遞效率,還可以將該引數作為函式輸出集的一員,帶回結果
作為構造複雜資料結構的手段
使用指標構造資料物件之間的關聯,形成複雜資料結構
作為動態記憶體分配和管理的手段
在程式執行期間動態構造資料物件之間的關聯
作為執行特定程式程式碼的手段
使用指標指向特定程式碼段,執行未來才能實現的函式
指標與函式
資料交換函式
之前做過的swap()方法並不能實際調換兩個整數的值,現在可以用指標來實現整數互換的操作。
viod Swap(int *p, int *q)
{
if(!p || !q)
{
cout<<"Swap:Parameter(s) illegal."<<endl;
exit(1);
}
int t = *p;
*p = *q;
*q = t;
}
int main()
{
int x = 1;
int y = 2;
Swap(&x, &y);
}
常量指標與指標常量
常量指標:指向常量的指標
性質:不能通過指標修改目標資料物件的值,但可以改變指標值,使其指向其他地方
示例一:int n = 10; const int *p = &n; //更標準的寫法應該為int const *p = &n;
典型使用場合:作為函式引數,表示函式內部不能修改指標所指向的目標資料物件值
示例二:void PrintObject(const int *p); //定義成const*p,表示函式內部不能通過*p修改指標p所指向的Object物件中的值
指標常量:指標指向的位置不可變化
性質:不可將指標指向其他地方,但可改變指標所指向的目標資料物件值
示例三:int n = 10; int *const p = &n;
指標常量和其他常量一樣,必須在定義時初始化
常量指標常量:指向常量的指標常量(指標的雙重只讀屬性)
性質:指標值不可改變,指向的目標物件值也不可改變
示例四:
const int n = 10;
const int *const p = &n; //可以寫成int const *const p = &n;但不能寫成int const const*p = &n 因為const是左結合的,左邊沒有關鍵字再與右邊關鍵字結合。
典型使用場合:主要作為函式引數使用
返回指標的函式
指標型別可以作為函式返回值
函式內部返回某個資料物件的地址
呼叫函式後將返回值賦值 給某個指標
特別說明:不能返回函式內部定義的區域性變數地址,只能返回某個全域性變數的地址,或者作為函式的引數傳給函式的指標
指標與複合資料型別
指標與陣列
資料物件地址的計算
陣列定義:int a[8] = {1,2,3,4,5,6,7,8};
陣列基地址:&a或a
陣列元素地址
陣列首元素地址:&a[0]
陣列第i元素地址:&a[0] + i * sizeof(int)
陣列基地址與首元素地址數值相同,故陣列第i元素地址:a + i * sizeof(int)
作為函式引數的指標與陣列
void GenerateIntegers(int a[],unsigned int n)
{
unsigned int i;
Randomize();
for(i = 0; i < n; i++)
a[] = GenerateNumber(lower,upper);
}
//等價於
void GenerateIntegers(int *p,unsigned int n)
{
unsigned int i;
Randomize();
for(i = 0; i < n; i++)
*p++ = GenerateNumber(lower,upper);
}
指標與陣列的可互換性
互換情況
指標一旦指向陣列的基地址,則使用指標和陣列格式訪問元素時的地址計算方式是相同的,此時可以互換指標與陣列操作格式
示例
int a[3] = {1,2,3};
int *p = &a;
int i;
for(i = 0;i < 3; i ++)
cout<<p[i]<<endl;
for(i = 0; i < 3; i++)
cout<<*(a + i)<<endl;
例外情況
陣列名為常數,不能在陣列格式上進行指標運算
示例:
//正確,指標p可賦值,指向下一元素
for(i = 0; i < 3; i++)
cout<<*p++<<endl;
//錯誤,不能將陣列a當作指標賦值
for(i = 0; i < 3; i++)
cout<<*a++<<endl;
指標與陣列的差異
使用指標或陣列宣告的資料物件性質不同
示例:int a[3] = {1,2,3}; int *p = &a; //給陣列a分配了3*32位,12位元組的記憶體 給p分配了32位4個位元組的記憶體
定義陣列的同時確定了陣列元素的儲存佈局:a為靜態分配記憶體的陣列;若a為全域性陣列,則程式執行前分配記憶體;若a為區域性陣列,則在進入該塊時分配記憶體
定義指標時規定指標資料物件儲存佈局:p為指標,若p為全域性變數,則程式執行前分配記憶體;若為區域性變數,則在進入該塊時分配記憶體
定義指標時未規定目標資料物件的儲存佈局:p為指標,指向一個已存在陣列的基地址,即指向該位置處的整數a[0];若p未初始化,則目標資料物件未知
使用指標時,應顯示構造指標與目標物件的關聯
多維陣列作為函式引數
直接傳遞元素個數也不妥當,只能處理固定元素個數的陣列,應用場合十分受限
void PrintTwoDimensinalArray(int a[8][8], unsigned int m, unsigned int n); //正確,但有不妥
不能每維都不傳遞元素個數,語法規則不允許
void PrintTwoDimensinalArray(int a[][], unsigned int m, unsigned int n); //錯誤,不符合語法規則,最多隻能第一個中括號內不寫
a為指向陣列基地址的整數指標,m為第一維元素個數,n為第二維元素個數,函式內部使用指標運算訪問某個元素。如第i行j列元素,使用指標運算a + n * i + j的結果指標指向
void PrintTwoDimensinalArray(int *a, unsigned int m, unsigned int n); //正確,但把二維陣列化為一維陣列儲存,有不妥
綜上,C++目前沒有多維陣列作為函式引數的妥當方案,使用第一種方案最為合適。
指標與結構體
指向結構體的指標
struct STUDENT{int id; STRING name; int age;};
STUDENT student = {2007010367, "Bear",19};
STUDENT *pstudent = &student;
訪問指標所指向的結構體物件的成員
必須使用括號:選員操作符優先順序高於引領操作符
(*pstudent).id = 2007010367;
(*pstudent).name = DuplicateString("Bear");
(*pstudent).age = 19;
選員操作符“->”
pstudent->id = 2007010367; //不用書寫括號,更方便
結構體成員型別為指標
struct ARRAY{unsigned int count; int *elements;};
int a[8] = {1,2,3,4,5,6,7,8};
ARRAY array = {8,&a};
訪問指標型別的結構體成員
訪問elements的第i個元素:array.elements[i]
若有定義:ARRAY *parray = &array;
訪問parray指向的結構體物件elements的第i個元素:(*parray).elements[i]或parray->elements[i]
結構體指標的使用場合
使用指向結構體物件的指標作為函式引數
好處1. 節省結構體整體賦值的時間成本,因為只需把規模較大的結構體的地址傳給指標。
好處2. 解決普通函式引數不能直接待會結果的問題,可以在函式內部改變目標結構體物件的值。
構造複雜的資料結構
動態建立和管理這些複雜的資料結構
動態陣列:struct ARRAY{unsigned int count; int *elements;};
指標運算
引入問題
int a[8] = {1,2,3,4,5,6,7,8};
int *p = &a[0]; //p指向陣列首元素 *p = &a仍表示p指向陣列首元素
int *q = &a[2]; //q指向a[2]
如何表達p、q之間的聯絡?它們都指向同一陣列中的元素
指標與整數加減運算
設p為指向整數陣列中某元素的指標,i為整數,則p + i表示指標向後滑動i個整數,p - i表示指標向前滑動i個整數
例:p指向a[0],則p + 2指向a[2];q指向a[2],則q - 2指向a[0]
指標與整數加減運算的結果仍為指標型別量,故可賦值
例:p指向a[0],則p + 2指向a[2],故可q = p + 2,使得q指向a[2]
指標與整數加減運算規律
以指標指向的目標資料物件型別為單位,而不是以位元組為單位
指標的遞增遞減運算
例:p指向a[0],則p++指向a[1]; q指向a[2],則 --q指向a[1]
指標減法運算
兩個指標的減法運算結果為其間元素個數
例:p指向a[0],q指向a[2],則q - p的結果為2
指標關係運算
可以測試兩個指標是否相等,p == q
空指標:NULL
指標值0:表示指標不指向任何地方,表示為NULL
應用:測試指標是否有意義 if(p!=NULL) 等價於 if(p)
使用指標前一定要測試其是否有意義!
字串
字串的表示
三種理解角度:作為字元陣列,作為指向字元的指標,作為抽象的字串整體
編寫函式,返回字元c在字串s中首次出現的位置
unsigned int FindCharFirst(char c, char s[]) //當作字元陣列訪問
{
unsigned int i;
if(!s)
{
cout<<"FindCharFirst:Illegal string.\n";
exit(1);
}
for(i = 0; s[i]!='\0';i++)
{
if(s[i] == c)
return i;
}
return inexistent_index; //0xFFFFFFFF
}
unsigned int FindCharFirst(char c, char *s)
{
char *t;
if(!s)
{
cout<<"FindCharFirst:Illegal string.\n";
exit(1);
}
for(t = s;*t != '\0';t++)
{
if(*t == c)
return t-s;
}
return inexistent_index; //0xFFFFFFFF
}
字元陣列
定義格式與普通陣列定義格式相同
示例:char s[8] = {'a','b','c','d','e','f','g','h'};
字元陣列與字元指標的差異
字元陣列定義後分配陣列元素個數個位元組的儲存空間
字元指標分配4個位元組32位的儲存空間用來儲存陣列基地址
按指標格式定義字串,可以直接賦值
示例:char *s; s = "Hello World!"; //正確
字串文字首先分配空間,然後將其基地址賦給s,使s指向該字串基地址
按字串陣列格式定義字串,不能直接賦值
示例:char s[13]; s = "Hello World!"; //錯誤
不能對陣列進行整體賦值操作
標準字串庫
C語言字串庫標頭檔案為cstring,C++字串庫標頭檔案為string
cstring中常用的字串函式
char *strcat(char *dest, const char *src);
char *strcpy(char *dest, const char *src);
int strcmp(const char *s1, const char *s2);
int strlen(const char *s);
char *strtok(char *token, const char *delimiters);
string類
宣告與構造string物件
string s = "abcdefg"; 或string s("abcdefg");
第二種方法更好
讀取與寫入string物件
cout<<s<<endl;
cin>>s; //讀取以空格、製表符與回車符分隔的單詞
getline(cin,s,'\n'); //讀取包含空格和製表符在內的整行
常用函式
length(); //獲取string物件的長度
resize(32); //將s設為32字元長,多餘捨棄,不足空閒
resize(32,'='); //多餘捨棄,不足補’=‘
s1.append(s2); //將字串s2追加到s1尾部
s1 = s1 + s2; //將s2追加到s1尾部,並將新字串賦值給s1
s1.compare(s2,0); //從0號位開始比較字串
s1.find(s2,0); //從字串開頭開始查詢,結果為s2在s1中首次出現的位置
動態儲存管理
記憶體分配
C標準庫的動態儲存管理函式
記憶體分配函式malloc
記憶體釋放函式free
C++的記憶體分配操作符:new與delete
靜態記憶體分配方式
適用物件:全域性物件與靜態區域性變數
分配與釋放時機:在程式執行前分配,程式結束時釋放
自動記憶體分配方式
適用物件:普通區域性變數
分配與釋放時機:在程式進入該函式或該塊時自動進行,退出時自動釋放
動態記憶體分配方式
適用物件:匿名資料物件(指標指向的目標資料物件)
分配與釋放時機:在執行特定程式碼端時按照該程式碼段的要求動態分配與釋放,程式設計師做主
動態記憶體分配的目的
靜態與自動記憶體分配方式必須事先了解資料物件的格式和儲存空間大小,部分場合無法確定資料物件的大小,比如宣告一個包含n個元素的整數陣列,n由使用者輸入
動態記憶體分配的位置
計算機維護的一個專門的儲存區:堆
所有動態分配的記憶體都位於堆中
動態記憶體分配的關鍵技術
使用指標指向動態分配的記憶體區
使用引領操作符操作目標資料物件
動態儲存管理函式的原型
C語言中標頭檔案:"cstlib"和"cmalloc",兩者包含其一即可
記憶體分配函式原型:void *malloc(unsigned int size);
記憶體釋放函式原型:void free(void *memblock);
void *型別
特殊的指標型別,指向的目標資料物件型別未知
不能在其上使用引領操作符訪問目標資料物件
可以轉換為任意指標型別,不過轉換後型別是否有意義要看程式邏輯
可以在轉換後的型別上使用引領操作符
主要目的:作為一種通用指標型別,首先構造指標物件與目標資料物件的一般性關聯,然後由程式設計師在未來明確該關聯的性質
malloc函式的一般用法
首先定義特定型別的指標變數:char *p;
呼叫malloc函式分配記憶體:p = (char *)malloc(11);
引數表示所需要分配的儲存空間大小,以位元組為單位
例:要分配能夠儲存10個字元出的字串,分配11個位元組,將返回值轉換為char *型別賦值給原指標,使p指向新分配空間的匿名目標資料物件
free函式的一般用法
傳遞一個指向動態分配記憶體的目標資料物件的指標
示例一:char *p; p = (char *)malloc(11); free(p);
示例二:int *p = (int *)malloc(10 * sizeof(int)); free(p);
示例二分配能夠容納10個整數的連續儲存空間,使p指向該空間的基地址最後呼叫free函式釋放p指向的整個空間
特別說明:有分配就要有釋放
呼叫free(p)後,p指向的空間不再有效,但p仍指向它,為保證在釋放目標資料物件空間後,不會再次使用p訪問,應該在free後,為p賦空值。
free(p); p = NULL;
9239