C语言基础之指针
目录
文章目录
概要
指针的基础知识
指针的意义
在程序中如何使用地址
指针的表现形式
指针的应用
应用
*的用法
通过指针变量给地址空间取值和赋值操作 -- *
指针偏移
指针与一维数组
把数组名当成指针变量通过操作指针的形式操作数组
把指针变量当成数组名,用数组的形式操作数组元素
指针与字符数组
数组指针和指针数组
数组指针
指针数组
指针与二维数组
获取二维数组数组元素的四种方式
多级指针
概要
构造数据类型除了之前提到的数组外,C语言中还存在一种保存地址的构造数据类型--指针
指针的基础知识
指针的意义
1,在嵌入式底层开发中,往往需要CPU对底层数据进行操作,这种操作一般是CPU通过寄存器中存储的数据地址来实现的
计算机中地址的表现形式是以十六进制表示的
2,为了数据安全通常也需要用到指针

假设想通过fun函数实现对变量a与变量b的数据交换,当我们使用上图的函数-- 数值传参 并不能实现我们想要的效果,理由是传参数时,只将a和b的数值传给了函数进行交换,而对主函数中的a和b其实并没有影响,因为没有对a和b变量的空间内的数值进行修改,要使用这种修改需要用到指针
在程序中如何使用地址
1,直接使用地址进行操作 -- 不能直接使用

当我们自己定义一个整型地址(int *)并对其进行解地址操作并赋值时,程序会出错
2,间接使用地址进行操作 -- 先把地址保存起来再进行操作 --- 指针操作
指针的表现形式
类型 *标识符
指针的大小:
#include <stdio.h>int main()
{ //指针的大小printf("char * = %ld\n",sizeof(char *));printf("short * = %ld\n",sizeof(short *));printf("int * = %ld\n",sizeof(int *));printf("long * = %ld\n",sizeof(long *));printf("float* = %ld\n",sizeof(float *));printf("double * = %ld\n",sizeof(double *));return 0;
}

通过sizeof计算发现指针的大小与数据类型无关,而是同一个固定值 8 (64位操作系统)
指针的应用
指针变量:类型 *标识符=初始值
类型 -- 所有的数据类型(可以是指针)
* -- 此处是表示指针变量
标识符 -- 同变量的标识符
初始值
1,无初始值, int *p; --- 默认随机值--野指针 -- 不建议使用
2,控制地址(NULL),int *p = NULL;表示空地址,

3,初始化具体值:
int num = 10; int *p =# -- 通过取地址进行赋值
应用
1,赋值和取值
a,对给指针变量赋值
指针变量是不能直接进行数值赋值的,而需要通过取地址符(&)进行赋值
int a = 100;
int *p =&a;
int *n = p;
int*p=#float*q=p;---不同数据类型之间赋值需要进行数据转换,转换规则通基本数据类型
*的用法
1, a*b -- 乘法
2,int * p= &num -- 表示指针变量
3,*p= 20; --- 解地址 -- 表示p指针所指向的变量
通过指针变量给地址空间取值和赋值操作 -- *
int num = 10;
int *p = #
*p = 20;//此时num值变为20
printf("%d\n",num);
int num = 10;
int *p = #
printf("%d\n",*p);//输出结果是num的值10;输入同理
int num1 =10;
int num2 =20;
int *p =&num1;
printf("%d\n",*p);//输出结果时num1 = 10;
p= &num2;//一个指针可以指向不同变量
printf("%d\n",*p);//输出结果时num2 = 20;
int num = 10;
int *a = #
int *b = #
*b=20;
printf("%d\n",*a);//输出结果也是20,两个指针都是对num进行操作 指针偏移
指针变量偏移一位,对应的物理地址偏移一个数据类型的空间大小
变量的定义-- 默认是在栈区开辟一个数据类型的空间大小,栈区的特点--自动开辟,自动收回,线性存储(地址连续)
1,*p+1和*(p+1)
#include <stdio.h>int main()
{ //*p+1和*(p+1)int num =10;int num1 =20;//变量的定义-- 默认是在栈区开辟一个数据类型的空间大小,栈区的特点--自动开辟,自动收回,线性存储(地址连续)int *p = #int *n =#printf("%d\n",*p+1);//11 -- *p等于10,10+1 = 11printf("%d\n",*(n+1));//20 -- n+1地址移位等于num1的地址,解地址等于20return 0;
} 
#include <stdio.h>int main()
{ //*p++ 和++*p 以及 *++pint num =10;int num1 =20;int *a=#int *b=#int *c =#printf("%d\n",*a++);//结果是10,*a++,先等于在加一,*a=10;printf("%d\n",++*b);//结果是11,++*b,*与加减同一优先级,从右到作,*b=10;再+1=11在赋值输出=11;printf("%d\n",*++c);//结果是20,*++c,从右到左,先++b=&num1,再解地址等于20;return 0;} 
第一个是将字符型指针变量p转变成长整型指针变量,再进行移位,长整数的数据类型是8字节,所以结果是0x100009;
第二个是再第一个的基础上注意十六进制的进位
第三个是先把指针变量变成长整型再加1
指针与一维数组
1,数组名可以表示数组收个元素的地址,因为数组名其实也可以看成是一个指针变量
把数组名当成指针变量通过操作指针的形式操作数组
主要用到的就是指针偏移
示例:
#include <stdio.h>
int main()
{//把数组名当成指针变量通过操作指针的形式操作数组 int num[5]={1,2,3,4,5};for(int i = 0;i<5;i++){printf("%d\t",*(num+i));}return 0;
}
注意不能使用 *num++:因为num是一个数组名,是不能进行赋值操作的
错误示例:
#include <stdio.h>
int main()
{//把数组名当成指针变量通过操作指针的形式操作数组 int num[5]={1,2,3,4,5};for(int i = 0;i<5;i++){printf("%d\t",*num++);}return 0;
}

把指针变量当成数组名,用数组的形式操作数组元素
也就是通过下角标
示例;
#include <stdio.h>
int main()
{//把数组名当成指针变量通过操作指针的形式操作数组 int num[5]={1,2,3,4,5};int *p = num;// 等价于 int *p = &num【0】;for(int i = 0;i<5;i++){printf("%d\t",p[i]);}return 0;
}

注意:这个时候p【i】就表示一个元素了,不能对其进行解地址
指针与字符数组
1,不使用取地址符直接创建一个字符串:char *p = "hello";
注意这里的hello是一个储存在文字常量区的,不能通过*p改变字符串的内容,这个p指向的是h的地址,跟字符数组(字符串)的名字指向相同

这三者的区别:
对于第一个,const修饰的是p也就是指针变量,这个变量值被固定不能修改了,但*p是指这个地址p所指向的变量的空间,这个空间并没有被const修饰,所以*p的值可以改变
第二个,const修饰的是*p,也就是说p解地址解出来的空间被const修饰了,那么就不能通过解地址,对这个地址空间进行赋值;
第三个是第二个另一种写法,其本质是一样的·
以指针的形式实现字符串常用函数
这里提供一个实例,其方法与使用数组的形式实现类似
示例:
#include <stdio.h>
int main()
{//仿写字符串函数功能:复制,比较,求长度,以及字符串连接//初始化char ch1[1024]={0};char *p = ch1;char *p1="hello";char ch2[1024] = "world";//复制 -- *p1到ch1while(*p1){*p=*p1;p++;p1++;}printf("复制后的ch1是%s\n",ch1);//比较 -- ch1与ch2进行比较char *p2= ch2;p = ch1;//重置p的指向int num =0;//记录差值while(*p!=0||*p2!=0){num = (int)(*p-*p2);p++;p2++;if(num !=0){break;}}if(num ==0){printf("ch1与ch2相同\n");}else{printf("ch1与ch2不同差值是%d\n",num);}//求长度p = ch1;//重置p的指向int i = 0;//记录长度while(*p){i++;p++;}printf("ch1的长度是%d\n",i);//拼接 -- 把world拼接到hello后面p = ch1;//重置p的指向p2 = ch2;while(*p){p++;}while(*p2){*p = *p2;p++;p2++;}*p ='\0';printf("%s\n",ch1);return 0;
}

需要注意的是指针在使用后,他所指向的位置是可以改变的在下次使用前需要重置指向
数组指针和指针数组
数组指针
数组在前指针在后,表示类型是数组的指针,也就是说这个指针指向的是整个数组的地址
定义:类型 (*标识符) [长度] = 初始值;[]的优先级是比*大的,使用小括号提高*的优先级,就表示是一个指针
应用;
对于指针,我们只知道指针偏移一位,物理地址偏移一个数据类型的大小,这里同理有,对于数组指针,指针偏移一位,物理地址偏移一整个数组的空间大小
示例:
#include <stdio.h>
int main()
{int a[5] = {1,2,3,4,5};int *ptr = (int *)(&a +1);/*a是数组名,既表示首个元素的地址,又表示整个元素,这里对a取地址,即对整个元素取地址,所以&a是一个数组指针,存储整个数组的地址,&a+1,指针偏移一位,地址移动一整个数组5*4=20位,那么这个数组指针的周地址就从1的位置移到了5的后一位,在强转成,所以此时ptr指向5的后一位,那么最后输出时的ptr-1左移一位,这时数据类型是int *型,移一位,从5后一位移回5的位置,最终输出5而*(a+1),a是首位1,移一位到2的位置再解地址输出2*/printf("%d,%d",*(a+1),*(ptr -1));return 0;
}
a是数组名,既表示首个元素的地址,又表示整个元素,这里对a取地址,即对整个元素取地址,所以&a是一个数组指针,存储整个数组的地址,&a+1,指针偏移一位,地址移动一整个数组5*4=20位,那么这个数组指针的周地址就从1的位置移到了5的后一位,在强转成,所以此时ptr指向5的后一位,那么最后输出时的ptr-1左移一位,这时数据类型是int *型,移一位,从5后一位移回5的位置,最终输出5
而*(a+1),a是首位1,移一位到2的位置再解地址输出2

指针数组
其本质是一个类型为指针的数组,数组的元素是一个个指针(地址)
定义:类型 *标识符[长度] = 初始值
应用:
数组的首地址是首个数组元素的地址,这里元素是一个指针,所以,数组名是指针(地址)的地址--二级指针
示例;
#include <stdio.h>
int main()
{char *a[] = {"Pascal","C language","dBase","Coble"};char (**p);//定义一个二级指针int j ;p = a+3;//a是数组名,表示首个元素的地址,指向“Pascal",a+3移到指向"Coble"for(int j = 3;j>=0;j--){printf("%s\n",*(p--));//p --;从"Coble"开始往前移位 ,再解地址,因此结果应该是Coble,dBase,C language,Pascal}return 0;
}

指针与二维数组
二维数组的数组名表示第一个元素(数组)的首地址,也就是第一行元素的首地址 类型是一个数组指针类型
#include <stdio.h>
int main()
{int num[3][4]= {1,2,3,4,5,6,7,8,9,10,11,12};printf("num == %p\n",num);//数组名int (* p)[4] printf("&num[0] == %p\n",&num[0]);//第零行元素的地址 int (* p)[4] printf("num[0] == %p\n",num[0]);//第零行数组的数组名 int *printf("num[0][0] == %p\n",&num[0][0]);//·第0行0列的元素 int *return 0;
}

获取二维数组数组元素的四种方式
1,纯数组形式:num[i][j]
2,纯地址(解地址操作):a,*num第0行元素
b,*(num+i)第i行元素
c,*(*(num+i)+j)第i行j列
3,行地址列角标:a,*num第0行元素
b,*(num+i)第i行元素
c,*(num+i)[j]第i行j列
4, 行角标列地址:a,num[i] 第i行元素
b,num[i]+j 第i行j列的地址
c,*(num[i]+j)第i行j列的元素
