C语言指针难吗???我用全网最全的指针知识告诉你。它不难!!!
-
- 前言
- 1.认识指针
- 2.为什么需要指针——通过两个练习来说明
- 3.对前面的指针知识咱们可以做一次作业来巩固
- 4.通过指针引用数组
- 5.通过指针引用数组练习
- 6.指针和二维数组
- 7.数组指针
- 8.函数指针
- 9.指针数组
- 10.指针函数——返回指针值的函数
- 11.二级(多级)指针
- 12.指针总结
-
- 1.中小公司大概率考题
-
- 1.一个整型数:int a;
- 2.一个指向整型数的指针:int *a;
- 3.一个指向指针的指针,它指向的指针指向一个整型数:int **a;
- 4.一个有10个整型数的数组:int a[10];
- 5.一个有10个指针的数组,每个指针指向一个整型数:int* a[10];
- 6.一个指向有10个整型数的数组的指针:int (*a)[10];
- 7.一个指向指针的指针,被指向的指针指向一个有10个整型数的数组:int(**a)[10];
- 8.一个指向数组的指针,该数组有10个整型指针:int* (*a)[10];
- 9.一个指向函数的指针,该函数有一个整型参数并返回一个整型数:int (*a)(int);
- 10.一个有10个指针的数组,每个指针指向一个函数,该函数有一个整型参数并返回一个整型数:int(*a[10])(int)
- 11.一个函数指针,指向的函数的类型是有两个整型参数并且返回一个函数指针的函数,返回的函数指针指向有一个整型参数且返回整型数的函数:int (*(*a)(int,int))(int)
- 13.结束语
前言
本篇博文是本人在学习嵌入式软件C语言中指针章节的笔记,适用于小白入门C语言的同学。希望能帮到大家。有什么不足可以评论告诉我。
在阅读本篇博文时,想学习的同学可以打打博文中的代码,甚至默写代码,改写代码,有助于你的学习。C语言注重多打代码,多练习,才能学得扎实。
1.认识指针
1.指针 == 地址
1.情景引入
假如你的女朋友和你约定在***酒店,你女朋友悄悄去了酒店开好了房间。你可以通过两种方式找到:
1.通过酒店的房间名称:“紫萱主题酒店森林主题房”——类比为:变量名
2.通过地址:“xx省xx市xx路xx号”——类比为:变量的地址
2.定义一个整型变量——int a = 10 ;
变量的四要素:类型、变量名、内存地址、值
变量的访问:1.通过变量名访问。2.通过地址也能访问('&'取地址运算符 '*'将地址内的值读出运算符)
2.指针变量 == 存放地址的变量
1.如何定义一个指针变量
int *a; //'*'的标识作用,只产生在指针变量定义或声明的时候
2.如何使用一个指针变量
int a = 10;
int *pa = &a; //定义一个指针变量指向 a
printf("*pa = %d\n",*pa); //'*'的运算作用,取出地址中的值
3.如何访问这两种方式
1.直接按照变量名进行访问,称为“直接访问”方式
2.“间接访问”方式,即将变量 a 的地址存放在另一变量中,然后通过该变量来找到变量 a 的地址,从而访问a变量
#include <stdio.h>
int main()
{
int a = 10;
int *pa = &a; //定义一个指针变量指向 a
printf("a = %d\n",a); //直接访问
printf("*pa = %d\n",*pa); //'*'的运算作用,取出地址中的值,间接访问
return 0;
}
4.类型
1.类型决定了指向空间的大小: int *pint; char *pchar;
2.决定增量。
#include <stdio.h>
/*
时间 2023年6月1日22:41:57
程序功能:指针变量为什么要求类型
*/
int main()
{
int a = 0x1234;
int *p;
char *c;
p = &a;
c = &a;
puts("程序功能:指针变量为什么要求类型");
puts("a通过指针变量int *p间接访问为 a = 1234");
puts("a通过指针变量char *c间接访问为 a = 34");
puts("是因为整型变量占4个字节,字符型变量占1个字节,一个字节内存空间无法存放16进制数");
puts("0x1234,所以显示为34\n\n");
puts("printf(“p+1 = %p杠n”,++p);");
puts("printf(“c+1 = %p杠n”,++p);");
puts("输出的结果分别是:");
puts("p+1 = 0x7fff665f6408");
puts("c+1 = 0x7fff665f6405");
puts("从输出的结果可以看出:");
puts("整型指针变量偏移4个字节,地址加4");
puts("字符型指针变量偏移1个字节,地址加1");
printf("p = %p\n",p);
printf("c = %p\n",c);
printf("a通过指针变量int *p间接访问为 a = %x\n",*p);
printf("a通过指针变量char *c间接访问为 a = %x\n",*c);
printf("p+1 = %p\n",++p);
printf("c+1 = %p\n",++c);
return 0;
}
3.完整代码示例
1.指针 == 地址
#include <stdio.h>
/*
时间 2023年5月31日18:25:24
程序功能:指针 == 地址
*/
int main()
{
int a;
a = 10;
puts("程序功能:指针 == 地址");
puts("");
puts("printf(“a的值为 %d杠n”,*(&a)); //*号为取值运算符,他把后面跟的内存地址中的数据");
puts("“取出来”");
puts("");
printf("a的值为 %d\n",a);
printf("a的地址为 %p\n",&a);
printf("a的值为 %d\n",*(&a)); //*号为取值运算符,他把后面跟的内存地址中的数据“取出来”
return 0;
}
2.指针变量
#include <stdio.h>
/*
时间 2023年5月31日21:01:07
程序功能:指针变量
*/
int main()
{
int a;
int *p; //这里的*是一个标识符,告诉系统我是一个指针变量,是用来保存别人地址的,和下方
a = 10;//的运算符不同
p = &a;
puts("什么是整型变量,存放整型数的变量");
puts("什么是字符变量,存放字符型数据的变量");
puts("什么是指针变量,存放指针的变量");
puts("什么是指针变量,存放地址的变量");
puts("");
puts("int *p; //这里的*是一个标识符,告诉系统我是一个指针变量,是用来保存别人地址的,");
puts("和下方的运算符不同");
puts("");
puts("不能这样写 《*p = &a》会出现这样的警告:");
puts("assignment to 'int' from 'int *' makes integer from pointer without a cast [-Wint-conversion]*p = &a;");
puts("从'int *'对'int'赋值使指针的整数不进行强制转换[-Wint-conversion] *p = a;");
puts("类型不相同 *p=a *号只是一个标识符");
puts("");
puts("");
puts("");
printf("变量名访问a:%d\n",a);
printf("a的地址为:0x%p\n",&a);
printf("地址访问a:%d\n",*(&a));//*号为取值运算符,他把后面跟的内存地址中的数据“取出来”
printf("指针变量的方式访问a:%d\n",*p);
return 0;
}
3.指针变量为什么要求类型
#include <stdio.h>
/*
时间 2023年6月1日22:41:57
程序功能:指针变量为什么要求类型
*/
int main()
{
int a = 0x1234;
int *p;
char *c;
p = &a;
c = &a;
puts("程序功能:指针变量为什么要求类型");
puts("a通过指针变量int *p间接访问为 a = 1234");
puts("a通过指针变量char *c间接访问为 a = 34");
puts("是因为整型变量占4个字节,字符型变量占1个字节,一个字节内存空间无法存放16进制数");
puts("0x1234,所以显示为34");
printf("p = %p\n",p);
printf("c = %p\n",c);
printf("a通过指针变量int *p间接访问为 a = %x\n",*p);
printf("a通过指针变量char *c间接访问为 a = %x\n",*c);
printf("p+1 = %p\n",++p);
printf("c+1 = %p\n",++c);
return 0;
}
2.为什么需要指针——通过两个练习来说明
练习1:封装一个函数,实现两个数交换
1.两数交换算法解析
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int tmp; //定义一个过渡变量
printf("交换前a和b的值分别是%d %d\n",a,b);
//下面是交换算法
tmp = a;
a = b;
b = tmp;
printf("交换后a和b的值分别是%d %d\n",a,b);
return 0;
}
/*
交换算法:
假设有两个杯子a和b,a存放100mL水,b存放50mL水。如果要将两个杯子里的水互换,就应该拿出c杯子用于过渡存放。
方法步骤为:
1.将a杯子中的水倒入c中
2.将b中的水倒入a中
3.将c中的水倒入b中
这时你将会发现a和b中的水进行了互换。
写成代码就是这样的:
tmp = a;
a = b;
b = tmp;
*/
2.程序代码
#include <stdio.h>
/*
时间 2023年6月2日20:45:22
程序功能:练习1:封装一个函数,实现两个数交换
*/
void Exchange(int *pdata1,int *pdata2)
{
int temp;
temp = *pdata1; //这里的*号为取值运算,他把后面跟的内存地址中的数据“取出来”
*pdata1 = *pdata2; //也就相当于data1,data2本身的值在做交换
*pdata2 = temp;
}
int main()
{
int data1 = 10;
int data2 = 20;
puts("语句:");
puts("temp = *pdata1;");
puts("*pdata1 = *pdata2;");
puts("*pdata2 = temp;");
puts("这里的*号为取值运算,他把后面跟的内存地址中的数据“取出来”");
puts("");
puts("");
puts("语句:");
puts("void Exchange(int *pdata1,int *pdata2)");
puts("这里的*是一个标识符,告诉系统我是一个指针变量,是用来保存别人地址的");
puts("");
puts("");
printf("两数交换前的值为data1 = %d,data2 = %d\n",data1,data2);
Exchange(&data1,&data2);
printf("两数交换后的值为data1 = %d,data2 = %d\n",data1,data2);
return 0;
}
练习2:指针指向固定的区域
1.程序代码
#include <stdio.h>
/*
时间 2023年6月2日21:57:39
程序功能:练习2:指针指向固定的区域
*/
int main()
{
int a = 10;
puts("程序功能:练习2:指针指向固定的区域");
puts("语句:");
puts("int *p = (int *)0x000000000000000B;");
puts("(int *):代表把后面的地址“0x000000000000000B”16进制数强制转换成指针类型才能赋给指针变量");
puts("");
puts("");
puts("语句:");
puts("volatile unsigned int *q = (volatile unsigned int *)0x000000000000000c;");
puts("volatile的作用为防止编译器优化地址,导致地址改变");
puts("unsigned无符号类型,存放地址的类型应是无符号类型");
puts("");
puts("");
printf("a的地址是:0x%p\n",a);
int *p = (int *)0x000000000000000B; //将16进制的地址强制转换成为指针类型才能赋给指针变量
volatile unsigned int *q = (volatile unsigned int *)0x000000000000000c;
printf("p = 0x%p\n",p);
printf("q = 0x%p\n",q);
return 0;
}
3.对前面的指针知识咱们可以做一次作业来巩固
作业1:输入三个数a,b,c;要求不管怎么输入,在输出的时候,a,b,c就是由大到小的顺序输出,用函数封装实现
1.作业内由大到小算法演示图
2.程序代码
#include <stdio.h>
/*
时间 2023年6月3日19:40:31
程序功能:输入三个数a,b,c;要求不管怎么输入,在输出的时候,a,b,c就是由大到小的顺序输出,
用函数封装实现
*/
void dataSort(float *data1,float *data2,float *data3)
{
float temp;
if(*data1<*data2)
{
temp = *data1;
*data1 = *data2;
*data2 = temp;
}
if(*data2<*data3)
{
temp = *data2;
*data2 = *data3;
*data3 = temp;
}
if(*data1<*data2)
{
temp = *data1;
*data1 = *data2;
*data2 = temp;
}
}
int main()
{
float data1;
float data2;
float data3;
puts("程序功能:输入三个数a,b,c;要求不管怎么输入,在输出的时候,a,b,c就是由大到小的");
puts("顺序输出,用函数封装实现");
puts("");
puts("");
puts("");
puts("请输入三个数,以空格间隔,按回车结束");
scanf("%f %f %f",&data1,&data2,&data3);
dataSort(&data1,&data2,&data3);
printf("你排序后的结果为 %f\t%f\t%f\t\n",data1,data2,data3);
return 0;
}
4.通过指针引用数组
1.定义一个指针变量指向数组
1.指向数组首元素的地址
可以用一个指针变量指向一个数组元素。例如:
int arr[10] = {
1,3,5,7,9,11,13,15,17,19}; //定义arr为包含10个整型数据的数组
int *p; //定义p为指向整型变量的指针变量
p = &arr[0]; //把arr[0]元素的地址赋给指针变量p
2.数组名就是数组首元素地址:指向数组起始位置
在c语言中,数组名(不包括形参数组名,形参数组并不占据实际的内存单元)代表数组中首元素(即序号为0的元素)的地址。因此下面两个语句等价:
p = &arr[0]; //p的值a[0]的地址
p = arr; //p的值是数组arr首元素(即arr[0])的地址
3.完整代码演示
#include <stdio.h>
/*
时间 2023年6月4日18:46:48
程序功能:定义一个指针变量指向数组
*/
int main()
{
int arr[3]={
1,2,3};
int *p;
int *q;
puts("程序功能:定义一个指针变量指向数组");
puts("p = &arr[0]; 指针变量p指向数组arr的首元素地址,也就是数组arr的首地址");
puts("q = arr;数组名arr代表数组arr的首地址。并且指针变量q指向数组arr的首元");
puts("素地址,也就是数组arr的首地址");
puts("");
puts("");
p = &arr[0];
printf("p = &arr[0]; p = %d\n",*p);
q = arr;
printf("q = arr; q = %d\n",*q);
return 0;
}
2.指针增量和数组的关系
1.代码演示
#include <stdio.h>
/*
时间 2023年6月4日21:42:39
程序功能:指针增量和数组关系
*/
int main()
{
int arr[3]; //定义一个数组
int i; //循环条件变量i
int *p; //定义一个指针变量p
p = arr; //指针变量指向数组首元素
puts("程序功能:指针增量和数组关系");
puts("scanf(“%d”,&*(p+i));//取指针变量地址上的值的地址来进行遍历");
puts("其中p+i,p+1为指针变量偏移");
puts("指针变量偏移时地址加上的值通常是类型大小(字节数)的倍数。这是因为变量在内存中占用");
puts("的空间大小与其数据类型有关,而整型类型通常会占用4个字节,因此偏移时地址加4是为了");
puts("移动到相邻的变量的起始地址。");
puts("");
puts("");
puts("开始输入");
for(i=0; i<3; i++)
{
printf("请输入第%d个元素的值。\n",i+1);
scanf("%d",&*(p+i));//取指针变量地址上的值的地址来进行遍历
}
puts("输入结束!!!");
puts("");
puts("");
puts("开始输出");
for(i=0; i<3; i++)
{
printf("%d\t",*(p+i));
}
puts("\n输出结束!!!");
return 0;
}
2.图示说明
3.指针变量补充,记得回到数组首地址
#include <stdio.h>
/*
时间 2023年6月5日20:37:24
程序功能:指针变量补充,记得回到数组首地址
*/
void introduce()
{
puts("程序功能:指针变量补充,记得回到数组首地址");
puts("指针偏移还可以这样写: