C语言基础(七)
系列合集 初窥C语言
八、指针
8.1 地址和指针的概念
数据在内存中(计算机中)的存储:
1)为了正确地访问这些内存单元,必须为每个内存单元编号。根据一个内存单元的编号即可准确的找到该内存单元。
2)内存单元的编号就是该单元的地址。地址指示了内存单元的位置,所以通常也把这个地址称为指针。
3)通过内存单元的指针可以提取该单元存放的内容。
在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。
严格的说,一个指针是一个地址,是一个常量。而一个指针变量却可以被赋予不同的指针值,是变量。
为了避免混淆,约定:
“指针”是指地址,是常量。
“指针变量”是指取值为地址的变量,是变量。
定义指针的目的是为了通过指针去访问内存单元。指针变量的值是一个地址。
在C语言中,一种数据类型或数据结构往往都占有一组连续的内存单元。用“地址”这个概念并不能直观准确地指明其位置。“指针”虽然实际上也是一个地址,但它却可以作为一个数据机构的首地址。由它指向一个数据结构的约定部位,概念更清晰,表示更明确。这也是引入指针概念的一个重要原因。
8.2 变量的指针和指针变量
变量的指针就是变量的地址。
一个指针变量的值就是某个变量的(首)地址或称为某变量的指针。
对指针变量的定义,包括三部分:
1)指针类型说明,即定义变量为一个指针变量。
2)指针变量名
3)变量值(指针)所指向的变量的数据类型。
指针变量的引用:
指针变量只能用地址赋值,不能赋予任何其他数据否则会出错。
C语言中提供了地址运算符&来表示变量的地址,一般形式为&+变量名
“ & ” 取地址运算符
作用:取变量的地址
单目运算符,优先级:2 ,
结合性:自右向左
“ * ” 指针运算符(或称间接访问运算符)
作用:取指针所指向的变量的内容
单目运算符,优先级:2
结合性:自右向左
直接访问:按变量地址存取变量值
间接访问:通过存放变量地址的变量去访问变量
为了表示指针变量和它所指向的变量之间的关系,在程序中用“ * ”符号表示“指向”。
例如:i_pointer代表指针变量,而*i_pointer是其所指向的变量。
设有指向整数变量的指针变量p,如要把整型变量a的地址赋给p可以有以下两种方式:
(1)指针变量初始化
int a;
int *p = &a;
(2)赋值语句
int a;
int *p;
p = &a;
指针变量作为函数参数:
函数的参数不仅可以是整型,实型,字符型等数据,还可以是指针类型,它的作用是将一个变量的地址传送到另一个函数中。
8.3 数组与指针
一个变量有一个地址,一个数组包含若干个元素,每个数组元素都在内存中占有存储单元,它们都有其相应的地址。
数组的指针是数组的起始地址,数组元素的指针是数组元素的地址。
8.3.1 指向数组元素的指针
一个数组是由一块连续的内存单元组成的,数组名就是这块连续内存单元的首地址。一个数组也是由各个数组元素(下标变量)组成的,每个数组元素按其类型占有相应的内存单元。一个数组元素的首地址也是指它所占有的几个内存单元的首地址。
定义一个指向数组元素的指针变量的方法,与前面介绍的指针变量相同
8.3.2 指针的运算
1)指针变量的赋值运算
p = &a; //将变量a地址->p
p = array; //将数组array首地址->p
p = &array[i]; //将数组元素地址->p
p1 = p2; //指针变量p2的值->p1
不能把一个整数=>p,也不能把p的值=>整型变量
指针变量与其指向的变量具有相同的数据类型
2)指针的算术运算
p ± i 等价于 ±i*d(i为整数型,d为p指向的变量所占的字节数)
p++,p–,p+i,p-i,p+=i,p-=i等同上
若p1和p2指向同一数组,p1-p2为两指针间元素个数,等价于(p1-p2)/d
p1+p2无意义
8.3.3 指针的关系运算
若p1和p2指向同一数组,则
p1 < p2 表示p1指向的元素在前
p1 > p2 表示p1指向的元素在后
p1 = p2 表示指向同一元素
若p1,p2不指向同一数组,比较无意义
8.3.4 通过指针引用数组元素
a[i] <=> *(a + i)
a[i] <=> p[i] <=> *(p + i) <=> *(a + i)
C语言规定:如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素,引入指针变量后,就可以用两种方法来访问数组元素。
如果 p 的初值为&a[0],则:
p+i 和 a+i 就是 a[ i ] 的地址,或者首它们指向a数组的第i个元素。
注意:
1)指针变量可以实现本身的值的改变。如p++是合法的,而a++是错误的。因为a是数组名,它是数组的首地址,是常量。
2)要注意指针变量的当前值。
3)虽然定义数组时指定它包含 i 个元素,但指针变量可以指到数组之后的内存单元,系统并不认为非法。
4)*p++,由于++和* 同优先级,结合方向自右而左,等价于 *(p++)
5)*(p++) 和 *(++p)作用不同,若p的初值为a,则*(p++)等价于a[0],*(++p)等价于a[1]。
6)(*p)++表示p所指向的元素值+1。
7)如果p当前指向a数组中的第i个元素,则:
*(p- -)相当于a[i- -]
*(++p)相当于a[++i]
*(- -p)相当于a[- -i]
8.3.5 数组名作为函数参数
数组名可以作为函数的实参和形参
main(){
int array[10];
...
f(array,10);
...
}
f(int arr[],int n){
...
}
array为实参数组名,arr为形参数组名。
数组名就是数组的首地址,实参向形参传递数组名实际上就是传递数组的地址,形参得到该地址后也指向同一数组。
8.3.6 指向多维数组的指针和指针变量
多维数组的地址:
设有整型二维数组a[3][4]如下
0 1 2 3
4 5 6 7
8 9 10 11
它的定义为int a[3][4] = { {0,1,2,3} , {4,5,6,7} , {8,9,10,11} }
设数组a的首地址为1000
从二维数组的角度来说,a是二维数组名,a代表整个二维数组的首地址,也是二维数组0行的首地址,等于1000。a+1代表第“1”行的首地址,等于1008.
把二维数组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列的元素的值。
二维数组指针变量说明的一般形式:
类型说明符 (*指针变量名)[长度]
8.3.7 多维数组的指针作为函数参数
表示形式 | 含义 | 地址 |
---|---|---|
a | 二维数组名,数组首地址 | 1000 |
a[0] , *(a+0) , *a | 第0行第0列元素地址 | 1000 |
a+1 | 第1行首地址 | 1008 |
a[1] , *(a+1) | 第1行第0列元素地址 | 1008 |
a[1]+2 , *(a+1)+2 , &a[1][2] | 第1行第2列元素地址 | 1012 |
*(a[1]+2) , *(*(a+1)+2) , a[1][2] | 第一行第二列元素值 | 1012 |
8.4 字符串与指针
1.字符串表示形式
1)用字符数组存放一个字符串,然后输出该字符串
2)用字符串指针指向一个字符串
2.字符串指针作函数参数
将一个字符串从一个函数传递到另一个函数,可以用地址传递的方式,即用字符数组名做参数或用指向字符串的指针变量做参数。
3.使用字符串指针变量与字符数组的区别
用字符数组和字符指针变量都可以实现字符串的存储和运算,但使用时应注意:
1)字符数组由若干元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址(字符串的首地址),所以不能将字符串放到字符指针变量中。
2)赋值方式:对字符数组只能对各个元素赋值,不能用以下方法对字符数组赋值
char str[20];
str = "I love china";
3)对字符指针变量赋初值:
char *a = "I love china ";
4)如果定义了一个字符数组,在编译时为它分配内存单元,它有确定的地址。而定义一个字符指针变量时,给指针变量分配内存单元,是在其中可以放一个地址值。
5)指针变量的值可以改变
6)用指针变量指向一个格式字符串,可以用它代替printf函数中的格式字符串。
字符串指针变量的定义说明与指向字符变量的指针变量说明是相同的。只能按对指针变量的赋值不同来区别。对指向字符变量的指针变量应赋予该字符变量的地址。
char c;
*p = &c;
//表示p是一个指向字符变量c的指针变量
char *s = " C language"
//表示s是一个指向字符串的指针变量,把字符串的首地址赋给s。
8.5 指向函数的指针
在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使该指针变量指向该函数。然后通过该指针变量就可以找到并调用这个函数。我们把这种指向函数的指针变量称为“函数指针变量”。
函数指针变量定义的一般形式:
类型说明符 (*指针变量名)();
类型说明符表示被指函数的返回值类型
“ * ”后面的变量是定义的指针变量
()说明指针变量所指的是一个函数
如: int (*pf)();
表示pf是一个指向函数入口的指针变量,该函数的返回值(函数值)是整型。
8.6 返回指针值的函数
所谓函数类型是指函数返回值的类型。在C语言中允许一个函数的返回值是一个指针(即地址),这种返回指针值的函数值称为指针型函数。
定义指针型函数的一般形式:
类型说明符 *函数名(形参表){
//函数体
}
类型说明符:返回的指针值所指向的数据类型
“ * ”号表明返回值是一个指针
8.7 指针数组和指向指针的指针
8.7.1 指针数组的概念
一个数组的元素值为指针则是指针数组。
指针数组是一组有序的指针的集合,指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量。
指针数组说明的一般形式:
类型说明符 *数组名[数组长度]
其中类型说明符为指针值所指向的变量的类型,如 Int *pa[3]
表示pa是一个指针数组,它有三个数组元素,每个元素都是一个指针,指向整型变量。
8.7.2 指向指针的指针变量
如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。
指针访问变量称为间接访问。由于指针变量直接指向变量,所以称为“单级间址”,而如果通过指向指针的指针变量来访问变量则称为“二级间址”。
8.7.3 main函数的参数
一般main函数都是不带参数的,因此main后的括号都是空括号。实际上,main函数可以带参数,这个参数可以认为是main函数的形参。C语言规定main函数的参数只能有2个,习惯上写为 argc 和 argv。第一个参数argc必须是整型变量,第二个参数argv必须是指向字符串的指针数组。加上形参后,main函数的函数头写为:main (int argc, char *argv[])
由于main函数不能被其它函数调用,因此不可能在程序内部取得实际值。所以实际上,main函数的参数值是从操作系统命令行上获得的,当我们执行一个可执行文件时,在DOS提示符下键入文件名,再输入实际参数即可把这些实参传入main的形参中去。
DOS提示符下命令行的一般形式为:
C:> 可执行文件名 参数 参数
应注意main函数的两个形参和命令行中的参数在位置上不是一一对应的。因为main的形参只有两个,而命令行中的参数个数原则上未加限制。argc参数表示命令行中参数的个数(注意:文件名本身也算一个参数),argc的值是在输入命令行时由系统按实际参数的个数自动赋予的。如:
C:> E24 BASIC foxpro FORTRAN
由于文件名E24本身也算一个参数,所以共4个参数,因此argc取得的值为4。argv参数是字符串指针数组,其各个元素为命令行中各字符串(参数均按字符串处理)的首地址。指针数组的长度即为参数个数,数组元素初值由系统自动赋予。
8.8 指针应用举例
1.数组元素的位置翻转
#include <stdio.h>
void inv(int *x, int n){
int *p, m, temp, *i, *j;
m = (n - 1) / 2;
i = x;
j = x + n -1;
p = x + m;
for(;i < p; i++,j--){
temp = *i;
*i = *j;
*j = temp;
}
return;
}
int main(){
int i, arr[10], *p = arr;
printf("the original array :\n");
for(i = 0; i < 10; i++,p++){
scanf("%d",p);
}
printf("\n");
p = arr;
inv(p,10);
printf("the array has been inverted:\n");
for(p = arr; p < arr + 10;p++){
printf("%d", *p);
}
}
2.求数组的最大最小值
#include <stdio.h>
int max,min;
void max_min_value(int array[],int n){
int *p, *array_end;
array_end = array + n;
max = min = array[0];
for(p = array + 1; p < array_end; p++){
if(*p >= max) max = *p;
else if(*p < min) min = *p;
}
return;
}
int main(){
int i, number[10];
printf("enter 10 integer number:\n");
for(i = 0; i < 10; i++){
scanf("%d", &number[i]);
}
max_min_value(number,10);
printf("max = %d\nmin = %d\n",max,min);
}
3.使用选择法对10个整数降序排列
(1)
#include <stdio.h>
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]) k = j;
}
if(k != j){
t = x[i];
x[i] = x[k];
x[k] = t;
}
}
}
int main(){
int *p,i,a[10];
p = a;
for(i = 0; i < 10; i++){
scanf("%d",p++);
}
p = a;
sort(p,10);
for(p = a,i = 0; i < 10; i++,p++){
printf("%d\n",*p);
}
}
参考8.3.4 ,sort函数部分x[i]可以改为:
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)) k = j;
}
if(k != j){
t = *(x + i);
*(x + i) = *(x + k);
*(x + k) = t;
}
}
}
4.字符串复制
#include <stdio.h>
int main(){
char a[] = "hello world", b[20];
int i;
for(i = 0; *(a + i) != '\0'; i++){
*(b + i) = *(a + i);
}
*(b + i) = '\0';
printf("string a is : %s\n",a);
printf("string b is : ");
for(i = 0; b[i] != '\0'; i++){
printf("%c",b[i]);
}
}