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;
	// 循环交换 
	while(i<j){
		/* 使用指针的方式 */ 
		temp=*(arr+i);
		*(arr+i)=*(arr+j);
		*(arr+j)=temp;
		/* 使用数组的方式 */ 
//		temp=arr[i];
//		arr[i]=arr[j];
//		arr[j]=temp;
		i++; 
		j--;
	}
	// 打印最终的交换结果 
	printStr(arr);
}

int main()
{
	char str[]="Hello World!";
	printf("%s\n",str);
	reverseStr(str);
	
	return 0;
 } 

(2)实参用数组名,形参用指针变量。如:

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

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

实例如下:

#include <stdio.h>

/**********************************
(2)实参用数组名,形参用指针变量
***********************************/

int length(char *arr){
	int count=0;
	/* 使用数组下标方式 */ 
//	int i=0;
//	while(arr[i]!='\0'){
//		count++;
//		i++;
//	}
	/* 使用指针方式 */
	while(*arr!='\0'){
		count++;
		arr++;
	} 
	return count;
}

void printStr(char *arr){
	/* 使用数组下标方式 */ 
//	int i;
//	for(i=0;i<=length(arr)-1;i++){
//		printf("%c",arr[i]);
//	}
	/* 使用指针方式 */
	while(*arr!='\0'){
		printf("%c",*arr);
		arr++;
	}
}

void reverseStr(char *arr){
	int i=0,j=length(arr)-1;
	char temp; 
	while(i<j){
		/* 使用指针方式 */
		temp=*(arr+i);
		*(arr+i)=*(arr+j);
		*(arr+j)=temp;
		/* 使用数组下标方式 */ 
//		temp=arr[i];
//		arr[i]=arr[j];
//		arr[j]=temp;
		i++;
		j--;
	}
	printStr(arr);
}

int main()
{
	char str[]="Hello World!";
	printf("%s\n",str);
	reverseStr(str);
	
	return 0;
 } 

(3)实参形参都用指针变量

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

void main()
{
    int a[10], *p=a;
    f(a, 10);
}
#include <stdio.h>

/**********************************
(3)实参形参都用指针变量
***********************************/

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){
	int i=0,j=length(arr)-1;
	char temp; 
	while(i<j){
		/* 使用指针方式 */ 
		temp=*(arr+i);
		*(arr+i)=*(arr+j);
		*(arr+j)=temp;
		/* 使用数组方式 */ 
//		temp=arr[i];
//		arr[i]=arr[j];
//		arr[j]=temp;
		i++;
		j--;
	}
	printStr(arr);
}

int main()
{
	char str[]="Hello World!";
	char *p=str;
	printf("%s\n",str);
	reverseStr(p);
	
	return 0;
 } 

(4)实参为指针变量,形参为数组名

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

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

实例如下:

#include <stdio.h>

/**********************************
(4)实参为指针变量,形参为数组名
***********************************/

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[]){
	int i=0,j=length(arr)-1;
	char temp; 
	while(i<j){
		/* 使用指针 */ 
//		temp=*(arr+i);
//		*(arr+i)=*(arr+j);
//		*(arr+j)=temp;
		/* 使用数组 */ 
		temp=arr[i];
		arr[i]=arr[j];
		arr[j]=temp;
		i++;
		j--;
	}
	printStr(arr);
}

int main()
{
	char str[]="Hello World!";
	char *p=str;
	printf("%s\n",str);
	reverseStr(p);
	
	return 0;
 } 

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

用指针变量可以指向一维数组中的元素,也可以指向多维数组中的元素。

但在概念上和使用上,多维数组的指针比一维数组的指针要复杂。

多维数组元素的地址可以认为是二维数组是“数组的数组”,例:

int a[3][4]={
    {1,3,5,7},
    {9,11,13,15},
    {17,19,21,23}
}

则二维数组a是由3各一维数组所组成的。设二维数组的首行的首地址为2000,则有:

看下面的代码:

#include <stdio.h>

void main() {
	int a[3][4]= {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
	
	// 二维数组名,指向一维数组a[0],即0行首地址 
	printf("a = %d\n", a);
	
	// 其中a[0],*(a+0),*a都是等价的,代表第0行第0列元素的地址 
	printf("a[0] = %d  *(a+0) = %d  *a = %d\n", a[0], *(a+0), *a);
	
	// 其中a+1, &a[1]是等价的,代表1行的首地址
	printf("a+1 = %d  &a[1] = %d\n", a+1, &a[1]);
	
	// 其中a[1], *(a+1)是等价的,代表1行0列元素a[1][0]的地址
	printf("a[1] = %d  *(a+1) = %d\n", a[1], *(a+1));
	
	// 其中a[1]+2, *(a+1)+2, &a[1][2]是等价的,代表1行2列元素a[1][2]的地址
	printf("a[1]+2 = %d  *(a+1)+2 = %d  &a[1][2] = %d\n", a[1]+2, *(a+1)+2, &a[1][2]);
	
	// 其中*(a[1]+2), *(*(a+1)+2), a[1][2]是等价的,代表1行2列元素a[1][2]的元素值
	printf("*(a[1]+2) = %d  *(*(a+1)+2) = %d  a[1][2]=%d", *(a[1]+2), *(*(a+1)+2), a[1][2]); 
}

总结

1、在数组中,数组名表示首地址,即第0行第0列的元素的地址,无论是一维数组还是多维数组。

#include <stdio.h>

void main() {
	// 二维数组 
	int a[3][4]= {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
	// 一维数组 
	int b[3]={1,2,3}; 
	
	// 打印二维数组 
	printf("二维数组:\n");
	int i,j;
	for(i=0;i<3;i++){
		for(j=0;j<4;j++){
			printf("a[%d][%d]=%d,%d\t", i, j, a[i][j], &a[i][j]);
		}
		printf("\n");
	}
	printf("\n");
	// 打印一维数组
	printf("一维数组:\n");
	int m;
	for(m=0;m<3;m++){
		printf("b[%d]=%d,%d\t", m, b[m], &b[m]);
	} 
	printf("\n");
	
	printf("a=%d, b=%d", a, b);
} 

2、数组名也表示二维数组中第一(下标是0)行的首地址,如果加1表示第二(下标是1)行的首地址,依次如此。

#include <stdio.h>

void main() {
	// 二维数组 
	int a[3][4]= {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
	// 一维数组 
	int b[3]={1,2,3}; 
	
	// 打印二维数组 
	printf("二维数组:\n");
	int i,j;
	for(i=0;i<3;i++){
		for(j=0;j<4;j++){
			printf("a[%d][%d]=%d,%d\t", i, j, a[i][j], &a[i][j]);
		}
		printf("\n");
	}
	printf("\n");
	// 打印一维数组
	printf("一维数组:\n");
	int m;
	for(m=0;m<3;m++){
		printf("b[%d]=%d,%d\t", m, b[m], &b[m]);
	} 
	printf("\n\n");
	
	
	printf("a = %d\n", a);// 二维数组第1行(行下标为0)首地址 
	printf("a+1 = %d\n", a+1);// 二维数组第2行(行下标为1)首地址 
	printf("a+2 = %d\n", a+2);// 二维数组第3行(行下标为2)首地址 
} 

 

注意:a[0]、a[1]、a[2]等价于a、a+1、a+2,都表示第1行(行下标为0)、第2行、第3行的行首地址。

#include <stdio.h>

void main() {
	// 二维数组 
	int a[3][4]= {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
	// 一维数组 
	int b[3]={1,2,3}; 
	
	// 打印二维数组 
	printf("二维数组:\n");
	int i,j;
	for(i=0;i<3;i++){
		for(j=0;j<4;j++){
			printf("a[%d][%d]=%d,%d\t", i, j, a[i][j], &a[i][j]);
		}
		printf("\n");
	}
	printf("\n");
	// 打印一维数组
	printf("一维数组:\n");
	int m;
	for(m=0;m<3;m++){
		printf("b[%d]=%d,%d\t", m, b[m], &b[m]);
	} 
	printf("\n\n");
	
	
	printf("a = %d \t a[0] = %d\n", a, a[0]);// 二维数组第1行(行下标为0)首地址 
	printf("a+1 = %d \t a[1] = %d\n", a+1, a[1]);// 二维数组第2行(行下标为1)首地址 
	printf("a+2 = %d \t a[2] = %d\n", a+2, a[2]);// 二维数组第3行(行下标为2)首地址 
} 

3、取值操作符"*"和取址操作符"&"的应用*a、*(a+1)、&a[1]等

取值操作符"*"就是取指针变量所指向变量的值。

#include <stdio.h>

void main() {
	// 二维数组 
	int a[3][4]= {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
	// 一维数组 
	int b[3]={1,2,3}; 
	
	// 打印二维数组 
	printf("二维数组:\n");
	int i,j;
	for(i=0;i<3;i++){
		for(j=0;j<4;j++){
			printf("a[%d][%d]=%d,%d\t", i, j, a[i][j], &a[i][j]);
		}
		printf("\n");
	}
	printf("\n");
	// 打印一维数组
	printf("一维数组:\n");
	int m;
	for(m=0;m<3;m++){
		printf("b[%d]=%d,%d\t", m, b[m], &b[m]);
	} 
	printf("\n\n");
	
	printf("a = %d \t *a = %d \t &a = %d\n", a, *a, &a);
	printf("a+0 = %d \t *(a+0) = %d\n", a+0, *(a+0));// 注意,不能&(a+0),这是非法语句,无法编译,因为a+0的结果放在寄存器,无法取址 
	printf("a+1 = %d \t *(a+1) = %d\n", a+1, *(a+1));// 注意,不能&(a+1) 
	printf("a+2 = %d \t *(a+2) = %d\n", a+2, *(a+2));// 注意,不能&(a+2) 
} 

取址操作符是取变量的地址。

#include <stdio.h>

void main() {
	// 二维数组 
	int a[3][4]= {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
	// 一维数组 
	int b[3]={1,2,3}; 
	
	// 打印二维数组 
	printf("二维数组:\n");
	int i,j;
	for(i=0;i<3;i++){
		for(j=0;j<4;j++){
			printf("a[%d][%d]=%d,%d\t", i, j, a[i][j], &a[i][j]);
		}
		printf("\n");
	}
	printf("\n");
	// 打印一维数组
	printf("一维数组:\n");
	int m;
	for(m=0;m<3;m++){
		printf("b[%d]=%d,%d\t", m, b[m], &b[m]);
	} 
	printf("\n\n");
	
	printf("a[0] = %d \t &a[0] = %d \t *a[0] = %d\n", a[0], &a[0], *a[0]);
	printf("a[1] = %d \t &a[1] = %d \t *a[1] = %d\n", a[1], &a[1], *a[1]);
	printf("a[2] = %d \t &a[2] = %d \t *a[2] = %d\n", a[2], &a[2], *a[2]);
} 

所以a[0]和&a[0]等价,都是地址相等,而*a[0]表示取第1(下标为0)行首元素的值。

4、第2(下标为1)行第3(下标为2)列的地址和值

#include <stdio.h>

void main() {
	// 二维数组 
	int a[3][4]= {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
	// 一维数组 
	int b[3]={1,2,3}; 
	
	// 打印二维数组 
	printf("二维数组:\n");
	int i,j;
	for(i=0;i<3;i++){
		for(j=0;j<4;j++){
			printf("a[%d][%d]=%d,%d\t", i, j, a[i][j], &a[i][j]);
		}
		printf("\n");
	}
	printf("\n");
	// 打印一维数组
	printf("一维数组:\n");
	int m;
	for(m=0;m<3;m++){
		printf("b[%d]=%d,%d\t", m, b[m], &b[m]);
	} 
	printf("\n\n");
	
	printf("a[1]+2 = %d \t *(a+1)+2 = %d \t &a[1][2] = %d\n", a[1]+2, *(a+1)+2, &a[1][2]);
	printf("*(a[1]+2) = %d \t *(*(a+1)+2) = %d \t a[1][2] = %d\n", *(a[1]+2), *(*(a+1)+2), a[1][2]);
} 

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

把二维数组a分解为一维数组a[0], a[1], a[2]后,设p为指向二维数组的指针变量。

可以定义为:int (*p)[4];

它表示p是一个指针变量,它指向包含4各元素的一维数组。若指向第一个一维数组a[0],其值等于a, a[0]或&a[0][0]等。

从上面的分析得知:

  • *(p+i)+j是二维数组i行j列的元素的地址。
  • *(*(p+i)+j)是二维数组i行j列元素的值。

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

类型说明符 (*指针变量名)[长度];

其中“类型说明符”为所指数组的数据类型。“*”表示其后的变量是指针类型。“长度”表示二维数组分解为多个一维数组时,一维数组的长度,也就是二维数组的列数。

例如:

#include <stdio.h>

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

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

字符串的表示形式

在C语言中,可以使用两种方法访问一个字符串:

(1)用字符数组存放一个字符串,然后输出该字符串。

#include <stdio.h>

void main(){
	char string[]="I love C!";
	printf("%s\n", string);
}

(2)用字符指针指向一个字符串。

#include <stdio.h>

void main(){
	char *string="I love C!";
	printf("%s\n", string);
}

首先定义string是一个字符指针变量,然后把字符串的首地址赋予string(应写出整个字符串,以便编译系统把该串装入连续的一块内存单元),并把首地址送入string。程序中的:

char *p="I love C!";

// 等效于

char *p;
p="I love C!";

字符串中字符的存取方法

对字符串中字符的存取,可以用下标方法,也可以用指针方法。

  • 下标法举例:将字符串a复制到字符串b
#include <stdio.h>

void main(){
	char a[]="I love C!";
	char b[10];
	int i;
	
	for(i=0;*(a+i)!='\0';i++){// *(a+i)表示不断遍历数组的下一个元素,字符数组的最后一个元素是'\0' 
		*(b+i)=*(a+i);
	}
	*(b+i)='\0';// 最后一个字符是'\0'
	
	printf("String a is: %s\n", a);
	printf("String b is: ");
	for(i=0; b[i]!='\0';i++){
		printf("%c",b[i]);
	} 
	printf("\n\n");
}
  • 指针方法举例:将字符串a复制为字符串b
#include <stdio.h>

void main(){
	// 普通变量和指针变量的声明 
	char a[]="I love C!";
	char b[10];
	char *p1, *p2;
	int i;
	
	// 将数组首地址赋给两个指针变量 
	p1=a;
	p2=b;
	
	// 利用指针复制字符数组中的 
	for(;*p1!='\0';p1++,p2++){
		*p2=*p1;
	}
	*p2='\0';
	
	// 打印字符串 
	printf("String a is: %s\n", a);
	printf("String b is: ");
	for(i=0; b[i]!='\0'; i++){
		printf("%c", b[i]);
	}
	printf("\n");
}

字符指针作为函数参数

  • 实例:用函数调用实现字符串的复制,用字符数组作参数
#include<stdio.h>

void main(){
	void copy_string(char from[], char to[]);
	
	char a[]="I love C!";
	char b[]="I also love Java!";
	
	printf("String a = %s\nString b = %s\n\n", a, b);
	printf("copy string a to string b:\n");
	
	copy_string(a,b);// 调用函数
	
	printf("\nstring a = %s\nstring b = %s\n", a, b); 
}

// 复制字符数组 
void copy_string(char from[], char to[]){
	int i=0;
	
	while(from[i]!='\0'){
		to[i]=from[i];
		i++;
	}
	to[i]='\0';
}

  • 实例:用函数调用实现字符串的复制,形参用字符指针变量
#include<stdio.h>

void main(){
	// 函数使用声明 
	void copy_string(char from[], char to[]);
	
	// 定义变量 
	char *a="I love C!";
	// char *b="I also love Java!";// 不能定义为*b而应该是b[],否则无任何改变 
	char b[]="I also love Java!";
	
	printf("String a = %s\nString b = %s\n\n", a, b);
	printf("copy string a to string b:\n");
	
	copy_string(a,b);// 调用函数
	
	// 打印复制后的结果 
	printf("\nstring a = %s\nstring b = %s\n", a, b); 
}

// 复制字符数组 
void copy_string(char *from, char *to){
	for(; *from!='\0'; from++,to++){
		*to=*from;
	}
	*to='\0';
}

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

虽然用字符数组和字符指针变量都能实现字符串的存储和运算,但它们二者之间是有区别的,不能混为一谈。

主要概括有如下几点:

1、字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址(字符串第1个字符的地址),决不是将字符串放到字符指针变量中。

#include <stdio.h>

void main(){
	// 字符串数组
	char a[]="I love C!";
	// 字符指针变量
	char *b="I love C!";
	
	// 打印结果
	int i;
	printf("字符串数组:\n");
	for(i=0;a[i]!='\0';i++){
		printf("%c", a[i]);
	} 
	printf("\n\n");
	
	printf("字符指针变量:\n");
	for(;*b!='\0';b++){// 注意,*b表示值,而b表示指针变量,所以*b!='\0'比较的是字符的值是否到最后一个字符,而b++表示指针向数组的下一个元素移动一个单位 
		printf("%d\t", b);
		// printf("%c", *b);// 打印的指针变量的值 
	}
}

2、赋值方式。

对字符数组只能对各个元素赋值,不能用以下方法对字符数组赋值:

// 不能这样赋值
char str[10];
str = "I love C!";

而对字符指针变量,可以采用下面方法赋值:

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

但注意赋给a的不是字符,而是字符串第一个元素的地址。

#include <stdio.h>

void main(){
	// 不能使用如下方式对字符数组赋值
	char str[10];
	// error: assignment to expression with array type
	// str="I love C!"; 
	
	// 但对字符指针变量,可以这样赋值
	char *a;
	a="I love C!";
	printf("%d\n", a);// 打印的字符串第一个元素的地址
	printf("%c", *a); // 打印值  
} 

3、对字符指针变量赋初值

char *a="I love C!";

等价于

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

而对数组的初始化

char str[20] = {"I love C!"};

不能等价于

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

4、如果定义了一个字符数组,在编译时为它方配内存单元,它有确定的地址。而定义一个字符指针变量时,给指针变量行配内存单元,在其中可以放一个字符变量的地址也就是说,该指针变量可以指向一个字符型数据,如果未对它随予一个地址值,则它并未具体指向一个确定的字符数据。如:

char str[10];
scanf("%s", str);
// 完全是可以的

一般有人使用下面的方式,目的想要输入一个字符串,虽然能够运行,但是危险的:

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

5、指针变量的值是可以改变的,如:

#include <stdio.h>

void main(){
	char *a="I love C!";
	printf("%s\n", a);
	
	a+=3;// 在原第一个元素指针的基础上加上3,表示指针向后移动三个单位 
	printf("%s\n", a); 
}

另外需要说明的是,若定义了一个指针变量,并使它指向一个字符串,就可以使用下标形式引用指针变量所指的字符串中的字符。

#include <stdio.h>

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

函数指针变量

函数指针变量概念

在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。我们把这种指向函数的指针变量称为“函数指针变量”。

函数指针变量定义的一般形式为:

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

其中“类型说明符”表示被指函数的返回值的类型。“(* 指针变量名)”表示“*”后面的变量是定义的指针变量。最后的空括号表示指针变量所指的是一个函数。

例如:

    int (*pf)();

表示pf是一个指向函数入口的指针变量,该函数的返回值(函数值)是整型。

使用指针前:

#include <stdio.h>

int max(int a,int b) {
	if(a>b) return a;
	else return b;
}

void 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\n", a, b, c);
}

使用指针后:

#include <stdio.h>

int max(int a,int b) {
	if(a>b) return a;
	else return b;
}

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

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

函数指针变量常用的用途之一是把指针作为参数传递到其他函数。

函数的参数可以是变量、指向变量的指针变量、数组名、指向数组的指针变量等,那么指向函数的指针也可以作为参数,来实现函数地址的传递,这样就能够在被调用的函数中使用实参函数。

代码解释:有一个函数(假设函数名为sub),它有两个形参(x1和x2),定义x1和x2为指向函数的指针变量。在调用函数sub时,实参为两个函数名f1和f2,给形参传递的是函数f1和f2的地址。这样在函数sub中就可以调用f1和f2函数了。

返回指针值的函数

一个函数可以返回一个整型值、字符值、浮点数值,也可以返回指针型的数据,即地址。

这种返回指针值的函数,一般定义形式为:

类型名 *函数名(参数列表);

例如:

int *a(int x, int y);

例如:

#include <stdio.h>

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

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

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

  • 指针函数是带指针的函数,即本质是一个函数。
  • 函数指针是指向函数的指针变量,因而函数指针本身首先应是指针变量,指不上该指针变量指向函数。

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

推荐博文链接:https://blog.csdn.net/weibo1230123/article/details/75541862

C语言中函数参数传递的三种方式:

  • (1)传值,即传变量的值给函数作形参,在函数内对形参的改变不会影响到函数外该变量的值
  • (2)传址,即传变量的地址赋给函数里形参的指针,使指针指向真实的变量的地址,在函数内对参数的修改会影响到函数外变量的值
  • (3)传引用,实际是通过指针实现,效果如传址,使用方式却是传值。
#include <stdio.h>

/*
C语言中函数参数传递的三种方式:
(1)传值,即传变量的值给函数作形参,在函数内对形参的改变不会影响到函数外该变量的值 
(2)传址,即传变量的地址赋给函数里形参的指针,使指针指向真实的变量的地址,在函数内对参数的修改会影响到函数外变量的值
(3)传引用,实际是通过指针实现,效果如传址,使用方式却是传值。 
*/

void change(int *a, int &b, int c)
{
	c=*a;
	b=30;
	*a=20;	
} 

int main()
{
	int a=10,b=20,c=30;
	change(&a, b, c);
	printf("%d %d %d",a,b,c);
	return 0;	
} 

/*
总结:
(1)指针传参(int *a),将变量的地址直接传入函数,可以修改它的值
(2)引用传参(int &b),将变量的引用传入函数,效果和指针相同,也可以对其值进行修改
(3)值传参(int c),对函数外部的变量c没有任何影响 
*/

三种不同方式的实例:

  • 值传参

形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。

#include <stdio.h>

/********************************
值传参 
*********************************/

void swap(int x,int y)
{
	int temp;
	temp=x;
	x=y;
	y=temp;
}

int main()
{
	int a,b;
	printf("请输入待交换顺序的两个整数:");
	scanf("%d %d",&a,&b);
	swap(a,b);// 直接交换两个整数,不成功 
	printf("调用交换函数后的结果是:%d %d\n",a,b);
	return 0;	
} 
  • 指针传参

形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。

#include <stdio.h>

/********************************
指针传参 
*********************************/

void swap(int *x,int *y)
{
	int temp;
	temp=*x;
	*x=*y;
	*y=temp;
}

int main()
{
	int a,b;
	printf("请输入待交换顺序的两个整数:");
	scanf("%d %d",&a,&b);
	swap(&a,&b);// 交换两个整数的地址,成功 
	printf("调用交换函数后的结果是:%d %d\n",a,b);
	return 0;	
} 
  • 引用传参

形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作。

#include <stdio.h>

/********************************
引用传参 
*********************************/

void swap(int &x,int &y)
{
	int temp;
	temp=x;
	x=y;
	y=temp;
}

int main()
{
	int a,b;
	printf("请输入待交换顺序的两个整数:");
	scanf("%d %d",&a,&b);
	swap(a,b);// 交换两个整数的地址,成功 
	printf("调用交换函数后的结果是:%d %d\n",a,b);
	return 0;	
} 

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

指针数组的概念

指针数组的概念:一个数组,若其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都相当于一个指针变量。

一维指针数组的定义形式为:

类型名 数组名[数组长度];

例如:

int *name[4];

实例:

#include <stdio.h>

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

应该注意指针数组和二维数组指针变量的区别。这两者虽然都可用来表示二维数组,但是其表示方法和意义是不同的。

二维数组指针变量是单个的变量,其一般形式中"(*指针变量名)"两边的括号不可少。而指针数组类型表示的是多个指针(一组有序指针)在一般形式中"*指针数组名"两边不能有括号。

例如:

    int (*p)[3];

表示一个指向二维数组的指针变量。该二维数组的列数为3或分解为一维数组的长度为3。

    int *p[3]

表示p是一个指针数组,有三个下标变量p[0],p[1],p[2]均为指针变量。

指向指针的指针

定义一个指向指针数据的指针变量的形式:

char **p;

p的前面有两个*号。*运算符的结合性是从右到左,因此**p相当于*(*p),显然*p是指针变量的定义形式。如果没有最前面的*,那就是定义了一个指向字符数据的指针变量。

现在前面又有一个*号,表示指针变量p是指向一个字符指针变量的。*p就是p所指向的另一个指针变量。

#include <stdio.h>

void main(){
	char *name[]={"C", "Java", "HTML", "CSS", "JavaScript"};
	char **p;
	int i;
	
	for(i=0;i<5;i++){
		p=name+i;
		printf("%s\n", *p);
	}
}

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

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

指针运算小结

1、指针变量加(减)一个整数

例如:p++、p--、p+i、p-i、p+=i、p-=i等。

2、指针变量赋值:将一个变量地址赋给一个指针变量。

如:

  • p=&a; (将变量a的地址赋给p)
  • p=arry; (将数组array首元素地址赋给p)
  • p=&array[i]; (将数组array第i个元素的地址赋给p)
  • p=max; (max为已定义的函数,将max的入口地址赋给p)
  • p1=p2; (p1和p2都是指针变量,将p2的值赋给p1)

3、指针变量可以有空值

即该指针变量不指向任何变量,可以这样表示:p=NULL;

4、两个指针变量可以相减

如果两个指针变量都指向同一个数组中的元素,则两个指针变量值之差是两个指针之间的元素个数。

5、两个指针变量比较:如果两个指针变量指向同一个数组的元素,则两个指针变量可以进行比较。指向前面的元素的指针变量“小于” 指向后面的元素的指针变量。

使用指针遇到的问题总结

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

在写一个交换数组元素顺序的程序时,遇到了这个问题

#include <stdio.h>

void print(char *str)
{
	while(*str!='\0')
	{
		printf("%c",*str);
		str++;
	}
}

int length(char *str)
{
	int count=0;
	while(*str!='\0')
	{
		count++;
		str++; 
	}
	return count;
}

void swap(char &a, char &b)
{
	char temp;
	temp=a;
	a=b;
	b=temp;
}

void reverse(char *str)
{
	int i=0, j=length(str)-1;
	while(i<j)
	{ 
		swap(str[i], str[j]);
		i++;
		j--;
	}
}

int main()
{
	char *str="Hello World!";
	reverse(str);
	print(str);
	
	return 0;
}

可以看到没有任何的结果输出。

但对char *str="Hello World!"修改为char str[]="Hello World!"后,就可以了

原因是在reverse函数内通过swap()方法操作了字符指针所指向字符串的数据。

在一个双引号""内的字符序列或者转义字符序列称为字符串常量。例如:"Hello World!"。

这些字符串常量是不能改变的,因为这些字符串常量是存在静态内存区的,不可以改变。

例如下面的代码就是非法的:

#include <stdio.h>

int main()
{
	char *a="Hello World!";
	printf("%s\n",a);
	
	a[2]='N';// 非法的,但也不会报错
	printf("%s\n",a);
	
	return 0;
}

因为a[2]本来就是一个常量,不能被改变,但可以获取它的值。

但如果把*a改成a[]就不会有问题,例如:

#include <stdio.h>

int main()
{
	char a[]="Hello World!";
	printf("%s\n",a);
	
	a[2]='N';
	printf("%s\n",a);
	
	return 0;
}

这个a[2]就是一个char类型的变量,可以改变值。

但这样又是可以的。

#include <stdio.h>

int main()
{
	char *a="Hello World!";
	printf("%s\n",a);
	
	a="Hello C!";
	printf("%s\n",a);
	
	return 0;
}

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

在函数形参中,字符指针变量作为形参,一旦在函数内改变其值,那么在函数外的实参值也会发生改变。但如果仅仅是指针向后或向前移动一个或几个位置,那么函数外部的变量没有任何影响。

实例如下:

#include <stdio.h>

void withChangePos(char *str)
{
	printf("函数内前:%d\n",str);
	while(*str!='\0')
	{
		printf("%c", *str);
		str++;// 指向数组的下一个元素 
	}
	printf("\n函数内后:%d\n",str);
}

void withChangeValue(char *str)
{
	str[0]='0';
	str[1]='1';
	str[2]='2';
}

int main()
{
	char str[]="Hello World!";
	withChangePos(str);
	printf("函数外:%d\n\n",str);
	
	withChangeValue(str);
	printf("%s\n",str);

	return 0;	
} 

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

#include <stdio.h>

void printB(char *b)// *b参数 
{
	printf("%c\n",*b);
}

void printA(char &a)// &a参数 
{
	printf("%c\n",a);
	printB(&a);// 调用指针变量的函数,这边传的实参是&a 
} 

int main()
{
	char b='A';
	printA(b);

	return 0;	
} 

 

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值