C语言指针总结

目录

一、指针

1.1概念

1.2指针的相关操作

1.3指针和变量的关系

1.4指针的基本使用 

1.5指针变量的大小

1.6指针的运算

二、指针与字符串

三、指针与一维数组

四、指针与二维数组

4.1指针和二维数组

4.2数组指针

4.3指针数组

五、二级指针

六、指针与函数  

6.1指针函数 

6.2函数指针

6.3函数指针数组

6.4函数指针数组指针

 七、总结​​​​​​​

一、指针

1.1概念

        内存中每个字节都有自己的地址,地址有一个独一无二的编号,这个编号就叫做指针(也就是地址)。专门用来存放指针的变量叫做指针变量

        一般称呼:

      地址:地址编号

      指针:指针变量

1.2指针的相关操作

        &    :取地址符,获取变量的地址。

                  对多字节的变量取地址得到的是首地址,也就是(地址)编号最小的哪个。

        *    :在定义指针变量时,*  只起到一个标识作用,表示定义的是一个指针变量。

                  在其他场景下,表示操作指针指向空间的内容

1.3指针和变量的关系

        指针变量中保存的是某个变量的地址

eg:

int a = 10;    //变量a中保存的是常量10
int *p = &a;    //指针变量p中保存的是变量a的首地址

1.4指针的基本使用 

        变量的类型决定了这个变量内存的大小。

int a;
a = 10;    //通过变量名 操作其内存空间

printf("%p",&a);    //通过取地址符 & 可以得到变量的地址
                    //通过 %p 来打印地址

格式:

数据类型 *指针变量名;

        同样可以通过指针变量名来给指针赋值。

eg:

int a = 10;
int *p;    //也可以写为 int *p = &a;
p = &a ;   //指针保存了变量a的地址,我们称为指针p指向变量a

 注意:

        1.普通的变量保存的地址没有意义。

        2.指针只能保存已经分配好的地址,不能手动指定。

                eg:

int a;
int *p = a;    //正确
*p = 10;    //错误

        3.指针的类型决定了指针能操作的内存大小。

                eg:char * 类型的指针只能操作 1 个字节

                        int * 类型的指针只能操作 4 个字节

        4. 未初始化的指针里面保存的是随机值,是有害的,被称之为“野指针”。

            可以将指针指向 NULL 来解决野指针问题。

        5.可以同时定义多个指针 。

 eg:

int *p1, p2;    //错误写法,其中p1是指针 p2是int类型的变量
int *p3, *p4;    //p3,p4都是指针

        6. 常量没有指针。

1.5指针变量的大小

        32位系统中 指针的大小都是4字节

        64位系统中 指针的大小都是8字节

1.6指针的运算

        指针运算的本质是操作指针中保存的地址。只有相同类型的指针之间做运算才有意义,因为他们操作的内存大小相同。

  指针能做的运算有:

        算数运算: + - ++ --

        关系运算:> < == !=

        赋值运算:= 

注意:

        1.只有指针指向的是连续的空间时,指针之间的运算才有意义。

                例如,指针指向的是一个一维数组 。

        2.指针变量名加上一个整数n,表示加上n个指针类型的大小。

        3.指针的强转是安全的 因为在一个系统中指针的大小是相同的。

        4.指针可以被重新赋值。

二、指针与字符串

        字符串常量储存在字符串常量区

        字符串常量区在虚拟内存的 ro段(readonly)这是只读数据段。

        所以字符串常量区的内容不可以改变 。

        栈区的内容可以修改,所以我们可以用指针改变栈区保存的字符串。

        也可以使用指针直接指向字符串常量,此时的字符串常量实在字符串常量区,不可以改变。

                eg: 

char s1[32] = "hello world";    //表示用字符串常量区的hello world来初始化栈区的数组s1
*s1 = 'H';    //正确的 
                //修改的是栈区的数组s1,而不是字符串常量区的hello world
char *p = "hello world"    //表示通过指针p直接指向字符串常量区的字符串hello world
*p = 'H';    //错误的
            //字符串常量区的内容不可以被修改

三、指针与一维数组

        int s[5] = {10,20,30,40,50};

        其中一维数组的数组名(s)就是数组的首地址,s是一个常量

                -->这样我们就可以得到是 s[i] <==>*(s+i)

        int *p = s;

        指向一维数组的指针保存的是一维数组的首地址,p是一个变量

                 -->这样我们就可以得到 s[i] <==>*(s+i) <==> p[i] <==> *(p+i)

        通过指针和一维数组的结合,我们可以实现一维数组的遍历👇

int s[5] = {1,2,3,4,5};
int i = 0;
for(i=0; i<5; i++){
    printf("%d ",*(p+i));
}
printf("\n");

四、指针与二维数组

4.1指针和二维数组

int s[3][4] = {{1,2,3,4},
                {5,6,7,8},
                {9,10,11,12}}; 

        其中二维数组的数组名(s)就是数组的首地址,s是一个常量。

        二维数组名操作的空间是一整行元素,也称之为行指针。

        对二维数组的数组名取 * 操作 表示“降维”操作 

        这样我们可以得到-->

                                        s[i] <==> *(s+i)

                                        s[i][j] <==> *(s[i]+j) <==> *(*(s+i)+j)

        通过指针和二维数组的结合,我们可以实现一维数组的遍历👇 

int s[3][4] = {{1,2,3,4},
                {5,6,7,8},
                {9,10,11,12}}; 

int i = 0;
int j = 0;
for(i=0; i<3; i++){
    for(j=0; j<4; j++){
        printf("%d ",*(*(s+i)+j));    
    }
    printf("\n");
}

注意:

        二维数组的数组名是一个行指针,所以操作的是一行元素 已经超过基本类型的范围了。

        所以不能用普通的指针来指向二维数组,因为普通的指针不能取 ** 操作 。

4.2数组指针

        本质是一个指针,用来指向二维数组的。也叫做行指针

        多用于二维数组作为函数的参数传递时

        格式:

数据类型 (*数组指针名)[列宽];

          eg:

int s[3][4] = {{1,2,3,4},
                {5,6,7,8},
                {9,10,11,12}};    //定义了一个 3行4列的二维数组
int (*p)[4] = s;    //定义了个数组指针 并让这个指针指向二维数组s

指向二维数组的指针保存的是二维数组的首地址,p是一个变量。

这样我们就可以得到-->

                                s[i][j] <==> *(s[i]+j) <==> *(*(s+i)+j) <==>

                                p[i][j] <==>*(p[i]+j) <==> *(*(p+i)+j)

4.3指针数组

        本质是一个数组,数组中每一个元素都是一个指针。

格式:

数据类型 *指针数组名[下标];

eg: 

char *s[5];    //定义了一个数组名为s的数组 数组中一共有5个元素

                //每一个元素都是一个 char * 类型的指针

                //对 s[i] 操作和操作 单个的 int * 指针是一样的
s[5] = {NULL};    //指针数组的初始化(指向NULL)
s[5] = {"hello","beijin","nihao"};    //不完全初始化

五、二级指针

         二级指针是用来保存一级指针的地址的。

        多用于一级指针的地址作为函数的参数传递时。

        变量、一级指针、二级指针的关系👇

int a = 10;    //变量
int *p = &a;    //一级指针
int **q = &p;    //二级指针

**q = 1234;    //相当于给 a 赋值

  这样我们就可以得到-->

 a <==> *p <==> **q

 &a <==> p <==> *q

&p <==> q

         注意:

                一级指针保存一级指针的地址没有意义

                 因为一级指针不能取 ** 操作

六、指针与函数  

6.1指针函数 

         本质是一个函数,返回值是一个指针类型。

格式:

返回值类型 *函数指针名(函数形参表);

注意:

        1.不能返回局部变量的地址。(局部变量的地址在函数结束时会被系统回收) 

        2.可以返回全局变量的地址。

        3.可以返回static关键字修饰的局部变量的地址。

        4.可以返回传递给函数的参数的地址。(函数形参表中传入的地址)

        eg:

int *my_func(int x,int y){
    int sum = x + y;
    return &sum;    //错误的,sum是局部变量 在函数结束时会被回收
}

int *my_func(int x, int y,int *sum){
    *sum = x + y;
    return sum;    //正确的
}

6.2函数指针

         本质是一个指针,可以指向一个函数。

格式:

返回值类型 (*函数指针名)(函数的形参表);

         eg:

#include <stdio.h>

int my_add(int x, int y){
    return x+y;
}

int main(int argc, const char *argv[])
{
    int a = 10;
	int b = 20;
	printf("%d\n", my_add(a, b));//30

	int (*p)(int, int) = NULL;
	//定义了一个函数指针 指针名叫p 可以指向一个 返回值为int
	//形参列表为(int, int)类型的函数

	p = my_add;    //让函数指针p指向函数my_add 
                    //函数名就是函数的首地址

	//指针指向函数之后 就可以通过指针来调用函数了
	printf("%d\n", p(a, b));//30

	return 0;
}

 函数指针的经典使用场景--->回调函数

        eg:

#include <stdio.h>

int my_add(int x, int y){    //函数my_add
    return x+y;
}

int my_sub(int x, int y){    //函数mu_sub
    return x-y;
}

int jisuan(int x, int y, int (*p)(int, int)){    //第三个参数就是一个函数指针
	return p(x, y);    //调用函数指针p指向的函数(回调)
                        //通过传递不同的函数实现不同的功能
}

int main(int argc, const char *argv[])
{
	int a = 10;
	int b = 20;
	printf("a+b = %d\n", jisuan(a, b, my_add));//调用函数my_add
	printf("a+b = %d\n", jisuan(a, b, my_sub));//调用函数my_sub

	return 0;
}

6.3函数指针数组

         本质是一个数组,数组中每一个元素都是一个函数指针。

格式:

返回值类型 (*函数指针名[下标])(函数的形参表) 

        eg: 

#include <stdio.h>

int my_add(int x, int y){
    return x+y;
}

int my_sub(int x, int y){
    return x-y;
}

int main(int argc, const char *argv[])
{
	int (*s[2])(int, int) = {NULL};    //定义了一个函数指针数组,数组名叫s 数组中共有2个元素
	                                   //每个元素都是一个可以指向返回值为int
	                                    //形参列表为 (int, int) 的函数指针
	s[0] = my_add;    //将每个元素指向函数
	s[1] = my_sub;     
	               

	//当函数指针数组的元素指向函数之后 就可以通过他调用函数了
	int a = 10;
	int b = 20;
	printf("a+b = %d\n", s[0](a, b));//通过函数指针数组中的元素调用函数
	printf("a-b = %d\n", s[1](a, b));
	
	return 0;
}

6.4函数指针数组指针

        本质是一个指针,指向一个函数指针数组。

格式:  

 返回值类型  (*(*函数指针数组指针名))(函数的形参表);

eg:

#include <stdio.h>

int my_add(int x, int y){
    return x+y;
}

int my_sub(int x, int y){
    return x-y;
}

int main(int argc, const char *argv[])
{

	int (*s[2])(int, int) = {my_add, my_sub};//定义了一个函数指针数组,
                                             //数组名叫s 数组中共有2个元素
	                                         //每个元素都是一个可以指向返回值为int 
	                                         //形参列表为 (int, int) 的函数指针
	                                         //s[0]指向函数my_add;s[1]指向函数my_sub
	int a = 10;
	int b = 20;
	printf("a+b = %d\n", s[0](a, b));//30
	printf("a-b = %d\n", s[1](a, b));//-10

	
	int (*(*p))(int, int) = NULL;    //定义了一个 函数指针数组指针 
                                        
	p = s;    //让函 数指针数组指针 p 保存 函数指针数组 的首地址
	
	printf("a+b = %d\n", p[0](a, b));//通过指针就可以访问函数指针数组的元素了
	printf("a-b = %d\n", (*(p+1))(a, b));
	
	return 0;
}

 七、总结

int p = 0;                //变量

int p[5] = {NULL};        //数组

int *p = NULL;            //一级指针

int **P = NULL;           //二级指针

int (*p)[5] = NULL;       //数组指针

int *p[5] = {NULL};       //指针数组

int *p();                 //指针函数

int (*p)() = NULL;        //函数指针

int (*p[5])() = {NULL};   //函数指针数组

int (*(*p))() = NULL;    //函数指针数组指针
  • 18
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
C语言中,可以使用指针来访问寄存器。通过指针,我们可以直接操控寄存器,进行硬件级别的操作。一个常见的方法是使用强制类型转换和指针来实现访问MCU的寄存器。举个例子,我们可以通过以下方式定义一个寄存器的指针: #define DDRB (*(volatile unsigned char *)0x25) 这里,DDRB被定义为一个指向地址0x25的无符号字符型指针。通过解引用这个指针,我们可以像操作普通变量一样操作寄存器。例如,如果我们想读取或写入DDRB寄存器的值,我们可以使用"*"运算符来操作这个指针的值。 另外,对于单片机的特殊功能寄存器(SFR),在C语言环境下,我们可以使用两种方法进行访问。一种方法是使用指针,通过指针来直接访问寄存器的地址。另一种方法是使用#define指令,将寄存器地址定义为一个变量,然后通过这个变量来访问寄存器。例如,如果我们定义了一个指向地址0x25的指针,并将其命名为i,那么*(volatile unsigned char *)0x25就是一个固定的指针,而不是指针变量。如果我们使用#define i (*(volatile unsigned char *)0x25),那么i就是一个普通的unsigned char变量,只不过它的地址是固定的。通过这种方式,我们可以像操作普通变量一样操作寄存器。 总结起来,C语言中可以使用指针来访问寄存器。通过定义一个指向寄存器地址的指针,我们可以直接操控寄存器,并进行硬件级别的操作。此外,我们还可以使用#define指令将寄存器地址定义为一个变量,然后通过这个变量来访问寄存器。这些方法使得C语言成为嵌入式开发的基础语言之一,方便了对硬件的控制。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

枸杞桑葚菊花茶

创作不易,鼓励==>动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值