C语言学习笔记---指针(2)

声明:文字版看不惯的请看每小节截图哦~

文章的末尾还有笔记的完整版长截图,可用电脑查看~

---------我是分割线-------

目录

//指针类型的意义

//以上这些有什么意义?

//第一个案例

//第二个案例

//void*指针,可以理解为无具体类型的指针,可以接受任意类型地址

//const修饰指针

//指针运算

//指针-指针=整数

//指针-指针有什么用?

//指针的关系运算

//野指针

//造成野指针的原因?

//1.指针没有初始化,局部变量如果不初始化,变量的值是随机的

//2.指针越界访问

//3.指针指向的空间释放

//如果规避野指针?

//指针初始化;

//小心指针越界

//避免返回局部变量的地址,如上例子:3.指针指向的空间释放


//指针类型的意义


int main()
{
    int a = 0x11223344;
    char* pa = &a;//pa这里能存放的下a的地址吗?
    //肯定能,X64的环境下指针变量都是8个字节,一定能放得下int类型的a
    *pa = 0;//赋值后再内存中改动的只有一个字节,是因为pa是存放了地址的char*类型的指针,解引用之后的值就是char类型
    //不同类型的指针解引用之后的类型是不一样的,这就是指针类型的意义;什么类型的指针解引用之后就是什么类型的数据,就占什么类型的大小空间
    //但是如果是int*类型的指针可以直接访问4个字节的数据,而char*类型的指针只能访问一个字节。
    //总结:指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字节)
    return 0;
}
//double*--8个字节
//short*--2个字节

int main()
{
    int a = 10;
    int* pa = &a;
    char* pc = (char*) & a;//原本取出来的地址应该是int*类型,可以用(char*)强制性转换,以免编译器报警告
    printf("pa=%p\n", pa);
    printf("pa=%p\n", pc);

    printf("pa+1=%p\n", pa+1);//+1相当于加了一个类型的空间大小
    printf("pc+1=%p\n", pc+1);//总结:指针的类型决定了指针向前或者向后走一步有多大(距离)

    return 0;
}
//每次运行后的地址可能不一样,看编译器

//以上这些有什么意义?


//第一个案例


int main()
{
    int arr[10] = { 0 };
    int i = 0;
    int* p = &arr[0];
    for (i = 0; i < 10; i++)
    {
        *p = 1; 
        p++;///*(p=p+1)*///或者arr[i] = 1;
        //注意p++是地址加,不是数值加
    }
    //此时存放了地址的p以后指向了第9个元素的第一个字节的首地址
    p = &arr[0];//要从头打印的话,得让p回到&arr[0]
    for (i = 0; i < 10; i++)
    {
        printf("%d ",*p);//arr[i]
        p++;//p找到一个元素打印出来之后++找到下一个元素再打印
    }
    return 0;
}

//第二个案例


//可以把int* p = &arr[0];一次访问00000001000000010000000100000001
//改成char* p = &arr[0];可以一次访问1一个字节00000001

//void*指针,可以理解为无具体类型的指针,可以接受任意类型地址


// 但是也有局限性:
//void*指针不能直接加减和解引用运算
int main()
{
    int a = 10;
    char* ch = 'w';
    int* pa = &a;
    char* pc = &a;//编译器报警告是因为两边的类型不一样
    void* pv = &a;//不会报警告是因为void*可以接受任何类型的地址
    char* pv2 = &ch;

    *pv = 20;//err void*类型的指针不能直接进行解引用操作
    pv++;//void*没有具体类型,所以不知道跳过几个字节

    return 0;
}
//一般void*类型的指针是使用在函数参数的部分,用来接收不同类型数据的地址

//const修饰指针


//常属性的意思(不变)
int main()
{
    const int a = 10;//不能改a,但是本质还是变量,const仅仅只是在语法上做了限制,
    //习惯上称a是常变量
    a = 20;//不能赋值了,会报警告
    printf("%d\n", a);

    return 0;
}

int main()
{
    const int a = 10;
    int* pa = &a;
    //但是一旦这样const int*pa=&a,就不能通过通过pa改a了
    *pa = 0;//可以改a,是因为const仅仅只是在语法上做了限制,
    //我们可以把a的地址给pa,然后通过pa改a
    printf("%d\n", a);

    return 0;
}
//const可以用来修饰指针,可以放在*的左边,也可以放在*的右边,

int main()
{
    const int a = 10;
    int const* p = &a;//和const*int p=&a效果是一样的,只要const在a的左边就是一样的
    //int* const p = &a;//当const放在*的右边的时候限制的是p,而不是*p,所以*pa是可以改的
    
    //*p = 0;//err
    int b = 20;
    p = &b;//ok
    printf("%p", p);


    return 0;
}
//p里面存放的是a的地址,同时p是变量,也有自己的地址;
//*p是p指向的空间(里面的值a)
//const*p=&a表示不能通过p来修改p指向的空间的内容,不会限制p,可以改变p里面存放的地址
//int*const p 限制的是p,不限制*p,可以通过p来修改p指向的空间的内容,
//但是不能改变p里面存放的地址,也就是不能指向其他变量了

//指针运算


int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
        //使用指针打印数组的内容;
    int* p = &arr[0];//初始化指针变量
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", *p);
        p++;//地址+一个类型的大小,int是一次加4个字节
        //以上两行代码还可以合成:
        //printf("%d ",*(p+i));p+i加的是i*sizeof(int),加的是i个整型的大小
    }
    return 0;
}

//指针-指针=整数


//指针+整数=指针
//可以类比为日期-日期=中间的天数,
//日期+天数=日期
//日期-天数=日期
int main()
{
    //指针-指针==地址-地址
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10};
    printf("%d\n", &arr[9] - &arr[0]);//指针-指针的绝对值得到的是指针之间的元素个数
    //高地址-低地址得到的是正数
    //低地址-高地址得到的是负数
    //指针-指针运算的前提条件是:两个指针指向同一块空间;
    char ch[20] = { 0 };
    printf("%d\n", &ch[0]- &arr[0]);//算不出来,不是同一块空间
    //这是因为这两个数组之间是否存在空隙不确定,
    //结果(指针-指针的绝对值得到的是指针之间的元素个数)是按char来算还是int来算,这个是不确定的

    return 0;
}

//指针-指针有什么用?


#include<string.h>
int my_strlen(char* s)//传过来的是第一个字符的地址
{
    int count = 0;
    while (*s != '\0')
    {
        count++;
        s++;
    }
    return count;
}
//可以换一种写法:用\0的地址-第一个字符的地址
int my_strlen(char* s)
{
    char* start = s;
    //while (*s != '\0')
    //也可以写成
    while(*s)//只要是真就进入循环,\0的ASCII码值是0,退出循环
    {
        s++;
    }
    return s - start;//指针-指针
}
//
int main()
{
    //strlen求字符串的长度-统计的是\0前面出现的字符的个数
    int len = my_strlen("abcefg");//不包含\0
    //传字符串给strlen时传的不是字符串的长度,而是第一个字符的地址
    printf("%d\n", len);
    return 0;
}

//指针的关系运算


//其实就是指针比较大小(地址比较大小)
int main()
{
    int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
    int sz = sizeof(arr) / sizeof(arr[0]);//求元素个数10
    //使用while循环打印arr的内容
    int* p = &arr[0];
    //arr是数组名,数组名其实是数组首元素的地址,arr<==>&arr[0]
    while (p < arr+ sz)//只要p存放的地址小于它的地址+元素个数就保持循环
    //&arr[0]+sz相当于跳过了10个元素,
    //当p最后指向第11个元素的首地址时就等于arr+sz指向的地址,就跳出循环

    {
        printf("%d ", *p);
        p++;
    }
    return 0;
}

//野指针


//野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
//全局变量如果不初始化,变量的值默认是0;静态变量不初始化,值默认也是0;
//因为全局变量和静态变量都是放在静态区的

//造成野指针的原因?


//1.指针没有初始化,局部变量如果不初始化,变量的值是随机的


int main()
{
    int* ptr;//野指针
    *ptr = 20;//非法访问内存了
    return 0;
}

//2.指针越界访问


int main()
{
    int arr[5] = { 0 };
    int i = 0;
    int* p = arr;
    for (i = 0; i < 10; i++)//i<10导致指正越界访问
    {
        *p = 1;
        p++;
    }
    return 0;
}

//3.指针指向的空间释放

int* test()
{
    int a = 10;
    return &a;
}

int main()
{
    
    int*p= test();//a出了函数test()就把空间还给操作系统了
    //这个时候虽然也能带回来a的地址,但是这个地址已经不属于a了,
    //一旦p接收了这个地址就能野指针,因为它指向了一个不明确的位置
    printf("hehe\n");
    //如果在打印*p前先随便加别的东西破坏掉原先那个函数的栈帧空间,结果打印出来的*p就不再是10了。
    printf("%d\n", *p);
    //这个时候虽然还能打印出来10,是因为这个空间的内容还没有被覆盖
    return 0;
}

//如果规避野指针?


//指针初始化;


//int a=10;
//int* pa = &a;//这里明确知道pa应该指向a,所以拿a到的地址初始化
//注:可能不知道给指针初始化谁的地址,直接一个NULL空,是0;
//int* p = NULL;//转换成空指针
//NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址会报错。
int main()
{
    int* p = NULL;//一旦这么写,那所有读者都应该知道这个指针没有明确的指向,不要乱用它
    *p = 20;//一旦乱用程序就会报错了
    return 0;
}
//野指针就像一只野狗,int* p = NULL相当于是给野狗栓在树上被警告人们不要碰他,当有人要碰他时就很危险,程序报错了;
//最后就是给野狗找到主人(初始化指针)

//小心指针越界


//指针变量不再使⽤时,及时置NULL,留着以后用,指针使⽤之前检查有效性
//只要是NULL指针就不去访问,同时使⽤指针之前可以判断指针是否为NULL
int main()
{
    int arr[10] = { 1,2,3,4,5,67,7,8,9,10 };
    int i = 0;
    int* p = &arr[0];
    for (i = 0; i < 10; i++)
    {
        *(p++) = i;//后置++:先让*p等于i,p再++
    }
    //此时p已经越界了,可以把p置为NULL
    p = NULL;
    //下次使⽤的时候,判断p不为NULL的时候再使⽤
    //...
    p = &arr[0];//重新让p获得地址!
    if (p != NULL) //判断!!!!!!!!!!!
    {
        //...
    }
    return 0;
}


//避免返回局部变量的地址,如上例子:3.指针指向的空间释放

---------我是分割线-------

笔记完整版:(手机看比较模糊,可用电脑查看)

预知后事如何,请听下回分解......

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Vera工程师养成记

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值