C 程序设计教程(05)—— C 语言的数据类型(三):指针类型
该专栏主要介绍 C 语言的基本语法,作为《程序设计语言》课程的课件与参考资料,用于《程序设计语言》课程的教学,供入门级用户阅读。
目录
- C 程序设计教程(05)—— C 语言的数据类型(三):指针类型
- 一、指针与指针变量
- 1、指针与指针变量的概念
- 2、指针变量的定义
- 3、指针变量的引用
- 4、运算符 & 和 \*
- 二、指针的算术运算
- 三、指针与数组
- 四、指针与结构体类型
指针是一个特殊的变量,它里面存储的数值被解释成为内存中的一个地址。有了指针技术,可以描述复杂的数据结构,对字符串的处理可以更灵活,对数组的处理更方便。
一、指针与指针变量
1、指针与指针变量的概念
在程序中定义的变量,编译系统为其分配相应的内存单元。也就是说,每个变量在内存中都会有固定的位置,有具体的地址。由于变量的数据类型不同,它所占用的内存单元数也不相同。可以使用 sizeof() 函数返回某种类型或某个变量占用的内存大小(单位为字节)。例如:
#include <stdio.h>
#include <string.h>
int main()
{
int a=1,b=2;
float x=3.2,y=4.7;
double m=3.1415926;
char ch1='a',ch2='b';
printf("int类型的大小:%d ,%d\n",sizeof(a),sizeof(b));
printf("float类型的大小:%d ,%d\n",sizeof(x),sizeof(y));
printf("double类型的大小:%d\n",sizeof(m));
printf("字符型的大小:%d ,%d\n",sizeof(ch1),sizeof(ch2));
return 0;
}
以上程序的运行结果如下:
访问变量时,首先找到该变量在内存的地址,这个地址称为指针。而用来保存地址的变量就是指针变量,通过指针变量对所指向内存的访问,就是“间接访问”。
例如:
#include <stdio.h>
#include <string.h>
int main()
{
int a=1,*pa=&a;
float x=4.7,*px=&x;
double m=3.1415926,*pm=&m;
char ch='a',*pch=&ch;
printf("变量a的地址:%p, %p\n",pa,&a);
printf("变量x的地址:%p, %p\n",px,&x);
printf("变量m的地址:%p, %p\n",pm,&m);
printf("变量ch的地址:%p, %p\n",pch,&ch);
return 0;
}
以上程序的运行结果如下:
2、指针变量的定义
定义指针变量的语法格式如下:
类型* 变量名;
以下代码表示定义三个指针变量 ptr1、ptr2、ptr3。其中 ptr1 可以指向一个整型变量,ptr2 可以指向一个实型变量,ptr3 可以指向一个字符变量。换句话说,ptr1、ptr2、ptr3 可以分别保存整型变量的地址、实型变量的地址、字符型变量的地址。
int* ptr1;
float* ptr2;
char* ptr3;
int m = 3;
float f = 4.5;
char ch = 'a';
ptr1 = &m; //将变量m的地址赋给指针变量ptr1
ptr2 = &f; //将变量f的地址赋给指针变量ptr2
ptr3 = &ch; //将变量ch的地址赋给指针变量ptr3
3、指针变量的引用
指针变量提供了对变量的一种间接访问形式。对指针变量的引用格式为:
*指针变量
例如:
#include <stdio.h>
#include <string.h>
int main()
{
int* p;
int m;
printf("请输入变量m的值:");
scanf("%d",&m);
p=&m; //指针变量p指向变量m
printf("指针所指向的变量m的值为:%d\n",*p); //对指针所指的变量的引用形式,与m意义相同
return 0;
}
以上程序的运行结果如下:
4、运算符 & 和 *
(1)& 是取地址运算符。&a 的运算结果是一个指针,指针的类型是 a 的类型加个 *,指针所指向的类型是 a 的类型,指针所指向的地址就是 a 的地址。
(2)* 是间接运算符。*p 的运算结果就是 p 所指向的东西,这个东西有以下特点:它的类型是 p 指向的类型,它所占用的地址是 p 所指向的地址。
例如:
#include <stdio.h>
#include <string.h>
int main()
{
int a=12;
int* p;
p = &a; //&a的结果是一个指针,类型是 int*,指向的类型是 int,指向的地址是 a 的地址。
*p = 24; //*p 的结果类型是 int,它所占用的地址是p所指向的地址,显然,*p 就是变量 a。
printf("%d,%d\n",*p,a);
printf("%p,%p",p,&a);
return 0;
}
以上程序的输出结果如下:
从以上结果可以看出:指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为 sizeof(指针所指向的类型) 的一片内存区。
我们说一个指针的值是 XX,就相当于说该指针指向了以 XX 为首地址的一片内存区域。我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。指针所指向的内存区和指针所指向的类型是两个完全不同的概念。
当我们遇到一个指针,都应该思考一下:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?
二、指针的算术运算
一个指针 pold 加(减)一个整数 n 后,结果是一个新的指针 pnew,pnew 的类型和 pold 类型相同,pnew 所指向的类型和 pold 所指向的类型也相同。pnew 的值将比 pold 的值增加(减少)n 乘 sizeof(pold 所指向的类型) 个字节。就是说,pnew 所指向的内存区将比 pold 所指向的内存区向高(低)地址方向移动了 n 乘 sizeof(ptrold 所指向的类型) 个字节。
指针和指针进行加减:两个指针不能进行加法运算,这是非法操作。两个指针可以进行减法操作,但必须类型相同,一般用于数组。
下面的例子中,指针 ptr 的类型是 int*,它指向的类型是 int,它被初始化为指向整型变量 a。当指针 ptr 加 1 以后,指针ptr 的值加上了 sizeof(int),即加了 4。ptr 所指向的地址由原来的变量 a 的地址向高地址方向增加了4 个字节。
#include <stdio.h>
#include <string.h>
int main()
{
int a[5]={1,2,3,4,5};
int* ptr = a;
printf("指针的地址:%p, 指针指向的数组元素的值:%d\n",ptr,*ptr);
ptr++;
printf("指针的地址:%p, 指针指向的数组元素的值:%d\n",ptr,*ptr);
return 0;
}
以上程序的输出结果如下:
可以使用指针和一个循环来遍历数组,例如:
#include <stdio.h>
#include <string.h>
int main()
{
int arr[10]={0,1,2,3,4,5,6,7,8,9},i;
int* ptr = &arr[0];
for (i=0;i<10;i++)
{
(*ptr)++;
printf("指针的地址:%p, 指针指向的数组元素的值:%d\n",ptr,*ptr);
printf("数组元素arr[%d]的值:%d\n",i,arr[i]);
ptr++;
}
return 0;
}
以上程序的输出结果如下:
三、指针与数组
数组的数组名可以看作一个指针。例如:
#include <stdio.h>
#include <string.h>
int main()
{
int array[10]={0,1,2,3,4,5,6,7,8,9},value,pvalue;
value=array[0];
pvalue=*array;
printf("array[0]的值:%d, *array的值:%d\n",array[0],*array);
value=array[3];
pvalue=*(array+3);
printf("array[3]的值:%d, *(array+3)的值:%d\n",array[3],*(array+3));
value=array[4];
pvalue=*(array+4);
printf("array[4]的值:%d, *(array+4)的值:%d\n",array[4],*(array+4));
return 0;
}
以上程序的输出结果如下:
说明:上例中的数组名 array 代表数组本身,类型是int[10]。如果把 array 看做指针的话,它指向数组的第 0 个单元,类型是 int*,所指向的类型是数组单元的类型即 int。因此 *array 等于0。同理,array+3 是一个指向数组第3 个单元的指针,所以 *(array+3) 等于 3。
关于数组的数组名的总结:以数组 int array[10] 为例,则数组名称 array 有两层含义:
(1)它代表整个数组,它的类型是 int[10];
(2)它是一个【常量指针】,该指针的类型是 int*,该指针指向的类型是 int,该指针指向的内存区就是数组第 0 号单元,该指针自己占有单独的内存区,注意它和数组第 0 号单元占据的内存区是不同的。该指针的值是不能修改的,即类似 array++ 的表达式是错误的。
(3)在不同的表达式中数组名 array 可以扮演不同的角色。
在表达式 sizeof(array) 中,数组名 array 代表数组本身,这时sizeof 函数测出的是整个数组的大小。
在表达式 *array 中,array 扮演的是指针,因此这个表达式的结果就是数组第 0 号单元的值。sizeof(*array) 测出的是数组单元的大小。
表达式 array+n(n=0,1,2,…)中,array 扮演的是指针,故 array+n 的结果是一个指针,它的类型是 int*,它指向的类型是 int,它指向数组第 n 号单元。故 sizeof(array+n) 测出的是指针类型的大小。
例如,有如下代码:
#include <stdio.h>
#include <string.h>
int main()
{
int array[5]={1,2,3,4,5},a=1,*p=&a;
printf("指针p的大小:%d\n",sizeof(p));//指针类型的大小
printf("*array的值:%d\n",*array); //第一个数组元素的值
printf("array的大小:%d\n",sizeof(array));//整个数组的大小
printf("指针array+1的大小:%d\n",sizeof(array+1));//指针类型的大小
printf("指针array+2的大小:%d\n",sizeof(array+2));//指针类型的大小
printf("array的地址:%p\n",array); //第一个数组元素的地址
printf("array的地址:%p\n",&array[0]);//第一个数组元素的地址
printf("*array的大小:%d\n",sizeof(*array));//第一个数组元素的大小
return 0;
}
以上程序的输出结果如下:
四、指针与结构体类型
如果需要访问结构体对象的成员,分两种情况:
(1)如果访问的是结构体变量的成员,采用【变量名.成员名】的格式。
(2)如果访问的是结构体指针指向的结构体成员,采用【变量名->成员名】的格式。
例如:
#include <stdio.h>
#include <string.h>
int main()
{
struct MyStruct
{
int a;
int b;
int c;
};
struct MyStruct s = {20,30,40};
struct MyStruct* ptr = &s; //声明一个指向结构对象s的指针。类型是MyStruct*,它指向的类型是MyStruct。
printf("访问结构体变量的成员:%d,%d,%d\n",s.a,s.b,s.c);
printf("访问结构体指针的成员:%d,%d,%d\n",ptr->a,ptr->b,ptr->c);
}
以上程序的输出结果如下: