c语言入门-指针

目录

指针

指针概念

指针变量

定义指针变量

引用指针变量

指针变量作为函数参数

指针运算

数组名做函数参数

二维数组的指针

 数组指针的传参

 字符指针

指向函数的指针

 指向指针的指针

野指针


指针

指针概念

要想明白指针,我们先要明白数据在内存中是如何存储的。

1.计算机的内存空间是由许多存储单元构成的,每一个存储单元代表着一个字节,每个存储单元对应一个唯一的编号,也就是地址。程序在运行时,数据就存放在这些存储单元内,使用数据时有两种方法,一是使用变量名直接读取,另外一种就是通过变量的地址来读取内容。

2.指针-即地址。我们把一个变量的地址称为该变量的指针。由于通过地址能够找到所需的变量单元,也就是说地址指向该变量单元,因此,将地址形象化地称为”指针“。

指针变量

指针变量:用于存放变量地址的变量 

指针:变量的地址。

区分两者概念:例如”int i=0; i的地址是0x 11223344,int *p=&i;那么p就是指针变量,而0x 11223344就是变量i的指针。

定义指针变量

指针变量的一般定义形式:类型名 * 指针变量名

例如:int * pa ,int 表示pa所指向的数据类型是int类型,*表示变量为指针变量,pa是指针变量名

类型名:用于指定指针变量指向的变量类型

在c语言中数据是分类型的,不同的数据类型,内存所分配的字节数目和存储方式都是不同的(整数:补码形式,实数:指数形式),如果说只给定了地址,但是不知道数据所占空间大小和存储方式,是无法正确取出数据的。

例如:int *pa pa是指向int类型变量的指针变量,float *pa pa是指向float类型变量的指针变量 

指针类型:指向整形数据的指针类型为int *,读作指向int的指针。指向字符型数据的指针类型为char *,读作指向char的指针。

例如:int *pa ,pa的类型是int *,int * 是pa的类型。float *pa ,pa的类型是float *,float* 是pa的类型。

 从语法上看,只要把指针声明语句里的指针名去掉,剩下的部分就是指针的类型

例如:int *pa,指针类型是int *,int **p,指针的类型是int**,int (*p)[7],指针类型是int (*)[7],int *(*p)[7],指针类型是int *(*)[7]。

从语法上看,只要把指针声明里的指针名和名字左边的指针声明符去掉,就是指针所指向的类型

例如:int *pa,pa是指向int类型的指针变量 ,int **p,p是指向int *类型的指针变量,int (*p)[7],p是指向int ()[7]类型的指针变量,int *(*p)[7],p是指向int *()[7]类型的指针变量

#include<stdio.h>
int main()
{
	int a=10, b=20;
	int* pa, * pb;
	pa = &a;
	pb = &b;
	printf("%d,%d\n", a, b);
	printf("%d,%d", *pa ,* pb);
	return 0;
}

引用指针变量

两种运算符 &(取地址运算符):取出变量地址,*(指针运算符),*p代表指针变量p指向的对象。

1.定义指针变量和a变量 int *p,a;

1.指针变量赋初值:p=&a;将a的地址赋给了指针变量p

2.赋值语句:*p=1,将1赋给了a,a=1

3.引用指针变量所指向的值 printf("%d",*p) 以输出整数形式输出指针变量所指向的值,即a的值。

将两个整数按照从小到大输出

#include<stdio.h>
int main()
{
	int a, b, tmp;
	int *pa, * pb, * p;
	pa = &a;
	pb = &b;
	printf("输入两个数字\n");
	scanf("%d%d", &a, &b);
	if (*pa > *pb)
	{
		 tmp = *pa;
		*pa = *pb;
		*pb = tmp;  // 改变了a,b的值

		//p = pa;
		//pa = pb;
		//pb = p;// a,b的值没有交换
	}
	printf("%d,%d\n", a, b);
	printf("%d,%d", *pa ,* pb);
	return 0;
}

指针变量作为函数参数

交换两个整数

#include<stdio.h>
void swap(int* pa, int* pb)
{
	int t;
	t = *pa;
	*pa = *pb;
	*pb = t;
 // 传址调用,改变了形参,实参改变
}
int main()
{
	int a, b;
	int* pa = &a; int* pb = &b;
	printf("输入两个数字\n");
	scanf("%d%d", &a, &b);
	swap(pa, pb);
	printf("%d,%d\n", a, b);
	printf("%d,%d\n", *pa, *pb);
	return 0;
}

指针运算

我们以一维数组为例来探讨指针运算。

指针可以进行加减运算

在除了两种特殊情况(sizeof(数组名)和 &arr时数组名代表整个数组),其余情况数组名都表示首元素地址。

int arr[5]; 定义一个一维数组

int *p=&arr[0];  int *p=arr;  两个语句等价,都是将数组的首元素地址赋给p。

(1)如果p指向数组中的一个元素,那么 p+1指向同一数组中的下一个元素,p-1指向同一数组的上一个元素。注意:指针运算是指对应地址跳过相应类型的字节数,例如int型的指针,int *p,p+1是指p的地址加四个字节,得到了下一个元素的地址,char *p,p-1是指在p所对应的地址减去1个字节。

(2)如果说int *p=&arr[0];那么p+i对应的就是arr[i]的地址。

(3)*(p+i) 和*(arr+i) 和arr[i]等价

(4)如果说p1,p2都指向同一个数组的元素,那么p2-p1的值是(p2-p1)/数组元素类型的字节数(p2-p1:地址之差),得到的是两个地址之间的元素个数。例如int *p2=&arr[3],int *p1=&arr[1],那么p2-p1的结果是2。

int* p1 = &arr[1];
 int* p2 = &arr[9];
  printf("%d\n", p2 - p1);  // 8  两个之间的元素个数
  printf("%d\n", *p2 - *p1);//两个元素之差

注意:两个地址不能相加:例p1+p2是没有任何意义的。

打印数组的所有元素

#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,20 };
	int* p = arr;
	int i = 0;
	for (i=0;i<10;i++)
	printf("%d ", *(p+i));
	return 0;
}

 (5)自加自减

*p++:由于*和++优先级处于一级,结合性从右往左,所以先++,后解引用。

所以*p++和*(p++)等价。

*(++p),先++,后解引用  int *p=arr; *(++p)=arr[1]

*p++ 等价*(p++) ,先解引用,后++  int *p=arr; *(p++)=arr[0]

++(*p)元素值+1  int *p=arr;  ++(*p)=arr[0]+1

数组名做函数参数

#include<stdio.h>
void exchange(int * str,int n)
{
	//int sz = sizeof(str) ; 4
	for (int i=0;i<n;i++)
		printf("%d ", *(str+i));
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	exchange(arr,sz);
	return 0;
}

数组名作为函数参数时,在此情况下,数组名表示首元素地址,所以形参变量需要使用指针来接收,可以证明这一点的是,在所调用的函数内部使用sizeof来计算str的大小,所得的是指针大小。也就是在32位平台是4,64位平台是8。

二维数组的指针

我们设定一个二维数组。int arr[3][4],表示一个三行四列的二维数组,每一行又是一个一维数组。

 所以说可以认为二维数组是数组的数组,即arr是由3个一维数组所构成的。

以二维数组的角度来看,arr代表二维数组首元素的地址,而现在的首元素是由第一行的所有元素构成的,所以说arr代表的是第一行的地址,arr+1代表的是第二行的地址,由于第一行是一维数组,一维数组的地址是首元素地址,所以说arr[0]是arr[0][0]的地址,arr[1] 代表arr[1][0]的地址。

那么,我们如何表示某一个元素的地址呢。arr[0]是一位数组名,那么第一行的第一个元素地址就可以用arr[0]+0来表示,arr[0]+2也表示arr[0][2]的地址。

arr[i]和*(arr+i)等价,那么arr[i][j]的值就是*(arr[i]+j),也等价于*(*(arr+i)+j)。

注意:二维数组*(arr+2)代表的是arr[2],也就是arr[2][0]的地址。 arr是数组名,表示第一行的地址,*arr等价于*(arr+0),等价于arr[0],得到的是首元素arr[0][0]的地址,而*arr[0],arr[0]是arr[0][0]的地址,解引用表示对arr[0][0]的地址解引用,得到的是arr[0][0]的值。

输出二维数组的元素

#include<stdio.h>
int main()
{
	int arr[3][2] = { 1,3,4,2,5,9 };
	int i = 0;
	for (int k = 0; k < 3; k++)
	{
		for (i = 0; i < 2; i++)
		{
			printf("%d ", *(*(arr+k) + i));
		}
	}
	return 0;
}

了解了这些,我们开始用指针指向二维数组。

我们可以定义p指针,p指针指向一个包含有m个元素的一维数组。

例如 int arr[4][8];四行八列的整形二维数组,定义数组指针来指向这一个二维数组,int (*p)[8]

由于[]的优先级比*高,所以说使用()来改变优先级。

数组指针:int (*p)[4]:(*p)表示p是一个指针变量,[4]表示指针变量指向一个一维数组,再与int 结合,表示数组里面的元素是int类型。也就是p是一个指向由整形数据所组成的数组的指针。就是说p是一个指针,指向的内容是一个int类型的数组。

指针数组:int *p[4],p先和[4]结合,表示p是一个数组,和*结合,表示数组里面的元素全部都是指针类型,int 表示数组所指向的内容是整形的,p是一个由返回类型是int的指针所构成的数组。就是说,p是一个数组,数组里面的元素是int *类型的指针。

输出二维数组的元素

#include<stdio.h>
int main()
{
	int arr[3][2] = { 1,3,4,2,5,9 };
	int i = 0;
	int(*p)[2];//定义p指针变量指向一个包含有两个整形的一维数组
	p = arr;//p指向二维数组的第0行
	int k = 0;
	for (k = 0; k < 3; k++)
	{
		for (int i=0;i<2;i++)
	//	printf("%d ", *(*(p + k) + i));
		//	printf("%d ", *(p[k] + i));
			printf("%d ", p[k][i]);
	}
	return 0;
}

 数组指针的传参

有三名同学,每一位同学有四门成绩,计算平均分,并查找学生成绩。

#include<stdio.h>
void average(int *p,int n) //指向arr的每一个元素
{
	float sum = 0;
	for (int i = 0; i < n; i++)
		sum += *(p + i);
	printf("平均成绩是");
	printf("%lf\n", sum / n);
}
void search(int(*p)[4],int i)
{
	for (int j = 0; j < 4; j++)
		printf("%d ", *(p[i-1] + j));
}
int main()
{
	int arr[3][4];
	int(*p)[4] = arr;
	int i = 0;
	printf("请输入学生成绩;");
	for (i = 0; i < 3; i++)
	{
		for (int j = 0; j < 4; j++)
			scanf("%d", &arr[i][j]);
	}
	average(* arr,12);//传入arr[0][0]的地址
	printf("输入想要查找的学生成绩:学生序号1~%d\n",3);
	scanf("%d", &i);
	search(p, i);//查找某一位同学的成绩 对行进行查找
	return 0;
}

 字符指针

#include<stdio.h>
int main()
{
	char* p = "Hello World";
	printf("%s", p);
	return 0;
}

直接用字符串常量来初始化一个指针。c语言对于字符串常量按照字符数组来处理,但是我们现在没有定义字符数组的名字,所以说只可以用指针来引用。

对于字符指针变量的初始化,实际上把字符串首元素地址赋给了指针变量。

实现字符串复制

#include<stdio.h>
void string_copy(char *p, char *str)
{
	/*while (*str)
	{
		*p = *str;
		p++;
		str++;
	}
	*p = '\0';*/

	/*while (*str)
	{
		*p++ = *str++;
	}
	*p = '\0';*/

	while (*p++ = *str++)
		;
}
int main()
{
	char arr[] = "Hello World";
	char* p = arr;
	char* str = "HI";
	 string_copy(p, str);
	printf("%s", p);
	return 0;
}

指向函数的指针

什么是函数指针:在程序中定义了一个函数,编译的时候会把函数的源代码转化为可执行代码并分配存储空间,这份空间有起始地址,函数名代表起始地址

定义函数指针:int (*p)(int ,int),定义p是一个指向函数的指针变量,它可以指向函数类型为整形且具有两个整形参数的函数,这时指针的类型是int (*)(int int)。

通过函数指针来调用函数

#include<stdio.h>
int exchange(int *a, int *b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	printf("a=%d,b=%d\n", a, b);
	int (*p)(int*, int*);//定义一个指向int类型且参数是int *,int*的函数的指针
	p = exchange;//p指针指向函数
	(*p)(&a, &b);//调用函数
	printf("a=%d,b=%d", a, b);
	return 0;
}

指针数组:一个数组里面的元素是指针。指针数组可以用来指向多个字符串。

将字符串按照从小到大输出
#include<stdio.h>
#include<string.h>
void sort(char** p)//使用冒泡排序,使得每一个指针所指向的内容发生改变
{
	char* s;
	for (int i = 0; i < 3; i++) 
	{
		for (int j = 0; j < 3 - i; j++)//3
		{
			if ((strcmp(p[j], p[j + 1])) >0)
			{
				s = p[j];
				p[j] = p[j + 1];
				p[j + 1] = s;
			}
		}
	}
}
void print(char ** p)
{
	for (int i = 0; i < 4; i++)
	{
		printf("%s\n", p[i]);
	}
}
int main()
{
	char* p[4]={"base","hello","oo","hi"}; // 定义一个指针数组,其内的元素是指针
	sort(p);
	print(p);
	return 0;
}

 指向指针的指针

*p=&i;p是指向i的指针

**pp=&p;pp是指向p的指针

int  **p,p和*结合,表示p是一个指针,再次和*结合,表示指针所指向的内容也是指针,int表示指针所指向的是一个int*类型的指针

#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;//p是指向arr的指针 p指向arr【0】
	int** pp = &p;//pp是指向p的指针,pp指向arr【0】,*pp得到arr【0】的地址,*pp+1 得到arr【1】的地址
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(*pp + i));//打印数字
	}
	printf("\n");
	for (int i = 0; i < 10; i++)
	{
		if( (*(*pp+i))%2==0)
		printf("%d ", *(*pp + i));//打印偶数
	}
	return 0;
}

int *(*p(int))[3]: p和()结合,说明p是一个函数,int说明函数的参数是int,*表示函数返回的是一个指针,和【】结合,表示指针指向的是一个数组,再和*结合,说明数组的每一个元素都是指针,int表示指针的每一个元素类型是int ,也就是说p指向一个函数,函数参数为int,返回类型是一个指针,指针指向一个数组,这个数组里面的元素是int类型的指针。

野指针

1.局部指针变量没有初始化,默认随机值 int *p;p没有指向对象。

2.指针越界访问

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言入门到精通》是一本针对C语言学习的教程。本书以系统完整的方式介绍了C语言的基本概念、语法和应用,并通过大量的示例和练习帮助读者快速掌握C语言编程技巧。 书籍的第一部分主要介绍了C语言的基础知识。首先,它介绍了C语言的发展历程以及与其他编程语言的区别。然后,书中详细解释了C语言的数据类型、运算符、流程控制语句和函数等基本概念,帮助读者建立起对C语言编程的基础理论知识。 第二部分是关于C语言的高级应用和技巧。本书详细介绍了C语言的数组、指针、结构体和文件操作等高级特性。读者可以通过学习这些知识,掌握更加灵活、高效的编程方法,提高自己的程序设计能力。 此外,本书还包含了一些常用的C语言库函数和相关工具的介绍,如字符串处理函数、内存管理函数和调试工具等。这些内容帮助读者更好地理解和使用C语言的相关资源,提高自己的编程效率。 《C语言入门到精通》不仅提供了理论知识的讲解,还提供了大量的实战编程示例和练习题。这些示例和练习题既可以帮助读者巩固所学知识,又可以提供编程实践的机会,培养读者的编程思维和解决问题的能力。 总的来说,这本书适合初学者作为C语言学习的入门教材,也适合有一定编程基础的读者作为C语言进阶学习的参考书。无论是新手还是专业人士,通过学习这本书,都可以逐步掌握C语言的基本概念和高级应用,从入门到精通。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值