C Language NO.5 指针
- 1. 什么是指针
- 2. 指针使用
- 3. 指针与数组
- 4. 函数与指针
1. 什么是指针
指针是某个内存块的地址,这个内存块包含一个变量。
指针是一个值,这个值代表一个内存地址,因此指针相当于指向某个内存地址的路标。
变量存储的地址
int age = 23;
printf("%p",&age);
// 00CFF7C0
使用&
运算符获取内存中该变量的地址值
使用%p
格式说明符打印指针的值。
结果会打印出来一串数字00CFF7C0
,它是age
变量的内存地址,以十六进制形式表示。
上面,通过&符号获取变量的内存地址,那获取之后如何来表示这是一个地址,而不是一个普通的值呢? 也就是在 C 语言中如何表示地址这个概念呢? 对,就是指针。
指针变量
如果有一个变量专门用来存放另一变量的地址(即指针),则它称为“指针变量”。
int age = 23;
int *ptr = &age;
printf("%x\n", ptr);
printf("%p\n", ptr);
//96fdb0
//0096FDB0
上述的ptr
就是一个指针变量。指针变量就是地址变量,用来存放地址,指针变量的值是地址(即指针)。
两个输出结果实际上表示的是相同的内存地址,只是格式不同。
%x
格式说明符输出不包含前缀 0x。
%p
格式说明符输出包含前缀 0x。
2. 指针使用
在 C 语言中通过操作符 *就可以拿到一个指针所指地址的内容了。*
为解引用操作符,通常跟在类型关键字的后面,表示指针指向的是什么类型的值。
比如,char*
表示指向字符的指针,简称char
指针,float*
表示一个指向float
类型的值的指针,*p
代表指针变量p
,指向的对象。
字符&
是取地址运算符,&a
是变量a
的地址。
指针的定义
一般形式:类型名 *指针变量名;
int* ptr; // 指向 int 类型的指针
char* ptr; // 指向 char 类型的指针
float* ptr; // 指向 float 类型的指针
指针的赋值
定义指针变量的同时,可以对它进行初始化
int a = 10; // 声明并初始化一个 int 类型变量 a
int* ptr1 = &a; // 声明一个指向 int 类型变量的指针,指向变量 a
char b = 'x'; // 声明并初始化一个 char 类型变量 b
char* ptr2 = &b; // 声明一个指向 char 类型变量的指针,指向变量 b
float c = 3.14; // 声明并初始化一个 float 类型变量 c
float* ptr3 = &c; // 声明一个指向 float 类型变量的指针,指向变量 c
指针的操作
取地址操作
p = &a;
把变量a的地址赋给指针变量p;
取内容操作
*p
如:b=*p;
把指针变量p的内容给b;修改指针变量的内容*p=1;
p++
使指针向下移动一个数据宽度,如p=p+5向下移动5个数据宽度;
p--
使指针向上移动一个数据宽度,如p=p-5向上移动5个数据宽度;
注:int类型数据宽度为4、char类型为1
示例1、输入a和b两个整数,按先大后小的顺序输出a和b,用指针方法交换a和b的地址。
#include <stdio.h>
int main()
{
int a, b;
int* p1, * p2, * p;
printf("请输入两个整数:");
scanf("%d,%d", &a, &b);
p1 = &a;
p2 = &b;
if (a < b)
{
p = p1;
p1 = p2;
p2 = p;
}
printf("%d, %d\n", a, b);
printf("%d, %d\n", p1, p2);
printf("%d, %d\n", *p1, *p2);
return 0;
}
********************************
请输入两个整数:12,33
12, 33
12517072, 12517084
33, 12
总结,由结果可知,a、b的值未变,而是交换两个指针变量的值,值就是a、b的地址。
示例2、.现在用函数处理,指针类型的数据作函数参数
#include <stdio.h>
int main()
{
void swap(int* p1, int* p2);
int a, b;
int* pointer1, * pointer2;
printf("请输入两个整数:");
scanf("%d,%d", &a, &b);
pointer1 = &a;
pointer2 = &b;
if (a < b)
swap(pointer1, pointer2);
printf("%d, %d\n", a, b);
printf("%d, %d\n", pointer1, pointer2);
printf("%d, %d\n", *pointer1, *pointer2);
return 0;
}
void swap(int* p1, int* p2)
{
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
***************************
请输入两个整数:12,33
33, 12
11532420, 11532408
33, 12
总结,交换a和b的值,而p1和p2不变,以及pointer1和2指向的地址也没变
3. 指针与数组
3.1 通过指针引用数组
一个数组包含若干元素,每个元素都在内存中占用存储单元,因此都会有相应的地址,指针变量可以指向数组元素,即数组元素的指针就是数组元素的地址。
int a[] = { 1,2,3,4,5 };
int* p;
p = &a[0];//把a[0]元素地址给指针变量p
p = &a;//与上面等价
数组名只代表数组中首元素的地址,不代表整个数组。
当指针指向数组元素的时候,允许对指针进行加和减的运算。
p+1指向同一数组中的下一个元素,p-1指向同一数组中的上一个元素。同时注意p+1是加上一个,数组元素所占用的字节数。比如数组元素是int型,p+1使p的值加4个字节,来指向下一个数组元素,即p+1x字节数。
int a[] = { 1,2,3,4,5 };
int* p;
p = &a;
printf("p=%d\n", p);
printf("p+1=%d\n", p+1);
printf("*p=%d\n", *p);
printf("*p+1=%d\n", *p+1);
访问数组元素
int a[] = { 1,2,3,4,5 };
for (int i = 0; i < 5; i++)
//两种输出方法
printf("%d\t", a[i]);//下标法引用数组元素
printf("%d\t", *(a+i));//通过数组名和元素序号计算元素地址,再找到该元素
//用指针变量指向数组元素
int a[] = { 1,2,3,4,5 };
int* p;
for (p=a; p <(a+5); p++)
printf("%d\t", *p);
//最后一种执行效率比前两种要快
3.2 二维数组与指针
这里以二维数组为例,来解释和说明与指针的关系。
int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};
a是二维数组,数组a有3个行元素,a[0],a[1],a[2],每一行有4个元素。
a[0]代表一维数组,该数组包含4个元素:a[0][0],a[0][1],a[0][2],a[0][3],可以这么说,二维数组a是由三个一维数组组成的。
从二维数组角度来看,a代表二维数组首元素的地址,它是由4个整型元素所组成的一维数组,因此a代表的是首行(0行)的起始地址。a+1代表序号为1的行的起始位置。
如果二维数组首行起始地址为2000,一个int数据类型占4个字节,那么a+1的值应该是2016(因为0行有4个int类型),如下图
进一步,a[0]代表一维数组a[0]中第0列的元素地址,那么如何表示二维数组a中0行1列元素?
显然用a[0]+1来表示,a[0]地址是2000,则a[0]+1为2004。如图
a[0]
和⁕(a+0)
等价、a[1]
和⁕(a+1)
等价,a[i]
和⁕(a+i)
等价;
a[0]+1
和⁕(a+0)+1
等价,且都是&a[0][1]
;
如何得到a[0][1]的值呢?
既然a[0]+1
和⁕(a+0)+1
都是a[0][1]
的地址,那么⁕(a[0]+1)
和⁕(⁕(a+0)+1)
就是a[0][1]
的值。
然后得出结论:⁕(a[i]+j)
和⁕(⁕(a+i)+j)
就是a[i][j]
的值。
注意:
1、在二维数组中a+1是二维数组序号是1的行的起始地址,在一维数组它是第1个元素地址;
2、在二维数组中⁕(a+1)它是a[1],它是一维数组名,所以也是地址,指向a[1][0]。但是在一维数组它是该地址指向的存储单元中的内容。
3、因此,在二维数组和一维数组中,指针的加法操作具有不同的含义:在二维数组中是跨行的操作,而在一维数组中是跨元素的操作。
3.3 数组指针
数组指针是指向一个数组的指针,指向数组的首地址,它可以被用来访问数组元素。
数组指针声明方式:
data_type (*ptr)[size];
示例
#include <stdio.h>
int main() {
int a[] = {1, 2, 3, 4, 5};
int (*p)[5]; // 声明一个指向包含5个整数的数组的指针
p = &a;
printf("a[2] = %d\n", (*p)[2]); // 输出数组a中的第3个元素
printf("a[4] = %d\n", (*p)[4]); // 输出数组a中的第5个元素
return 0;
}
3.4 指针数组
指针数组是一个数组,其中的每个元素都是指针类型。也就是说指针数组中的每一个元素都存放一个地址,相当于一个指针变量,它可以用于存储多个指向不同变量或对象的指针。比如存储字符串的数组、存储不同类型的指针、存储函数指针。
类型名 *数组名[数组长度]
int *ptr_arr[10]; // 定义一个包含十个整型指针的数组
这个数组包含了十个整型指针,我们可以通过下标来访问该数组中的任何一个元素。
int a = 10, b = 20, c = 30;
int *ptr_arr[3] = { &a, &b, &c }; // 定义并初始化一个指针数组
// 访问数组中的元素,并输出它们的值
printf("%d\n", *ptr_arr[0]); // 输出 10
printf("%d\n", *ptr_arr[1]); // 输出 20
printf("%d\n", *ptr_arr[2]); // 输出 30
存储字符串的数组
const char* strArray[3] = {"Hello", "World", "GPT"}; // 声明一个包含3个指向字符常量的指针的数组
存储不同类型的指针
int num1 = 10;
float num2 = 3.14;
char ch = 'A';
void* ptrArray[3];
ptrArray[0] = &num1; // 指向整数的指针
ptrArray[1] = &num2; // 指向浮点数的指针
ptrArray[2] = &ch; // 指向字符的指针
存储函数指针
int num1 = 10;
float num2 = 3.14;
char ch = 'A';
void* ptrArray[3];
ptrArray[0] = &num1; // 指向整数的指针
ptrArray[1] = &num2; // 指向浮点数的指针
ptrArray[2] = &ch; // 指向字符的指针
4. 函数与指针
4.1 指向函数的指针变量
在程序中定义一个函数,在编译的时候会给他分配一段存储空间,这个空间有一个起始地址,即函数的入口地址。每次调用函数的时候都是从此入口开始执行函数代码。这里函数名就是代表函数的起始地址,即函数名就是函数的指针。
定义一个***指向函数的指针变量***,用来存放某个函数的起始地址。如:
int(*p)(int,int);
上面定义p是一个指向函数的指针变量,指向整型且有两个整型参数的函数。
如果想调用一个函数,通过函数名调用之外,还可以通过指向函数的指针变量来调用该函数。
#include <stdio.h>
int main()
{
int max(int, int);
int(*p)(int, int); //定义指向函数的指针变量p,
int a, b, c;
p = max; //使p指向max函数
printf("please enter a and b:");
scanf("%d,%d", &a, &b);
c = (*p)(a, b); //通过指针变量调用max函数
printf("a=%d\nb=%d\nmax=%d\n", a, b, c);
return 0;
}
int max(int x, int y)
{
int z;
if (x > y)
z = x;
else
z = y;
return(z);
}
p=max;
表示将函数max
的入口地址赋给指针变量p
。和数组名代表首元素地址类似,函数名代表该函数的入口地址。调用*p
就是调用max函数,注意不能用*(p+1)
来表示函数的下一条命令。
说明:
1、定义一个指向函数的指针变量,并不是说这个指针变量可以指向任何函数,只有把函数的地址赋给指针变量,它才指向该函数,同时要参数类型和返回值类型相同。一个指针变量可以先后指向同类型的不同函数。
2、如果想用指针调用函数,必须先使指针变量指向该函数。如
p=max;
3、给函数指针赋值时候,只给函数名不用给参数,如
p=max(a,b)
是不正确的;
4、用函数指针变量调用函数时,只需用(*p)代替函数名,在它之后括号中根据需要写上实参,如
c=(*p)(a,b);
5、指向函数的指针变量不能进行算术运算;
6、使用函数名调用函数,只能调用所指定的一个函数,而通过指针变量调用函数,可以根据不同情况先后调用不同的函数。
下面示例,来说明指向函数的指针变量可以指向不同函数,并且调用不同函数。根据用户输入的是1还是2,使指针变量p指向max函数或min函数。
int max(int, int);
int min(int, int);
int (*p)(int, int);
int a, b, c, n;
printf("please chose 1 or 2:");
scanf("%d", &n);
if (n == 1)
p = max;
else if (n == 2)
p = min;
c = (*p)(a, b);
printf("a=%d,b=%d\n", a, b);
if (n == 1)
printf("max=%d\n", c);
else
printf("min=%d\n", c);
4.2 指向函数的指针作函数参数
即将函数的入口地址作为参数传递到其他函数。
下面示例根据不同情况,将不同的函数名作为调用fun函数的实参,把函数的入口地址传递给函数fun中的形参,此时调用fun函数就能分别执行不同的函数。
//主函数
int fun(int x,int y,int (*p)(int, int));
int max(int, int);
int min(int, int);
int add(int, int);
int a = 34, b = -21, n;
printf("please chose 1,2 or3:");
scanf("%d", &n);
if (n == 1)
fun(a, b, max);
else if (n == 2)
fun(a, b, min);
else if (n == 3)
fun(a, b, add);
return 0;
**************************************
//fun函数定义
int fun(int x, int y, int(*p)(int, int))//声明形参p是指向函数的指针
{
int result;
result = (*p)(x, y);
printf("%d\n", result);
}
当输入1时,调用fun函数,将a,b作为实参传递给fun函数的形参x和y,同时把函数名max作为实参,入口地址传递给fun函数的形参p,(*p)(x,y)
相当于max(x,y)
,调用该函数就可以输出a和b中大者。从上面示例来看,不论调用哪个一个函数,函数fun不会变,只是改变实参函数名。
4.3 返回指针值的函数
一个函数可以返回一个整型值、字符值、实型值等,也可以返回指针类型的数据,即地址。
例如 int *a(int x,int y);
a 为函数名,函数前有一个 *
,表示此函数指针型函数,(函数值是指针),int 表示返回的指针指向整型变量。调用后得到一个 int *
型的指针,即整型数据的地址。x 和 y 是函数 a 的形参且为整型。
有a个学生,每个学生有b门课程的成绩。要求在用户输入学生序号以后,输出该学生的全部成绩,使用指针函数。
#include <stdio.h>
int main()
{
// 定义包含三个学生成绩的二维数组,每行代表一个学生的成绩,每列代表一门课程的成绩
float score[][4] = { {60,70,80,90},{56,89,67,88},{34,78,90,66} };
// 声明 search 函数,接受一个二维数组指针和一个整型参数,返回一个指向浮点数的指针
float* search(float(*pointer)[4], int n);
float* p; // 定义一个指向浮点数的指针,用于保存查询到的学生成绩数组的首地址
int i, k; // 定义两个整型变量
printf("enter the number of student:"); // 提示用户输入要查询的学生序号
scanf("%d", &k); // 读取用户输入的值到变量 k 中
printf("The scores of No.%d are :\n", k); // 输出查询学生的序号
p = search(score, k); // 调用 search 函数查找并返回该学生的成绩指针,并将指针保存在变量 p 中
// 循环输出该学生的成绩
for (i = 0; i < 4; i++) {
printf("%5.2f\t", *(p + i));
}
printf("\n");
return 0;
}
// search 函数的实现
float* search(float(*pointer)[4], int n)
{
float* pt; // 声明一个指向浮点数的指针
pt = *(pointer + n); // 使用指针算术运算找到特定学生的成绩数组,并将其指针赋值给 pt
return pt; // 返回指向该学生成绩数组的指针
}
函数search定义为指针型函数,它的形参pointer是指向一维数组的指针变量,该一维数组包含4个元素。pointer 是指向第0行起始地址,pointer+1指向score数组第1行起始地址,*(pointer+1)指向1行0列元素,即pointer[1][0]的地址。
在main函数中调用search函数,将score数组首行地址传给形参pointer,k是要查找的学生序号。调用search函数后,main函数得到一个地址&score[k][0],该地址为第k个学生第0门课程成绩,然后把地址赋给p,最后使用*(p+i)依次输出第i门课程成绩。