文章目录
一: 致命打击 ❤️
(一)和指针相关的简单知识你了解多少?
指针数组了解吗?
函数指针了解吗?
指向指针的指针你想了解吗?💔
指向函数的指针数组你还想了解吗?💔 💔
二:“💀灵魂💀”简介
(一)基础储备
①&运算符:查找地址。(❓不知道你是否想到了 scanf() 函数,scanf(“%d”,&x); scanf()函数就是从用户那里得到了数据之后存放到变量 x 的地址上的。)
②*运算符:间接运算符或称为解引用运算符,就是“解封"的意思(敏感词汇💥)
(解除地址的封印,根据地址来找住在这里的对象)
③“解除地址的封印”:从字面上理解,就是解除地址的封印啊,就是挖开这个地址,看看这个地址上有什么东西(💪简单粗暴的,往往是最好的解决方式👏)。
很好👀。那么假如有 int a; &a 就代表变量a的地址,
那么 *(&a) 代表的还是变量 a 喽。我说的有道理吧(解除地址的封印)
(二)什么是指针?
从根本上来看,指针就是一个值为内存地址的变量。
🌀*分析:
①值为地址:说明指针是存储了一个值,这个值是某个地址,地址不就相当于我们家的门牌号吗🐶
②变量:指针也是一个变量类型,就像整形变量、字符型变量一样,说明指针变量也占据空间,那么也就是说中指针也有自己的地址,
那么也就是说可以用另一个指针来指向一个指针喽🌚,因为指针也是有地址的啊,那就是存在"指向指针的指针喽"
我说的是不是很有道理?💕💕💕💕
(三)为什么说指针是C语言的灵魂
在我看来,所有的编程语言几乎都是一样的,都是为了让计算机来来实现某个功能,只不过是简单与复杂罢了,可能是因为编程初学者的头发都比较旺盛,所以C语言有了指针,让C语言变得特别灵活(数组指针、指针数组、指向函数的指针等等),大大大大的提高了编程的效率,也大大大大大的提高了我们学习的难度。如果没有指针,可能在工作中没有人会愿意选择C语言来实现编程,毕竟编程语言是越来越简洁的,比如后续的Java、Pathon等等。
不管指针有多难,不管你现在指针有多么的反感,我都希望宝子们能够沉下心来💕💕,慢慢学习指针,我也是学了很久的指针才稍微搞懂了其中的奥秘。加油~💕💕!
(四)指针的声明
整型变量的声明是: int x;
那指针变量的声明能否写成 pointer p; 呢?🐳
显然是不可以的🌝,因为在声明指针变量的同时,必须要让指针指导它指向的变量的类型,这样它才能知道自己指向的空间的类型(因为不同的类型所占用的内存空间是不同的),那么指针该如何定义呢👻👻👻?
①首先要有一个标识来说明声明的变量是一个指针的类型
②要标明所声明的指针指向的数据的类型
eg:
int * pi; //pi是一个指向int类型变量的指针✈️
char * pc; //pc是一个指向char 类型变量的指针✈️
float * pf; //pf是一个指向float类型变量的指针✈️
pi、pc、pf是指向不同类型变量的指针,也就是说pi、pc、pf上储存的都是对应变量类型的一个地址,那么*pi 就代表一个 int 类型变量;
*pc 就代表是一个 char 类型的变量;
同理 *pf 也就是代表一个指向float类型的变量
(现在是不是稍微有了点儿头绪呢?🚀 🚀🚀 🚀)
(五)指针变量和整数类型
地址,好像都是整数数据,没有0.5的内存存在,既然是这样,那么也就是说指针存储的数据类型都是整数类型呗,那么指针变量不就是整数类型吗💯?
想啥呢,臭宝儿💩,指针实际上是一个崭新的类型,其实我也不知道为什么👻👻,
明明它存储的就是整数啊,
希望读者能帮助我解答这个问题,哈哈哈🎉🎉。
一些处理整数的操作不能用来处理指针的,比如可以把两个整数相乘,但是不能把两个指针来相乘,
不服气的话你可以去试试,
明说,指针可以相加,可以相减,可以指针++,指针- -
(六)提前感受一波指针的魅力吧
😃😃😃😃😃😃😃
#include<stdio.h>
void change1(int x,int y) //传入普通的int类型数据
{
int t;
t=x;
x=y;
y=t;
return;
}
void change2(int *x,int *y) //传入指向int类型的指针,即传入的是地址
{
int t; //交换地址上的值
t=*x; //传入的是地址,而需要交换的是地址上的值,所以要用 *x 、*y ,来获取 x、y 的值来进行交换
*x=*y;
*y=t;
return;
}
int main()
{
int x,y;
x=1;
y=3;
printf("x=%d\ny=%d",x,y); //初始数据
change1(&x,&y); //调用函数change1
printf("\nchange1:");
printf("\nx=%d\ny=%d",x,y); //x,y的值没有交换
printf("\n*****************************************");
change2(&x,&y); //调用函数change2
printf("\nchange2:");
printf("\nx=%d\ny=%d",x,y); //x,y的值进行了交换
return 0;
}
从上面这个简单的例子中你应该有了一点自己的见解,是不是说想要交换变量的值,准确来说是改变另一个函数中数据的值,就要用到指针来传递变量的地址来实现呢?
当需要在函数中访问另一个函数中的数据时,可以把指针作为函数的参数。
(七)重新认识变量(名称、值、地址)
🔔编写程序时可以认为变量具有两个属性:名称和值(当然还有其他性质,如类型,现在暂不讨论)。计算机编译和加载程序后认为变量也有两个属性:地址和值。地址就是变量在计算机内部的名称。
💨💨在许多语言中,地址都归计算机负责管理,对编程者隐藏,然而在C语言中,编程者可以通过地址运算符&来获取变量的地址,通过解引用运算符来获取地址上的值。
🔥🔥简而言之,普通变量把值作为基本量,把地址作为通过&运算符获取的派生量,而指针变量把地址作为基本量,把值作为通过*运算符获取的派生量。
💨💨虽然打印地址可以满足我们的好奇心,到那时这并不是&运算符的主要用途,更重要的是使用和&运算符来操纵地址和地址上的内容。
三:指针和数组
(宝子,要认真听o)💕💕💕💕💕
(一)指针和数组的关系
一句话,数组表示法其实是在变相的使用指针。
(二)指针的相关操作
😑😑回顾😑😑:数组名是该数组首元素的地址。也就是说,如果flizany是一个数组,那么下面的关系成立:
int flizany[10];
flizany=&flizany[0];
两者都表示数组flizany数组首元素的地址,两者都是常量,在程序运行的过程中不会改变,但是可以通过把两者赋值给指针,通过指针的运算来间接对他们两者进行运算。
1.把数组赋值给指针(地址)
int *p1;
p1=flizany;
int *p2;
p2=&flizany[2];
2.解引用指针
*p1就是flizany[0上的值,两者等价;
同理,*p2就是flizany[2]上的值,两者等价;
3.解引用未初始化的指针
❗️ ❗️ ❗️❗️ ❗️ ❗️❗️ ❗️ ❗️
宝子~你一定要牢记:千万不要解引用未初始化的指针。
why❓
考虑下面的例子:
int * pt;
*pt=5;
很好👍👍👍,现在我想问问宝子们💕:指针 pt 指向哪里呢?谁的值又是 5 呢?是昨天定义的变量 x 吗?还是上学期定义的变量 y 啊?👊👊
第2行的意思是把5储存在pt指向的位置,但是pt未被初始化,其值是一个随机值,所以并不知道 5 将会被储存在何处,这可能不会出什么错,也可能会擦写数据或者代码,或者导致程序崩溃。
🔥🔥🔥切记:创建一个指针时,系统只分配了储存指针本身的内存,并没有分配储存数据的内存。因此,在使用指针之前,必须先用已分配的地址来初始化它。
🚀🚀例如:可以使用一个现有变量的地址来初始化该指针(使用带指针形参的函数时就属于这种情况)。或者还可以用malloc()函数来动态申请空间。
总之,无论如何,在使用指针时千万不要解引用未初始化的指针。
记住了吗,宝儿子💕💕?
4.指针与整数相加(减)
p1+1;---->代表指针指向数组flizany的下一个元素,即flizany[1]🚀
p2-1 ;---->代表指针指向数组flizany的上一个元素,即flizany[1]🚀
5.递增(递减)指针
我有点儿乏了,这一小部分我不想写了,就是这么随意🙉,自己类比“指针与整数相加(减)”吧
温馨提示:前缀或者后缀递增递减都是可以用的O💌💌
6.指针求差
p2-p1;---->代表两个指针中间相差的元素个数,并不是字节数哦。
🚀🚀回想一下指针声明的时候为什么要让你指明指针指向的数据类型,指明了数据类型,才能知道需要对少字节哦,是否顿悟了🙀🙀🙀
7.指针比较
🚀🚀(洗脑时间)指针的值是地址,地址的值有大小,所以指针当然可以进行比较,不就是指针大的在后,指针小的在前吗,简简单单,真的是这样吗?
☀️别看了,我是不会告诉你的💢,就问你敢不敢自己动手去写代码试试🙉🙉🙉(可以作为条件语句或者循环语句的判断条件来测试O)
(三)指针和数组的关系图
从本质上看,同一个对象有两种表示方法。
实际上,C语言标准在描述数组表示法时确实借助了指针。
也就是说,定义 ar[n] 的意思是 *(ar+n)。可以认为 *(ar+n)的意思是“到内存ar的位置,然后移动n个单元,检索储存在那里的值”。**
注意💕💕 *(ar+n) 和 *ar+n 代表的意思不同,间接运算符(*)的优先级高于(+),所以*ar+n,相当于(*ar)+n
四:指针和多维数组
(💕💕宝儿,这也是重点,作为新手咱们精通二维数组就足够了)
(一)指针和多维数组之间的关系
👑👑👑为了方便理解,在此我用一个例子来讲解他们之间的关系
假设有下面的声明:
🚀🚀 int zippo[4][2]; //二维数组,内含 int 数组的数组
数组名 zippo 是该数组首元素的地址。
zippo 的首元素是一个内含两个int值的数组,所以zippo是这个内含两个int值的数组的地址。
-
🏰因为 zippo 是数组首元素的地址,所以 zippo 的值和 &zippo[0] 的值相同。而 zippo[0] 本身是一个内含两个整数的数组,所以zippo[0]的值和它首元素(一个整数)的地址(即 &zippo[0][0] 的值)相同。简而言之,zippo[0] 是一个占用一个int大小对象的地址,而zippo是一个占用两个int大小对象的地址。由于这个整数和内含两个整数的数组都开始于同一个地址,所以 zippo 和 zippo[0] 的值相同。🐒🐒💣💣💣
-
🏰给指针或者地址加1,其值会增加对应类型大小的数值。在这方面,zippo 和 zippo[0] 不同,因为 zippo 指向的对象占用了两个 int 大小,而 zippo[0] 指向的对象占用了一个int大小。因此,zippo+1 和zippo[0]+1 的值不同。🐒🐒💣💣💣
-
🏰解引用一个指针(在指针前使用*运算符)或在数组名后使用带下标的[]运算符,得到引用对象代表的值。因为 zippo[0] 是该数组首元素( zippo[0][0] )的地址,所以 *(zippo[0]) 表示储存在 zippo[0] [0] 上的值(即一个int类型的值)。与此类似,*zippo代表该数组首元素(zippo[0])的值,但是 zippo[0] 本身是一个 int 类型的地址。该值的地址是 &zippo[0][0] ,所以 *zippo 就是 &zippo[0][0] ,即一个int类型的值。简而言之,zippo 是地址的地址,必须解引用两次才能获得原始的值。地址的地址或指针的指针就是双重间接的例子。🐒🐒💣💣💣
💩💩看不懂了吧?懵b了吧? 看不懂就对了👊👊,有一说一,我也有点儿看不懂😐😐😐
(二)指针结合多维数组的运算
为了方便宝子们的理解,依旧使用上述定义的二维数组来进行说明:
💥💥与zippo[2][1] 等价的指针表示方法是 *(*(zippo+2)+1)
可能你看了这个比较懵逼,没关系,下面我将为你一步一步的介绍缘由,
请宝子们认真学习哦:💕💕💕💕
🚀zippo ------> 二维数组首元素的地址(每个元素都是内含两个 int 类型元素的一维数组)
🚀zippo+2 ------>二维数组的第3个元素(即一维数组)的地址
🚀*(zippo+2)------>二维数组的第3个元素(即一维数组)的首元素(一个 int 类型的值)地址
🚀*(zippo+2)+1------>二维数组的第3个元素(即一维数组)的第2个元素(也是一个 int 类型的值)地址
🚀*( *(zippo+2)+1)------>二维数组的第3个一维数组元素的第2个 int 类型元素的值,即数组的第3行第2列的值(zippo[2][1])
看了上面的步骤,不知道是否有了新的认识呢,
如果还是没有头脑,不要着急,慢慢来✊✊,
多看几遍慢慢理解,需要时间的沉淀💌💌💌
(三)直接给宝儿上图
🌹🌹🌹🌹🌹🌹
五:指针数组
(一)基本概念
你应该知道什么叫做整型数组、字符型数组吧?
数组元素为整型量,称之为整型数组;数组元素为字符型量,称之为字符型数组。
因此,若数组元素是指针,那是不是应该把它叫做指针数组呢?
是的,完全正确🚀🚀,终于猜对一次
🚁🚁指针也是有类型的,如果指针数组中的元素为整型指针,那么我们就把它称作整型指针数组。
🚁🚁同理,有float型指针数组、double型指针数组、char型指针数组等。
(二)定义规则
<类型名> *<指针数组名>[<元素个数>];
🐾🐾来给宝子们举个例子吧💕💕
如 int *p[10] ; 表示定义了一个整型指针数组p[10],它有10个元素p[0] , p[1] , p[2] , p[3] , … ,p[9] ,每个元素均为整型指针,指针类型为 int*。
(三)操作方法
简单理解就是一下儿声明了一大堆指针👍👍🚀🚀🚀🚀🚀🚀
六:数组指针
(一)定义
🚀🚀直接上例子:💕💕
int (*p)[2]; //这就是一个数组指针,指向一个内含两个int类型值的数组
(二)使用
七:指针和函数
(一)函数指针
1.基本概念
一个函数被编译连接后生成一段二进制代码,改代码的首地址称为函数的入口地址。
可以定义一种特殊的指针变量,专门用于存放函数的入口地址,
这种变量称为函数指针变量,简称函数指针。
(知道大概的意思就行了,关键是会使用)
2.定义
**语法格式:
<数据类型> (*<函数指针变量名>)(<参数类型表>);**
例如:
int (*fp)(int ,int);//定义一个函数指针变量fp,它指向具有两个整型参数且返回值为整型量的函数🚀🚀
int max(int ,int);
fp=max;
注意:
-
🚀🚀函数指针指向程序代码区,一般变量的指针指向数据区
-
🚀🚀(*fp)的小括号不能省,因为() 运算符的优先级高于 * 运算符,若省略,则变成int *fp(int ,int); 它表示返回指针值的函数。
-
🚀🚀函数指针不能进行++、–、+、-等运算,因为在程序的一次执行过程中,函数代码区的起始地址不会变动,即函数指针是指针常量。
3.使用
❗️❗️❗️引入函数指针的目的是为了编写通用函数。
💕💕直接给宝子们来个我们财大OJ题吧,直说,我就是懒😇😇,
不想再敲一遍代码了,OJ可以直接复制代码,啊哈哈哈哈✌️✌️✌️✌️
- 🚀🚀题目描述
- 输入n和n个整数,然后按要求排序,若输入1,请输出升序排序序列;若输入2,请输出降序排序
序列,若输入3,请输出按绝对值升序排序序列。*
#include<stdio.h>
#include<math.h>
void sort(int a[],int n,int (*cmp)(int x,int y))
{
int i,j;
for(i=0;i<n-1;i++)
{
for(j=0;j<n-i-1;j++)
{
if((*cmp)(a[j],a[j+1]))
{
int t;
t=a[j];
a[j]=a[j+1];
a[j+1]=t;
}
}
}
for(i=0;i<n;i++)
{
printf("%d ",a[i]);
}
return ;
}
int CmpAsc(int x, int y)
{
//如果x>y返回1,否则返回0;
if(x>y)
{
return 1;
}
else
{
return 0;
}
}
int CmpDec(int x, int y)
{
//如果x<y返回1,否则返回0;
if(x<y)
{
return 1;
}
else
{
return 0;
}
}
int CmpAbsAsc(int x, int y)
{
//如果abs(x)>abs(y)返回1,否则返回0
if(abs(x)>abs(y))
{
return 1;
}
else
{
return 0;
}
}
int main()
{
int a[100],i,n;
scanf("%d",&n);
for(i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
int slt;
/*读入n和n个整数,存入数组a*/
/*读入用户的选择,存入slt; */
scanf("%d",&slt);
switch(slt)
{
case 1: sort(a, n, CmpAsc); break;
case 2: sort(a, n, CmpDec); break;
case 3: sort(a, n, CmpAbsAsc);break;
}
return 0;
}
慢慢看吧 💩💩,加油✊✊💕💕
不懂得可以私信我,或者评论区留言哦,
全心全意为人民服务💕💕
(二)指针函数
1.基本概念
你应该知道函数的返回值可以是基本类型量,如整型、实型、字符型等,
那么函数的返回值当然也可以是指针类型的量啊。
那么返回指针的函数我们就可以称之为 指针函数 🚀🚀🚀🚀。
2.定义
语法格式:
<数据类型> * <函数名>(<参数列表>);
例如:
int *func(...)
{
...
}
3.使用
继续上财大OJ例题:(懒是会上瘾的😇😇)
- 🚀🚀题目描述
- 读入一个实数,输出该实数的小数部分,小数部分若多余的末尾0,请去掉。如输入
111111.12345678912345678900
则输出0.123456789123456789。若去掉末尾0之后小数部分为0,则输出“No decimal part”。注意该实数的位数不超过100位。
请定义并使用如下函数。
char *decimal(char p)
{
将字符串p表示的实数的自小数点开始的小数部分存入一个字符串,并由函数返回,若p为“123.456”,则返回的字符串为“.456”。若小数部分为0,返回空指针NULL。
}
输入
输入一个实数。不超过100位。
输出
输出小数部分,输出占一行。
#include<stdio.h>
#include<string.h>
char*decimal(char *p)
{
int i,j=0;
for(i=(strlen(p)-1);i>=0;i--)
{
if(*(p+i)=='0'&&*(p+i-1)!='0')
{
*(p+i)='\0';
}
else if(*(p+i)!='0')
{
break;
}
}
while(*(p+j)!='.')
{
j++;
}
if(i==j)
{
return NULL;
}
else
{
return (p+j);
}
}
int main()
{
char num[100],*p;
int i;
scanf("%s",num);
p=decimal(num);
if(p)
{
printf("0%s",p);
}
else
{
printf("No decimal part");
}
return 0;
}
慢慢看吧啊 💩💩,加油✊✊💕💕
不懂得可以私信我,或者评论区留言哦,
全心全意为人民服务💕💕
(我摊牌了,这句话我是复制的上面的,都说了 懒是会上瘾的 🐒🐒🐒)
八:指向指针的指针
(一)概念
🚀🚀指针变量用于存放地址值,它本身也占用若干存储单元,也是有起始地址的。
那么我们同样可以定义一个变量来储存它的地址。
存放指针变量起始地址的变量,称为指向指针的指针。
✈️✈️✈️✈️✈️✈️✈️✈️✈️✈️✈️✈️✈️✈️✈️✈️✈️✈️
(二)定义
<类型说明符> ** <指针变量名>;(注意是两个)*
例如:
int x=3,*p1,**p2;
p1=&x;
p2=&p1;
这里又点绕,认真阅读💕💕💕💕
(三)使用
定义一个指向指针的指针,用它指向指针数组。
#include<stdio.h>
int main()
{
char **p;
char *s[]={"up","down","left","right"};
int i;
p=s;
for(i=0;i<4;i++)
{
printf("%s\n",*p++);
}
return 0;
}
有时候我就在想,知道个指针不就好了吗,为啥还会有指向指针的指针,
到底该在什么情况下才适合用到指针的指针。
但是,回头再想想,知道了总比不知道要好,“指向指针的指针”听起来就很NB。
九:财大OJ例题找Bug
(本道题涉及指针指向结构体的相关知识)
这道题是我在写完本片博客后过了几天加进来的,觉得这道题很有价值,代码是我自己写的,里面只有一个bug,调试了将近两个小时,从坐着找bug,到躺着找bug,再到走着想着bug,最后终于找到了问题所在,
下面我把OJ原题附上,并且附上我的错误代码(只有一处错误),既然我又回过头特意把这道题放在本篇博客,相信读者也能隐约感受到这段代码中的bug和指针有关(算作是个小小提示吧),
希望读者能够耐心读完这段代码,有疑问欢迎进行提问,我将尽我最大的能力帮助读者解答。
题目描述
从键盘输入若干个学生的信息,每个学生信息包括学号、姓名、3门课的成绩,计算每个学生的总分,输出总分最高的学生的信息。
输入
首先输入一个整数n(1<=n<=100),表示学生人数,然后输入n行,每行包含一个学生的信息:学号(12位)、姓名(不含空格且不超过20位),以及三个整数,表示语文、数学、英语三门课成绩,数据之间用空格隔开。
输出
输出总成绩最高的学生的学号、姓名、及三门课成绩,用空格隔开。若有多个最高分,只输出第一个。
样例输入 Copy
3
541207010188 Zhangling 89 78 95
541207010189 Wangli 85 87 99
541207010190 Fangfang 85 68 76
样例输出 Copy
541207010189 Wangli 85 87 99
#include<stdio.h>
//定义结构体
typedef struct Student
{
char num[13];
char name[21];
int score[3];
} lucky;
//给结构体赋值
lucky *Student_puts(lucky *pc)
{
scanf("%s",&pc->num );
scanf("%s",&pc->name );
int i=2;
while(i>=0)
{
scanf("%d",&pc->score[i]);
i--;
}
return pc;
}
//打印结构体中的数据
void show(lucky *pc)
{
printf("%s ",pc->num);
printf("%s ",pc->name);
int i=2;
while(i>=0)
{
printf("%d ",pc->score[i]);
i--;
}
return ;
}
//计算对应学生的总分数
int number(lucky *pc)
{
int numbers=0;
int i=2;
while(i>=0)
{
numbers+=pc->score[i];
i--;
}
return numbers;
}
int main()
{
int n;
lucky *pc_1,*pc_2;
lucky student_max,student_new;
scanf("%d",&n);
pc_1=Student_puts(&student_max);
while((n-1)>0)
{
pc_2=Student_puts(&student_new);
if((number(pc_1)) < (number(pc_2))) //不加等于号,因为题目要求是成绩重复的情况下只打印第一个学生的信息
{
pc_1=pc_2;
}
n--;
}
show(pc_1);
return 0;
}
十:完结
讲真的,我现在还没有完全理解指针相关的知识,已经学了很久了,中间也有停下来不断温习,反复看书,一遍又一遍,现在觉得我还有很多相关的知识没有搞懂,后续还是要多加努力的,我个人觉得学会指针还是很有必要的,学不会指针就相当于没有学C语言(仅代表个人观点)。
🚀🚀🚀以上就是我最近对指针相关知识点的大概总结,希望对大家能有帮助!!💕💕
🚀🚀🚀内容不全面,而且可能存在错误的地方,希望大家能够私信我进行纠正,不尽感激。💕💕
🚀🚀🚀我是一名编程初学者,这是我的第三篇博客,希望能够得到大家的支持,一起努力吧,加油🌻🌻🌻🌻🌻!!
拜拜🐾🐾🐾🐾🐾🐾
💕💕
💕💕
💕💕
💕💕
2022.05.09 || 1:56