C语言指针学习

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;
}
********************************
请输入两个整数:1233
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;
}
***************************
请输入两个整数:1233
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门课程成绩。

  • 22
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值