1.什麼是指標
指標是一種變數,也稱指標變數,它的值不是整數、浮點數和字元,而是記憶體地址。指標的值就是變數的地址,而變數有擁有一個具體值。因此,可以理解為變數直接引用了一個值,指著間接地引用了一個值。一個存放變數地址的型別稱為該變數的“指標”。
指標變數的大小?
以
32位
系統為例,每個位元組(即一個記憶體單元)都擁有一個地址編號,地址範圍為0x00000000~0xffffffff。當指標變數佔4
個位元組(即32bit)時,剛好能夠表示所有地地址編號。
不管什麼型別的指標,其大小隻和系統編譯器有關係。
2.指標的定義與使用
2.1 指標的定義
在C語言中,所有變數在使用前都需要宣告。例如,宣告一個指標變數的語句如下:
int *qPtr, q;
q是整型變數,表示要存放一個整型型別的值;qPtr是一個整形指標變數,表示要存放一個變數的地址,而這個變數是整數型別。qPtr叫做一個指向整型的指標。
在宣告指標變數時,“*”只是一個指標型別識別符號,指標變數的宣告也可以寫成 int* qPtr。
定義指標三步驟(來自傳智播客):
- *與符號相結合代表是一個指標變數,比如*p;
- 要儲存誰的地址,就寫出它的宣告語句,比如int a, int a[10];
- 用*p替換掉變數名稱,即int a→int *p,int a[10]→int (*p)[10](陣列指標);
指標變數可以在宣告時賦值,也可以在宣告後賦值。例如,在宣告時為指標變數賦值的語句如下:
int q = 12;
int *qPtr = &q;
也可以在宣告後為指標變數賦值:
int q = 12, *qPtr;
qPtr = &q;
2.2 指標的使用
指標變數主要透過取地址運算子&和指標運算子*來存取資料。例如,&a指的是變數a的地址(取址),*ptr表示ptr所指向的記憶體單元存放的內容(取值)。
#include<stdio.h>
int main(){
int q=12;
int *qptr;
qptr = &q;
printf("q的地址是:%p\nqptr中的內容是:%p\n", &q, qptr);
printf("q的值是:%d\n*qptr的值是:%d\n", q, *qptr);
// 運算子'&'和'*'是互逆的
printf("&*qptr=%p, *&qptr=%p\n因此有&*qptr=*&qptr\n", &*qptr, *&qptr);
return 0;
}
3.指標的寬度(步長)
#include<stdio.h>
int main(){
int num = 0x01020304;
char *p1 = (char *)#
short *p2 = (short *)#
int *p3 = #
printf("%#x\n", *p1);
printf("%#x\n", *p2);
printf("%#x\n", *p3);
return 0;
}
透過*取指標變數所指向那塊記憶體空間內容時,取得記憶體的寬度和指標變數本身指向變數的型別有關。
題目:
int a[5] = {1, 2, 3,4 , 5};
int *ptr = (int *)(&a+1);
printf("%d,%d", *(a+1), *(ptr-1));
輸出結果為:A
.2,5 B.2,4 C.1,5 D.1,4
分析:
&a+1:跨過的是整個陣列的寬度
&a[0]+1:跨過的是陣列內單個元素的寬度
所以&a+1指向的記憶體地址已經不屬於陣列了,然後int *ptr = (int *)(&a+1)將&a+1強轉為int*型,所以ptr-1將後退一個4個位元組即一個陣列元素大小,即指向a[4]=5
4.野指標和空指標和萬能指標
4.1 野指標
野指標就是指標指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)。
#include<stdio.h>
int main(){
int *p ;
*p = 200;
printf("%d\n", *p);
return 0;
}
上述程式碼出現問題的原因:指標變數未初始化
任何指標變數剛被建立時不會自動成為 NULL 指標,它的預設值是隨機的。
所以,指標變數在建立的同時應當被初始化,要麼將指標設定為 NULL ,要麼讓它指向合法的記憶體。
如果沒有初始化,編譯器會報錯‘point’ may be uninitializedin the function。
4.2 空指標
#include<stdio.h>
int main(){
// 將0號地址編號0x00000000號記憶體賦予指標內部值,等價於賦予NULL
int *p = NULL;
*p = 200; // 因為p儲存了0號記憶體的地址,這個地址為記憶體的起始地址,是不可使用的,非法
printf("%d\n", *p);
return 0;
}
NULL是C語言標準定義的一個值,這個值其實就是0,只不過為了使得看起來更加具有意義,才定義了這樣的一個宏,中文的意思是空,表明不指向任何東西。
任何程式資料都不會儲存在地址為0的記憶體塊中,它是被作業系統預留的記憶體塊。
空指標的作用:
如果指標使用完畢,需將指標賦予NULL;在使用指標前需判斷指標是否為NULL
4.3 萬能指標
即void *p
,可以儲存任意的地址。
void p:不可遺定義void型別的變數,因為編譯器不知道給該變數分配多大的記憶體空間;
void *p:可以定義void *變數,因為指標都是4個位元組(32位系統)。
#include<stdio.h>
int main(){
int a = 10;
void *p = (void *)&a;
printf("%d\n", *p);
return 0;
}
程式將報錯,無法編譯。因為雖然p記憶體內確實儲存的是變數a的地址,但是由於p指向void型變數,導致根據p指標內部儲存地址去取相應位置值時不知道取多大的記憶體大小。
#include<stdio.h>
int main(){
int a = 10;
void *p = (void *)&a;
printf("%d\n", *(int *)p);
return 0;
}
*(int *)p:將指標p強轉為int *型,此時根據p指標內部儲存地址去取相應位置值時,將讀取4個位元組大小。
5.const修飾的指標變數
引子:const int a = 10;
const修飾變數a,表示不能再透過a修改a記憶體裡面的內容。
5.1 指向常量的指標
const修飾*,表示不能透過該指標修改指標所指記憶體的數值,但是指標指向可以變。
5.2 指標常量
修飾p,指標指向不能變,指標指向的記憶體可以被修改。
注:const int * const p =&a;
表示p指標指向記憶體區域不能被修改,同時p的指向也不能被改變。
6.多級指標
#include<stdio.h>
int main(){
int a = 10;
int *p = &a;
int **q = &p;
// 透過q獲取a的值
printf("%d\n", **q);
return 0;
}
7.指標陣列與陣列指標
7.1 指向陣列元素的指標
例如定義一個整型陣列和一個指標變數,語句如下。
int a[5]={10,20,30,40,50};
int *aPtr;
這裡的a是一個陣列,它包含了5個整型資料。變數名a就是陣列a的首地址,它與&a[0]等價。如果令aPtr=&a[0]或者
aPtr=a,則aPtr也指向了陣列a的首地址。
也可以在定義指標變數時直接賦值,如以下語句是等價的。
int *aPtr=&a[0];
int *aPtr;
aPtr =&a[0];
與整型、浮點型資料一樣,指標也可以進行算術運算,但含義卻不同。當一個指標加1(或減)1並不是指標值增加(或減少)1,而是使指標指向的位置向後(或向前)移動了一個位置,即加上(或減去)該整數與指標指向物件的大小的乘積。例如對於aPtr+=3,如果一個整數佔用4個位元組,則相加後aPtr=2000+4*3=2012(這裡假設指標的初值是2000)。同樣指標也可以進行自增(++)運算和自減(--)運算。
也可以用一個指標變數減去另一個指標變數。例如,指向陣列元素的指標aPtr的地址是2008,另一個指向陣列元素的指標bPtr的地址是2000,則a=aPtr-bPtr的運算結果就是把從aPtr到bPtr之間的元素個數賦給a,元素個數為(2008-2000)/4=2(假設整數佔用4個位元組)。
我們也可以透過指標來引用陣列元素。例如以下語句。
*(aPtr+2);
如果aPtr是指向a[0],即陣列a的首地址,則aPtr+2就是陣列a[2]的地址,*(aPtr+2)就是30。
注意:指向陣列的指標可以進行自增或自減運算,但是陣列名則不能進行自增或自減運算,這是因為陣列名是一個常量指標,它是一個常量,常量值是不能改變的。
#include<stdio.h>
int main(){
int a[5] = {10, 20, 30, 40, 50};
int *aPtr, i;
aPtr = &a[0];
for(i=0;i<5;i++){ //透過陣列下標引用元素的方式輸出陣列元素
printf("a[%d]=%d\n", i, a[i]);
}
for(i=0;i<5;i++){ //透過陣列名引用元素的方式是輸出陣列元素
printf("*(a+%d)=%d\n", i, *(a+i));
}
for(i=0;i<5;i++){ //透過指標變數下標引用元素的方式輸出陣列元素
printf("aPtr[%d]=%d\n", i, aPtr[i]);
}
for(aPtr=a, i=0; aPtr<a+5; aPtr++, i++){ //透過指標變數偏移的方式輸出陣列元素
printf("*(aPtr+%d)=%d\n", i, *aPtr);
}
return 0;
}
7.2 指標陣列
定義:指標陣列其實也是一個陣列,只是陣列中的元素是指標型別的資料。換句話說,指標陣列中的每一個元素都是一個指標變數。
定義指標陣列的方式如下:
int *p[4]
例1:使用指標陣列儲存字串並將字串列印輸出。
#include<stdio.h>
int main(){
// 定義指標陣列
const char *s[4] = {"ABC", "DEF", "GHI", "JKL"};
int n = 4;
int i;
const char *aPtr;
// 方法1:透過陣列名輸出字串
for(i=0;i<n;i++){
printf("第%d個字串:%s\n", i+1, s[i]);
}
// 方法2:透過指向陣列的指標輸出字串
for(aPtr=s[0],i=0;i<n;aPtr=s[i]){
printf("第%d個字串:%s\n", i+1, aPtr);
i++;
}
return 0;
}
執行結果圖示:
注:常量與指標間的轉換 warning: ISO C++ forbids converting a string constant to 'char*'
Q:為什麼s[i]列印的是值而不是地址?
A:%s佔位符的特點就是隻要告訴他字串的首地址,就可以讀取整個字串
例2:利用指標陣列實現對一組變數的值按照從小到大排序,排序時交換變數的指標值。
/* 利用指標陣列實現對一組變數的值按照從小到大排序,排序時交換變數的指標值。 */
#include<stdio.h>
int main(){
int a, b, c, d, e;
int *s[5] = {&a, &b, &c, &d, &e};
int i,j;
int n=5;
int *p;
// 使用者輸入5個數(大小任意)
printf("請輸入5個任意正整數(空格分隔):\n");
scanf("%d%d%d%d%d", &a, &b, &c, &d, &e);
printf("排序前:\n");
for(i=0;i<n;i++){
printf("%d\n",*s[i]);
}
// 排序:氣泡排序
for(i=0;i<n-1;i++){ // 執行n-1趟
for(j=0;j<n-i-1;j++){ // 每一趟需要執行n-1-i次比較操作
if(*s[j] > *s[j+1]){
p = s[j];
s[j] = s[j+1];
s[j+1] = p;
}
}
}
printf("排序後:\n");
for(i=0;i<n;i++){
printf("%d\n",*s[i]);
}
return 0;
}
執行結果圖示:
7.3 陣列指標
定義:陣列指標是指向陣列的一個指標。如下定義:
int (*p)[4]
其中,p是指向一個擁有4個元素的陣列的指標,陣列中每個元素都為整型。與前面剛剛介紹過的指標陣列做比較,這裡定義的陣列指標多了一對括號,*p兩邊的括號不可以省略。這裡定義的p僅僅是一個指標,不過這個指標有點特殊,這個p指向的是包含4個元素的一維陣列。
陣列指標p與它指向的陣列之間的關係可以用下圖來表示。
如果有如下語句:
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
p=a;
陣列指標p與陣列a中元素之間的關係如圖所示。其中,(*p)[0]、(*p)[1]、(*p)[2]、(*p)[3]分別儲存的是元素值為1、2、3、4的值。p、p+1和p+2分別指向二維陣列的第一行、第二行和第三行,p+1表示將指標p移動到下一行。
*(p+1)+2表示陣列a第1行第2列的元素的地址,即&a[1][2],*(*(p+1)+2)表示a[1][2]的值即7,其中1表示行,2表示列。
Q:為什麼(*p)[0]、(*p)[1]、(*p)[2]、(*p)[3]分別儲存的是元素值為1、2、3、4的值而不是它們的地址呢?
A:指標[i] == *(指標+i)
(*p)[0] == *(*p+0)→p本來表示的是第0行一整行的資料,出現在表示式中將自動轉為指向a[0][0]的指標→*p+0還是指向a[0][0]的指標→*(*p+0)即對地址取值。
下面程式設計輸出以上陣列指標的值和陣列的內容。
#include<stdio.h>
int main(){
int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
int (*p)[4] = a; // 宣告陣列指標p(p是一個指向'內含4個整型元素的陣列'的指標)
int row, col;
// 輸出陣列的內容
for(row=0;row<3;row++){
for(col=0;col<4;col++){
printf("a[%d][%d]=%-4d", row, col, *(*(p+row)+col));
}
printf("\n");
}
// 輸出陣列指標的值
for(row=0;row<3;row++, p++){
for(col=0;col<4;col++){
printf("(*p[%d])[%d]=%p\t", row, col, (*p+col));
}
printf("\n");
}
return 0;
}
執行結果圖示:
註釋:[] == *()→如,p[0] 等價於 *(p+0)
8.指標函式與函式指標
8.1 指標函式
指標函式是指函式的返回值是指標型別的函式。例如,以下是一個指標函式的宣告:
float *func(int a, int b);
func是函式名,前面的'*'表明返回值的型別是指標型別,因為前面的型別識別符號是float,所以返回的指標是指向浮點型的。
例:假設若干個學生的成績存放在二維陣列中,要求輸入學生編號,利用指標函式實現其成績的輸出。
#include<stdio.h>
int *FindAddress(int (*ptrScore)[4], int index);
void Display(int *, int n);
int main(){
int score[3][4] = {{83, 78, 79, 88}, {71, 88, 92, 63}, {99, 92, 87, 80}};
int n = 4;
int row;
int *p;
while(1){
printf("請輸入學生編號(1 or 2 or 3),輸入0退出程式:\n");
scanf("%d", &row);
if(row == 0){
break;
}else if(row == 1 || row == 2 || row == 3){
printf("第%d名學生的各科成績分別為:\n", row);
p = FindAddress(score, row-1);
Display(p,n);
}else{
printf("輸入不合法,請重新輸入!\n");
}
}
return 0;
}
int *FindAddress(int (*ptrScore)[4], int index){
/*查詢某條學生成績記錄地址函式。透過傳遞的行地址找到要查詢學生成績所在行,並返回該行的首元素地址*/
int *ptr;
ptr = *(ptrScore+index);
return ptr;
}
void Display(int *ptr, int n){
/*輸出學生成績的實現函式。利用傳遞過來的指標輸出每門課的成績*/
int col;
for(col=0; col<n; col++){
printf("%4d", *(ptr+col));
}
printf("\n");
}
注:
p = FindAddress(score, row-1);
二維陣列的陣列名錶示啥?若a是一維陣列,則a指向的是第一個元素。
若a是二維陣列,也可以將a看成一個一維陣列,那麼其元素是其行向量。則a指向的是第一個行向量。
8.2 函式指標
指標可以指向變數、陣列,也可以指向函式,指向函式的指標就是函式指標。
1)函式指標的呼叫
例1:透過一個函式求兩個數的乘積,並透過函式指標呼叫該函式。
#include<stdio.h>
int Mult(int a, int b);
int main(){
int a, b;
int (*func)(int, int);
printf("請輸入2個數:\n");
scanf("%d%d", &a, &b);
/*方法1:函式名呼叫*/
printf("%d * %d = %d\n", a, b, Mult(a, b));
/*方法2:函式指標呼叫*/
func = &Mult; // 因為函式名本身就是地址,所以&可以省略
printf("%d * %d = %d\n", a, b, func(a, b));
return 0;
}
int Mult(int x, int y){
return x*y;
}
2)函式指標作為函式引數的使用
例2:利用函式指標作為函式引數,實現選擇排序演算法的升序排列和降序排列。
#include<stdio.h>
void SelectSort(int *, int, int (*)(int, int)); //選擇排序,函式指標作為引數呼叫
int Ascending(int, int); // 是否進行升序排列
int Descending(int, int); // 是否進行降序排列
void swap(int *, int *);
void Display(int a[], int n);
int main(){
int a[10] = {13, 23, 11, 4, 9, 16, 22, 23, 9, 10};
printf("排序前數列:\n");
Display(a, 10);
printf("升序排列:\n");
SelectSort(a, 10, Ascending);
Display(a, 10);
printf("降序排列:\n");
SelectSort(a, 10, Descending);
Display(a, 10);
return 0;
}
void swap(int *a, int *b){
int temp = *a;
*a = *b;
*b = temp;
}
void Display(int a[], int n){
int i;
for(i=0;i<n;i++){
printf("%4d", a[i]);
}
printf("\n");
}
int Ascending(int a, int b){
if(a>b){
return 1;
}else{
return 0;
}
}
int Descending(int a, int b){
if(a<b){
return 1;
}else{
return 0;
}
}
void SelectSort(int *ptr, int n, int (*compare)(int, int)){
/*選擇排序基本思想(升序):每一趟排序從n-i個元素中選取關鍵字最小的元素作為有序序列的第i個元素*/
int i, j, k;
// 將第i個元素與後面n-i個元素進行比較,將關鍵字最小的元素放在第i個位置
for(i=0;i<n;i++){
j=i; // 初始時,關鍵字最小的元素下標為i
for(k=j+1;k<n;k++){
if(compare(*(ptr+j), *(ptr+k))){
j=k;
}
}
if(j!=i){
swap(ptr+j, ptr+i);
}
}
}
其中,函式SelectSort(a,N,Ascending)中的引數Asscending是一個函式名,傳遞給函式定義void SelectSort(int *p,int n,int(*compare)(int,int))中的函式指標compare,這樣指標就指向了Asscending。從而可以在執行語句(*compare)(a[j], a[j+1])時呼叫函式Ascending(int a,int b)判斷是否需要交換陣列中兩個相鄰的元素,然後呼叫swap(&a[j],&a[j+1])進行交換。
8.3 函式指標陣列
假設有3個函式f1、f2和f3,可以把這3個函式作為陣列元素存放在一個陣列中,需要定義一個指向函式的指標陣列指向這
三個函式,程式碼如下:
void (*f[3])(int)={f1,f2,f3};
f是包含3個指向函式指標元素的陣列,f[0]、f[1]和f[2]分別指向函式f1、f2和f3。透過函式指標f呼叫函式的形式如下。
f[n](m); /*n和m都是正整數*/
例:宣告一個指向函式的指標陣列,並透過指標呼叫函式。
#include<stdio.h>
void f1(int n); /*函式f1宣告*/
void f2(int n); /*函式f2宣告*/
void f3(int n); /*函式f3宣告*/
int main(){
void (*f[3])(int)={f1,f2,f3}; /*宣告指向函式的指標陣列*/
int flag;
printf("呼叫函式請輸入1、2或者3,結束程式請輸入0。\n");
scanf("%d",&flag);
while(flag){
if(flag==1||flag==2||flag==3){
f[flag-1](flag); /*透過函式指標呼叫陣列中的函式*/
printf("請輸入1、2或者3,輸入0結束程式.\n");
scanf("%d",&flag);
}else{
printf("請輸入一個合法的數(1~3),輸入0結束程式.\n");
scanf("%d",&flag);
}
}
printf("程式結束.\n");
return 0;
}
void f1(int n) /*函式f1的定義*/
{
printf("函式f%d:呼叫第%d個函式!\n",n,n);
}
void f2(int n) /*函式f2的定義*/
{
printf("函式f%d:呼叫第%d個函式!\n",n,n);
}
void f3(int n) /*函式f3的定義*/
{
printf("函式f%d:呼叫第%d個函式!\n",n,n);
}
函式指標不能執行像f+1、f++、f--等運算。