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(p, 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个元素的一维数组。
p指向第一个一维数组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] = 11

四、字符串与指针

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);
	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


展开阅读全文

Python数据分析与挖掘

01-08
92讲视频课+16大项目实战+源码+¥800元课程礼包+讲师社群1V1答疑+社群闭门分享会=99元   为什么学习数据分析?       人工智能、大数据时代有什么技能是可以运用在各种行业的?数据分析就是。       从海量数据中获得别人看不见的信息,创业者可以通过数据分析来优化产品,营销人员可以通过数据分析改进营销策略,产品经理可以通过数据分析洞察用户习惯,金融从业者可以通过数据分析规避投资风险,程序员可以通过数据分析进一步挖掘出数据价值,它和编程一样,本质上也是一个工具,通过数据来对现实事物进行分析和识别的能力。不管你从事什么行业,掌握了数据分析能力,往往在其岗位上更有竞争力。    本课程共包含五大模块: 一、先导篇: 通过分析数据分析师的一天,让学员了解全面了解成为一个数据分析师的所有必修功法,对数据分析师不在迷惑。   二、基础篇: 围绕Python基础语法介绍、数据预处理、数据可视化以及数据分析与挖掘......这些核心技能模块展开,帮助你快速而全面的掌握和了解成为一个数据分析师的所有必修功法。   三、数据采集篇: 通过网络爬虫实战解决数据分析的必经之路:数据从何来的问题,讲解常见的爬虫套路并利用三大实战帮助学员扎实数据采集能力,避免没有数据可分析的尴尬。   四、分析工具篇: 讲解数据分析避不开的科学计算库Numpy、数据分析工具Pandas及常见可视化工具Matplotlib。   五、算法篇: 算法是数据分析的精华,课程精选10大算法,包括分类、聚类、预测3大类型,每个算法都从原理和案例两个角度学习,让你不仅能用起来,了解原理,还能知道为什么这么做。
©️2020 CSDN 皮肤主题: 黑客帝国 设计师: 上身试试 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值