C語言入門系列之8.指針的概念與應用

一、指針引入

指針是C語言中的一個重要的概念,也是C語言的一個重要特色。
正確而靈活地運用它,可以有效地表示覆雜的數據結構;能動態分配內存;能方便地使用字符串;有效而方便地使用數組等。
掌握指針的應用,可以使程序簡潔、緊湊、高效。可以說,不掌握指針就是沒有掌握C的精華。

1.地址的概念

數據在內存中的存儲和讀取如下:
數據在內存中的存儲和讀取
內存區的每一個字節有一個編號,稱爲地址
如果在程序中定義了一個變量,在對程序進行編譯時,系統就會給這個變量分配內存單元。

在C語言中,對變量的訪問有兩種方式:

  • 直接訪問
    a=5;
    系統在編譯時,已經對變量分配了地址,例如,若變量a分配的地址是2000,則該語句的作用就是把常數5保存到地址爲2000的單元。
  • 間接訪問
    scanf("%d",&a);
    調用函數時,把變量a的地址傳遞給函數scanf,函數首先把該地址保存到一個單元中,然後把從鍵盤接收的數據通過所存儲的地址保存到a變量中。

2.初識指針

在C語言中,指針是一種特殊的變量,它是存放地址的。
假設我們定義了一個指針變量int *i_pointer,通過語句i_pointer = &i;來存放整型變量i的地址,如下:
初始指針-指針的定義和存放
將i的地址(2000)存放到i_pointer中,這時,i_pointer的值就是(2000) ,即變量i所佔用單元的起始地址。
要存取變量i的值,可以採用間接方式:
先找到存放i的地址的變量i_pointer,從中取出i的地址(2000),然後取出i的值3,如下:
間接方式取變量值

3.兩個操作符

*:是取值操作符;
&:是取址操作符。

如:

int i = 2000;
int *pointer;
pointer = &i;
printf("%d\n", *pointer);

二、指針與指針變量

知道了一個變量的地址,就可以通過這個地址來訪問這個變量,因此,又把變量的地址稱爲該變量的指針
C語言中可以定義一類特殊的變量,這些變量專門用來存放變量的地址,稱爲指針變量。
指針變量的值(即指針變量中存放的值)是地址(即指針)。

1.定義指針變量*

定義指針變量的一般形式爲:

類型說明符  *變量名;

其中,*表示這是一個指針變量,變量名即爲定義的指針變量名,類型說明符表示本指針變量所指向的變量的數據類型。
例如float *pointer_1;中,指針變量名是pointer_1,而不是*pointer_1。

下面都是合法的定義:

float  *pointer_3;     // pointer_3是指向float型變量的指針變量
char *pointer_4;      // pointer_4是指向字符型變量的指針變量

可以用賦值語句使一個指針變量得到另一個變量的地址,從而使它指向一個該變量,如下:
定義指針指向變量
在定義指針變量時必須指定基類型
需要特別注意,只有整型變量的地址才能放到指向整型變量的指針變量中,如下∶

float  a;
int  * pointer_1; 
pointer_1 = &a;          

將float型變量的地址放到指向整型變量的指針變量中,是錯誤的。

2.指針變量的引用&

指針變量中只能存放地址(指針),不要將一個整數(或任何其他非地址類型的數據)賦給一個指針變量,否則編譯器也會把該值當成一個地址來處理。
C語言中提供了地址運算符&來表示變量的地址,其一般形式爲:

&變量名;

&a表示變量a的地址,&b表示變量b的地址。
當然,變量本身必須預先聲明。

通過指針變量訪問整型變量練習如下:

#include <stdio.h>

int main(){
	int a, b;
	int *pointer_1, *pointer_2;
	a = 100;
	b = 10;
	pointer_1 = &a;
	pointer_2 = &b;
	
	printf("%d, %d\n", a, b);
	printf("%d, %d\n", *pointer_1, *pointer_2);
	
	return 0;
} 

打印:

100, 10
100, 10

運行原理如下:
通過指針變量訪問整型變量練習

3.對&和*運算符的說明

如果已執行了語句pointer_1 = &a;
(1)&*pointer_1的含義是:
&和*兩個運算符的優先級別相同,但按自右而左方向結合,因此先進行*pointer_1的運算,它就是變量a,再執行&運算;
因此,&*pointer_1與&a相同,即變量a的地址。
如果有pointer_2 = &*pointer_1;,它的作用是將&*(a的地址)賦給pointer_2 ,如果pointer_2原來指向b,經過重新賦值後它已不再指向b了,而指向了a,如下:
重新賦值改變指向
(2) *&a的含義是:
先進行運算&a,得a的地址,再進行*運算,即&a所指向的變量,也就是變量a;
*&a和*pointer_1的作用是一樣的,它們都等價於變量a,即*&a與a等價。

(3)(*pointer_1)++相當於a++
其中,括號是必要的,如果沒有括號,就成爲了*pointer_1++,而++和*爲同一優先級別,而結合方向爲自右而左,因此它相當於*(pointer_1++)
由於++在pointer_1的右側,是後加,因此先對pointer_1的原值進行*運算,得到a的值,然後使pointer_1的值改變,這樣pointer_1不再指向a了。

練習:
輸入a和b兩個整數,按先大後小的順序輸出a和b。
代碼如下:

#include <stdio.h>

int main(){
	int *p1, *p2, *p, a, b;
	scanf("%d %d", &a, &b);
	p1 = &a;
	p2 = &b;
	
	if(a < b){
		p = p1;
		p1 = p2;
		p2 = p;
	}
	
	printf("a = %d, b = %d\n", a, b);
	printf("max = %d, min = %d\n", *p1, *p2);
	
	return 0;
} 

打印:

5 9
a = 5, b = 9
max = 9, min = 5

執行原理如下:
a b執行原理

指針變量作爲函數參數練習:
對輸入的兩個整數按大小順序輸出,需要用函數實現交換功能。
代碼如下:

#include <stdio.h>

int main(){
	void swap(int *p1, int *p2);
	int *pointer_1, *pointer_2, a, b;
	scanf("%d %d", &a, &b);
	pointer_1 = &a;
	pointer_2 = &b;
	
	if(a < b){
		swap(pointer_1, pointer_2);
	}
	
	printf("%d > %d\n", a, b);
	
	return 0;
}

void swap(int *p1, int *p2){
	int temp;
	printf("Swaping......\nPlease wait^_^\n");
	temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

打印:

5 9
Swaping......
Please wait^_^
9 > 5

執行過程如下:
指針變量作爲函數參數練習

練習:
輸入a、b、c3個整數,按大小順序輸出。
代碼如下:

#include <stdio.h>

int main(){
	void exchange(int *p1, int *p2, int *p3);
	int *p1, *p2, *p3, a, b, c;
	scanf("%d %d %d", &a, &b, &c);
	p1 = &a;
	p2 = &b;
	p3 = &c;
	
	exchange(p1, p2, p3);
	
	printf("%d > %d > %d\n", a, b, c);
	
	return 0;
}

void exchange(int *p1, int *p2, int *p3){
	void swap(int *q1, int *q2);
	if(*p1 < *p2){
		swap(p1, p2);
	}
	if(*p1 < *p3){
		swap(p1, p3);
	}
	if(*p2 < *p3){
		swap(p2, p3);
	}
}

void swap(int *q1, int *q2){
	int temp;
	printf("Swaping......\nPlease wait^_^\n");
	temp = *q1;
	*q1 = *q2;
	*q2 = temp;
}

打印:

20 12 25
Swaping......
Please wait^_^
Swaping......
Please wait^_^
25 > 20 > 12

三、數組與指針

一個變量有地址,一個數組包含若干元素,每個數組元素都在內存中佔用存儲單元,它們都有相應的地址。
指針變量既然可以指向變量,當然也可以指向數組元素(把某一元素的地址放到一個指針變量中)。
所謂數組元素的指針就是數組元素的地址。

1.指向數組元素的指針

定義一個指向數組元素的指針變量的方法,與以前介紹的指向變量的指針變量相同。
例如,int a[10];定義a爲包含10個整型數據的數組;
int *p定義p爲指向整型變量的指針變量。
應當注意,如果數組爲int型,則指針變量的基類型亦應爲int型。
對該指針變量賦值:

p = &a[0];

把a[0]元素的地址賦給指針變量p,也就是使p指向a數組的第0個元素,如下圖:
對指針變量賦值

2.通過指針引用數組元素

引用一個數組元素,有2種方式:

  • 下標法
    a[i]
  • 指針法
    *(a+i)*(p+i)

其中的a是數組名,p是指向數組元素的指針變量,其初值p=a。
數組名即翻譯成數組的第一個元素的地址。

練習:
輸出數組中的全部元素:
假設有一個a數組,整型,有10個元素,要輸出各元素的值有三種方法:
(1)下標法。
(2)通過數組名計算數組元素地址,找出元素的值。
(3)用指針變量指向數組元素。

方式一代碼如下:

#include <stdio.h>

int main(){
	int a[10], i;
	for(i = 0; i < 10; i++){
		scanf("%d", &a[i]);
	}
	printf("\n");
	
	for(i = 0; i < 10; i++){
		printf("%d ", a[i]);
	}
	
	return 0;
}

打印:

1 2 3 4 5 6 7 8 9 10

1 2 3 4 5 6 7 8 9 10

方式二代碼如下:

#include <stdio.h>

int main(){
	int a[10], i;
	for(i = 0; i < 10; i++){
		scanf("%d", &a[i]);
	}
	printf("\n");
	
	for(i = 0; i < 10; i++){
		printf("%d ", *(a + i));
	}
	
	return 0;
}

運行效果與方式一相同。

方式三代碼如下:

#include <stdio.h>

int main(){
	int a[10], i, *p;
	for(i = 0; i < 10; i++){
		scanf("%d", &a[i]);
	}
	printf("\n");
	
	for(p = a; p < (a+10); p++){
		printf("%d ", *p);
	}
	
	return 0;
}

運行效果與之前相同。

3.用數組名作函數參數

形式爲:

f(int arr[], int n)

在編譯時是將arr按指針變量處理的,相當於將函數f的首部寫成f(int *arr, int n),這兩種寫法是等價的。

C語言調用函數時虛實結合的方法都是採用值傳遞方式,當用變量名作爲函數參數時傳遞的是變量的值,當用數組名作爲函數參數時,由於數組名代表的是數組首元素地址,因此傳遞的值是地址,所以要求形參爲指針變量。

練習:
將數組a中n個整數按相反順序存放,如下:
用數組名作函數參數練習
常規方式代碼如下:

#include <stdio.h>

int main(){
	void reverse(int x[], int n);
	int i, a[10] = {3, 7, 9, 11, 0, 6, 7, 5, 4, 2};
	printf("The original array:\n");
	for(i = 0; i < 10; i++){
		printf("%d ", a[i]);
	}
	printf("\n");
	reverse(a, 10);
	printf("The reversed array:\n");
	for(i = 0; i < 10; i++){
		printf("%d ", a[i]);
	}
	printf("\n");
	
	return 0;
}

void reverse(int x[], int n){
	int temp, i, j, m;
	m = (n - 1) / 2;
	for(i = 0; i <= m; i++){
		j = n - i - 1;
		temp = x[i];
		x[i] = x[j];
		x[j] = temp;
	}
}

打印:

The original array:
3 7 9 11 0 6 7 5 4 2
The reversed array:
2 4 5 7 6 0 11 9 7 3

指針方式代碼如下:

#include <stdio.h>

int main(){
	void reverse(int *a, int n);
	int i, a[10] = {3, 7, 9, 11, 0, 6, 7, 5, 4, 2};
	printf("The original array:\n");
	for(i = 0; i < 10; i++){
		printf("%d ", a[i]);
	}
	printf("\n");
	reverse(a, 10);
	printf("The reversed array:\n");
	for(i = 0; i < 10; i++){
		printf("%d ", a[i]);
	}
	printf("\n");
	
	return 0;
}

void reverse(int *x, int n){
	int temp, *i, *j, *p, m;
	m = (n - 1) / 2;
	i = x;
	j = x + n - 1;
	p = x + m;
	for( ; i <= p; i++, j--){
		temp = *i;
		*i = *j;
		*j = temp;
	}
}

與第一種方式運行效果相同。

練習:
從10個數中找出其中最大值和最小值。
常規方式代碼如下:

#include <stdio.h>

int max,min;

int main(){
	void find_max_min(int a[], int n);
	int i, a[10];
	printf("Input 10 numbers:\n");
	for(i = 0; i < 10; i++){
		scanf("%d", &a[i]);
	}
	find_max_min(a, 10);
	printf("max = %d, min = %d\n", max, min);
	
	return 0;
}

void find_max_min(int a[], int n){
	max = min = a[0];
	int i;
	for(i = 1;i < n;i++){
		if(a[i] > max){
			max = a[i];
		}
		else if(a[i] < min){
			min = a[i];
		}
	}
}

打印:

Input 10 numbers:
3 7 9 11 0 6 7 5 4 2
max = 11, min = 0

指針方式:

#include <stdio.h>

int max,min;

int main(){
	void find_max_min(int *a, int n);
	int i, a[10];
	printf("Input 10 numbers:\n");
	for(i = 0; i < 10; i++){
		scanf("%d", &a[i]);
	}
	find_max_min(a, 10);
	printf("max = %d, min = %d\n", max, min);
	
	return 0;
}

void find_max_min(int *a, int n){
	max = min = *a;
	int i;
	for(i = 1;i < n;i++){
		if(*(a + i) > max){
			max = *(a + i);
		}
		else if(*(a + i) < min){
			min = *(a + i);
		}
	}
}

數組名作函數參數歸納:
如果有一個實參數組,想在函數中改變此數組中的元素的值,實參與形參的對應關係有以下4種情況:
(1)形參和實參都用數組名,如:

void main(){
	int a[10];
	f(a, 10);
}

void f(int x[], int n){
	...
}

(2)實參用數組名,形參用指針變量,如:

void main(){
	int a[10];
	f(a, 10);
}

f(int *a, int n){
	...
}

(3)實參形參都用指針變量。例如:

void main(){
	int a[10], *p = a;
	f(a, 10);
}

void f(int *x, int n){
	...
}

(4)實參爲指針變量,形參爲數組名,如:

void main(){
	int a[10], *p = a;
	f(p, 10);
}

void f(int x[], int n){
	...
}

練習:
對數組中10個整數按由大到小順序排序。
代碼如下:

#include <stdio.h>

int main(){
	void sort(int x[], int n);
	int i, *p, a[10] = {3, 7, 9, 11, 0, 6, 7, 5, 4, 2};
	printf("The original array:\n");
	for(i = 0; i < 10; i++){
		printf("%d ", a[i]);
	}
	p = a;
	sort(p, 10);
	printf("\nThe sorted array:\n");
	for(i = 0; i < 10; i++){
		printf("%4d", *p);
		p++;
	}
	
	return 0;
}

void sort(int x[], int n){
	int i, j, k, t;
	for(i = 0;i < n - 1;i++){
		k = i;
		for(j = i + 1;j < n;j++){
			if(x[j] > x[k]){
				t = x[j];
				x[j] = x[k];
				x[k] = t;
			}
		}
	}
}

打印:

The original array:
3 7 9 11 0 6 7 5 4 2
The sorted array:
  11   9   7   7   6   5   4   3   2   0

4.多維數組與指針

基本概念

用指針變量可以指向一維數組中的元素,也可以指向多維數組中的元素;
但在概念上和使用上,多維數組的指針比一維數組的指針要複雜一些。

可以認爲二維數組是數組的數組,如定義int a[3][4] = {{1, 3, 5, 7}, {9, 11, 13, 15}, {17, 19, 21, 23}};,二維數組a是由3個一維數組所組成的。
設二維數組的首行的首地址爲2000,則有:
內存中存放二維數組

在二維數組中,常見表達式及其含義如下:
二維數組表達式及含義
練習:
輸出二維數組有關的值。
代碼如下:

#include <stdio.h>

int main(){
	int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
	
	printf("a: %d\n", a);

    printf("*a: %d\n", *a);

    printf("a[0]: %d\n", a[0]);

    printf("&a[0]: %d\n", &a[0]);

    printf("&a[0][0]: %d\n", &a[0][0]);

    printf("a+1: %d\n", a+1);

    printf("*(a+1): %d\n", *(a+1));

    printf("a[1]: %d\n", a[1]);

    printf("&a[1]: %d\n", &a[1]);

    printf("&a[1][0]: %d\n", &a[1][0]);

    printf("a+2: %d\n", a+2);

    printf("*(a+2): %d\n", *(a+2));

    printf("a[2]: %d\n", a[2]);

    printf("&a[2]: %d\n", &a[2]);

    printf("&a[2][0]: %d\n", &a[2][0]);

    printf("a[1]+1: %d\n", a[1]+1);

    printf("*(a+1)+1: %d\n", *(a+1)+1);

    printf("*(a[1]+1): %d\n", *(a[1]+1));

    printf("*(*(a+1)+1): %d\n", *(*(a+1)+1));
	
	return 0;
}

打印:

a: 6487536
*a: 6487536
a[0]: 6487536
&a[0]: 6487536
&a[0][0]: 6487536
a+1: 6487552
*(a+1): 6487552
a[1]: 6487552
&a[1]: 6487552
&a[1][0]: 6487552
a+2: 6487568
*(a+2): 6487568
a[2]: 6487568
&a[2]: 6487568
&a[2][0]: 6487568
a[1]+1: 6487556
*(a+1)+1: 6487556
*(a[1]+1): 6
*(*(a+1)+1): 6

指向多維數組元素的指針變量

把二維數組a分解爲一維數組a[0]、a[1]、a[2]之後,設p爲指向二維數組的指針變量,可定義爲:

int (*p)[4]

它表示p是一個指針變量,它指向包含4個元素的一維數組。
若指向第一個一維數組a[0],其值等於a、a[0]、&a[0][0]等。
而p+i則指向一維數組a[i]。

*(p+i)+j是二維數組i行j列的元素的地址,而*(*(p+i)+j)則是i行j列元素的值。
二維數組指針變量說明的一般形式爲:

類型說明符  (*指針變量名)[長度]

其中類型說明符爲所指數組的數據類型,*表示其後的變量是指針類型,長度表示二維數組分解爲多個一維數組時,一維數組的長度,也就是二維數組的列數。

練習:
用指針變量輸出二維數組元素的值。
代碼如下:

#include <stdio.h>

int main(){
	int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
	int (*p)[4];
	int i, j;
	p = a;
	for(i = 0;i < 3;i++){
		for(j = 0;j < 4;j++){
			printf("%4d", *(*(p + i) + j));
		}
		printf("\n");
	}
	
	return 0;
}

打印:

   1   2   3   4
   5   6   7   8
   9  10  11  12

練習:
通過輸入指定行數和列數打印出二維數組對應任一行任一列元素的值。
代碼如下:

#include <stdio.h>

int main(){
	int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
	int (*p)[4], i, j;
	p = a;
	printf("i = ");
	scanf("%d", &i);
	while(i < 0 || i >= 3){
		printf("i = ");
		scanf("%d", &i);
	}
	printf("j = ");
	scanf("%d", &j);
	while(j < 0 || j >= 4){
		printf("j = ");
		scanf("%d", &j);
	}
	printf("a[%d, %d] = %d\n", i, j, *(*p+i)+j);
	
	return 0;
}

打印:

i = 3
i = 2
j = -1
j = 2
a[2, 2] = 5

四、字符串與指針

1.字符串定義

字符數組形式:
用字符數組存放一個字符串。

練習:
定義一個字符數組,對它初始化,然後輸出該字符串。
代碼如下:

#include <stdio.h>

int main(){
	char string[] = "I love China!";
	printf("%s", string);
	
	return 0;
}

打印:

I love China!

存放原理如下:
字符數組定義字符串

指針形式:
用字符指針指向一個字符串。

練習:
定義一個字符指針,用字符指針指向字符串中的字符。
代碼如下:

#include <stdio.h>

int main(){
	char *string = "I love China!";
	printf("%s", string);
	
	return 0;
}

打印:

I love China!

2.字符串中字符的存取方法

對字符串中字符的存取,可以用下標方法,也可以用指針方法。

練習:
將字符串a複製爲字符串b。
下標法:

#include <stdio.h>

int main(){
	char a[] = "I love China!",b[40];
	int i;
	for(i = 0;*(a+i) != '\0';i++){
		*(b+i) = *(a+i);
	}
	*(b+i) = '\0';
	printf("String a is: \n%s\n", a);
	printf("String b is: \n");
	for(i=0;b[i]!='\0';i++){
		printf("%c", b[i]);
	}
	
	return 0;
}

打印:

String a is:
I love China!
String b is:
I love China!

指針方法:

#include <stdio.h>

int main(){
	char a[] = "I love China!",b[40], *p1, *p2;
	p1 = a;
	p2 = b;
	for( ;*p1 != '\0';p1++, p2++){
		*p2 = *p1;
	}
	*p2 = '\0';
	printf("String a is: \n%s\n", a);
	printf("String b is: \n");
	int i;
	for(i=0;b[i]!='\0';i++){
		printf("%c", b[i]);
	}
	
	return 0;
}

執行效果與下標法相同。

3.字符指針作函數參數

練習:
用函數調用實現字符串的複製。
用字符數組做參數代碼如下:

#include <stdio.h>

int main(){
	void copy_string(char from[], char to[]); 
	char a[] = "I am a teacher.", b[] = "You are a student.";
	printf("String a = %s\nString B = %s\n", a, b);
	printf("Copy string a to string b:\n");
	copy_string(a, b);
	printf("String a = %s\nString B = %s\n", a, b);
	
	return 0;
}

void copy_string(char from[], char to[]){
	int i = 0;
	while(from[i] != '\0'){
		to[i] = from[i];
		i++;
	}
	to[i] = '\0';
}

打印:

String a = I am a teacher.
String B = You are a student.
Copy string a to string b:
String a = I am a teacher.
String B = I am a teacher.

形參用字符指針變量代碼如下:

#include <stdio.h>

int main(){
	void copy_string(char *from, char *to); 
	char *a = "I am a teacher.", b[] = "You are a student.";
	printf("String a = %s\nString B = %s\n", a, b);
	printf("Copy string a to string b:\n");
	copy_string(a, b);
	printf("String a = %s\nString B = %s\n", a, b);
	
	return 0;
}

void copy_string(char *from, char *to){
	for( ; *from != '\0'; from++, to++){
		*to = *from;
	}
	*to = '\0';
}

運行效果與之前相同。
注意:在定義和初始化b數組時不能通過指針方式,因爲指針方式定義的數組爲常量,存儲在常量區,不可改變,因此調用函數改變b數組時會出現異常。

代碼還可以進行簡化或改寫:
簡化1:

#include <stdio.h>

int main(){
	void copy_string(char *from, char *to); 
	char *a = "I am a teacher.", b[] = "You are a student.";
	printf("String a = %s\nString B = %s\n", a, b);
	printf("Copy string a to string b:\n");
	copy_string(a, b);
	printf("String a = %s\nString B = %s\n", a, b);
	
	return 0;
}

void copy_string(char *from, char *to){
	while((*to = *from) != '\0'){
		to++;
		from++;
	}
}

簡化2:

#include <stdio.h>

int main(){
	void copy_string(char *from, char *to); 
	char *a = "I am a teacher.", b[] = "You are a student.";
	printf("String a = %s\nString B = %s\n", a, b);
	printf("Copy string a to string b:\n");
	copy_string(a, b);
	printf("String a = %s\nString B = %s\n", a, b);
	
	return 0;
}

void copy_string(char *from, char *to){
	while((*to++ = *from++) != '\0'){
		;
	}
}

簡化3:

#include <stdio.h>

int main(){
	void copy_string(char *from, char *to); 
	char *a = "I am a teacher.", b[] = "You are a student.";
	printf("String a = %s\nString B = %s\n", a, b);
	printf("Copy string a to string b:\n");
	copy_string(a, b);
	printf("String a = %s\nString B = %s\n", a, b);
	
	return 0;
}

void copy_string(char *from, char *to){
	while(*from != '\0'){
		*to++ = *from++;
	}
	*to = '\0';
}

簡化4:

#include <stdio.h>

int main(){
	void copy_string(char *from, char *to); 
	char *a = "I am a teacher.", b[] = "You are a student.";
	printf("String a = %s\nString B = %s\n", a, b);
	printf("Copy string a to string b:\n");
	copy_string(a, b);
	printf("String a = %s\nString B = %s\n", a, b);
	
	return 0;
}

void copy_string(char *from, char *to){
	while(*to++ = *from++){
		;
	}
}

簡化5:

#include <stdio.h>

int main(){
	void copy_string(char *from, char *to); 
	char *a = "I am a teacher.", b[] = "You are a student.";
	printf("String a = %s\nString B = %s\n", a, b);
	printf("Copy string a to string b:\n");
	copy_string(a, b);
	printf("String a = %s\nString B = %s\n", a, b);
	
	return 0;
}

void copy_string(char *from, char *to){
	for( ; *to++ = *from++; ){
		;
	}
}

簡化6:

#include <stdio.h>

int main(){
	void copy_string(char *from, char *to); 
	char *a = "I am a teacher.", b[] = "You are a student.";
	printf("String a = %s\nString B = %s\n", a, b);
	printf("Copy string a to string b:\n");
	copy_string(a, b);
	printf("String a = %s\nString B = %s\n", a, b);
	
	return 0;
}

void copy_string(char *from, char *to){
	char *p1, *p2;
	p1 = from;
	p2 = to;
	while((*p2++ = *p1++) != '\0'){
		;
	}
}

這個方式變得稍複雜了點,重在說明方法。

4.字符指針變量和字符數組的比較

雖然用字符數組和字符指針變量都能實現字符串的存儲和運算,但它們二者之間是有區別的,不應混爲一談。
主要概括起來有以下幾點:
(1)字符數組由若干個元素組成,每個元素中放一個字符,而字符指針變量中存放的是地址(字符串第1個字符的地址),而不是將字符串放到字符指針變量中。
(2)賦值方式:
對字符數組只能對各個元素賦值,不能用以下辦法對字符數組賦值。

char  str[20];
str = "I love China!";

而對字符指針變量,可以採用下面方法賦值:

char *a;
a = "I love China!";

但注意賦給a的不是字符,而是字符串第一個元素的地址。
(3)初始化
對字符指針變量賦初值char *a = "I love China!";等價於

char *a;
a = "I love China!";

而對數組的初始化char str[20] = {"I love China!"};不能等價於

char str[20];
str[] = "I love China!";

(4)如果定義了一個字符數組,在編譯時爲它分配內存單元,它有確定的地址;而定義一個字符指針變量時,給指針變量分配內存單元,在其中可以放一個字符變量的地址也就是說,該指針變量可以指向一個字符型數據,但如果未對它賦予一個地址值,則它並未具體指向一個確定的字符數據。
例如:

char str[10];
scanf("%s", str);

是可以的,下面的方式:

char *a;
scanf("%s", a);

雖然一般也能運行,但這種方法是危險的。
(5)指針變量的值是可以改變的。

改變指針變量的值測試:

#include <stdio.h>

int main(){
	char *a = "I am Corley!!";
	printf("%s\n", a);
	a += 7;
	printf("%s\n", a);
	
	return 0;
}

打印:

I am Corley!!
rley!!

若定義了一個指針變量,並使它指向一個字符串,就可以用下標形式引用指針變量所指的字符串中的字符。

下標形式引用指針變量測試:

#include <stdio.h>

int main(){
	char *a = "I am Corley!!";
	int i;
	printf("The sixth character is %c\n", a[5]);
	for(i = 0; a[i] != '\0'; i++){
		printf("%c", a[i]);
	}
	printf("\n");
	
	return 0;
}

打印:

The sixth character is C
I am Corley!!

五、指向函數的指針

1.用函數指針變量調用函數

可以用指針變量指向整型變量、字符串、數組,也可以指向一個函數。一個函數在編譯時被分配給一個入口地址,這個函數的入口地址就稱爲函數的指針。

練習:
常規方式代碼如下:

#include <stdio.h>

#if(1)
int main(){
	int max(int, int);
	int a, b, c;
	
	scanf("%d %d", &a, &b);
	c = max(a, b);
	printf("a = %d, b = %d, max = %d\n", a, b, c);
	
	return 0;
}
#endif

int max(int a, int b){
	return a > b ? a : b;
}

打印:

12 20
a = 12, b = 20, max = 20

指針方式:

#include <stdio.h>

#if(1)
int main(){
	int max(int, int);
	int a, b, c;
	int (*p)();
	p = max;
	scanf("%d %d", &a, &b);
	c = (*p)(a, b);
	printf("a = %d, b = %d, max = %d\n", a, b, c);
	
	return 0;
}
#endif

int max(int a, int b){
	return a > b ? a : b;
}

執行效果與常規方式相同。

2.用指向函數的指針作函數參數

函數指針變量常用的用途之一是把指針作爲參數傳遞到其他函數。
函數的參數可以是變量、指向變量的指針變量、數組名、指向數組的指針變量等;
指向函數的指針也可以作爲參數,以實現函數地址的傳遞,這樣就能夠在被調用的函數中使用實參函數。

//實參函數名     f1             f2
//             ↓               ↓
void  sub(int (*x1)(int), int (*x2)(int,int)){
	int a, b, i, j;
	a = (*x1)(i);
	b = (*x2)(i, j);
	...
}

其大致原理如下:
有一個函數(假設函數名爲sub),它有兩個形參(x1和x2),定義x1和x2爲指向函數的指針變量。在調用函數sub時,實參爲兩個函數名f1和f2,給形參傳遞的是函數f1和f2的地址,這樣在函數sub中就可以調用f1和f2函數了。

練習:
設一個函數process,在調用它的時候,每次實現不同的功能(有點類似多態)。
輸入a和b兩個數,第一次調用process時找出a和B中大者,第二次找出其中小者,第三次求a與b之和。
代碼如下:

#include <stdio.h>

int main(){
	int max(int, int);
	int min(int, int);
	int sum(int, int);
	void process(int, int, int(*fun)());
	int a, b;
	printf("Input a and b:\n");
	scanf("%d %d", &a, &b);
	printf("Max = ");
	process(a, b, max);
	
	printf("Min = ");
	process(a, b, min);
	
	printf("Sum = ");
	process(a, b, sum);
	
	return 0;
}

int max(int a, int b){
	return a > b ? a : b;
}

int min(int a, int b){
	return a < b ? a : b;
}

int sum(int a, int b){
	return a + b;
}

void process(int x, int y, int(*fun)()){
	int result = (*fun)(x, y);
	printf("%d\n", result);
}

打印:

Input a and b:
12 20
Max = 20
Min = 12
Sum = 32

3.返回指針值的函數

一個函數可以帶回一個整型值、字符值、實型值等,也可以返回指針型的數據,即地址。
這種帶回指針值的函數一般定義形式爲:

類型名  *函數名(參數表列);

例如int *a(int x, int y);

練習:
有若干個學生的成績(每個學生有4門課程),要求在用戶輸入學生序號以後,能輸出該學生的全部成績,用指針函數來實現。
代碼如下:

#include <stdio.h>

int main(){
	double scores[][4] = {{60.0, 70.0, 80.5, 90.5}, {56.0, 89.0, 67.0, 88.0}, {34.2, 78.5, 90.5, 66.0}};
	double *search(double(*pointer)[4], int n);
	double *p;
	int i, m;
	printf("Please input the number of student:");
	scanf("%d", &m);
	printf("The scores of No.%d are:\n", m);
	p = search(scores, m);
	for(i = 0; i < 4; i++){
		printf("%8.2f", *(p+i));
	}
	
	return 0;
}

double *search(double(*pointer)[4], int n){
	return *(pointer + n);
}

打印:

Please input the number of student:2
The scores of No.2 are:
   34.20   78.50   90.50   66.00

改進:
對於前面的練習,找出其中有不及格課程的學生及其學生號。
代碼如下:

#include <stdio.h>

int main(){
	double scores[][4] = {{60.0, 70.0, 80.5, 90.5}, {56.0, 89.0, 67.0, 88.0}, {34.2, 78.5, 90.5, 66.0}};
	void *search(double(*pointer)[4], int n);
	double *p;
	int i, m;
	search(scores, 3);
	
	return 0;
}

void *search(double(*pointer)[4], int n){
	int i, j;
	for(i = 0; i < n;i++){
		double *p = *(pointer + i);
		for(j = 0; j < 4; j++){
			if(*(p+j) < 60){
				printf("No.%d has score below 60.\n", i);
				break;
			} 
		}
	}
}

打印:

No.1 has score below 60.
No.2 has score below 60.

指針函數和函數指針的區別

這兩個概念都是簡稱:
指針函數是指帶指針的函數,即本質是一個函數;
函數指針是指向函數的指針變量,因而函數指針本身首先應是指針變量,只不過該指針變量指向函數。

4.指針數組和指向指針的指針

指針數組的概念:
一個數組,若其元素均爲指針類型數據,稱爲指針數組,也就是說,指針數組中的每一個元素都相當於一個指針變量。
一維指針數組的定義形式爲

類型名 *數組名[數組長度];

例如int *name[4];

練習:

#include <stdio.h>

int main(){
	int a[5] = {1, 2, 3, 4, 5};
	int *p[5] = {&a[0], &a[1], &a[2], &a[3], &a[4]};
	int i;
	for(i = 0; i < 5; i++){
		printf("%5d", *p[i]);
	}
	printf("\n");
	
	return 0;
}

打印:

    1    2    3    4    5

練習:
將下邊字符串按字母順序(由小到大)輸出:

{“baidu.com”, “www.baidu.com”, “pan.baidu.com”, “baidu.com/profile”}

實現思路:
聲明一個數組指針來指向;
將排序利用strcmp()函數來解決;
各個功能抽象爲函數或文件。

代碼如下:

#include <stdio.h>
#include <string.h>

int main(){
	void sort(char *name[], int n);
	void print(char *name[], int n);
	
	char *name[] = {"baidu.com", "www.baidu.com", "pan.baidu.com", "baidu.com/profile"};
	int n = 4;
	sort(name, n);
	print(name, n);
	
	return 0;
}

void sort(char *name[], int n){
	char *temp;
	int i, j, k;
	for(i = 0; i < n - 1; i++){
		k = i;
		for(j = i + 1; j < n; j++){
			if(strcmp(name[k], name[j]) > 0){
				k = j;
			}
			if(k != i){
				temp = name[i];
				name[i] = name[k];
				name[k] = temp;
			}
		}
	}
}

void print(char *name[], int n){
	int i;
	for(i = 0; i < n; i++){
		printf("%s\n", name[i]);
	}
}

打印:

baidu.com
baidu.com/profile
pan.baidu.com
www.baidu.com

定義一個指向指針數據的指針變量,形式下如:

類型名 **指針變量名;

例如char **p;
p的前面有兩個*號,*運算符的結合性是從右到左,因此**p相當於*(*p),顯然*p是指針變量的定義形式。
如果沒有最前面的*,那就是定義了一個指向字符數據的指針變量;
現在它前面又有一個*號,表示指針變量p是指向一個字符指針變量的,*p就是p所指向的另一個指針變量。

練習:

#include <stdio.h>
#include <string.h>

int main(){
	char *name[] = {"baidu.com", "www.baidu.com", "pan.baidu.com", "baidu.com/profile"};
	char **p;
	int i;
	for(i = 0; i < 4; i++){
		p = name + i;
		printf("%s\n", *p);
	}
	
	return 0;
}

打印:

baidu.com
www.baidu.com
pan.baidu.com
baidu.com/profile

5.指針數組作爲main函數的形參

指針數組的一個重要應用是作爲main函數的形參,在之前的程序中,main函數的第一行一般寫成以下形式:

void  main(){
	...
}

括號中是空的,實際上,main函數可以有參數,如void main(int argc, char *argv[]),argc和argv就是main函數的形參。
main函數是由操作系統調用的,其形參的值不是在程序中得到,實際上實參是和命令一起給出的,也就是在命令行中包括命令名和需要傳給main函數的參數。
命令行的一般形式爲:

命令名 參數1 參數2 …… 參數n

練習:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]){
	int i;
	printf("The number of string is: %d\n", argc - 1);
	for(i = 1;i < argc; i++){
		printf("The string %d is: %s\n", i, argv[i]);
	}
	
	return 0;
}

編譯生成.exe可執行文件(這裏文件名爲pointer.exe),在命令行當前路徑下執行:

pointer C Java Python

打印:

The number of string is: 3
The string 1 is: C
The string 2 is: Java
The string 3 is: Python

六、指針小結

1.數據類型小結

指針數據類型小結

2.指針運算小結

(1)指針變量加(減)一個整數
例如p++、p–、p+i、p-i、p+=i、p-=i等。

(2)指針變量賦值:
將一個變量地址賦給一個指針變量,如:

p = &a;				// 將變量a的地址賦給p
p = array;			// 將數組array首元素地址賦給p
p = &array[i];		// 將數組array第i個元素的地址賦給p
p = max;			// max爲已定義的函數,將max的入口地址賦給p
p1 = p2; 			// p1和p2都是指針變量,將p2的值賦給p1

(3)指針變量可以有空值,即該指針變量不指向任何變量,可以表示爲p = null;

(4)兩個指針變量可以相減:
如果兩個指針變量都指向同一個數組中的元素,則兩個指針變量值之差是兩個指針之間的元素個數 ,如下:
指針相減
圖中p2-p1的值爲3。

(5)兩個指針變量比較
若兩個指針指向同一個數組的元素,則可以進行比較,指向前面的元素的指針變量小於指向後面元素的指針變量。

3.void類型和const修飾指針

void真正發揮的作用在於:

  • 對函數返回的限定;
  • 對函數參數的限定。

例如void abc(void);

ANSI C新標準增加了一種void指針類型,即不指定它是指向哪一種類型數據的指針變量。
例如,void *p;表示指針變量p不指向一個確定的類型數據,它的作用僅僅是用來存放一個地址。

void指針可以指向任何類型數據,也就是說,可以用任何類型的指針直接給void指針賦值;
但是,如果需要將void指針的值賦給其他類型的指針,則需要進行強制類型轉換。

const指針:
當用const修飾指針時,根據const位置的不同有三種效果。
原則是:修飾誰,誰的內容就不可變,其他的都可變。

在定義const char *str = "Welcome to China!!\n";時,通過str = "Welcome to Beijing!!\n";改變字符串是合法的,而通過str[0] = 'w';改變字符串是不合法的;
在定義char * const str = "Welcome to China!!\n";時,通過str[0] = 'w';改變字符串是合法的,通過str = "Welcome to Beijing!!\n";改變字符串是不合法的;
在定義const char * const str = "Welcome to China!!\n";時,通過str[0] = 'w';str = "Welcome to Beijing!!\n";改變字符串是都是不合法的。

擴展-memcpy

memcpy是memory copy的縮寫,意爲內存複製,在寫C語言程序的時候,常常會用到它。
函數原型如下:

void *memcpy(void *dest, const void *src, size_t n);

功能是從src的開始位置拷貝n個字節的數據到dest,如果dest存在數據,將會被覆蓋。
memcpy函數的返回值是dest的指針。
memcpy函數定義在string.h頭文件裏。

練習:

#include <memory.h>
#include <string.h>
#include <stdio.h>

char string1[60] = "The quick brown dog jumps over the lazy fox";
char string2[60] = "The quick brown fox jumps over the lazy dog";

void main()
{
   printf( "Function:\tmemcpy without overlap\n" );
   printf( "Source:\t\t%s\n", string1 + 40 );
   printf( "Destination:\t%s\n", string1 + 16 );
   memcpy( string1 + 16, string1 + 40, 3 );
   printf( "Result:\t\t%s\n", string1 );
   printf( "Length:\t\t%d characters\n\n", strlen(string1));

   memcpy( string1 + 16, string2 + 40, 3 );

   printf( "Function:\tmemmove with overlap\n" );
   printf( "Source:\t\t%s\n", string2 + 4 );
   printf( "Destination:\t%s\n", string2 + 10 );
   memmove( string2 + 10, string2 + 4, 40 );
   printf( "Result:\t\t%s\n", string2 );
   printf( "Length:\t\t%d characters\n\n", strlen(string2));

   printf( "Function:\tmemcpy with overlap\n" );
   printf( "Source:\t\t%s\n", string1 + 4 );
   printf( "Destination:\t%s\n", string1 + 10 );
   memcpy( string1 + 10, string1 + 4, 40 );
   printf( "Result:\t\t%s\n", string1 );
   printf( "Length:\t\t%d characters\n\n", strlen(string1));
}

打印:

Function:       memcpy without overlap
Source:         fox
Destination:    dog jumps over the lazy fox
Result:         The quick brown fox jumps over the lazy fox
Length:         43 characters

Function:       memmove with overlap
Source:         quick brown fox jumps over the lazy dog
Destination:    brown fox jumps over the lazy dog
Result:         The quick quick brown fox jumps over the lazy dog
Length:         49 characters

Function:       memcpy with overlap
Source:         quick brown dog jumps over the lazy fox
Destination:    brown dog jumps over the lazy fox
Result:         The quick quick brown dog jumps over the lazy fox
Length:         49 characters


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章