1、概念
在C语言中,指针是一种特殊的变量,它是存放地址的。假设我们定义了一个指针变int *i_pointer。将i的地址(2000)存放到i_pointer中。这时, i_pointer的值就是(2000) ,即变量i所占用单元的起始地址。要存取变量i的值,可以采用间接方式:先找到存放“i的地址”的变量i_pointer ,从中取出i的地址(2000),然后取出i的值3。用来存放整型变量 i 的地址。可以通过语句:i_pointer =&i;
*:这玩意叫做取值操作符
&:而这玩意叫做取址操作符
#include<stdio.h>
int main()
{
int i = 2000;
int *pointer;
pointer = &i;
printf("%d\n", *pointer);
system("PAUSE");
return 0;
}
2、指针与指针变量
知道了一个变量的地址,就可以通过这个地址来访问这个变量,因此,又把变量的地址称为该变量的“指针” 。
C语言中可以定义一类特殊的变量,这些变量专门用来存放变量的地址,称为指针变量。
注意:指针变量的值(即指针变量中存放的值)是地址(即指针)。请区分“指针”和“指针变量”这两个概念。
注意:
- 指针变量前面的“*”,表示该变量的类型为指针型变量。
其一般形式为:类型说明符 *变量名;
其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。
(2)在定义指针变量时必须指定基类型。
需要特别注意的是,只有整型变量的地址才能放到指向整型变量的指针变量中。下面的赋值是错误的∶
float a;
int * pointer_1;
pointer_1=&a; /*将float型变量的地址放到指向整型变量的指针变量中,错误 */
C语言中提供了地址运算符&来表示变量的地址。其一般形式为: &变量名;
#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);
system("PAUSE");
return 0;
}
说明:
(1)如果已执行了语句 pointer_1=&a;&* pointer_1的含义是什么?
“&”和“*”两个运算符的优先级别相同,但按自右而左方向结合,因此先进行* pointer_1的运算,它就是变量a,再执行&运算。因此,&* pointer_1与&a相同,即变量a的地址
(2)如果有:pointer_2 =&* pointer_1;
它的作用是将&a(a的地址)赋给pointer_2 ,如果 pointer_2 原来指向b,经过重新赋值后它已不再指向b了,而指向了a。
(3)*&a的含义是什么?
先进行&a运算,得a的地址,再进行*运算。即&a所指向的变量,也就是变量a。
*&a和*pointer_1的作用是一样的,它们都等价于变量a。即*&a与a等价。
(4)(*pointer_1)++相当于a++。
注意括号是必要的,如果没有括号,就成为了*pointer_1++,从附录可知:++和*为同一优先级别,而结合方向为自右而左,因此它相当于*(pointer_1++)。
由于++在pointer_1的右侧,是“后加”,因此先对pointer_1的原值进行*运算,得到a的值,然后使pointer_1的值改变,这样pointer_1不再指向a了。
#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;
} //此后,p1指向b, p2指向a ^_^
printf("a = %d, b = %d\n", a, b);
printf("max = %d, min = %d\n", *p1, *p2);
system("PAUSE");
return 0;
}
3、指针变量作为函数参数
对输入的两个整数按大小顺序输出!这次用函数实现交换功能!
#include <stdio.h>
void swap(int *p1, int *p2);
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); //swap实现的是交换……
}
printf("\n%d > %d\n", a, b);
system("PAUSE");
return 0;
}
void swap(int *p1, int *p2)
{
int temp;
printf("I'm swapping……\n");
printf("Please wait^_^");
temp = *p1; //temp = a;
*p1 = *p2; //a = b;
*p2 = temp; //b = temp;
}
输入a、b、c 3个整数,按大小顺序输出
#include <stdio.h>
void main()
{
void exchange(int *q1, int *q2, int *q3);
int a, b, c, *p1, *p2, *p3;
scanf("%d %d %d", &a, &b, &c);
p1 = &a;
p2 = &b;
p3 = &c;
exchange(p1, p2, p3); //确保a > b > c
printf("%d %d %d\n", a, b, c);
system("PAUSE");
return 0;
}
void exchange(int *q1, int *q2, int *q3) //int *q1 = p1;
{
void swap(int *pt1, int *pt2); //用于交换&……&%
if( *q1 < *q2 )
{
swap(q1, q2);
}
if( *q1 < *q3 )
{
swap(q1, q3);
}
if( *q2 < *q3 )
{
swap(q2, q3);
}
}
void swap(int *pt1, int *pt2)
{
int temp;
temp = *pt1;
*pt1 = *pt2;
*pt2 = temp;
}
4、数组指针
一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。
指针变量既然可以指向变量,当然也可以指向数组元素(把某一元素的地址放到一个指针变量中)。所谓数组元素的指针就是数组元素的地址。
定义一个指向数组元素的指针变量的方法,与以前介绍的指向变量的指针变量相同。
例如:
int a[10];
(定义a为包含10个整型数据的数组)
int*p;
(定义p为指向整型变量的指针变量)
应当注意,如果数组为int型,则指针变量的基类型亦应为int型。
p=&a[0];
把a[0]元素的地址赋给指针变量p。
引用一个数组元素,可以用:
(1) 下标法,如a[i]形式;
(2) 指针法,如*(a+i)或*(p+i)。
其中的a是数组名,p是指向数组元素的指针变量,其初值 p=a。注意:数组名即“翻译成数组的第一个元素的地址!
5、数组名作函数参数
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语言调用函数时虚实结合的方法都是采用“值传递”方式,当用变量名作为函数参数时传递的是变量的值,当用数组名作为函数参数时,由于数组名代表的是数组首元素地址,因此传递的值是地址,所以要求形参为指针变量。
实例:将数组a中n个整数按相反顺序存放
方法一:数组名作函数
#include <stdio.h>
void reverse(int x[],int n); /*形参x是数组名*/
void main()
{
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 array has been inverted:\n");
for( i=0; i < 10; i++)
{
printf("%d ", a[i]);
}
printf("\n");
system("PAUSE");
return 0;
}
void reverse(int x[], int n) /*形参x是数组名*/
{
int temp, i, j, m;
m = (n-1)/2;
for( i=0; i <= m; i++)
{
j = n-1-i; //j指向对应的元素
temp = x[i];
x[i] = x[j];
x[j] = temp;
}
}
方法二:指针作函数
#include <stdio.h>
void reserve(int *x, int n); /*形参x为指针变量*/
void main()
{
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");
reserve(a, 10);
printf("The array has benn inverted:\n");
for( i=0; i < 10; i++)
{
printf("%d ", a[i]);
}
printf("\n");
system("PAUSE");
return 0;
}
void reserve(int *x, int n) /*形参x为指针变量*/
{
int *p, temp, *i, *j, m;
m = (n-1)/2;
i = x; //i指向数组的第一个元素
j = x-1+n; //j指向的是数组的最后一个元素
p = x+m; //指向中间,配对……
for( ; i <= p; i++, j--)
{
temp = *i;
*i = *j;
*j = temp;
}
}
归纳起来,如果有一个实参数组,想在函数中改变此数组中的元素的值,实参与形参的对应关系有以下4种情况:
(1)形参和实参都用数组名
(2)实参用数组名,形参用指针变量
(3)实参形参都用指针变量。
(4)实参为指针变量,形参为数组名。
6、多维数组与指针
定义int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};
则二维数组a是由3个一维数组所组成的。设二维数组的首行的首地址为2000
第一行地址a[0] | 2000 a[0][0] | 2004 a[0][1] | 2008 a[0][2] | 2012 a[0][3] |
存放数据 | 1 | 3 | 5 | 7 |
第二行地址a[1] | 2016 a[1][0] | 2020 a[1][1] | 2024 a[1][2] | 2028 a[1][3] |
存放数据 | 9 | 11 | 13 | 15 |
第三行地址a[2] | 2032 a[2][0] | 2036 a[2][1] | 2040 a[2][2] | 2044 a[2][3] |
存放数据 | 17 | 19 | 21 | 23 |
表 示 形 式 | 含义 | 地 址 |
a | 二维数组名,指向一维数组a[0],即0行首地址 | 2000 |
a[0], *(a+0), *a | 0行0列元素地址 | 2000 |
a+1,&a[1] | 1行首地址 | 2016 |
a[1],*(a+1) | 1行0列元素a[1][0]的地址 | 2016 |
a[1]+2, *(a+1)+2, &a[1][2] | 1行2列元素a[1][2] 的地址 | 2024 |
*(a[1]+2), *(*(a+1)+2), a[1][2] | 1行2列元素a[1][2]的值 | 元素值为13 |
把二维数组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列元素的值。
二维数组指针变量说明的一般形式为:类型说明符 (*指针变量名)[长度]
其中“类型说明符”为所指数组的数据类型。“*”表示其后的变量是指针类型。“长度”表示二维数组分解为多个一维数组时,一维数组的长度,也就是二维数组的列数。
7、字符串与指针
(1) 用字符数组存放一个字符串,然后输出该字符串。
(2) 用字符指针指向一个字符串
8、指向函数的指针
对使用字符指针变量和字符数组的讨论
(1)字符数组由若干个元素组成,每个元素中 放一个字符,而字符指针变量中存放的是地址(字符串第1个字符的地址),决不是将字符串放到字符指针变量中。
(2)赋值方式。对字符数组只能对各个元素赋值,不能用以下办法对字符数组赋值。
char str[20];
str=″I love Fishc.com!″;
而对字符指针变量,可以采用下面方法赋值:
char*a;
a=″I love Fishc.com!″;
但注意赋给a的不是字符,而是字符串第一个 元素的地址。
(3)对字符指针变量赋初值:
char *a=″I love Fishc.com!″;
等价于
char*a;
a=″I love Fishc.com!″;
而对数组的初始化:
char str[20]={″I love Fishc.com!″};
不能等价于
char str[20];
str[ ]=″I love Fishc.com!″;
(4)如果定义了一个字符数组,在编译时为它分配内存单元,它有确定的地址。而定义一个字符指针变量时,给指针变量分配内存单元,在其中可以放一个字符变量的地址也就是说,该指针变量可以指向一个字符型数据,但如果未对它赋予一个地址值,则它并未具体指向一个确定的字符数据。
(5) 指针变量的值是可以改变的,如:
另外需要说明的是,若定义了一个指针变量,并使它指向一个字符串,就可以用下标形式引用指针变量所指的字符串中的字符。
9、指针与函数
可以用指针变量指向整型变量、字符串、数组,也可以指向一个函数。一个函数在编译时被分配给一个入口地址。这个函数的入口地址就称为函数的指针。
(1)函数指针变量常用的用途之一是把指针作为参数传递到其他函数。
(2)前面介绍过,函数的参数可以是变量、指向变量的指针变量、数组名、指向数组的指针变量等。
(3)现在介绍指向函数的指针也可以作为参数,以实现函数地址的传递,这样就能够在被调用的函数中使用实参函数。
#include <stdio.h>
void main()
{
int max(int, int); /* 函数声明 */
int min(int, int); /* 函数声明 */
int add(int, int); /* 函数声明 */
void process( int, int, int(*fun)() ); /* 函数声明 */
int a, b;
printf("Endter a and b: ");
scanf("%d %d", &a, &b);
printf("max = ");
process(a, b, max);
printf("min = ");
process(a, b, min);
printf("sum = ");
process(a, b, add);
system("pause");
return 0;
}
int max(int x, int y) /* 函数定义 */
{
int z;
if( x > y )
{
z = x;
}
else
{
z = y;
}
return z;
}
int min(int x, int y) /* 函数定义 */
{
int z;
if( x < y )
{
z = x;
}
else
{
z = y;
}
return z;
}
int add(int x, int y)
{
int z;
z = x + y;
return z;
}
void process( int x, int y, int(*fun)() ) /* 函数定义 */
{
int result;
result = (*fun)(x, y);
printf("%d\n", result);
}
一个函数可以带回一个整型值、字符值、实型值等,也可以带回指针型的数据,即地址。其概念与以前类似,只是带回的值的类型是指针类型而已。
这种带回指针值的函数,一般定义形式为
类型名 *函数名(参数表列);
例如: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.0}};
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\n\n");
system("pause");
return 0;
}
double *search(double (*pointer)[4], int n)
{
double *pt;
pt = *(pointer + n);
return pt;
}
10、指针函数和函数指针的区别
(1)指针函数是指带指针的函数,即本质是一个函数。
(2)函数指针是指向函数的指针变量,因而函数指针本身首先应是指针变量,只不过该指针变量指向函数。
11、指针数组
指针数组的概念:一个数组,若其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都相当于一个指针变量。一维指针数组的定义形式为
类型名数组名[数组长度];
例如: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\n");
system("pause");
return 0;
}
指向指针的指针:
怎样定义一个指向指针数据的指针变量呢?
形式可以如: char**p;
p的前面有两个*号。*运算符的结合性是从右到左,因此**p相当于*(*p),显然*p是指针变量的定义形式。如果没有最前面的*,那就是定义了一个指向字符数据的指针变量。
现在它前面又有一个*号,表示指针变量p是指向一个字符指针变量的。*p就是p所指向的另一个指针变量。
12、指针数组作main函数的形参
指针数组的一个重要应用是作为main函数的形参。在以往的程序中,main函数的第一行一般写成以下形式:
-
- void main()
括弧中是空的。实际上,main函数可以有参数。
例如:void main(int argc,char *argv[ ])
argc和argv就是main函数的形参。
main函数是由操作系统调用的。那么,main函数的形参的值从何处得到呢?
显然不可能在程序中得到。实际上实参是和命令一起给出的。也就是在一个命令行中包括命令名和需要传给main函数的参数。
命令行的一般形式为:
命令名 参数1 参数2……参数n
#include <stdio.h>
void main(int argc, char *argv[])
{
while( argc > 1 )
{
++argv;
printf("%s\n", argv);
--argc;
}
system("pause");
return 0;
}
13、小结
定义 | 含义 |
int i; | 定义整型变量i |
int *p; | p为指向整型数据的指针变量 |
int a[n]; | 定义整型数组a,它有n个元素 |
int *p[n]; | 定义指针数组p,它由n个指向整型数据的指针元素组成 |
int (*p)[n]; | p为指向含n个元素的一维数组的指针变量 |
int f(); | f为带回整型函数值的函数 |
int *p(); | p为带回一个指针的函数,该指针指向整型数据 |
int (*p)(); | p为指向函数的指针,该函数返回一个整型值 |
int **p; | p是一个指针变量,它指向一个指向整型数据的指针变量 |
(1)指针变量加(减)一个整数
例如:p++、p--、p+i、p-i、p+=i、p-=i等。
指针变量赋值
将一个变量地址赋给一个指针变量。如:
p=&a; (将变量a的地址赋给p)
p=array; (将数组array首元素地址赋给p)
p=&array[i];(将数组array第i个元素 的地址赋给p)
p=max;(max为已定义的函数,将ma x的入口地址赋给p)
p1=p2;(p1和p2都是指针变量,将 p2的值赋给p1)
(2)指针变量可以有空值,即该指针变量不指向任何变量,可以这样表示:p=NULL;
两个指针变量可以相减
如果两个指针变量都指向同一个数组中的元素,则两个指针变量值之差是两个指针之间的元素个数
若两个指针指向同一个数组的元素,则可以进行比较。指向前面的元素的指针变量“小于”指向后面元素的指针变量。
14、Void指针和const指针
void真正发挥的作用在于:
(1) 对函数返回的限定;
(2) 对函数参数的限定。
ANSI C新标准增加了一种“void”指针类型,即不指定它是指向哪一种类型数据的指针变量。
例如:void *p;
表示指针变量p不指向一个确定的类型数据,它的作用仅仅是用来存放一个地址。
void指针它可以指向任何类型数据。也就是说,可以用任何类型的指针直接给void指针赋值。但是,如果需要将void指针的值赋给其他类型的指针,则需要进行强制类型转换。
#include <stdio.h>
void main(int argc, char *argv[])
{
const char *str= "Welcome to Fishc.com!\n\n";
// 这个语句的含义是:声明一个名为str的指针变量,
// 它指向一个字符型常量,初始化str为指向字符串
// "Welcome to Fishc.com!\n\n"
printf("\n\n%s", str);
#if (1)
str[0] = 'w'; //这条语句是错误的,但可以改变str指针的值
#endif
str = "I love Fishc.com!\n\n"; //合法!
printf("\n\n%s", str);
system("pause");
return 0;
}
#include <stdio.h>
void main(void)
{
char * const str = "Welcome to Fishc.com!\n\n";
// 常量指针是一个固定的指针,不可以改变它的值,但它所指的数据可以改变。
str[0] = 'w'; //合法!
#if( 1 )
str = "I love Fishc.com!\n\n"; //非法!!
#endif
printf("\n\n%s", str);
system("pause");
return 0;
}