day15 指针

指针的引入

1.定义一个变量后,系统在内存上给其分配对应的数据类型的空间

2.每个字节在内存中都是有 内存编号的,这个编号就称为该空间的地址(指针)

3.指针就是内存地址,内存地址就是指针

4.由于内存编号是一个非常难记忆的值,一般会定义一个变量来存储。能够存储指针的变量,称为指针变量

指针变量

1.指针:就是某个空间的内存地址

2.指针变量:存储指针的变量称为指针变量

3.定义格式 :数据类型 *指针变量名;4.

4.注意:什么类型的变量的地址就应该定义相同数据类型的指针变量接收

指针变量的初始化

1.如何获取一个变量的地址,使用取地址运算符&来进行,该运算符是一个单目运算符

        例如:变量num的地址为 &num

总结一下&的作用

&&:双目运算符,逻辑与运算

&:  双目运算符,按位与运算

&:  单目运算符,取地址运算

1.使用一个相同数据类型的变量的地址,给指针变量进行初始化

int num=520;

int *ptr=num; //使用一个相同数据类型的变量的地址进行初始化

2.使用一个已经初始化了的相同数据类型的指针变量进行初始化

int *qtr=ptr;  //此时指针变量ptr和qtr都记录的变量num的地址

3.使用地址的0(NULL)给指针变量初始化

int *ktr=NULL;   //该指针变量不记录任何地址

4.定义指针时,如果没有进行初始化,那么该指针变量在是一个随机值(野指针)

#include<stdio.h>

int main(int argc, const char *argv[])
{
    int num = 520;

    printf("num的地址为:%p\n", &num);
    //此处的 &num,就是取得num所在的内存地址编号
    
    int * ptr = &num;   //定义一个指针变量并使用相同数据类型的变量地址进行初始化   定义一个指针,指向num
    printf("ptr = %p\n", ptr);


    int *qtr = ptr;   //使用一个已经初始化了的相同数据类型的指针变量进行初始化
    printf("qtr = %p\n", qtr);

    int * ktr = NULL;   //定义指针变量使用地址的0初始化
    printf("ktr = %p\n", ktr);

    int * ytr;           //定义指针不初始化,是一个野指针
    printf("ytr = %p\n", ytr);
    
    return 0;
}

指针变量的使用

指针变量访问元素

1.指针变量的值:该变量存储的其他空间的内存地址

2.指针变量指向的内存空间的值:当前指针变量记录的地址空间的内容

3.当指针变量存储一个地址空间时,我们称为将该指针指向一个空间、

4.使用指针变量时,访问指针变量指向 的内存空间中的值,需要引入一个新的运算符*(取值运算)

总结*号 的使用

*作为双目运算符表示乘法运算

在定义指针变量时,*是身份的象征,表示定义的变量为指针变量

在使用指针变量时,*指针变量名;表示取得某个地址中的值

5.*(取值运算)与&(取地址运算)互为逆运算

6.当一个指针指向某一个变量后,就产生了一个等价
  *ptr<==>num

#include<stdio.h>

int main(int argc, const char *argv[])
{
    int num = 520;       //在内存中申请了4个字节,存储了520,内存的名称为num
    //int *ptr;            //野指针
    int *ptr = NULL;            //空指针  变量名为ptr,这里的*号,只是身份的象征

    ptr = &num;            //将指针指向 num   

    printf("ptr = %p\n", ptr);        //输出指针变量的值  地址
    printf("*ptr = %d\n", *ptr);      //输出指针所指向的内存空间中的值  520
                                //此处的*号是取值运算符
    
    //验证&与*互为逆运算
    // *&num ==> *(&num) ==> *ptr ==> num
    printf("*&num = %d\n", *&num);         //520

    // &*ptr ==> &(*ptr) ==> &num ==> ptr
    printf("&*ptr = %p\n", &*ptr);          //地址

    // *&ptr ==> *(指针变量的地址) ==> ptr
    printf("*&ptr = %p\n", *&ptr);           //地址

    // &*num ==> &(*num)         //报错,普通变量不能使用取值运算
    //printf("&*num = %d\n", &*num);
    

    //验证 *ptr等价于 num
    num = 1314;
    printf("*ptr = %d\n", *ptr);      //1314

    *ptr = 666;       //通过指针变量,更改内存空间中的值
    printf("num = %d\n", num);        //666


    
    return 0;
}

指针变量大小相关内容

1.32位操作系统中指针大小为固定4字节,64位系统中,指针大小固定为8字节

2.一般情况下不同类型的指针,指向的是跟其数据类型相同的地址

3.既然所有指针变量所占的内存空间都是一样的,为什么要定义不同类型的指针?

       (1)不同类型的指针,使用取值运算符(*)时,解析的字节大小不同(跟数据类型有关)

       (2)当指针变量进行数加运算时,偏移的大小不同

4.指针变量可以使用的运算

        (1)*(取值运算):取得指针所指向的地址空间中的值

        (2)数加运算(偏移):指针变量每加1或者减1,就会向高地址或者低地址处偏移一个数据类型的大小

        (3)关系运算:用于判断两个指针所指向的空间是否相等  ptr=NULL

#include<stdio.h>

int main(int argc, const char *argv[])
{
    int num = 0x12345678;    //定义整形变量
    char value = 'A'; //定义字符变量

    int *ptr = &num;    //定义整形指针指向整形变量的地址
    char *qtr = &value;  //定义字符类型的指针,指向字符类型的地址

    printf("*ptr = %d, *qtr = %c\n", *ptr, *qtr);  //302419896 A
    printf("sizeof(ptr) = %ld, sizeof(qtr)=%ld\n", sizeof(ptr), sizeof(qtr)); // 8  8

    qtr = (char *)ptr;       //将ptr指向的地址赋值给qtr
                     //此时ptr和qtr同时指向num的起始地址
    printf("ptr = %p, qtr = %p, &num = %p\n", ptr, qtr, &num);    

    printf("*ptr = %#x, *qtr = %#x\n", *ptr, *qtr);  //0x12345678  0x78

    //对指针变量进行偏移
    ptr = ptr+1;
    qtr = qtr+1;

    printf("ptr = %p, qtr = %p, &num = %p\n", ptr, qtr, &num);    
    printf("*ptr = %#x, *qtr = %#x\n", *ptr, *qtr);  //           0x56


    return 0;
}

练习:使用指针完成验证自己的主机是大端存储还是小端存储

#include <stdio.h>
void pd()
{
	int num=0x12345678;
	//定义一个32位数据
	char *ptr=(char *)&num;
    //将整形变量的地址强行转换为字符类型的地址
	//赋值给字符指针变量
	if(*ptr==0x12)
	{
	printf("大端存储\n");
	}else if(*ptr==0x78)
	{
	printf("小端存储\n");
	}
}
int main(int argc, const char *argv[])
{
	//判断电脑大端存储还是小端存储
	//调用函数实现
	pd();
	return 0;
}

指针与一维数组

1.当指针指向一个普通变量时,往往不进行偏移运算,无论向上还是向下都是非法空间

2.当指针指向数组时,指针偏移才有了现实的意义

#include<stdio.h>

int main(int argc, const char *argv[])
{
    int arr[5] = {5,7,3,2,6};    //在内存中连续申请5个int类型的空间

    //访问数组元素
    printf("数组中的元素分别是:");
    for(int i=0; i<5; i++)
    {
        printf("%d\t", arr[i]);
    }
    printf("\n");

    //输出每个元素的地址
    printf("数组中的元素地址分别是:");
    for(int i=0; i<5; i++)
    {
        printf("%p\t", &arr[i]);
    }
    printf("\n");

    //通过数组名偏移输出每个元素的地址
    printf("数组中的元素地址分别是:");
    for(int i=0; i<5; i++)
    {
        printf("%p\t", arr+i);
    }
    printf("\n");

    //定义一个指针变量,指向整形数组
    int *ptr = arr;  //int *ptr = &arr[0]
    printf("数组中的元素地址分别是:");
    for(int i=0; i<5; i++)
    {
        printf("%p\t", ptr+i);
    }
    printf("\n");
    /************************************/

    //使用数组名的方式访问元素
    printf("数组中的元素分别是:");
    for(int i=0; i<5; i++)
    {
        printf("%d\t", *(arr+i));
    }
    printf("\n");

    //使用指针名的方式访问元素
    printf("数组中的元素分别是:");
    for(int i=0; i<5; i++)
    {
        printf("%d\t", *(ptr+i));
    }
    printf("\n");

    //使用指针名的方式访问元素
    printf("数组中的元素分别是:");
    for(int i=0; i<5; i++)
    {
        printf("%d\t", ptr[i]);
    }
    printf("\n");

    //使用指针名的方式访问元素
    printf("数组中的元素分别是:");
    for(int i=0; i<5; i++)
    {
    //    printf("%d\t", *(ptr+0));
        printf("%d\t", ptr[0]);
        ptr++;        //ptr = ptr+1
    }
    printf("\n");

    /*使用数组名的方式访问元素
    printf("数组中的元素分别是:");
    for(int i=0; i<5; i++)
    {
    //    printf("%d\t", *(ptr+0));
        printf("%d\t", *arr);
        arr++;        //arr = arr+1
    }
    printf("\n");
    */

    int *qtr = &arr[2];   //将指针指向第三个元素的地址
    printf("%d\n", qtr[0]);   //arr[2]
    printf("%d\n", qtr[-1]);   //arr[1]
    printf("%d\n", qtr[1]);   //arr[3]

    
    return 0;
}

指针与函数的关系

1.指针作为函数的参数

       1.指针接收普通变量的地址

#include <stdio.h>
void   exchange1 (int m,int n)
{
	int temp=m;
	m=n;
	n=temp;
	printf("m=%d,n=%d\n",m,n);//1314  520 


}
void  exchange2(int *p,int *q)
{
	//交接指针所指向内存空间中的值
	int temp=*p;
	*p=*q;
	*q=temp;
	printf("*p=%d,*q=%d\n",*p,*q);//1314  520 
}
void  exchange3(int *p,int *q)
{
	int *temp=p;
	p=q;
	q=temp;
	printf("*p=%d,*q=%d\n",*p,*q);//1314  520 
}//实质是地址交换


int main(int argc, const char *argv[])
{
	//三种数据交换的方法

	int num=520;
	int key=1314;
	//调用函数实现数据交换
	//exchange1(num,key);       //520 1314 
    //exchange2(&num,&key);   //1314 520 
	exchange3(&num,&key);   //520 1314
	printf("num=%d,key=%d",num,key);

	return 0;
}

2.指针接收数组

      2.指针作为函数的返回值:指针函数

在苍穹外卖项目 Day12 的开发过程中,空指针异常(NullPointerException)是一个常见的运行时错误。这类异常通常发生在试图访问一个未初始化(null)对象的属性或方法时。解决空指针异常的核心思路是进行对象的非空判断和合理的初始化。 ### 空指针异常的常见原因及解决方案 #### 1. **未初始化的对象调用方法** 在访问对象属性或调用方法前,未对该对象进行 null 判断。 ```java User user = getUserById(userId); String name = user.getName(); // 如果 user 为 null,此处抛出 NullPointerException ``` **解决方案:** 在调用方法前进行 null 判断: ```java User user = getUserById(userId); if (user != null) { String name = user.getName(); } else { // 处理 user 为 null 的情况 } ``` #### 2. **自动拆箱时的空指针** 当使用基本类型对应的包装类(如 Integer、Double)并进行自动拆箱时,如果对象为 null,则会抛出空指针异常。 ```java Integer age = getAge(); int userAge = age; // 如果 age 为 null,此处抛出 NullPointerException ``` **解决方案:** 使用默认值或判断 null: ```java Integer age = getAge(); int userAge = (age != null) ? age : 0; ``` #### 3. **集合或数组未初始化** 访问集合或数组的元素之前未判断其是否为空。 ```java List<String> names = getNames(); for (String name : names) { // 如果 names 为 null,此处抛出 NullPointerException System.out.println(name); } ``` **解决方案:** 确保集合或数组已初始化: ```java List<String> names = getNames(); if (names != null && !names.isEmpty()) { for (String name : names) { System.out.println(name); } } else { // 处理 names 为 null 或空的情况 } ``` #### 4. **Spring 依赖注入失败** 在使用 Spring 管理的 Bean 时,如果未正确注入,可能会导致对象为 null。 ```java @Autowired private UserService userService; public void someMethod() { userService.doSomething(); // 如果 userService 为 null,此处抛出 NullPointerException } ``` **解决方案:** - 确保 Bean 已正确注入。 - 检查组件扫描路径是否包含目标类。 - 使用 `@Primary` 或 `@Qualifier` 解决多个 Bean 冲突问题。 #### 5. **日志记录与调试建议** 在开发过程中,建议使用日志框架(如 Logback、Log4j)记录异常信息,便于定位问题。 ```java try { // 可能抛出 NullPointerException 的代码 } catch (NullPointerException e) { logger.error("发生空指针异常:", e); return Result.error("系统异常,请联系管理员"); } ``` ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值