C语言之指针学习

Posted 二木成林

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言之指针学习相关的知识,希望对你有一定的参考价值。

 

目录

指针

地址指针的基本概念

数据类型的字节数

关于直接访问和间接访问

指针

认识操作符"*"和"&"

变量的指针和指向变量的指针变量

定义一个指针变量

在定义指针变量时需要注意的情况

指针变量的引用

对"&"和"*"运算符的说明

指针变量作为函数参数

指针变量几个问题的进一步说明

数组指针和指向数组的指针变量

指向数组元素的指针

通过指针引用数组元素

数组名作函数参数

指向多维数组的指针和指针变量

总结

指向多维数组元素的指针变量

字符串的指针指向字符串的指针变量

字符串的表示形式

字符串中字符的存取方法

字符指针作为函数参数

对使用字符指针变量和字符数组的讨论

函数指针变量

函数指针变量概念

用指向函数的指针作函数参数

返回指针值的函数

指针函数和函数指针的区别

函数参数传递的三种方式(*)

指针数组和指向指针的指针

指针数组的概念

指向指针的指针

有关指针的数据类型和指针运算的小结

有关指针的数据类型的小结

指针运算小结

使用指针遇到的问题总结

字符指针变量指向的字符串常量中的内容是不能修改(不能对它再赋值)

字符指针变量作为形参在函数内是否修改值

关于&和*在函数内的相互使用


 

指针

地址指针的基本概念

数据类型的字节数

首先在计算机中,所有的数据都是存放在存储器中的。一般把存储器中的一个字节称为一个内存单元,不同的数据类型所占用的内存单元数不等。如整型量占2个单元,字符量占用1个单元等。

可以用下面的代码来查看各种数据类型占用的字节,即占用的内存单元(不同的电脑和编译环境可能有所不同):

#include <stdio.h>

void main() {
	printf("Size of int is:%d\\n", sizeof(int));
	printf("Size of unsigned int is:%d\\n", sizeof(unsigned int));
	printf("Size of short is:%d\\n", sizeof(short));
	printf("Size of unsigned short is:%d\\n", sizeof(unsigned short));
	printf("Size of long is:%d\\n", sizeof(long));
	printf("Size of unsigned long is:%d\\n", sizeof(unsigned long));
	printf("Size of long long is:%d\\n", sizeof(long long));
	printf("Size of unsigned long long is:%d\\n", sizeof(unsigned long long));
	printf("Size of char is:%d\\n", sizeof(char));
	printf("Size of signed char is:%d\\n", sizeof(signed char));
	printf("Size of unsigned char is:%d\\n", sizeof(unsigned char));
	printf("Size of float is:%d\\n", sizeof(float));
	printf("Size of double is:%d\\n", sizeof(double));
	printf("Size of long double is:%d\\n", sizeof(long double));
}

在上面知道了内存单元的概念后,为了正确地访问这些内存单元,必须为每个内存单元编上号。根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址。既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。

根据下面的代码,但现在先不必关心代码的具体内容,可以对地址有个更直观的感受:

#include <stdio.h>

void main(){
	int a=100;		// 4字节 
	int b=5; 	    // 4字节 
	int c=1;   	    // 4字节 
	double d=14.0;	// 8字节 
	double e=1.23;  // 8字节
	double f=12.5;  // 8字节 
	 
	printf("| 变量名 |     值      |       地址       |\\n");
	printf("|--------|-------------|------------------|\\n");
	printf("|    a   |     %d     |    %d       |\\n", a, &a);
	printf("|--------|-------------|------------------|\\n");
	printf("|    b   |     %d       |    %d       |\\n", b, &b);
	printf("|--------|-------------|------------------|\\n");
	printf("|    c   |     %d       |    %d       |\\n", c, &c);
	printf("|--------|-------------|------------------|\\n");
	printf("|    d   |     %1.1f    |    %d       |\\n", d, &d);
	printf("|--------|-------------|------------------|\\n");
	printf("|    e   |     %1.2f    |    %d       |\\n", e, &e);
	printf("|--------|-------------|------------------|\\n");
	printf("|    f   |     %1.1f    |    %d       |\\n", f, &f);
	printf("|--------|-------------|------------------|\\n");
}

代码解释:

  • 其中a变量是一个int变量,从上面代码中知道占4个字节,这里看不出来,但是其他的数据类型看得出来。
  • 其中b变量是一个int变量,地址是6487580,距离a变量的地址为4字节。
  • c变量也是int变量,地址是6487572,距离b变量的地址也是4字节。
  • d变量是double型变量,距离c变量的地址是12字节,并不是double数据类型所占据的8字节。(至于为什么是12而不是8,暂时不清楚,不过可以不用关注这个点,C语言也没有规定说明变量必须是连续存储的。
  • e变量也是double型变量,距离d变量的地址是8字节,相同数据类型的变量看起来是连续存储的。
  • f变量同样如此。

需要注意的是,内存单元的指针和内存单元的内容是不同的概念。

对于一个内存单元来说,单元的地址即为指针,其中存放的数据才是该单元的内容。在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。

关于直接访问和间接访问

  • 直接访问如:a=5;

系统在编译时,已经对变量分配了地址,例如,若变量a分配的地址是2000,则该语句的作用就是把常数5保存到地址为2000的单元。

#include <stdio.h>

void main(){
	// 这是一个名为a的整型变量
	// 该变量的值是5
	// 而为变量a分配的地址是6487580
	// 该语句的作用就是把常数5保存到地址为6487580的单元 
	int a=5;
	// 这是一个名为b的整型变量
	// 该变量的值是6
	// 而为变量b分配的地址是6487576
	// 该语句的作用就是把常数6保存到地址为6487576的单元 
	int b=6;
	// 这是一个名为c的整型变量
	// 该变量的值是7
	// 而为变量c分配的地址是6487572
	// 该语句的作用就是把常数7保存到地址为6487572的单元
	int c=7;
	// 通过&a、&b、&c来输出变量a、b、c的地址 
	printf("&a=%d, &b=%d, &c=%d",&a,&b,&c);
} 

结果:

  • 间接访问如:scanf("%d", &a);

调用函数时,把变量a的地址传弟给函数scanf,函数首先把该地址保存到一个单元中,然后把从键盘接收的数据通过所存储的地址保存到a变量中。

#include <stdio.h>

void main(){
	int a;
	scanf("%d", &a);
	printf("您输入一个整数:%d", a);
} 

指针

在C语言中,指针是一个特殊的变量,存储地址。

假设我们定义了一个指针变量int *i_pointer用来存放整型变量i的地址。可以通过语句i_pointer=&i;。这里的*符号是用来标志该变量是一个指针变量的。

将i的地址(2000)存放到i_pinter中。这时,i_pointer的值就是(2000),即变量i所占用单元的起始地址。

要存取变量i的值,可以采用间接方式:先找到存放"i的地址"的变量i_pointer,从中取出i的地址(2000),然后就可以取出i的值了。

图形表示:

#include <stdio.h>

void main(){
	// 这是一个名为i,值为3的普通变量,可以通过&i打印出该变量的地址 
	int i=3;
	printf("变量i的地址为:%d\\n", &i);
	
	// 这里使用*符号声明了一个指针变量i_pointer 
	int *i_pointer; 
	// &i的意思是取变量i的地址,那么这句就是将i变量的地址赋值给指针变量i_pointer 
	i_pointer=&i;
	// 那么i_pointer的值就是变量i的地址,下面打印的也是i的地址 
	printf("变量i_pointer的值为:%d\\n", i_pointer);
	// 通过&i_pointer可以打印指针变量i_pointer的地址 
	printf("变量i_pointer的地址为:%d", &i_pointer); 
} 

认识操作符"*"和"&"

  • *:取值操作符
  • &:取址操作符
#include <stdio.h>

void main(){
	// 声明一个普通变量 
	int i=2000;
	// 声明一个指针变量,这里的*不是取值操作符使用 
	int *pointer;
	pointer=&i;// 将变量i的地址赋给指针变量pointer作为值
	printf("指针变量pointer所指向变量的值为:%d\\n", *pointer); 
	
	// 其他输出,通过这些输出更能深刻体会指针变量 
	printf("普通变量i的值为:%d\\n", i);
	printf("普通变量i的地址为:%d\\n", &i);
	// printf("普通变量i的值为:%d\\n", *i);// 该句代码运行会报错,因为变量i只是一个普通变量,不能使用取值操作符*。 
	printf("指针变量pointer的值为:%d\\n", pointer);// 由于pointer是一个指针变量,所以使用普通变量的打印值方式输出的就是变量i的地址
	printf("指针变量pointer的地址为:%d\\n", &pointer);// 无论是普通变量还是指针变量都可以通过&符号取该变量的地址 
}

代码解释:

  • 如果要声明一个指针变量,必须要使用*标识符,例如int *pointer。而int pointer就表示的是一个普通变量。
  • &是一个取址操作符,取的就是变量的地址,无论是普通变量还是指针变量都可以取地址,例如上面代码中的&i&pointer
  • 如果要输出变量的值,都可以直接使用变量名即可,无论是指针变量还是普通变量,例如上面代码中的ipointer
  • 但如果要输出指针变量所指向变量的值,那么就需要用到了取值操作符*了,例如上面代码中的*pointer
  • 注意,普通变量不能使用*取值操作符,例如上面代码中的*i,那么运行编译会报错。

变量的指针和指向变量的指针变量

指针与指针变量是有区别的,如果知道了一个变量的地址(内存中的地址),那么就可以通过这个地址来访问这个变量了,因此,就把变量的地址称为该变量的“指针”。

C语言中可以声明一类特殊的变量,这类变量专门用来存放普通变量的地址,称为指针变量。

注意:指针变量的值(即指针变量中存放的值)是普通变量的地址(即指针)。

严格地说,一个指针是一个地址,是一个常量。而一个指针变量却可以被赋予不同的指针值,是变量。但常把指针变量简称为指针。为了避免混淆,我们中约定:“指针”是指地址,是常量,“指针变量”是指取值为地址的变量。定义指针的目的是为了通过指针去访问内存单元。

定义一个指针变量

对指针变量的定义包括三个内容:

  • (1)  指针类型说明,即定义变量为一个指针变量;
  • (2)  指针变量名;
  • (3)  变量值(指针)所指向的变量的数据类型。

其一般形式为:

类型说明符  *变量名;

其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。

下面都是合法的指针变量定义,例如:

// pointer是指向float型变量的指针变量
float *pointer;

// c是指向字符型变量的指针变量
char *c;

可以用赋值语句使一个指针变量得到另一个变量的地址,从而使它指向一个该变量。

在定义指针变量时需要注意的情况

一、指针变量前面的"*",表示该变量的类型为指针型变量。

其一般形式为:

类型说明符 *变量名;

其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。

例如:float *pointer_1。注意,指针变量名是pointer_1,而不是*pointer_1。

二、在定义指针变量时必须指定基类型。

需要特别注意的是,只有整型变量的地址才能放到指向整型变量的指针变量中。

下面的赋值是错误的:

float a;
int *pointer_1;
pointer_1=&a;
/* 将float型变量的地址放到指向整型变量的指针变量中,是错误的 */

指针变量的引用

指针变量使用前不仅需要定义说明,而且必须赋予具体的值,未经赋值的指针变量不能使用,否则将造成系统混乱,甚至死机。

指针变量的赋值只能赋予地址,决不能赋予任何其它数据,否则将引起错误。

在C语言中,变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。

注意:指针变量中只能存放地址(指针),不要将一个整数(或任何其他非地址类型的数据)赋给一个指针变量,否则编译器也会把该值当成一个地址来处理。

C语言中提供了地址运算符&来表示变量的地址。

其一般形式为:

&变量名;

例如&a表示变量a的地址,&b表示变量b的地址,当然,变量本身是需要预先声明的。

(1)指针变量初始化的方法

int *p=&a;

(2)赋值语句的方法

int a;
int *p;
p=&a;

// 不允许将一个数赋予指针变量,故下面的赋值是错误的
int *p;
p=1000;

// 被赋值的指针变量前不能再加“*”说明符,如下写法也是错误的。
*p=&a;

以下是一个实例:

#include <stdio.h>

void main(){
	/* 定义普通变量和指针变量 */ 
	int a,b;// 普通变量a和b
	int *pointer_1,*pointer_2;// 指针变量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); 
} 
/**
 * 打印结果
 * 100, 10
 * 100, 10 
 */

对"&"和"*"运算符的说明

如果已经执行了语句pointer_1=&a;

(1)&*pointer_1的含义是什么?

#include <stdio.h>

void main(){
	int a=5;
	int *pointer_1;
	pointer_1=&a;
	
	printf("&a=%d\\n",&a);
	printf("&*pointer_1=%d",&*pointer_1);
}

“&”和"*"两个运算符的优先级别相同,但按自右而左方向结合,因此先进行*pointer_1的运算,它就算是变量a,再执行&运算。

因此&*pointer_1与&a相同,即变量a的地址。

如果有pointer_2=&*pointer_1;

它的作用是将&a(a的地址)赋给pointer_2,如果pointer_2原来指向b,经过重新赋值后它已不再指向b了,而指向了a。

#include <stdio.h>

void main(){
	int a=5,b=10;
	int *pointer_1,*pointer_2;
	pointer_1=&a;
	pointer_2=&b;
	
	printf("&a=%d\\n",&a);
	printf("&*pointer_1=%d\\n",&*pointer_1);
	printf("&*pointer_2=%d\\n",&*pointer_2);
	
	// 重新赋值 
	pointer_2=&*pointer_1;
	printf("&*pointer_2=%d\\n",&*pointer_2);
}

(2)*&a的含义是什么?

先进行&a运算,得到a的地址,再进行*运算。即&a所指向的变量,也就是变量a。

*&a和*pointer_1的作用是一样的,它们都等价于变量a。

*&a与a与*pointer_1等价

#include <stdio.h>

void main(){
	int a=5;
	int *pointer_1;
	pointer_1=&a;
	
	printf("a=%d\\n",a);
	printf("*pointer_1=%d\\n",*pointer_1);
	printf("*&a=%d\\n",*&a);
}

(3)(*pointer_1)++相当于a++。

注意,括号是必要的,如果没有了括号,就成为了*pointer_1++。其中++和*为同一优先级别,而结合方向是自右向左的,因此它就相当于*(pointer++)。

由于++在pointer_1的右侧,是“后加”,因此先对pointer_1的原值进行*运算,得到a的值,然后使pointer_1的值改变,这样pointer_1不再指向a了。

#include <stdio.h>

void main(){
	int a=5;
	int *pointer_1;
	pointer_1=&a;
	
	printf("a=%d\\n",a);
	printf("*pointer_1=%d\\n",*pointer_1);
	printf("a++=%d\\n",a++);// 本语句执行完成后a的值已经变成了6 
	printf("(*pointer_1)++=%d\\n",(*pointer_1)++);// 所以这里输出的值为6
	printf("a=%d\\n",a);// 又进行一个a++运算,所以最终a的值为7 
}

(4)p2=p1;和*p2=*p1是什么含义(其中p1、p2都是指针变量)

赋值表达式:

p2=p1

就使p2与p1指向同一对象i,此时*p2就等价于i,而不是j,图所示:

代码演示:

#include <stdio.h>

int main() {
	int i='a', j='b';
	int *p1, *p2;
	p1=&i, p2=&j;
	
	printf("p1 = %d, %d\\n", *p1, p1);
	printf("p2 = %d, %d\\n", *p2, p2);
	
	// p2=p1
	p2=p1;
	printf("\\np2=p1 : \\n");
	printf("p1 = %d, %d\\n", *p1, p1);
	printf("p2 = %d, %d\\n", *p2, p2);
}

如果执行如下表达式:

 *p2=*p1;

则表示把p1指向的内容赋给p2所指的区域, 此时就变成图所示

代码演示:

#include <stdio.h>

int main() {
	int i='a', j='b';
	int *p1, *p2;
	p1=&i, p2=&j;
	
	printf("p1 = %d, %d\\n", *p1, p1);
	printf("p2 = %d, %d\\n", *p2, p2);
	
	// *p2=*p1
	*p2=*p1;
	printf("\\n*p2=*p1 : \\n");
	printf("p1 = %d, %d\\n", *p1, p1);
	printf("p2 = %d, %d\\n", *p2, p2);
}

过指针访问它所指向的一个变量是以间接访问的形式进行的,所以比直接访问一个变量要费时间,而且不直观,因为通过指针要访问哪一个变量,取决于指针的值(即指向),例如"*p2=*p1;"实际上就是"j=i;",前者不仅速度慢而且目的不明。但由于指针是变量,我们可以通过改变它们的指向,以间接访问不同的变量,这给程序员带来灵活性,也使程序代码编写得更为简洁和有效。

指针变量可出现在表达式中, 设

int x,y,*px=&x;

指针变量px指向整数x,则*px可出现在x能出现的任何地方。例如:
 

y=*px+5;  /*表示把x的内容加5并赋给y*/

y=++*px;  /*px的内容加上1之后赋给y,++*px相当于++(*px)*/

y=*px++;  /*相当于y=*px; px++*/ 

因此有如下总结

  • a表示普通变量的值,&a表示普通变量a在内存中地址。
  • p表示指针变量的值,该值是指针变量所指向变量a的地址,*p表示指针变量所指向变量a的值,&p表示指针变量的地址。
#include <stdio.h>

void main(){
	int a=10;
	int *p=&a;
	
	printf("a=%d &a=%d\\n", a, &a);
	printf("p=%d *p=%d &p=%d\\n", p, *p, &p);
}

练习题1:输入a和b两个整数,按先大后小的顺序输出a和b。

不使用指针解决

#include <stdio.h>

/**
 * 题目:输入a和b两个整数,按先大后小的顺序输出a和b 
 */
void main(){
	// 声明a和b两个变量 
	int a,b;
	
	// 从键盘输入获取输入的a和b两个整数 
	scanf("%d",&a);
	scanf("%d",&b);
	
	// 进行判断处理
	if(a>b){
		printf("%d %d",a,b);
	} else {
		printf("%d %d",b,a);
	}
} 

使用指针解决:

#include <stdio.h>

/**
 * 题目:输入a和b两个整数,按先大后小的顺序输出a和b 
 */
void main(){
	// 声明a和b两个普通变量,p、p1和p2三个指针变量 
	int a,b;
	int *p1,*p2,*p; 
	
	// 从键盘输入获取输入的a和b两个整数,并将a和b的地址赋值给指针变量 
	scanf("%d",&a);
	scanf("%d",&b);
	p1=&a;
	p2=&b;
	
	// 进行判断处理
	if(a<b){// 交换两个指针变量的值,p充当交换的媒介 
		p=p1;
		p1=p2;
		p2=p;
	}
	
	// 打印结果
	printf("a=%d, b=%d\\n",a,b);
	printf("max=%d, min=%d\\n",*p1,*p2); 
} 

练习题2:输入a、b、c三个整数,按大小顺序输出。

#include <stdio.h>

/**
 * 题目:输入a、b、c三个整数,按大小顺序输出。 
 */
void main(){
	void exchange(int *q1, int *q2, int *q3);// 使用之前需要声明 
	
	// 声明a和b两个普通变量,p1、p2和p3三个指针变量 
	int a,b,c;
	int *p1,*p2,*p3; 
	
	// 从键盘输入获取输入的a、b和c,并将a、b和c的地址赋值给指针变量 
	scanf("%d",&a);
	scanf("%d",&b);
	scanf("%d",&c);
	p1=&a;
	p2=&b;
	p3=&c;
	
	// 进行判断处理
	exchange(p1,p2,p3);
	
	// 打印结果
	printf("%d %d %d\\n",a,b,c);
} 

void exchange(int *q1, int *q2, int *q3){
	void swap(int *pt1, int *pt2);// 使用之前需要进行声明
	
	if(*q1<*q2){// 如果q2大于q1,则交换q1与q2的值,那么q1>q2了 
		swap(q1,q2);
	} 
	if(*q1<*q3){// 如果q3大于q1,则交换q3与q1的值,那么q1>q3了  
		swap(q1,q3);
	}
	if(*q2<*q3){// 如果q3大于q2,则交换q3与q2的值,那么q2>q3了 
		swap(q2,q3);
	}
}

// 交换两个指针变量的值 
void swap(int *pt1, int *pt2){
	int temp;
	
	temp=*pt1;
	*pt1=*pt2;
	*pt2=temp;
}

指针变量作为函数参数

函数的参数不仅可以是整型、实型、字符型等数据,还可以是指针类型。它的作用是将一个变量的地址传送到另一个函数中。

#include <stdio.h>

// 交换两个数值 
swap(int *p1, int *p2){// 指针变量作为形参 
	int temp;// 临时变量,保存另一个变量的值 
	temp=*p1;// *p1,*p2表示取该指针变量所指向变量的值 
	*p1=*p2;
	*p2=temp;
}

void main(){
	int a, b;
	int *pointer_1, *pointer_2;
	 
	scanf("%d, %d", &a, &b);
	pointer_1=&a;
	pointer_2=&b;
	
	if(a<b){
		swap(pointer_1, pointer_2);// 注意调用函数时,传的是指针变量名,没有带有*号 
	}
	
	printf("\\n%d, %d\\n", a, b);
} 

代码解释:

swap是用户定义的函数,它的作用是交换两个变量(a和b)的值。swap函数的形参p1、p2是指针变量。程序运行时,先执行main函数,输入a和b的值。然后将a和b的地址分别赋给指针变量pointer_1和pointer_2,使pointer_1指向a,pointer_2指向b。

接着执行if语句,由于a〈b,因此执行swap函数。注意实参pointer_1和pointer_2是指针变量,在函数调用时,将实参变量的值传递给形参变量。采取的依然是“值传递”(pointer_1和pointer_2的值就是变量a和b的地址,在函数内部,就可以通过*pointer_1和*pointer_2取到a和b的值了)方式。因此虚实结合后形参p1的值为&a,p2的值为&b。这时p1和pointer_1指向变量a,p2和pointer_2指向变量b。

接着执行执行swap函数的函数体使*p1和*p2的值互换,也就是使a和b的值互换。

函数调用结束后,p1和p2不复存在(已释放)如图。

最后在main函数中输出的a和b的值是已经过交换的值。

下面这段代码有错误:

// 交换两个数值 
swap(int *p1, int *p2){
	int *temp;
	*temp=*p1;// 此语句有问题
	*p1=*p2;
	*p2=temp;
}

因为*p1表示所指向变量的值,即a的值,不是地址,而给指针变量temp赋值应该赋予一个地址。

指针变量几个问题的进一步说明

指针变量可以进行某些运算,但其运算的种类是有限的。它只能进行赋值运算和部分算术运算及关系运算。

1、指针运算符

  • 1)取地址运算符&:取地址运算符&是单目运算符,其结合性为自右至左,其功能是取变量的地址。在scanf函数及前面介绍指针变量赋值中,我们已经了解并使用了&运算符。
  • 2)取内容运算符*:取内容运算符*是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量。在*运算符之后跟的变量必须是指针变量。

需要注意的是指针运算符*和指针变量说明中的指针说明符*不是一回事。在指针变量说明中,“*”是类型说明符,表示其后的变量是指针类型。而表达式中出现的“*”则是一个运算符用以表示指针变量所指的变量。

#include <stdio.h>

void main(){
	int a=5 ;
	int *p;
	p=&a;
	
	printf("%d", *p);
}
// 打印结果:5 

代码解释:

  • 表示指针变量p取得了整型变量a的地址。printf("%d",*p)语句表示输出变量a的值。

2、指针变量的运算(这里可以暂时不用看,涉及到后面的知识

(1)赋值运算:指针变量的赋值运算有如下几种形式:

  • ①指针变量初始化赋值
int a=5;
int *p=&a;
  • ②把一个变量的地址赋予指向相同数据类型的指针变量
int a;
int *p;
p=&a; // 把整型变量a的地址赋予整型指针变量p
  • ③把一个指针变量的值赋予指向相同类型变量的另一个指针变量
int a;
int *p1=&a, *p2;
p2=p1; /*把a的地址赋予指针变量p2*/
  • ④把数组的首地址赋予指向数组的指针变量
int a[5];
int *p;
p=a;// 数组名表示数组的首地址,故可赋予指向数组的指针变量p

// 也可以写成这样:
p=&a[0];// 数组第一个元素的地址也是整个数组的首地址,也可以赋值给p

// 也可以写成这样,采用初始化赋值的方法
int a[5], *p=a;
  • ⑤把字符串的首地址赋予指向字符类型的指针变量
char *p;
p="I love C!";

// 或者也可以初始化赋值
char *p="I love C!";
// 这里应说明的是并不是把整个字符串装入指针变量,而是把存放该字符串的字符数组的首地址装入指针变量。
  • ⑥把函数入口地址赋予指向函数的指针变量
int (*pf)();
pf=f; /*f为函数名*/

(2)加减算术运算

对于指向数组的指针变量,可以加上或减去一个整数n。设pa是指向数组a的指针变量,则pa+n,pa-n,pa++,++pa,pa--,--pa运算都是合法的。

指针变量加或减一个整数n的意义是把指针指向的当前位置(指向某数组元素)向前或向后移动n个位置。

应该注意,数组指针变量向前或向后移动一个位置和地址加1或减1在概念上是不同的。因为数组可以有不同的类型,各种类型的数组元素所占的字节长度是不同的。如指针变量加1,即向后移动1 个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加1。例如:

int a[5], *pa;
pa=a; // pa指向数组a,也是指向a[0]
pa=pa+2; // pa指向a[2],即pa的值为&pa[2]

指针变量的加减运算只能对数组指针变量进行,对指向其它类型变量的指针变量作加减运算是毫无意义的。

(3)两个指针变量之间的运算:只有指向同一数组的两个指针变量之间才能进行运算,否则运算毫无意义。

 
  • ①两指针变量相减:两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。实际上是两个指针值(地址)相减之差再除以该数组元素的长度(字节数)。
#include <stdio.h>

void main(){
	int a[10]={1,2,3,4,5,6,7,8,9,10};
	int *p1, *p2;
	p1=a;
	p2=a+6;
	
	printf("p1=%d *p1=%d\\n", p1, *p1);	
	printf("p2=%d *p2=%d\\n", p2, *p2);	
	
	// 两指针变量相减
	printf("%d", p2-p1);// 相差6个元素 
} 

两个指针变量不能进行加法运算。 例如,pf1+pf2是什么意思呢?毫无实际意义。

  • ②两指针变量进行关系运算:指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。
pf1==pf2 // 表示pf1和pf2是否指向同一数组元素
pf1>pf2 // 表示pf1处于高地址位置
pf1<pf2 // 表示pf2处于低地址位置

// 指针变量还可以和0比较
p==0 // 表示p是空指针,它不指向任何变量。空指针是由对指针变量赋予0值而得到的。
p!=0 // 表示p不是空指针
// 对指针变量赋0值和不赋值是不同的。指针变量未赋值时,可以是任意值,是不能使用的。否则将造成意外错误。而指针变量赋0值后,则可以使用,只是它不指向具体的变量而已。

例如:

#include <stdio.h>

void main(){
	int a=10, b=20, s, t;
	int *pa, *pb; // 说明pa,pb为整型指针变量
	
	pa=&a; // 给指针变量pa赋值,pa指向变量a 
	pb=&b; // 给指针变量pb赋值,pb指向变量b 
	
	s=*pa + *pb; // 求a+b之和,(*pa就是a,*pb就是b)
	t=*pa * *pb; // 本行是求a*b之积
	
	printf("a=%d\\nb=%d\\na+b=%d\\na*b=%d\\n",a,b,a+b,a*b);
	printf("s=%d\\nt=%d\\n",s,t);
} 
/** 
打印结果:
	a=10
	b=20
	a+b=30
	a*b=200
	s=30
	t=200
*/ 

例如:

#include <stdio.h>

void main() {

	int a,b,c,*pmax,*pmin;                 /*pmax,pmin为整型指针变量*/

	printf("input three numbers:\\n");      /*输入提示*/
	scanf("%d%d%d",&a,&b,&c);             /*输入三个数字*/

	if(a>b) {                             /*如果第一个数字大于第二个数字...*/
		pmax=&a;                     /*指针变量赋值*/
		pmin=&b;
	}                                   /*指针变量赋值*/
	else {
		pmax=&b;                   /*指针变量赋值*/
		pmin=&a;
	}                                   /*指针变量赋值*/
	if(c>*pmax) pmax=&c;              /*判断并赋值*/
	if(c<*pmin) pmin=&c;              /*判断并赋值*/
	
	printf("max=%d\\nmin=%d\\n",*pmax,*pmin); /*输出结果*/
}

数组指针和指向数组的指针变量

一个数组是由连续的一块内存单元组成的。数组名就是这块连续内存单元的首地址。一个数组也是由各个数组元素(下标变量)组成的。每个数组元素按其类型不同占有几个连续的内存单元。一个数组元素的首地址也是指它所占有的几个内存单元的首地址。

一个变量有对应的内存单元地址,一个数组包含若干元素,每个数组元素都在内存中占有存储单元,它们都有相应的地址。

指针变量既可以指向变量,也可以指向数组元素(把某一元素的地址放到指针变量中)。

所谓数组元素的指针就是数组元素的地址。

指向数组元素的指针

定义一个指向数组元素的指针变量的方法,与之前介绍的指向变量的指针变量相同。

int a[10];// 定义a为包含10个整型数据的数组
int *p;// 定义p为指向整型变量的指针变量
// 注意:如果数组为int型,则指针变量的基类型也应该为int类型。
p=&a[0];// 对指针变量的赋值。把a[0]元素的地址赋给指针变量p。也就是说,p指向a数组的第0号元素。

// 也可以这样赋值。因为C语言规定,数组名代表数组的首地址,也就是第0号元素的地址。
p=a;// 等价于p=&a[0];

// 也可以在定义指针变量时就赋给初值
int *p=&a[0];
// 等效于
int *p;
p=&a[0];


// 定义时也可以写成这样
int *p=a;

数组指针变量说明的一般形式为:

类型说明符  *指针变量名;

其中类型说明符表示所指数组的类型。从一般形式可以看出指向数组的指针变量和指向普通变量的指针变量的说明是相同的。

通过指针引用数组元素

C语言规定:如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素。

引入指针变量后,就可以用两种方法来访问数组元素了。

如果p的初值为&a[0],则:

  • (1)p+i和a+i就是a[i]的地址,或者说它们指向a数组的第i个元素。
  • (2)*(p+i)或*(a+i)就是p+i或a+i所指向的数组元素,即a[i]。例如,*(p+5)或*(a+5)就是a[5]。
  • (3)指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价。

故引用一个数组元素,可以用:

  • 下标法,例如a[i]形式
  • 指针法,如*(a+i)或*(p+i)形式

其中a是数组名,p是指向数组元素的指针变量,其初值为p=a。

注意:数组名即“翻译成数组的第一个元素的地址”

数组名表示数组的第一个元素的地址,而数组名加i(例如a+i)表示数组中第i各元素的地址,可以通过"*"(例如*(a+i))来获取该地址对应元素的值。

#include <stdio.h>

void main() {
	// 一维数组
	int a[10]= {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

	// 打印一维数组
	int i;
	for(i=0; i<10; i++) {
		printf("%d\\t",a[i]);
	}
	printf("\\n");

	// 指针的应用
	for(i=0; i<10; i++) {
		printf("a+%d = %d\\t*(a+%d) = %d\\n", i, a+i, i, *(a+i));
	}
}

例:输出数组中的全部元素

假设有一个a数组,整型,有10个元素。要输出各元素的值有三种方法:

  • ①下标法。
#include <stdio.h>

void main(){
	// 定义一个数组和一个普通变量 
	int a[10];
	int i;
	
	// 从键盘输入获取数组元素
	for(i=0;i<10;i++){
		scanf("%d",&a[i]);
	} 
	
	// 换行符 
	printf("\\n");
	
	// 通过数组下标输出数组的全部元素
	for(i=0;i<10;i++){
		printf("%d\\t",a[i]);
	} 
}

  • ②通过数组名计算数组元素的地址,找出元素的值。
#include <stdio.h>

void main(){
	// 定义一个数组和一个普通变量 
	int a[10];
	int i;
	
	// 从键盘输入获取数组元素
	for(i=0;i<10;i++){
		scanf("%d",&a[i]);
	} 
	
	// 换行符 
	printf("\\n");
	
	// 通过数组名计算元素的地址,输出数组的全部元素
	for(i=0;i<10;i++){
		printf("%d\\t",*(a+i));
	} 
}

  • ③用指针变量指向数组元素。
#include <stdio.h>

void main(){
	// 定义一个数组和一个普通变量 
	int a[10];
	int i;
	int *p;// 定义一个指针变量
	p=a;// 数组名表示数组元素中的第一个元素的地址 
	
	// 从键盘输入获取数组元素
	for(i=0;i<10;i++){
		scanf("%d",&a[i]);
	} 
	
	// 换行符 
	printf("\\n");
	
	// 通过指针变量指向元素,输出数组的全部元素
	for(i=0;i<10;i++){
		printf("a[%d]=%d\\t",i,*(p+i));
	} 
}

几个注意的问题:

  • (1)指针变量可以实现本身的值的改变。如p++是合法的;而a++是错误的。因为a是数组名,它是数组的首地址,是常量。
  • (2)要注意指针变量的当前值。

这是一段错误的代码:

#include <stdio.h>

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

	for(i=0; i<10; i++)
	{
		*p++=i;
	}

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

改正:

#include <stdio.h>

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

	for(i=0; i<10; i++)
	{
		*p++=i;
	}
	
	p=a;// 将指针重新指向数组首元素地址 
	
	for(i=0; i<10; i++)
	{
		printf("a[%d]=%d\\n",i,*p++);
	}
}

代码解释:

    • 虽然定义数组时指定它包含10个元素,但指针变量可以指到数组以后的内存单元,系统并不认为非法。
      • *p++,由于++和*同优先级,结合方向自右而左,等价于*(p++)。
        • *(p++)与*(++p)作用不同。若p的初值为a,则*(p++)等价a[0],*(++p)等价a[1]。
          • (*p)++表示p所指向的元素值加1。
            • 如果p当前指向a数组中的第i个元素,则
              • *(p--)相当于a[i--];
              • *(++p)相当于a[++i];
              • *(--p)相当于a[--i]。

数组名作函数参数

数组名可以作函数的实参和形参。

一般形式如下:

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

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

f(int arr[ ], int n)在编译时将arr按指针变量处理的,相当于将函数f的首部写成f(int *arr, int n)。这两种写法是等价的。

数组名就是数组的首地址,实参向形参传送数组名实际上就是传送数组的地址,形参得到该地址后也指向同一数组。

注意:C语言调用函数时虚实结合的方法都是采用“值传递”方式,当用变量名作为函数参数传递的是变量的值;但用数组名作为函数参数时,由于数组名代表的是数组首元素的地址,因此传递的值是地址,所以要求形参为指针变量。

因此,如果有一个实参数组,想在函数中改变此数组中的元素的值,实参与形参的对应关系有以下四种情况:

(1)形参和实参都用数组名,如:

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

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

实例如下:

#include <stdio.h>

/**********************************
(1)数组名作函数的实参和形参 
***********************************/

// 获取数组的长度,参数为一个字符数组,返回字符数组的长度 
int length(char arr[]){
	int count=0;
	int i=0;
	while(arr[i]!='\\0'){
		count++;
		i++;
	}
	return count;
}

// 打印字符数组,参数为一个字符数组 
void printStr(char arr[]){
	int i;
	for(i=0;i<=length(arr)-1;i++){
		printf("%c",arr[i]);
	}
}

// 反序输出字符数组元素,参数为一个字符数组 
void reverseStr(char arr[]){
	// 其中i为数组首元素,j为数组尾元素 
	int i=0,j=length(arr)-1;
	// 临时变量,临时参与两个数组元素的交换 
	char temp

以上是关于C语言之指针学习的主要内容,如果未能解决你的问题,请参考以下文章

《深入理解C指针》学习笔记--- 指针之外

C语言之函数指针用法总结

为啥在访问二级指针时出现分段错误错误? C语言

C语言系列之 数组强化与三级指针-尹成-专题视频课程

C语言函数指针之回调函数

C语言之数组指针指针数组