11 指針
11.1 理解指針
在C語言中,指針是一種派生數據類型。它是從C語言的一種基本數據類型創建而來的。指針以內存地址作爲其值。由於內存地址表示在計算機內存中保存程序指令和數據的位置,因而可用指針來直接訪問和操作存儲在內存中的數據。
指針相關的基本概念:
- 計算機的內存地址指的是指針常量(pointer constant)。我們不能修改他們,只能用來存儲數據值。
- 我們不能直接保存地址的值,只能利用地址運算符(&),通過保存在該地址中那個的變量來或的該值。這樣獲得的值稱爲指針值(pointer value)。指針值(也就是變量的地址)在程序每次運行時都會發生變化。
- 一旦我們有了指針值,就可以把它存儲在另一個變量中。包含指針值的變量就成爲指針變量(pointer variable)
11.2 指針變量初始化
在C語言中,每個變量都必須聲明爲某種類型。由於指針變量包含的是存儲某種數據類型地址,因此在使用之前必須把它們聲明爲指針。指針變量的聲明如下:
data_type *pt_name;
上面的聲明語句告訴了編譯器關於變量pt_name的3件事:
- 星號說明變量pt_name是一個指針變量
- pt_name需要一個內存空間
- pt_name是指向data_type類型的變量
11.3 指針鏈
普通指針變量聲明: int *p;
指向指針的指針變量聲明:int **p1;我們可以使用指向指針的指針來間接地訪問目標值
11.4 指針的遞增與比例因子
指針可以通過如下方式遞增: p= p + 2; p = p + 1; p++;
表示是指針p指向其類型的下一個值。例如,如果p爲整形(4個字節)指針,其初始值爲2900,那麼經過++p後,p的值爲2904,而不是2901.也就是說,當指針進行遞增時,所增加的值爲該指針指向的數據類型的‘長度’。這種長度就成爲比例因子。
11.5 指針與數組
一維數組中訪問元素x[i]的表達式:*(x + i) 或 *(p + i)
二維數組中訪問元素x[i][j]的表達式:*(*(x + i) + j)
11.6 指針與字符串
從第8章我們知道字符串可以看做字符數組,聲明和初始化:char str[5] = "good";本章學習指針後,我們還可以使用char類型的指針變量來創建字符串:char *str = "good";還可以通過運行時賦值語句:char *string;string = "good";
注意:string = "good";不是字符串複製,因爲變量string是一個指針,而不是字符串。(正如第8章所指出的,C語言不支持通過賦值操作來把一個字符串複製給另一個字符串)
指針的一個重要使用就是處理字符串表。可通過如下方式聲明:char name[3][25]; 我們知道,每個字符串的長度很少是等長的。這種聲明方式浪費空間,所以我們可以用指針來指向變長的字符串
char *name[3] = {
"New Zealand",
"Australia",
"India"
};
下面語句可以用來顯示這3個名字:
for (int i = 0; i < 3; ++i)
{
printf("%s\n", name[i]);
}
要訪問第i個名字的第j個字符,可以通過: *(name[i] + j)
注意:*p[3]和(*p)[3]這兩種表示法的區別。由於*比[]的優先級更低,*p[3]表示的是把p聲明爲具有3個指針變量的數組,而(*p)[3]則表示把p聲明爲指向含有3個元素的數組的指針。
11.7 指向函數的指針
與變量一樣,函數也屬於某種數據類型,在內存中需要有存儲地址。因此可以聲明一個指向函數的指針。該指針又可以作爲一個參數在另一個函數中使用。聲明:type (*fptr) ();該語句告訴編譯器,fptr爲指向函數的指針,返回type類型的值。用括號把*fptr括起來是必要的。type *gptr();表示把gptr聲明爲函數,他返回一個指向type類型的指針。
只要把函數名賦給指針,就可以使函數指針指向某個函數。
double mul(int x, int y);
double (*p) ();
p = mul;
函數調用:(*p)(x, y); 等價於 mul(x, y);
/**
功能:使用函數指針作爲函數的參數
知識點:指向函數的指針
版本:2014/06/23
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define PI 3.1415926
double y(double);
double cos(double);
double table (double(*f) (double), double, double, double);
int main(int argc, char const *argv[])
{
printf("Table of y(x) = 2*x*x-x+1\n");
table(y, 0.0, 2.0, 0.5);
printf("\nTable of cos(x) \n");
table(cos, 0.0, PI, 0.5);
system("pause");
return 0;
}
double table (double(*f) (double), double min, double max, double step)
{
double i, value;
for ( i = min; i <= max; i += step)
{
value = (*f)(i);
printf("%5.2f %10.4f\n", i, value );
}
}
double y(double x)
{
return (2*x*x-x+1);
}
兼容性與類型轉換
聲明爲指針的變量不只是一個指針類型的變量。它也是指向某種基本數據類型的指針。因此,總是有一種數據類型與指針關聯。我們不能把一種類型的指針賦給另一種類型的指針,儘管兩者都是以內存地址作爲其值的。這成爲指針的不兼容性
所有指針變量存儲的都是內存地址,這些內存地址是兼容的,但它們所指向的數據類型是不兼容的。對不同類型的指針不能使用賦值運算符。但利用類型轉換運算符,就可以在不兼容的指針類型之間顯示的進行賦值操作,這與我們對基本數據類型所做的是一樣的。
int x;
char *p;
p = (char *) &x;
在這種情況下,必須確保使用指針p的所有操作都正確的進行了類型轉換。
這裏有一個例外。這個例外就是空指針(void *)。空指針是通用指針,可以表示任何的指針類型。所有指針類型都可以賦值給空指針,而空指針無需類型轉換就可以賦給任意指針。空指針創建: void (*vp);由於空指針沒有具體類型,因而不能進行間接引用。
11.8 指針與結構體
struct inventory
{
char name[30];
int number;
float price;
} product[2], *ptr;
把product的第0個元素的地址賦給ptr。
ptr = product;
指針遞增時,使得它指向數組的下一個元素,因此可通過for語句顯示product數組成員信息。
for ( ptr = product; ptr < product + 2; ++ptr)
{
printf("%s %d %f\n", ptr->name, (*ptr).number, ptr->price );
}
- 當使用結構體指針時,應小心各種運算符的優先級。
- 當把結構體作爲一個參數傳遞給函數時,一般通過以指向結構體的指針作爲傳遞參數,然後使用指針來操作成員
print_invent (struct invent *item)
{
printf("Name: %s\n", item->name);
}